From 0434181093ff0ca39407ac46e0b4fb09ac1f6f04 Mon Sep 17 00:00:00 2001 From: Srikanth Muppandam Date: Thu, 22 Oct 2026 14:27:44 +0530 Subject: [PATCH 1/2] camera: add shared runtime and overlay validation helpers Add runtime CAMX discovery, NHX checksum fallbacks, read-only RPM checks and bounded camera-service restoration. Provide package mappings, explicit CentOS CLO repository setup and dynamic efivarfs preparation. Support native-HAL executable discovery, bounded capability and capture helpers, exact-test results and fresh image collection while preserving wrapper interfaces. Distinguish EFI read failures from payload mismatches and retain separate before, write and readback diagnostics. Track pending selection by boot ID to prevent repeated writes and verify enabled CamX DT nodes after reboot. Camera runner integration follows separately. Revert its consumers before removing this shared foundation. Signed-off-by: Srikanth Muppandam --- Runner/config/pkg_command_map.conf | 5 +- Runner/utils/camera/lib_camera.sh | 794 ++++++++++++++++++++++++++++- Runner/utils/lib_pkg_provider.sh | 115 +++++ Runner/utils/lib_system.sh | 180 ++++++- 4 files changed, 1066 insertions(+), 28 deletions(-) diff --git a/Runner/config/pkg_command_map.conf b/Runner/config/pkg_command_map.conf index 54d6e5607..f0231f39a 100755 --- a/Runner/config/pkg_command_map.conf +++ b/Runner/config/pkg_command_map.conf @@ -268,6 +268,9 @@ centos:pgrep=procps-ng centos:killall=psmisc centos:lsmod=kmod centos:modprobe=kmod +centos:dtc=dtc +centos:fdtdump=dtc +centos:diff=diffutils # --------------------------------------------------------------------------- # Common opkg provider mappings. @@ -548,7 +551,7 @@ centos:Sensors:ssc_drva_test=qcom-sensors-test-core qcom-sensors-test-apps debian:package-set:camera-nhx=camx-dkms camx-glymur libcamx-glymur1 camx-firmware-glymur camx-nhx ubuntu:package-set:camera-nhx=camx-dkms camx-glymur libcamx-glymur1 camx-firmware-glymur camx-nhx -centos:package-set:camera-nhx=camx-dkms camx-firmware-kodiak camx-kodiak libcamx-kodiak1 +centos:package-set:camera-nhx=camx-dkms camx-firmware-kodiak camx-kodiak libcamx-kodiak1 dtc diffutils # Qualcomm Iris video overlay. Debian and Ubuntu use the public DKMS package, # whose post-install flow builds, registers, and loads iris_vpu. CentOS uses diff --git a/Runner/utils/camera/lib_camera.sh b/Runner/utils/camera/lib_camera.sh index 34e550a8e..8be7e23d2 100755 --- a/Runner/utils/camera/lib_camera.sh +++ b/Runner/utils/camera/lib_camera.sh @@ -1,14 +1,66 @@ #!/bin/sh # Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. -# SPDX-License-Identifier: BSD-3-Clause# Shared libcamera helpers +# SPDX-License-Identifier: BSD-3-Clause +# Shared libcamera helpers # ---------- Sensor & index helpers ---------- -# Return the number of sensors visible in `cam -l` +# libcam_list_indices_from_file +# List camera indices from retained `cam -l` output. +libcam_list_indices_from_file() { + libcam_indices_file="$1" + if [ ! -r "$libcam_indices_file" ]; then + printf '%s\n' "libcamera list output is not readable: $libcam_indices_file" >&2 + return 1 + fi + + awk ' + /^[[:space:]]*Available cameras:/ { + in_list=1 + next + } + !in_list { + next + } + /^[[:space:]]*\[[0-9]+\]/ { + index_value=$0 + sub(/^[[:space:]]*\[/, "", index_value) + sub(/\].*$/, "", index_value) + if (!seen[index_value]++) { + print index_value + } + next + } + /^[[:space:]]*[0-9]+:[[:space:]]/ { + index_value=$0 + sub(/^[[:space:]]*/, "", index_value) + sub(/:.*/, "", index_value) + if (!seen[index_value]++) { + print index_value + } + } + ' "$libcam_indices_file" +} + +# libcam_count_sensors_from_file +# Return the number of cameras in retained `cam -l` output. +libcam_count_sensors_from_file() { + libcam_count_file="$1" + if [ ! -r "$libcam_count_file" ]; then + printf '%s\n' "libcamera list output is not readable: $libcam_count_file" >&2 + return 1 + fi + + libcam_list_indices_from_file "$libcam_count_file" | + awk 'NF { count++ } END { print count + 0 }' +} + +# Return the number of sensors visible in `cam -l`. libcam_list_sensors_count() { out="$(cam -l 2>&1 || true)" printf '%s\n' "$out" | awk ' BEGIN { c=0; inlist=0 } - /^Available cameras:/ { inlist=1; next } + /^[[:space:]]*Available cameras:/ { inlist=1; next } + /^[[:space:]]*\[[0-9]+\]/ { if (inlist) c++ } /^[[:space:]]*[0-9]+:[[:space:]]/ { if (inlist) c++ } END { print c+0 } ' @@ -28,7 +80,12 @@ libcam_list_indices() { # Outputs space-separated indices to stdout. libcam_resolve_indices() { want="${1:-auto}" - all="$(libcam_list_indices | tr '\n' ' ' | sed 's/[[:space:]]\+/ /g;s/^ //;s/ $//')" + list_file="${2:-}" + if [ -n "$list_file" ]; then + all="$(libcam_list_indices_from_file "$list_file" | tr '\n' ' ' | sed 's/[[:space:]]\+/ /g;s/^ //;s/ $//')" + else + all="$(libcam_list_indices | tr '\n' ' ' | sed 's/[[:space:]]\+/ /g;s/^ //;s/ $//')" + fi [ -n "$all" ] || { printf '\n'; return 1; } case "$want" in @@ -51,6 +108,50 @@ libcam_resolve_indices() { esac } +# List enabled Qualcomm libcamera pipeline nodes from the runtime device tree. +libcam_list_enabled_pipeline_nodes() { + dt_list_compatible_nodes \ + '(^|[[:space:]])qcom,[^[:space:]]*camss([[:space:]]|$)' \ + regex +} + +# libcam_disable_ipa_config +# Moves one IPA config aside for a test-owned temporary workaround. +libcam_disable_ipa_config() { + libcam_ipa_config="$1" + libcam_ipa_backup="$2" + + [ -f "$libcam_ipa_config" ] || return 2 + if [ -e "$libcam_ipa_backup" ]; then + log_warn "IPA config backup already exists, leaving both files unchanged: $libcam_ipa_backup" + return 1 + fi + + if mv "$libcam_ipa_config" "$libcam_ipa_backup"; then + log_info "Temporarily moved IPA config to $libcam_ipa_backup" + return 0 + fi + + log_warn "Failed to move IPA config $libcam_ipa_config" + return 1 +} + +# libcam_restore_ipa_config +# Restores an IPA config moved by libcam_disable_ipa_config. +libcam_restore_ipa_config() { + libcam_ipa_config="$1" + libcam_ipa_backup="$2" + + [ -f "$libcam_ipa_backup" ] || return 0 + if mv "$libcam_ipa_backup" "$libcam_ipa_config"; then + log_info "Restored IPA config $libcam_ipa_config" + return 0 + fi + + log_warn "Failed to restore IPA config from $libcam_ipa_backup" + return 1 +} + # Pick first camera index (helper used by older paths) libcam_pick_cam_index() { want="${1:-auto}" @@ -558,8 +659,175 @@ camx_opkg_list_camx() { } # ----------------------------------------------------------------------------- -# DT camera nodes sample using fdtdump +# Runtime DT and camera overlay evidence # ----------------------------------------------------------------------------- +# camx_dump_runtime_dts +# Decodes the active runtime device tree from the resolved filesystem root. +camx_dump_runtime_dts() { + runtime_dts_output="$1" + runtime_dts_warnings="$2" + runtime_dts_root="" + + command -v dtc >/dev/null 2>&1 || return 2 + runtime_dts_root="$(dt_runtime_root 2>/dev/null || true)" + [ -n "$runtime_dts_root" ] || return 3 + + : >"$runtime_dts_warnings" + if ! dtc \ + -I fs \ + -O dts \ + -o "$runtime_dts_output" \ + "$runtime_dts_root" \ + 2>"$runtime_dts_warnings"; then + return 1 + fi + + [ -s "$runtime_dts_output" ] +} + +# camx_extract_runtime_dts_evidence +# Extracts line-numbered camera and sensor matches from a decoded runtime DTS. +camx_extract_runtime_dts_evidence() { + runtime_dts_input="$1" + runtime_camera_log="$2" + runtime_sensor_log="$3" + + [ -r "$runtime_dts_input" ] || return 1 + + if ! grep -nEi \ + 'camera|cam[-_@]|qcom,cam|cci(@|[-_])|csiphy' \ + "$runtime_dts_input" >"$runtime_camera_log"; then + : >"$runtime_camera_log" + fi + + if ! grep -nEi \ + 'imx[0-9]+|ov[0-9]+|ar[0-9]+|cam[_-]sensor|cam[_-]gmsl' \ + "$runtime_dts_input" >"$runtime_sensor_log"; then + : >"$runtime_sensor_log" + fi +} + +# camx_list_enabled_runtime_nodes +# Lists enabled downstream CamX nodes from the active runtime DT. +camx_list_enabled_runtime_nodes() { + { + dt_list_compatible_nodes \ + '(^|[[:space:]])qcom,cam[-_][^[:space:]]+([[:space:]]|$)' \ + regex 2>/dev/null || true + dt_list_compatible_nodes \ + '(^|[[:space:]])qcom,camera[^[:space:]]*([[:space:]]|$)' \ + regex 2>/dev/null || true + } | sort -u +} + +# camx_ensure_fit_selection variable value read_log pending_file +# Read the current EFI selection before changing it. Return 0 when selected, +# 2 when a manual reboot is pending, 1 on read/write or post-reboot failure. +# Log raw before/write/readback evidence to stdout and separate files. A small +# atomic pending record tracks variable, requested value and boot ID, never +# substitutes for runtime validation. Only explicit overlay callers may use it. +camx_ensure_fit_selection() { + cefs_variable="$1" + cefs_value="$2" + cefs_read_log="$3" + cefs_pending="$4" + if [ -z "$cefs_variable" ] || [ -z "$cefs_value" ] || + [ -z "$cefs_read_log" ] || [ -z "$cefs_pending" ]; then + log_fail "CAMX selection requires a variable, value and diagnostic/state paths" + return 1 + fi + cefs_payload=$(efi_text_variable_matches \ + "$cefs_variable" "$cefs_value" "$cefs_read_log" csv-token) + cefs_read_status=$? + log_file_with_label NHX-EFI-BEFORE "$cefs_read_log" + case "$cefs_read_status" in + 0|2) + ;; + *) + log_fail "Cannot read CAMX EFI selection reliably, no EFI write attempted" + return 1 + ;; + esac + log_info "CAMX EFI overlay list: $cefs_payload" + if [ -L "$cefs_pending" ] || { + [ -e "$cefs_pending" ] && [ ! -f "$cefs_pending" ] + }; then + log_fail "Invalid CAMX pending-selection file: $cefs_pending" + return 1 + fi + if [ "$cefs_read_status" -eq 0 ] && [ ! -e "$cefs_pending" ]; then + log_pass "CAMX FIT DTB is already selected in EFI, no write or reboot requested" + return 0 + fi + if ! IFS= read -r cefs_boot_id "$cefs_temp" || + ! mv "$cefs_temp" "$cefs_pending"; then + rm -f "$cefs_temp" + log_fail "Cannot record the pending CAMX selection, no EFI write attempted" + return 1 + fi + cefs_write_payload="$cefs_payload,$cefs_value" + log_info "Selecting CAMX FIT DTB for the next boot, preserving existing overlays: $cefs_write_payload" + efi_write_text_variable "$cefs_variable" "$cefs_write_payload" "$cefs_read_log.write" + cefs_write_status=$? + log_file_with_label NHX-EFI-WRITE "$cefs_read_log.write" + log_file_with_label NHX-EFI-READBACK "$cefs_read_log.write.readback" + if [ "$cefs_write_status" -ne 0 ]; then + log_fail "CAMX EFI write or readback failed. Retaining $cefs_pending for inspection, no automatic retry" + return 1 + fi + return 2 +} + +# camx_list_enabled_sensor_nodes +# Lists enabled physical or CamX sensor nodes from the active runtime DT. +camx_list_enabled_sensor_nodes() { + { + dt_list_compatible_nodes \ + '(^|[[:space:]])(sony,imx[^[:space:]]*|ovti,ov[^[:space:]]*|onsemi,ar[^[:space:]]*|onnn,ar[^[:space:]]*|aptina,ar[^[:space:]]*|qcom,cam[-_](sensor|gmsl[-_]sensor|gmsl[-_]deserializer))([[:space:]]|$)' \ + regex 2>/dev/null || true + } | sort -u +} + +# DT camera nodes sample using fdtdump. camx_fdtdump_has_cam_nodes() { command -v fdtdump >/dev/null 2>&1 || return 2 [ -r /sys/firmware/fdt ] || return 1 @@ -575,11 +843,383 @@ camx_fdtdump_has_cam_nodes() { # ---- NHX dump + checksum validation helpers (POSIX) ---- +# nhx_find_launcher mode override_path [libexec_root] +# Print one executable path. Wrapper discovery preserves the legacy order. +# Native discovery prefers an explicit path, then PATH, then camx* libexec +# directories without a SoC table. Resolve aliases before counting candidates. +# Return 0 for one executable, 2 when absent, 1 for invalid/ambiguous selection. +# Read-only discovery, diagnostics go to stderr. +nhx_find_launcher() { + nfl_mode="$1" + nfl_override="$2" + nfl_root="${3:-/usr/libexec}" + if [ "$nfl_mode" = wrapper ]; then + for nfl_name in nhx.sh camera-nhx; do + nfl_path=$(command -v "$nfl_name" 2>/dev/null || true) + if [ -n "$nfl_path" ] && [ -f "$nfl_path" ] && [ -x "$nfl_path" ]; then + printf '%s\n' "$nfl_path" + return 0 + fi + done + if [ -f "$nfl_root/camx/nhx.sh" ] && [ -x "$nfl_root/camx/nhx.sh" ]; then + printf '%s\n' "$nfl_root/camx/nhx.sh" + return 0 + fi + return 2 + fi + [ "$nfl_mode" = native ] || return 1 + if [ -n "$nfl_override" ]; then + case "$nfl_override" in + /*) + ;; + *) + printf '%s\n' 'Native binary override must be an absolute path' >&2 + return 1 + ;; + esac + if [ ! -f "$nfl_override" ] || [ ! -x "$nfl_override" ]; then + printf 'Native executable is unavailable: %s\n' "$nfl_override" >&2 + return 1 + fi + printf '%s\n' "$nfl_override" + return 0 + fi + nfl_path=$(command -v nativehaltest 2>/dev/null || true) + if [ -n "$nfl_path" ] && [ -f "$nfl_path" ] && [ -x "$nfl_path" ]; then + printf '%s\n' "$nfl_path" + return 0 + fi + nfl_selected="" + for nfl_path in "$nfl_root"/camx*/nativehaltest; do + if [ ! -f "$nfl_path" ] || [ ! -x "$nfl_path" ]; then + continue + fi + nfl_resolved=$(readlink -f "$nfl_path" 2>/dev/null) || return 1 + if [ -n "$nfl_selected" ] && [ "$nfl_resolved" != "$nfl_selected" ]; then + printf 'Multiple native HAL executables found: %s, %s. Select --native-bin\n' \ + "$nfl_selected" "$nfl_resolved" >&2 + return 1 + fi + nfl_selected="$nfl_resolved" + done + [ -n "$nfl_selected" ] || return 2 + printf '%s\n' "$nfl_selected" +} + +# nhx_native_check_cli binary log_directory camera_id [mode] +# Probe documented help only, never guess enumeration switches or run tests. +# Print the supported exact-test selector. Bound each help query to 15 seconds. +# Retry an advertised --h when --help returns a usage status. Accept status +# 1, 2 or 255 only with usage text and all required options, never crashes/timeouts. +# Retain help output. Return 1 for a broken client or missing required options. +# Optional json mode requires only --test, other modes retain capture checks. +nhx_native_check_cli() { + nncc_binary="$1" + nncc_logs="$2" + nncc_camera="$3" + if run_with_managed_timeout 15 "$nncc_logs" native-hal-help \ + "$nncc_binary" --help >"$nncc_logs/native-hal-help.log" 2>&1; then + nncc_status=0 + else + nncc_status=$? + fi + case "$nncc_status" in + 1|2|255) + if grep -Eq '^[[:space:]]*--h([[:space:]]|$)' "$nncc_logs/native-hal-help.log"; then + if ! cp "$nncc_logs/native-hal-help.log" "$nncc_logs/native-hal-help-long.log"; then + printf '%s\n' 'Could not retain the native HAL --help output' >&2 + return 1 + fi + printf 'Native HAL --help returned %s, trying advertised --h\n' "$nncc_status" >&2 + if run_with_managed_timeout 15 "$nncc_logs" native-hal-help \ + "$nncc_binary" --h >"$nncc_logs/native-hal-help.log" 2>&1; then + nncc_status=0 + else + nncc_status=$? + fi + fi + ;; + esac + case "$nncc_status" in + 0) + ;; + 1|2|255) + if ! grep -Eiq '(^|[^[:alnum:]_])usage([^[:alnum:]_]|$)' "$nncc_logs/native-hal-help.log"; then + printf 'Native HAL help failed with status %s and no usage text\n' "$nncc_status" >&2 + return 1 + fi + ;; + *) + printf 'Native HAL help query failed, status=%s, inspect %s/native-hal-help.log\n' \ + "$nncc_status" "$nncc_logs" >&2 + return 1 + ;; + esac + nncc_options='--frames --timeout --p' + if [ "${4:-test}" = json ]; then + nncc_options='--test' + fi + for nncc_option in $nncc_options; do + if ! grep -Eq "^[[:space:]]*$nncc_option([[:space:]]|$)" "$nncc_logs/native-hal-help.log"; then + printf 'Native HAL does not advertise required option %s\n' "$nncc_option" >&2 + return 1 + fi + done + if [ -n "$nncc_camera" ] && \ + ! grep -Eq '^[[:space:]]*-c([[:space:]]|$)' "$nncc_logs/native-hal-help.log"; then + printf '%s\n' 'Native HAL does not advertise the requested camera override' >&2 + return 1 + fi + for nncc_option in --test --gtest_filter; do + if grep -Eq "^[[:space:]]*$nncc_option([[:space:]]|$)" "$nncc_logs/native-hal-help.log"; then + printf '%s\n' "$nncc_option" + return 0 + fi + done + printf '%s\n' 'Native HAL does not advertise an exact testcase selector' >&2 + return 1 +} + +# nhx_run_native binary selector testcase frames camera_id run_log log_directory [mode] +# Run one explicitly selected native testcase with dumps enabled and stdout +# logging. A 5-second per-buffer timeout is separate from the 180-second total +# deadline. Capture raw output, then replay it to stdout. No wrapper semantics +# or service state are changed here. JSON mode passes only --test and its basename, +# preserving all JSON settings. Return the native process/timeout status. +nhx_run_native() { + nrn_binary="$1" + nrn_selector="$2" + nrn_test="$3" + nrn_frames="$4" + nrn_camera="$5" + nrn_log="$6" + nrn_directory="$7" + if [ "${8:-test}" = json ]; then + set -- "$nrn_binary" --test "$nrn_test" + log_info "Running native HAL JSON=$nrn_test, capture settings from JSON, total-timeout=180s" + else + set -- "$nrn_binary" "$nrn_selector" "$nrn_test" \ + --frames "$nrn_frames" --timeout 5000 --p + if [ -n "$nrn_camera" ]; then + set -- "$@" -c "$nrn_camera" + fi + log_info "Running native HAL testcase=$nrn_test frames=$nrn_frames camera=${nrn_camera:-client-default-physical} total-timeout=180s" + fi + run_with_managed_timeout 180 "$nrn_directory" native-hal-capture \ + "$@" >"$nrn_log" 2>&1 + nrn_status=$? + log_file_with_label NATIVE-HAL "$nrn_log" + return "$nrn_status" +} + +# nhx_native_result_counts run_log testcase [mode] +# Print PASSED FAILED SKIPPED counts for exactly one executed native testcase. +# Require matching RUN/OK markers and a completed single-test summary, either +# GoogleTest or CAMX Final Report. Reject zero/multiple tests, skips, conflicting +# summaries and unrelated successful testcases. No mutation, return 2 for an +# explicit zero-test report without execution markers, 1 for other gaps. +# JSON mode accepts one successful CAMX report without RUN/OK markers. If those +# markers exist, require exactly one matching pair (internal name may differ +# from the JSON basename). The caller must also validate staged JSON and dumps. +# CAMX quoted per-test reports are distinct from GoogleTest numeric totals. +# If present, require one PASSED report consistent with any execution markers. +nhx_native_result_counts() { + awk -v selected="$2" -v mode="${3:-test}" ' + /"[^"]+"[[:space:]]+report[[:space:]]*->/ { + match($0, /"[^"]+"[[:space:]]+report[[:space:]]*->/) + line=substr($0, RSTART) + split(line, camx_fields, /"/) + camx_name=camx_fields[2] + sub(/^"[^"]+"[[:space:]]+report[[:space:]]*->[[:space:]]*/, "", line) + camx_reports++ + if (camx_reports != 1 || line !~ /^\[[[:space:]]*PASSED[[:space:]]*\][[:space:]]*$/) bad=1 + if (mode != "json" && camx_name != selected) bad=1 + next + } + /\[[[:space:]]*RUN[[:space:]]*\]/ { + line=$0 + sub(/^.*\[[[:space:]]*RUN[[:space:]]*\][[:space:]]*/, "", line) + split(line, fields, /[[:space:]]+/) + runs++ + run_name=fields[1] + if (mode != "json" && fields[1] != selected) bad=1 + } + /\[[[:space:]]*OK[[:space:]]*\]/ { + line=$0 + sub(/^.*\[[[:space:]]*OK[[:space:]]*\][[:space:]]*/, "", line) + split(line, fields, /[[:space:]]+/) + oks++ + ok_name=fields[1] + if (mode != "json" && fields[1] != selected) bad=1 + } + /\[[[:space:]]*(FAILED|SKIPPED)[[:space:]]*\]/ { bad=1 } + /\[==========\].*ran\./ { + line=$0 + sub(/^.*\[==========\][[:space:]]*/, "", line) + split(line, fields, /[[:space:]]+/) + if (fields[1] == "0") empty_reports++ + if (fields[1] != 1) bad=1 + completed++ + } + /\[[[:space:]]*PASSED[[:space:]]*\]/ { + line=$0 + sub(/^.*\[[[:space:]]*PASSED[[:space:]]*\][[:space:]]*/, "", line) + split(line, fields, /[[:space:]]+/) + if (fields[1] != 1) bad=1 + summaries++ + } + /Final Report ->/ { + if ($0 ~ /\[0 PASSED\]/ && $0 ~ /\[0 FAILED\]/ && $0 ~ /\[0 SKIPPED\]/) empty_reports++ + if ($0 !~ /\[1 PASSED\]/ || $0 !~ /\[0 FAILED\]/ || $0 !~ /\[0 SKIPPED\]/) bad=1 + reports++ + } + END { + if (empty_reports == 1 && runs == 0 && oks == 0 && camx_reports == 0 && + (reports + completed) == 1) exit 2 + if (camx_reports && ((runs && run_name != camx_name) || + (oks && ok_name != camx_name))) bad=1 + if (mode == "json" && !bad && reports == 1 && completed == 0 && summaries == 0 && + ((runs == 0 && oks == 0) || + (runs == 1 && oks == 1 && run_name == ok_name))) { + print "1 0 0" + exit 0 + } + if (mode == "json") exit 1 + if (bad || runs != 1 || oks != 1 || + !((completed == 1 && summaries == 1 && reports == 0) || + (reports == 1 && completed == 0 && summaries == 0))) exit 1 + print "1 0 0" + } + ' "$1" +} + +# nhx_stop_native_capture +# Stop only the command and watcher owned by an interrupted native capture. +# Call before restoring camera services, only while the caller-owned native +# capture flag is set. Reuse bounded process termination and reap children. +# Log to stdout, return 1 if an owned process cannot be stopped safely. +nhx_stop_native_capture() { + for nsnc_pid in "${MANAGED_TIMEOUT_WATCHER_PID:-}" "${MANAGED_TIMEOUT_CMD_PID:-}"; do + case "$nsnc_pid" in + "") + continue + ;; + 0|1|*[!0-9]*) + log_fail "Invalid owned native capture PID: $nsnc_pid" + return 1 + ;; + esac + if ! kill_process "$nsnc_pid"; then + log_fail "Could not stop owned native capture process $nsnc_pid, camera service will not be restarted while capture may remain active" + return 1 + fi + wait "$nsnc_pid" 2>/dev/null || true + done + for nsnc_file in "${MANAGED_TIMEOUT_SLEEP_PID_FILE:-}" "${MANAGED_TIMEOUT_MARKER_FILE:-}"; do + [ -n "$nsnc_file" ] || continue + rm -f "$nsnc_file" + done + return 0 +} + +# nhx_restore_camera_service service log_directory +# Restore a service stopped by NHX and verify it is active. The caller owns +# the initial-state decision. Retain bounded systemctl output and log errors. +# Return 0 when active, 1 on start, timeout or post-start state failure. +nhx_restore_camera_service() { + nrcs_service="$1" + nrcs_directory="$2" + if ! run_with_managed_timeout 60 "$nrcs_directory" nhx-service-restore \ + systemctl start "$nrcs_service" >"$nrcs_directory/service-restore.log" 2>&1; then + log_file_with_label NHX-SERVICE "$nrcs_directory/service-restore.log" + log_fail "Could not restore $nrcs_service after NHX, inspect service-restore.log and the service journal" + return 1 + fi + if ! systemd_service_is_active "$nrcs_service"; then + log_fail "Restored $nrcs_service is not active after start, inspect the service journal" + return 1 + fi + return 0 +} + +# nhx_check_rpm_setup log_directory +# Read-only CentOS setup check using the configured camera-nhx package map. +# Required CAMX packages are checked locally. Repository listings use cached +# metadata only, never install, refresh, enable a repository or access a share. +# Log missing repositories/cache as provisioning diagnostics, not a requirement +# for an already installed offline NHX runtime. Return 0 when core packages +# exist, 2 when required tools/packages are absent, 1 on execution failure. +nhx_check_rpm_setup() { + ncrs_directory="$1" + command -v rpm >/dev/null 2>&1 || return 2 + ncrs_packages=$(pkg_lookup_package_set camera-nhx) || return 2 + ncrs_missing=0 + ncrs_failed=0 + ncrs_count=0 + for ncrs_package in $ncrs_packages; do + case "$ncrs_package" in + camx-*|libcamx-*) + ncrs_count=$((ncrs_count + 1)) + if LC_ALL=C run_with_managed_timeout 20 "$ncrs_directory" nhx-rpm-query \ + rpm -q "$ncrs_package" >"$ncrs_directory/package-$ncrs_package.log" 2>&1; then + : + else + ncrs_status=$? + if [ "$ncrs_status" -eq 1 ] && grep -Fq 'is not installed' "$ncrs_directory/package-$ncrs_package.log"; then + ncrs_missing=1 + log_warn "NHX package prerequisite missing: $ncrs_package. Provision the matching CAMX package set in the image" + else + ncrs_failed=1 + log_fail "NHX package query failed: $ncrs_package rc=$ncrs_status, inspect the retained package log" + fi + fi + log_file_with_label NHX-PACKAGE "$ncrs_directory/package-$ncrs_package.log" + ;; + esac + done + if command -v dnf >/dev/null 2>&1; then + if run_with_managed_timeout 20 "$ncrs_directory" nhx-repolist \ + dnf -C repolist >"$ncrs_directory/repositories.log" 2>&1; then + log_file_with_label NHX-REPOSITORIES "$ncrs_directory/repositories.log" + for ncrs_repo in epel qualcomm-linux-aarch64 qualcomm-linux-noarch clo-rpm-aarch64 clo-rpm-noarch; do + if awk -v repo="$ncrs_repo" '$1 == repo { found=1 } END { exit !found }' \ + "$ncrs_directory/repositories.log"; then + if ! run_with_managed_timeout 20 "$ncrs_directory" nhx-repository-packages \ + dnf -C repo-pkgs "$ncrs_repo" list \ + >"$ncrs_directory/repository-$ncrs_repo.log" 2>&1; then + log_warn "Cached package listing unavailable for $ncrs_repo. Refresh its metadata during image provisioning" + elif ! awk '$1 ~ /\.(aarch64|noarch)$/ { found=1 } END { exit !found }' \ + "$ncrs_directory/repository-$ncrs_repo.log"; then + log_warn "Cached repository $ncrs_repo exposes no aarch64/noarch packages. Check repository provisioning" + fi + log_file_with_label NHX-REPOSITORY "$ncrs_directory/repository-$ncrs_repo.log" 25 + else + log_warn "LRH setup repository $ncrs_repo is not enabled. Provision it if CAMX package resolution requires it" + fi + done + else + log_file_with_label NHX-REPOSITORIES "$ncrs_directory/repositories.log" + log_warn "DNF cached repository inventory is unavailable. Installed CAMX package checks remain authoritative for offline NHX" + fi + else + log_warn "dnf is unavailable, repository provisioning cannot be checked" + fi + [ "$ncrs_count" -gt 0 ] || return 2 + [ "$ncrs_failed" -eq 0 ] || return 1 + [ "$ncrs_missing" -eq 0 ] || return 2 + return 0 +} + +# nhx_pick_cksum_tool +# Print the first available checksum utility, or an empty line when absent. +# Uses image-provided tools only, preferring SHA-256 over MD5 and POSIX cksum. nhx_pick_cksum_tool() { if command -v sha256sum >/dev/null 2>&1; then echo "sha256sum" elif command -v md5sum >/dev/null 2>&1; then echo "md5sum" + elif command -v cksum >/dev/null 2>&1; then + echo "cksum" else echo "" fi @@ -665,10 +1305,11 @@ nhx_resolve_json_file() { # /etc/camera/test/NHX/${JSON_FILE}.json # # Usage: -# nhx_stage_json_for_launcher "" "" "" +# nhx_stage_json_for_launcher "" "" "" [mode] # # Prints launcher argument, for example: # Lemans/Prev_plus_Video_YUVNV12_MaxResolution_NHX +# Optional native mode stages at the root and returns only the basename. nhx_stage_json_for_launcher() { base_dir="$1" src_json="$2" @@ -692,7 +1333,9 @@ nhx_stage_json_for_launcher() { return 1 fi - if [ -z "$target" ]; then + if [ "${4:-wrapper}" = native ]; then + target="" + elif [ -z "$target" ]; then case "$src_json" in "$base_dir"/*) rel="${src_json#"$base_dir"/}" @@ -742,6 +1385,86 @@ nhx_stage_json_for_launcher() { return 1 } +# nhx_json_files_match first second +# Compare JSON bytes using the already-required checksum utility and stdin. +# No stdout or mutation. Return 0 for equal files, 1 for mismatch/read failure. +nhx_json_files_match() { + njfm_tool=$(nhx_pick_cksum_tool) || return 1 + [ -n "$njfm_tool" ] || return 1 + njfm_first=$("$njfm_tool" <"$1") || return 1 + njfm_second=$("$njfm_tool" <"$2") || return 1 + [ -n "$njfm_first" ] && [ "$njfm_first" = "$njfm_second" ] +} + +# nhx_prepare_native_json base_directory source_json state_directory +# Retain the selected JSON, reuse an identical existing root config or stage a +# new one through the shared staging helper. Never overwrite a different file +# or symlink. Record ownership for cleanup. Print basename, return 1 on failure. +nhx_prepare_native_json() { + npnj_base="$1" + npnj_source="$2" + npnj_state="$3" + npnj_name=${npnj_source##*/} + if ! printf '%s\n' "$npnj_name" | grep -Eq '^[A-Za-z_][A-Za-z0-9_-]*\.json$'; then + printf 'Native JSON requires a simple filename ending in .json: %s\n' "$npnj_name" >&2 + return 1 + fi + npnj_destination="${NHX_JSON_ROOT:-/etc/camera/test/NHX}/$npnj_name" + case "$npnj_destination" in + /*) + ;; + *) + printf 'Native JSON root must be an absolute directory\n' >&2 + return 1 + ;; + esac + if printf '%s' "$npnj_destination" | LC_ALL=C grep -q '[[:cntrl:]]'; then + printf 'Native JSON destination contains control characters\n' >&2 + return 1 + fi + if ! cp "$npnj_source" "$npnj_state/selected.json"; then + return 1 + fi + if [ -L "$npnj_destination" ] || { [ -e "$npnj_destination" ] && [ ! -f "$npnj_destination" ]; }; then + printf 'Refusing non-regular native JSON destination: %s\n' "$npnj_destination" >&2 + return 1 + fi + if [ -f "$npnj_destination" ]; then + if ! nhx_json_files_match "$npnj_state/selected.json" "$npnj_destination"; then + printf 'Different JSON already exists at %s, preserve or relocate it before retrying\n' "$npnj_destination" >&2 + return 1 + fi + printf '%s\n' "$npnj_destination" >"$npnj_state/destination" || return 1 + else + printf '%s\n' "$npnj_destination" >"$npnj_state/destination" || return 1 + : >"$npnj_state/created" || return 1 + nhx_stage_json_for_launcher "$npnj_base" "$npnj_source" "" native >/dev/null || return 1 + fi + nhx_json_files_match "$npnj_state/selected.json" "$npnj_destination" || return 1 + printf '%s\n' "${npnj_name%.json}" +} + +# nhx_restore_native_json state_directory +# Verify the selected file and remove it only if created by this run. Existing +# files are never removed. Retain selected.json. Return 1 and preserve changed files +# on conflict/read failure, logging to stdout. Safe to call repeatedly. +nhx_restore_native_json() { + nrnj_state="$1" + [ -n "$nrnj_state" ] && [ -f "$nrnj_state/destination" ] || return 0 + IFS= read -r nrnj_destination <"$nrnj_state/destination" || return 1 + if [ -L "$nrnj_destination" ] || + ! nhx_json_files_match "$nrnj_state/selected.json" "$nrnj_destination"; then + log_fail "Native JSON changed or cannot be read at $nrnj_destination, preserving it for manual cleanup" + return 1 + fi + if [ -f "$nrnj_state/created" ]; then + rm -f "$nrnj_destination" || return 1 + rm -f "$nrnj_state/created" || return 1 + log_info "Removed native JSON staged by this run: $nrnj_destination, retained $nrnj_state/selected.json" + fi + rm -f "$nrnj_state/destination" +} + nhx_collect_new_dumps() { # $1 = dump_dir, $2 = marker_file, $3 = output_list_file # Collect NHX yuvs created after marker. Fallback to any *.yuv if none match. @@ -751,6 +1474,19 @@ nhx_collect_new_dumps() { : >"$out" + if [ "${4:-wrapper}" = native ]; then + # Native captures need not carry the NHX filename tag. Restrict collection + # to image formats under the selected output root, excluding logs/metadata. + if ! find "$ddir" -type f \ + \( -name '*.yuv' -o -name '*.raw' -o -name '*.bin' \ + -o -name '*.jpg' -o -name '*.jpeg' \) \ + -newer "$marker" >"$out"; then + return 1 + fi + sort -o "$out" "$out" + return $? + fi + # Prefer NHX-tagged files (matches your observed names) find "$ddir" -maxdepth 1 -type f \ \( -name '*NHX*.yuv' -o -name '*NHX*.raw' -o -name '*NHX*.bin' \) \ @@ -762,6 +1498,12 @@ nhx_collect_new_dumps() { fi } +# nhx_validate_dumps_and_checksums dump_dir marker_file log_dir [unused] [unused] [mode] +# Validate fresh nonempty dumps and checksum diversity, retaining file lists +# and checksums in log_dir. The previous-run diff is advisory and optional. +# Optional mode is native for untagged image formats including JPEG, otherwise +# keep legacy NHX collection. Reset current list/checksums before validation so +# failures cannot expose stale captures. Log to stdout, return 1 on failure. nhx_validate_dumps_and_checksums() { # $1 = dump_dir, $2 = marker_file, $3 = log_dir ddir="$1" @@ -773,17 +1515,29 @@ nhx_validate_dumps_and_checksums() { prevsum="$ldir/nhx_checksums.prev.txt" tool="$(nhx_pick_cksum_tool)" + if ! { + mkdir -p "$ldir" && + : >"$list" && + : >"$sumfile" + }; then + log_fail "Cannot initialize capture artifacts in $ldir, check permissions and free space" + return 1 + fi + if [ ! -d "$ddir" ]; then log_fail "DUMP_DIR does not exist: $ddir" return 1 fi if [ -z "$tool" ]; then - log_skip "No checksum tool found (sha256sum/md5sum). Skipping dump checksum validation." + log_skip "No checksum tool found (sha256sum/md5sum/cksum). Skipping dump checksum validation." return 0 fi - nhx_collect_new_dumps "$ddir" "$marker" "$list" + if ! nhx_collect_new_dumps "$ddir" "$marker" "$list" "${6:-wrapper}"; then + log_fail "Could not collect fresh captures from $ddir" + return 1 + fi if [ ! -s "$list" ]; then log_fail "No NHX dump files found in $ddir (after marker). Dump/checksum validation not exercised." @@ -804,8 +1558,11 @@ nhx_validate_dumps_and_checksums() { return 1 fi - # Produce "HASH FILE" lines, same as sha256sum/md5sum - line="$($tool "$f" 2>/dev/null)" + # Retain native output, HASH FILE or CRC SIZE FILE for POSIX cksum. + if ! line="$("$tool" "$f" 2>/dev/null)"; then + log_fail "Checksum tool failed for: $f" + return 1 + fi if [ -z "$line" ]; then log_fail "Checksum tool failed for: $f" return 1 @@ -839,10 +1596,19 @@ nhx_validate_dumps_and_checksums() { # Optional drift check: compare against previous run (same log dir) if [ -f "$prevsum" ]; then - if ! diff -q "$prevsum" "$sumfile" >/dev/null 2>&1; then - log_warn "Dump checksums changed vs previous run ($prevsum). This may be expected; keeping as WARN." + if command -v diff >/dev/null 2>&1; then + if diff -q "$prevsum" "$sumfile" >/dev/null 2>&1; then + log_info "Dump checksums match previous run ($prevsum)" + else + nhx_diff_status=$? + if [ "$nhx_diff_status" -eq 1 ]; then + log_warn "Dump checksums changed vs previous run ($prevsum). This may be expected, keeping as WARN." + else + log_warn "Previous-run checksum comparison could not complete, diff rc=$nhx_diff_status. Current-run dump validation is unaffected" + fi + fi else - log_info "Dump checksums match previous run ($prevsum)" + log_info "Previous-run checksum comparison omitted because optional diff is unavailable. Current-run dump validation is unaffected" fi fi diff --git a/Runner/utils/lib_pkg_provider.sh b/Runner/utils/lib_pkg_provider.sh index 52ee8a7f3..bbc743456 100755 --- a/Runner/utils/lib_pkg_provider.sh +++ b/Runner/utils/lib_pkg_provider.sh @@ -1820,6 +1820,121 @@ pkg_ensure_qualcomm_rpm_repository() { return 0 } +# pkg_prepare_camx_rpm_repositories log_directory +# Prepare the CentOS 10 CAMX repositories only for explicit NHX overlay use. +# Reuse EPEL/Qualcomm setup, then preserve existing CLO definitions or create +# missing definitions in a separate managed file. Never overwrite or enable +# an administrator's disabled repository. Refresh metadata even when all IDs +# exist, since the shared update marker may predate CLO configuration. +# Return 0 when ready or not CentOS, 1 on configuration/repository failure. +# Writes the managed repository file and package metadata, logs to stdout and +# the supplied existing log directory. Repository queries are bounded. +pkg_prepare_camx_rpm_repositories() { + pccr_logs="$1" + pccr_os="$(pkg_detect_os_id)" + [ "$pccr_os" = centos ] || return 0 + pccr_version="$(pkg_os_release_value VERSION_ID)" + pccr_arch="$(uname -m)" + if [ "${pccr_version%%.*}" != 10 ] || [ "$pccr_arch" != aarch64 ]; then + pkg_log_fail "CAMX CLO repository setup supports CentOS 10 aarch64, detected version=$pccr_version architecture=$pccr_arch" + return 1 + fi + pkg_prepare_qualcomm_rpm_overlay_repositories || return 1 + pccr_tool="$(pkg_rpm_tool)" + [ -n "$pccr_tool" ] || return 1 + pccr_repo_log="$pccr_logs/camx-enabled-repositories.log" + if ! run_with_managed_timeout 60 "$pccr_logs" camx-repolist \ + "$pccr_tool" -C repolist >"$pccr_repo_log" 2>&1; then + cat "$pccr_repo_log" + pkg_log_fail "Could not inspect enabled CAMX repositories, no CLO configuration changed. Inspect $pccr_repo_log" + return 1 + fi + case "$PKG_QCOM_RPM_REPO_FILE" in + */*) + pccr_directory=${PKG_QCOM_RPM_REPO_FILE%/*} + ;; + *) + pccr_directory=. + ;; + esac + pccr_file="$pccr_directory/qcom-testkit-camx-clo.repo" + pccr_missing="" + for pccr_id in clo-rpm-aarch64 clo-rpm-noarch; do + if awk '{print $1}' "$pccr_repo_log" | grep -Fxq "$pccr_id"; then + pkg_log_info "Preserving enabled CAMX repository $pccr_id" + continue + fi + for pccr_existing in "$pccr_directory"/*.repo; do + [ -f "$pccr_existing" ] || continue + if grep -Eq "^[[:space:]]*\\[$pccr_id\\][[:space:]]*([#;].*)?$" "$pccr_existing"; then + pkg_log_fail "CAMX repository $pccr_id is defined but not enabled or readable by DNF. Correct $pccr_existing before retrying, no existing configuration was overwritten" + return 1 + fi + done + pccr_missing="$pccr_missing $pccr_id" + done + if [ -n "$pccr_missing" ]; then + if [ -e "$pccr_file" ] || [ -L "$pccr_file" ]; then + pkg_log_fail "Refusing to overwrite existing incomplete CAMX repository file $pccr_file" + return 1 + fi + if ! pkg_can_install; then + pkg_log_fail "CAMX repository preparation requires root and enabled package recovery" + return 1 + fi + pccr_temp="$(mktemp "${TMPDIR:-/tmp}/camx-clo-repositories.XXXXXX")" || return 1 + for pccr_id in $pccr_missing; do + pccr_type=${pccr_id#clo-rpm-} + if ! printf '%s\n' \ + "[$pccr_id]" \ + "name=Qualcomm Linux CLO RPM Repository - CentOS Stream 10 ($pccr_type)" \ + "baseurl=https://artifacts.codelinaro.org/artifactory/qli-rpm/centos/10/os/$pccr_type/" \ + 'enabled=1' \ + 'gpgcheck=0' \ + 'priority=10' \ + '' >>"$pccr_temp"; then + rm -f "$pccr_temp" + return 1 + fi + done + if ! install -m 0644 "$pccr_temp" "$pccr_file"; then + rm -f "$pccr_temp" + pkg_log_fail "Could not create CAMX CLO repository file $pccr_file" + return 1 + fi + rm -f "$pccr_temp" + pkg_log_info "Created CAMX CLO repository definitions in $pccr_file" + fi + if ! run_with_managed_timeout 60 "$pccr_logs" camx-repolist \ + "$pccr_tool" -C repolist >"$pccr_repo_log" 2>&1; then + cat "$pccr_repo_log" + pkg_log_fail "Could not verify enabled CAMX repositories, inspect $pccr_repo_log" + return 1 + fi + for pccr_id in clo-rpm-aarch64 clo-rpm-noarch; do + if ! awk '{print $1}' "$pccr_repo_log" | grep -Fxq "$pccr_id"; then + pkg_log_fail "CAMX CLO repository is still unavailable: $pccr_id, inspect $pccr_directory" + return 1 + fi + done + rm -f "$PKG_RPM_UPDATED_MARK" || return 1 + pkg_rpm_update || return 1 + for pccr_id in clo-rpm-aarch64 clo-rpm-noarch; do + pccr_list_log="$pccr_logs/camx-$pccr_id-packages.log" + if ! run_with_managed_timeout 120 "$pccr_logs" "camx-$pccr_id-list" \ + "$pccr_tool" --refresh --disablerepo='*' --enablerepo="$pccr_id" \ + --setopt="$pccr_id.skip_if_unavailable=False" \ + repo-pkgs "$pccr_id" list >"$pccr_list_log" 2>&1; then + cat "$pccr_list_log" + pkg_log_fail "CAMX repository package listing failed: $pccr_id, inspect $pccr_list_log" + return 1 + fi + pkg_log_info "CAMX repository package listing retained in $pccr_list_log" + done + pkg_log_pass "CAMX RPM repositories are ready, EPEL, Qualcomm and both CLO repositories verified" + return 0 +} + # Refresh rpm metadata. pkg_rpm_update() { rpm_tool="$(pkg_rpm_tool || true)" diff --git a/Runner/utils/lib_system.sh b/Runner/utils/lib_system.sh index 36b5f14bf..ce7286abf 100755 --- a/Runner/utils/lib_system.sh +++ b/Runner/utils/lib_system.sh @@ -225,6 +225,38 @@ efi_mount_exists() { ' /proc/mounts 2>/dev/null } +# efi_ensure_mounted log_file +# Mount efivarfs on an EFI boot when absent, preserving an existing ro/rw +# mount. A mount created for overlay preparation is intentionally left mounted. +# Do not call from read-only setup validation. Return 0 when mounted as +# efivarfs, 2 when EFI runtime is absent, 1 on mount/type/verification failure. +efi_ensure_mounted() { + eem_log="$1" + if [ ! -d "$EFIVARFS_PATH" ]; then + log_warn "EFI runtime directory is unavailable: $EFIVARFS_PATH" + return 2 + fi + if ! efi_mount_exists; then + log_info "Mounting efivarfs at $EFIVARFS_PATH for overlay preparation" + if ! mount -t efivarfs none "$EFIVARFS_PATH" >"$eem_log" 2>&1; then + system_log_file_excerpt efivarfs-mount "$eem_log" 40 + log_fail "Could not mount efivarfs, check EFI runtime support and mount permissions" + return 1 + fi + fi + if command -v partition_get_mount_fstype >/dev/null 2>&1; then + eem_type=$(partition_get_mount_fstype "$EFIVARFS_PATH") + else + eem_type=$(awk -v p="$EFIVARFS_PATH" '$2 == p { print $3; exit }' /proc/mounts) + fi + if [ "$eem_type" != efivarfs ]; then + log_fail "Expected efivarfs at $EFIVARFS_PATH, detected ${eem_type:-no mounted filesystem}" + return 1 + fi + log_info "EFI runtime filesystem is ready at $EFIVARFS_PATH" + return 0 +} + # Return efivarfs mount options. # Prefer partition_get_mount_options from functestlib.sh when available. efi_mount_options() { @@ -348,6 +380,8 @@ efi_variable_list_contains() { # $2 - file that receives efivar list output # Diagnostic output is written to stderr so command-substitution callers only # receive the variable name. +# Return 0 for exactly one match, 2 for absence after a successful query, +# or 1 for command failure, invalid arguments or ambiguous matches. efi_find_variable_by_name() { efvbn_name="$1" efvbn_log_file="$2" @@ -376,6 +410,10 @@ efi_find_variable_by_name() { ' "$efvbn_log_file")" efvbn_count="$(printf '%s\n' "$efvbn_matches" | awk 'NF { count++ } END { print count + 0 }')" + if [ "$efvbn_count" -eq 0 ]; then + printf '%s\n' "EFI variable $efvbn_name is absent" >&2 + return 2 + fi if [ "$efvbn_count" -ne 1 ]; then printf '%s\n' "Expected one EFI variable named $efvbn_name, found $efvbn_count" >&2 return 1 @@ -385,41 +423,73 @@ efi_find_variable_by_name() { return 0 } -# Return success when an EFI variable printout contains a text payload. +# Compare a bounded EFI read with exact ASCII text or a complete overlay entry. # Args: # $1 - EFI variable name in GUID-Name form # $2 - expected text payload, without a trailing newline # $3 - file that receives efivar print output +# $4 - optional exact (default) or csv-token comparison mode +# Return 0 for a match (optional trailing NULs), 2 for a readable different +# payload, or 1 for read/parse failure. Retain raw diagnostics, never write EFI. +# csv-token validates a comma-separated overlay-name list and prints that list +# on both match and mismatch. Exact mode prints no data. Diagnostics use stderr. efi_text_variable_matches() { etvm_var_name="$1" etvm_value="$2" etvm_log_file="$3" + etvm_mode="${4:-exact}" etvm_data_file="" etvm_expected_bytes="" - etvm_expected_pattern="" + etvm_actual_bytes="" if [ -z "$etvm_var_name" ] || [ -z "$etvm_value" ] || [ -z "$etvm_log_file" ]; then - log_warn "efi_text_variable_matches requires variable name, value, and log file" + log_warn "efi_text_variable_matches requires variable name, value, and log file" >&2 return 1 fi + case "$etvm_mode" in + exact) + ;; + csv-token) + case "$etvm_value" in + *,*) + log_warn "Invalid requested EFI overlay name: $etvm_value" >&2 + return 1 + ;; + esac + if ! printf '%s\n' "$etvm_value" | LC_ALL=C awk ' + NR != 1 || $0 !~ /^[!-~]+$/ { bad=1 } + END { exit (bad || NR != 1) } + '; then + log_warn "Invalid requested EFI overlay name: $etvm_value" >&2 + return 1 + fi + ;; + *) + log_warn "Unsupported EFI comparison mode: $etvm_mode" >&2 + return 1 + ;; + esac if ! command -v efivar >/dev/null 2>&1; then - log_warn "efivar command is unavailable" + log_warn "efivar command is unavailable" >&2 return 1 fi - if ! efivar -n "$etvm_var_name" -p > "$etvm_log_file" 2>&1; then + etvm_log_directory=$(dirname "$etvm_log_file") + if ! run_with_managed_timeout 15 "$etvm_log_directory" efi-text-read \ + efivar -n "$etvm_var_name" -p > "$etvm_log_file" 2>&1; then + log_warn "EFI variable read failed, inspect $etvm_log_file" >&2 return 1 fi etvm_data_file="$(mktemp "${TMPDIR:-/tmp}/efivar_payload.XXXXXX" 2>/dev/null || true)" if [ -z "$etvm_data_file" ]; then - log_warn "Could not create temporary EFI variable payload" + log_warn "Could not create temporary EFI variable payload" >&2 return 1 fi if ! printf '%s' "$etvm_value" > "$etvm_data_file"; then - log_warn "Could not write temporary EFI variable payload" + log_warn "Could not write temporary EFI variable payload" >&2 rm -f "$etvm_data_file" return 1 fi @@ -428,19 +498,101 @@ efi_text_variable_matches() { rm -f "$etvm_data_file" if [ -z "$etvm_expected_bytes" ]; then - log_warn "Could not derive EFI variable payload bytes" + log_warn "Could not derive EFI variable payload bytes" >&2 return 1 fi - etvm_expected_pattern="$(printf '%s\n' "$etvm_expected_bytes" | sed 's/ /[[:space:]][[:space:]]*/g')" - grep -Eq "$etvm_expected_pattern" "$etvm_log_file" + etvm_expected_bytes=$(printf '%s' "$etvm_expected_bytes" | tr -d ' ') + etvm_actual_bytes=$(awk ' + { + sub(/\r$/, "") + } + /^[[:space:]]*Value:[[:space:]]*$/ { + values++ + next + } + values && NF { + if (length($1) != 8 || $1 !~ /^[[:xdigit:]]+$/) { + bad=1 + next + } + rows++ + for (field=2; field<=NF; field++) { + if (substr($field, 1, 1) == "|") { + break + } + if (length($field) != 2 || $field !~ /^[[:xdigit:]]+$/) { + bad=1 + break + } + bytes=bytes tolower($field) + } + } + END { + if (values != 1 || !rows || bytes == "" || bad) { + exit 1 + } + print bytes + } + ' "$etvm_log_file") || { + log_warn "EFI variable payload could not be parsed, inspect $etvm_log_file" >&2 + return 1 + } + if [ "$etvm_mode" = csv-token ]; then + etvm_list=$(printf '%s\n' "$etvm_actual_bytes" | awk ' + { + digits="0123456789abcdef" + for (offset=1; offset<=length($0); offset+=2) { + pair=substr($0, offset, 2) + if (pair == "00") { + padding=1 + continue + } + if (padding) { + exit 1 + } + byte=(index(digits, substr(pair, 1, 1))-1)*16 + \ + index(digits, substr(pair, 2, 1))-1 + if (byte < 33 || byte > 126) { + exit 1 + } + character=sprintf("%c", byte) + value=value character + } + if (value == "" || value ~ /^,|,$|,,/) { + exit 1 + } + print value + } + ') || { + log_warn "Invalid EFI overlay list, inspect $etvm_log_file" >&2 + return 1 + } + printf '%s\n' "$etvm_list" + case ",$etvm_list," in + *,"$etvm_value",*) + return 0 + ;; + esac + return 2 + fi + case "$etvm_actual_bytes" in + "$etvm_expected_bytes"*) + etvm_padding=${etvm_actual_bytes#"$etvm_expected_bytes"} + while [ "${etvm_padding#00}" != "$etvm_padding" ]; do + etvm_padding=${etvm_padding#00} + done + [ -z "$etvm_padding" ] && return 0 + ;; + esac + return 2 } # Write a text payload to an EFI variable and verify its printed byte value. # Args: # $1 - EFI variable name in GUID-Name form # $2 - text payload, written without a trailing newline -# $3 - file that receives efivar write and print output +# $3 - write log, with separate .readback log for verification # The caller is responsible for arranging efi_restore_efivarfs_ro during # cleanup when efi_try_remount_rw changes the mount state. efi_write_text_variable() { @@ -476,7 +628,9 @@ efi_write_text_variable() { return 1 fi - if ! efivar -n "$ewtv_var_name" -w -f "$ewtv_data_file" > "$ewtv_log_file" 2>&1; then + ewtv_log_directory=$(dirname "$ewtv_log_file") + if ! run_with_managed_timeout 15 "$ewtv_log_directory" efi-text-write \ + efivar -n "$ewtv_var_name" -w -f "$ewtv_data_file" > "$ewtv_log_file" 2>&1; then log_warn "efivar write failed for $ewtv_var_name" rm -f "$ewtv_data_file" return 1 @@ -487,7 +641,7 @@ efi_write_text_variable() { if ! efi_text_variable_matches \ "$ewtv_var_name" \ "$ewtv_value" \ - "$ewtv_log_file"; then + "$ewtv_log_file.readback"; then log_warn "EFI variable printout does not contain the requested payload" return 1 fi From 317901574457d0a29bb1a7b70c7e6df76ae547fe Mon Sep 17 00:00:00 2001 From: Srikanth Muppandam Date: Thu, 22 Oct 2026 14:27:45 +0530 Subject: [PATCH 2/2] camera: strengthen libcamera and NHX validation Use shared helpers for runtime checks and read-only NHX setup with separate YAML and results. Keep base runs image-provided and explicit overlay setup responsible for missing CAMX packages and EFI selection. Reuse complete package sets without repository refresh or installation. Continue on an existing EFI selection, track a pending reboot without repeated writes and report read or post-reboot failures with retained diagnostics. Preserve NHX wrappers and JSON configurations while adding explicit native-HAL testcase, executable, frame, camera and dump-directory selections. Skip native capture without a known testcase and reject mixed native and JSON requests. Require successful execution, matching test results, fresh valid dumps and owned-service restoration. Document native validation limits, unchanged Kodiak streams and repeat-setup behavior. Depends on the preceding shared-helper commit. Revert this consumer integration before its shared foundation. Signed-off-by: Srikanth Muppandam --- .../Camera/Camera_NHX/Camera_NHX_Setup.yaml | 15 + .../Camera/Camera_NHX/Kodiak/README.md | 46 ++ .../Camera/Camera_NHX/README_Camera_NHX.md | 345 ++++++++- .../Multimedia/Camera/Camera_NHX/run.sh | 682 ++++++++++++++---- .../Libcamera_cam/libcamera_test_README.md | 72 +- .../Multimedia/Camera/Libcamera_cam/run.sh | 305 +++++--- 6 files changed, 1150 insertions(+), 315 deletions(-) create mode 100644 Runner/suites/Multimedia/Camera/Camera_NHX/Camera_NHX_Setup.yaml create mode 100644 Runner/suites/Multimedia/Camera/Camera_NHX/Kodiak/README.md diff --git a/Runner/suites/Multimedia/Camera/Camera_NHX/Camera_NHX_Setup.yaml b/Runner/suites/Multimedia/Camera/Camera_NHX/Camera_NHX_Setup.yaml new file mode 100644 index 000000000..2bb489082 --- /dev/null +++ b/Runner/suites/Multimedia/Camera/Camera_NHX/Camera_NHX_Setup.yaml @@ -0,0 +1,15 @@ +metadata: + name: camera_nhx_setup + format: "Lava-Test Test Definition 1.0" + description: "Read-only NHX runtime readiness, not camera capture validation" + os: + - yocto + scope: + - functional + +run: + steps: + - REPO_PATH=$PWD + - cd Runner/suites/Multimedia/Camera/Camera_NHX + - ./run.sh --check-setup + - $REPO_PATH/Runner/utils/send-to-lava.sh Camera_NHX_Setup.res diff --git a/Runner/suites/Multimedia/Camera/Camera_NHX/Kodiak/README.md b/Runner/suites/Multimedia/Camera/Camera_NHX/Kodiak/README.md new file mode 100644 index 000000000..ca7c1f034 --- /dev/null +++ b/Runner/suites/Multimedia/Camera/Camera_NHX/Kodiak/README.md @@ -0,0 +1,46 @@ +# Kodiak NHX configurations + +These four files configure `NativeHalX.OmniTest`. They can be selected through +an NHX wrapper or a nativehaltest client supporting JSON basenames via `--test`. +The runner passes the selected configuration without translating its settings +into an unrelated native testcase. + +| JSON | Streams configured | +| --- | --- | +| `Preview_YUVNV12_MaxResolution_NHX.json` | One format-35 preview output | +| `Video_YUVNV12_MaxResolution_NHX.json` | One format-34 video output | +| `Prev_plus_Video_YUVNV12_MaxResolution_NHX.json` | Format-35 preview and format-34 video outputs | +| `Snapshot_YUVNV12_MaxResolution_NHX.json` | Format-35 preview and format-33 snapshot outputs | + +All four retain their existing camera `0`, 150-frame, maximum-resolution, +stream usage/data-space and image-dump settings. In particular, the video JSON +uses format 34 despite its historical YUVNV12 filename. Renaming or translating +these fields would change existing coverage and is not part of native-HAL +support. + +Use the same JSON selection with either an NHX wrapper or a native executable: + +```sh +./run.sh --json Preview_YUVNV12_MaxResolution_NHX.json --target Kodiak +./run.sh --json Video_YUVNV12_MaxResolution_NHX.json --target Kodiak +./run.sh --json Prev_plus_Video_YUVNV12_MaxResolution_NHX.json --target Kodiak +./run.sh --json Snapshot_YUVNV12_MaxResolution_NHX.json --target Kodiak +``` + +Run these commands from the parent `Camera_NHX` directory. The folder name is +an explicit JSON selection, not automatic board or camera detection. + +When only `nativehaltest` is installed, the runner stages the selected JSON at +`/etc/camera/test/NHX/.json` and invokes `--test ` without +the extension. To choose native execution when a wrapper is also installed: + +```sh +./run.sh --json Preview_YUVNV12_MaxResolution_NHX.json --target Kodiak --native-bin /usr/libexec/camx-kodiak/nativehaltest +``` + +The executable path above is an explicit override, automatic discovery does +not hardcode Kodiak. JSON settings, including camera and frame count, are not +overridden by native CLI defaults. Do not combine JSON with `--native-test`, +`--native-camera` or `--native-frames`. Wrapper staging and arguments remain +unchanged. Native execution still requires a successful report and fresh, +nonempty capture dumps with valid checksums. diff --git a/Runner/suites/Multimedia/Camera/Camera_NHX/README_Camera_NHX.md b/Runner/suites/Multimedia/Camera/Camera_NHX/README_Camera_NHX.md index 8cd42f819..047bce342 100644 --- a/Runner/suites/Multimedia/Camera/Camera_NHX/README_Camera_NHX.md +++ b/Runner/suites/Multimedia/Camera/Camera_NHX/README_Camera_NHX.md @@ -1,6 +1,6 @@ # Camera_NHX -Camera NHX validation test for the Qualcomm CAMX proprietary camera stack. This test runs the available NHX launcher (`nhx.sh` or `camera-nhx`), collects generated image dumps, validates dumps (existence + non-zero size), and produces a PASS/FAIL `.res` file suitable for LAVA gating. +Camera validation test for the Qualcomm CAMX proprietary camera stack. This test prefers the available NHX launcher (`nhx.sh` or `camera-nhx`) and also supports an explicitly selected native-HAL testcase. It collects generated image dumps, validates dumps (existence, non-zero size and checksums), and produces a PASS/FAIL `.res` file suitable for LAVA gating. The test supports the legacy/default NHX flow, optional target-specific JSON selection for preview, video, preview+video, and snapshot validation, and an opt-in desktop CAMX package and FIT-DTB selection flow. @@ -57,15 +57,68 @@ Snapshot JSON files are currently expected only for targets where the files are ## What this test does +### NHX setup-only check + +Run `./run.sh --check-setup` to validate NHX runtime prerequisites without +launching NHX, capturing frames, changing camera services, installing packages, +writing EFI variables or rebooting. This mode writes `Camera_NHX_Setup.res`, +not a capture PASS. `Camera_NHX_Setup.yaml` publishes that separate result. +Do not combine it with `--overlay`, `--fit-dtb`, `--json` or `--target`, including +`NHX_JSON`/`NHX_TARGET` environment selections. + +On CentOS it checks the core CAMX packages from the existing package map, +the image-provided `efivar` utility and the `camx` EFI selection. It also retains +cached DNF listings for EPEL, both Qualcomm repositories and both CLO +repositories. Missing repositories or cache are provisioning warnings, not a +reason to fail an already installed offline NHX runtime. No repository is added, +enabled or refreshed in this mode. Use the explicit `--overlay` mode below for +repository and package recovery. If configured repositories cannot supply +`efivar`, the image owner must provision it with matching libraries. This test +does not access private shares or use `--allowerasing`. + +The remaining checks reuse the normal launcher, checksum, active CAMX DT, +loaded camera module, ICP firmware and package prerequisites. Missing runtime +prerequisites produce SKIP, package-query failures/timeouts produce FAIL. +PASS confirms NHX readiness only, not physical sensor or capture functionality. +Inspect stdout and `logs/package-*.log`, `logs/repositories.log` and +`logs/repository-*.log` for the CentOS package/repository evidence. + +Direct NHX is intentionally not gated on GStreamer, QMMF, `sscrpcd` or an active +`qti-cam-server`. Those belong to camera-service/GStreamer integration testing. +An initially inactive or absent camera server stays that way. If NHX stops an +active camera server, it must restore it and verify the active state. A start +failure, timeout or inactive post-start state makes the final result FAIL. +Exit and signal cleanup attempt restoration only for the service owned by this +run. Service start is bounded to 60 seconds and its output is retained in +`logs/service-restore.log`. + +Examples: + +```sh +# Read-only readiness, no package/EFI/service mutations +./run.sh --check-setup + +# Execute NHX against the image-provided runtime +./run.sh + +# Existing explicit overlay preparation, manual reboot if requested by the log +./run.sh --overlay +``` + 1. **Environment setup** - Locates and sources `init_env` - Sources `functestlib.sh` and `lib_camera.sh` 2. **Dependency checks** - Uses `check_dependencies` from `functestlib.sh` to ensure required commands exist. + - Uses `dtc` when available. `diff` is optional and only compares checksums with a previous run, its absence does not skip NHX or weaken current-run dump validation. + - Requires one image-provided checksum utility, selected in order: `sha256sum`, `md5sum`, then `cksum`. It does not require all three. If none is available, the suite skips before launching NHX and identifies the missing prerequisite. + - The CentOS `--overlay` package set includes `dtc` and `diffutils`, which provide `dtc`, `fdtdump`, and `diff`. + - Direct runs without `--overlay` do not install packages. This dependency policy is the same on Yocto, Debian, Ubuntu and CentOS. 3. **CAMX proprietary prechecks** - - Device-tree presence checks for CAMX/camera patterns, including downstream CAMX-style nodes such as: + - Decodes the active runtime tree with `dtc -I fs -O dts` from the resolved `/sys/firmware/devicetree/base` or `/proc/device-tree` root. + - Uses enabled runtime nodes, rather than text matches in the full DTS, as authoritative CAMX evidence. Downstream CAMX-style nodes include: - `qcom,cam-sensor` - `qcom,cam-gmsl-sensor` - `qcom,cam-gmsl-deserializer` @@ -74,7 +127,8 @@ Snapshot JSON files are currently expected only for targets where the files are - `qcom,csiphy` - `qcom,cam-tpg1031` - `qcom,camera` - - `fdtdump` scan for camera-related nodes through `lib_camera.sh` + - Retains line-numbered camera and sensor matches from the decoded DTS for diagnosis. These artifacts can include disabled nodes and are not used alone to declare the CAMX runtime active. + - Uses `fdtdump` only as optional secondary raw-FDT evidence. - Camera kernel module detection and loaded-state validation, including `camera_x1e80100` on Glymur - ICP firmware presence check (`CAMERA_ICP`) - `dmesg` scan for camera warnings/errors @@ -94,40 +148,60 @@ Snapshot JSON files are currently expected only for targets where the files are - Dump list is non-empty - Each dump file exists - Each dump file size is greater than `0` bytes - - Optionally generates checksums (`sha256sum`, `md5sum`, or `cksum`) and writes checksum files. + - Generates checksums using the selected utility and writes checksum files. A failed checksum command fails validation even if it prints partial output. + - Previous-run comparison is advisory. Without `diff` it is omitted with an INFO message. A comparison error is reported separately from changed checksums and does not fail current-run validation. 6. **Result decision** - Parses `Final Report -> [X PASSED] [Y FAILED] [Z SKIPPED]` from the NHX log. - FAIL if: - Final Report is missing or unparseable + - The NHX launcher exits nonzero, even if a success summary was printed + - No NHX tests passed - NHX reports `FAILED > 0` - No dumps are detected - Any dump is missing or zero bytes - Dump checksum validation helper fails + - Restoration of a camera server stopped by the test fails - Writes final result to: `Camera_NHX.res` --- ## Desktop CAMX overlay flow -Yocto and meta-qcom LAVA images keep their image-provided camera stack and do -not use this flow. On Debian and Ubuntu, an explicit overlay request installs -the `camera-nhx` package set: +On Debian and Ubuntu, an explicit overlay request ensures the complete mapped +`camera-nhx` package set: ```text camx-dkms camx-glymur libcamx-glymur1 camx-firmware-glymur camx-nhx ``` -On CentOS Stream 10, the runner follows the Qualcomm overlay installation -guide and installs the Kodiak package set: +On CentOS Stream 10, the runner ensures the complete mapped Kodiak package set: ```text -camx-dkms camx-firmware-kodiak camx-kodiak libcamx-kodiak1 -``` - -Before a missing CentOS package is installed, the shared package provider -ensures EPEL and both `qualcomm-linux-aarch64` and -`qualcomm-linux-noarch` repositories are enabled, then refreshes DNF metadata. +camx-dkms camx-firmware-kodiak camx-kodiak libcamx-kodiak1 dtc diffutils +``` + +Ubuntu uses the APT sources already configured in the image. Debian may use the +Qualcomm `qli-staging` source. CentOS prepares EPEL and the Qualcomm Linux +`aarch64` and `noarch` RPM repositories plus `clo-rpm-aarch64` and +`clo-rpm-noarch` before recovery when the mapped CAMX package set is incomplete. +When the complete set is already installed, the runner reuses it without +repository preparation, metadata refresh or package installation. An absent +`efivar` may still require the existing explicit-overlay command recovery. +The CLO repositories use +`https://artifacts.codelinaro.org/artifactory/qli-rpm/centos/10/os/`, with +`aarch64/` and `noarch/` respectively, `priority=10` and `gpgcheck=0` as specified +by the LRH setup procedure. Missing CLO definitions are written to a separate +`qcom-testkit-camx-clo.repo` alongside the Qualcomm repository file. Existing +enabled definitions are preserved. Disabled or conflicting definitions require +administrator correction and are not overwritten. Metadata is refreshed and +both CLO package listings are verified, with diagnostics retained in +`logs/camx-*-repositories.log` and `logs/camx-*-packages.log`. +Yocto does not use +this package-recovery flow and continues with its image-provided camera stack. +The runner passes the complete mapped package list to the shared package +provider instead of selecting a suite-local subset. If package recovery fails, +the explicit `--overlay` request reports FAIL with the complete required list. `--overlay` requires the CAMX FIT DTB and uses the compatibility name `camx`. The same requirement can be stated explicitly with `--fit-dtb camx`: @@ -136,11 +210,26 @@ The same requirement can be stated explicitly with `--fit-dtb camx`: ./run.sh --overlay --fit-dtb camx ``` +On Debian, Ubuntu and CentOS, explicit overlay preparation first reuses an +installed `efivar` or recovers the mapped package from configured repositories. +It mounts `efivarfs` at the runtime EFI directory only if not already mounted, +then verifies the filesystem type. A newly created mount remains mounted. +Existing read-only mounts are restored to read-only after EFI write handling. +An unavailable EFI runtime produces SKIP, while package recovery or mount +failures produce FAIL with diagnostics. A failed `efivar` query or duplicate +`VendorDtbOverlays` variables produces FAIL, not a missing-variable SKIP. +Base and setup-only runs do not install +`efivar` or mount efivarfs. + The test discovers the platform's `VendorDtbOverlays` EFI variable with `efivar -l`. The EFI variable GUID is never supplied by the user or hardcoded -in the test. It verifies that the payload is `camx` through `efivar -p`. If it -is not selected, the test writes `camx` without a trailing newline, reads the -variable back, and synchronizes storage. +in the test. It verifies the complete payload through a bounded `efivar -p` +read, accepting `camx` with optional trailing NUL bytes. Read errors and +unrecognized output fail without writing EFI. A confirmed different value +permits an initial write of `camx` without a trailing newline, followed by +readback and storage synchronization. Raw before, write and readback evidence +is retained separately under `logs/Camera_NHX_camx_efi_*.log`, with `.write` +and `.write.readback` suffixes, and replayed to stdout. When a new DTB selection is written, the test records `Camera_NHX SKIP` with a reboot-required message instead of rebooting within the LAVA test shell. Reboot @@ -152,11 +241,48 @@ reboot ./run.sh --overlay --fit-dtb camx ``` +The EFI value is a comma-separated overlay list. A complete `camx` entry in +`camx,el2gh` or `el2gh,camx` counts as selected, but `camx2` and `xcamx` do not. +There is no fixed entry count, order or allowlist of other overlay names. +Existing entries are preserved when adding a missing selection. Printable +ASCII names are treated as opaque entries, separated by commas. Malformed +lists and unreadable EFI values fail without a write. This list comparison is +limited to CAMX selection, other EFI comparisons and immediate write readback +still require the exact payload. + If the CAMX FIT DTB is already selected, the test continues with runtime DT and -camera-module checks. A loaded board-specific camera module is used as runtime -evidence that the current boot has activated the CAMX camera stack. If EFI is -set to `camx` but the module is not loaded, the test records SKIP and asks the -operator to reboot manually. The test never issues a reboot itself. +camera-module checks without rewriting EFI. The read-only `--check-setup` path +uses the same complete-entry comparison. A newly attempted write records +the variable, requested value and kernel boot ID in +`logs/camx-fit-selection.pending`. A repeat on the same boot only reminds the +operator to reboot, it does not rewrite the variable. After reboot, the runner +requires both the requested entry in EFI and enabled CamX runtime DT nodes before +retiring that record and proceeding. A missing entry, missing runtime nodes or +invalid pending record produces FAIL, not another automatic setup attempt. +The record is not a cached setup-success flag and never bypasses live checks. +Keep it across the manual reboot. After a failed write or firmware-selection +failure, inspect the retained diagnostics and correct the cause before +explicitly removing this one pending file to retry setup. Do not remove it +merely to bypass a failed post-reboot check. + +Qualcomm UEFI uses SoC and board identifiers from CDT to +select a compatible FIT configuration. Linux does not expose the firmware's +raw CDT decision directly, so the test validates its result: EFI selection, +the decoded active runtime tree, enabled downstream CamX nodes, and the loaded +board-specific camera module. This is generic across FIT-enabled Qualcomm SoCs +and does not hardcode a SoC-to-DTB table in the test. + +The implementation follows the public Qualcomm FIT metadata flow documented in +[`qcom-dtb-metadata`](https://github.com/qualcomm-linux/qcom-dtb-metadata/blob/main/Documentation.md) +and generated by the public +[`meta-qcom` FIT image class](https://github.com/qualcomm-linux/meta-qcom/blob/master/classes-recipe/dtb-fit-image.bbclass). + +If EFI is set to `camx` but the active runtime tree has no enabled downstream +CamX nodes, the test records FAIL because the selected FIT configuration and +the running tree disagree. If the enabled nodes are present but the camera +module artifact is present but the module is not loaded, the explicit EFI +selection path records FAIL and asks for module/probe diagnostics. It does not +repeatedly ask for another reboot. The test never issues a reboot itself. --- @@ -257,7 +383,7 @@ The NHX launcher does not accept an arbitrary absolute JSON path. It expects a J /etc/camera/test/NHX/${JSON_FILE}.json ``` -For this reason, when `--json` is used, `run.sh` resolves the source file from the test directory and stages it under: +For wrapper execution, `--json` resolves the source file from the test directory and stages it under: ```text /etc/camera/test/NHX//.json @@ -275,6 +401,31 @@ The same argument is used when legacy `nhx.sh` is selected. This causes the laun /etc/camera/test/NHX/Lemans/Prev_plus_Video_YUVNV12_MaxResolution_NHX.json ``` +If no wrapper is installed, the selected native executable receives the JSON +basename through `--test`. Native mode stages the file directly under +`/etc/camera/test/NHX/`, not its target subdirectory. `--target` still selects +the source folder. For example: + +```sh +./run.sh --json Preview_YUVNV12_MaxResolution_NHX.json --target Kodiak +# Native invocation when only nativehaltest is available: +# /usr/libexec/camx-kodiak/nativehaltest --test Preview_YUVNV12_MaxResolution_NHX + +# Explicit native selection even when a wrapper is installed: +./run.sh --json Kodiak/Preview_YUVNV12_MaxResolution_NHX.json --native-bin /usr/libexec/camx-kodiak/nativehaltest +./run.sh --overlay --json Kodiak/Preview_YUVNV12_MaxResolution_NHX.json +``` + +Native JSON execution preserves all JSON settings. It does not append frames, +camera, timeout or dump CLI overrides. A separate 180-second process deadline +still applies. Native JSON filenames must contain letters, digits, underscores +or hyphens, start with a letter or underscore, and end in `.json`. +An identical existing destination is reused unchanged. A different existing +file or a symlink produces FAIL rather than being overwritten. New files staged +by the run are removed after capture or during interruption cleanup, provided +their contents remain unchanged. Modified files are preserved and cleanup fails. +The selected JSON is retained under `logs/native-json.*/selected.json`. + The run log and summary include: ```text @@ -288,6 +439,128 @@ NHX JSON argument ## Command usage +### Native HAL executable mode + +Some CentOS CAMX packages provide only `nativehaltest`, for example +`camx-kodiak-1.0.45` installs `/usr/libexec/camx-kodiak/nativehaltest` without an +NHX wrapper. The runner preserves wrapper discovery order (`nhx.sh` in PATH, +`camera-nhx` in PATH, `/usr/libexec/camx/nhx.sh`). If no wrapper is found, it +discovers `nativehaltest` in PATH, then executable +`/usr/libexec/camx*/nativehaltest` files. This is a runtime path search, not a +board-to-path table. Symlinks to the same binary count once. Multiple different +binaries require `--native-bin /absolute/path/nativehaltest`. + +`--native-test Suite.Case` explicitly selects native mode even when a wrapper +exists. Use one exact testcase supported by the installed client, without +wildcards or multi-test filters. There is no guessed default testcase, automatic +full-binary run, or unadvertised test-list probe. Without a testcase or JSON, native +capture records an actionable SKIP. An unknown selected testcase, zero tests, +or a failed native CLI probe must not produce PASS. + +An explicit zero-test report fails with the requested testcase and executable +identified. Capture validation is not exercised in that case, so a missing +capture directory is not reported as a second failure. Obtain the exact +`Suite.Case` from the installed client's documentation or an advertised listing +interface, or use a supported `--json` configuration. The runner does not guess another testcase or run an unfiltered +suite. Help output is retained in `logs/native-hal-help.log`, but advertising +`--test` alone does not prove that a particular testcase is installed. + +Current dump-list and checksum artifacts are initialized even when captures +are absent. Other missing or invalid captures still fail validation, without +reading a nonexistent list or reusing an older run's list. + +On a native failure, the final diagnostic points to any camera warnings/errors +captured before invocation. A provisioned ICP firmware file does not prove it +loaded successfully. Historical boot errors are not attributed to this capture +or used alone to reject a successful capture. Likewise, `cam --list` describes +libcamera inventory, not the proprietary CAMX HAL's physical sensor inventory. + +Native-only options have no environment-variable equivalents: + +- `--native-test NAME`: exact `Suite.Case`, an alternative to `--json`. +- `--native-bin PATH`: absolute executable override, otherwise runtime discovery. +- `--native-frames N`: 1 through 10000, default 150. The total time limit still applies. +- `--native-camera ID`: optional nonnegative integer camera ID. When omitted, + leave selection to the installed client, whose supplied help defaults to + physical cameras. Do not infer camera ID 0 from a board name. +- `--native-dump-dir PATH`: absolute directory where this binary writes images, + default `/var/cache/camera/nativehaltest`. This selects the validation root, + it does not redirect the binary output. Confirm the directory from client + documentation or an observed capture before overriding it. + +Readiness can discover and inspect the native CLI without running a testcase: + +```sh +./run.sh --check-setup +./run.sh --check-setup --native-bin /usr/libexec/camx-kodiak/nativehaltest +``` + +For capture, substitute a verified installed testcase for the placeholder below: + +```sh +native_case='Suite.Case' # Replace with a testcase supported by this client. +./run.sh --native-test "$native_case" +./run.sh --native-test "$native_case" --native-frames 60 --native-camera 0 +./run.sh --native-test "$native_case" --native-bin /usr/libexec/camx-kodiak/nativehaltest --native-dump-dir /var/cache/camera/nativehaltest +./run.sh --overlay --native-test "$native_case" +``` + +The example camera ID is an explicit operator choice, not a detected default. +Native capture flags cannot be combined with `--check-setup`, except the +read-only `--native-bin` discovery override. Native JSON mode accepts the same +`--json`, `--target`, `NHX_JSON` and `NHX_TARGET` selections as the wrapper route. +Do not combine JSON with `--native-test`, `--native-frames` or `--native-camera`, +which would conflict with the configuration. These combinations fail before +overlay preparation. `--native-bin` and `--native-dump-dir` are allowed with JSON. + +The native CLI is checked with a bounded `--help` query. If it returns usage +status 1, 2 or 255 and advertises `--h`, the probe retries that option. Each query +has a 15-second limit. These nonzero statuses are accepted only when the output +contains usage text and all required capture options. Crashes, timeouts and +missing options still fail. When retried, the first response is retained in +`logs/native-hal-help-long.log` and the final response in +`logs/native-hal-help.log`. This probes help only, never the full test binary. +Explicit `Suite.Case` execution uses the advertised `--test` or `--gtest_filter` selector, +`--frames`, `--p` for stdout +and `--timeout 5000` for per-buffer/result waits. A separate 180-second managed +deadline bounds the whole capture. No `--nid` option is passed, image dumping +must remain enabled. Raw output is retained in the normal run log and replayed +to stdout when execution ends. Help evidence is retained in +`logs/native-hal-help.log`. Capture timeout, nonzero exit and service-restoration +failure all fail the result. Interrupted native captures stop their owned +process and timeout watcher before camera-service restoration. +JSON mode requires only the advertised `--test` option and invokes the verified +JSON basename, without the other capture options described above. + +Explicit `Suite.Case` result parsing requires exactly one matching `RUN` and `OK` testcase +marker plus either a completed single-test GoogleTest summary or one CAMX +`Final Report -> [1 PASSED] [0 FAILED] [0 SKIPPED]`. Missing or unfamiliar +result formats fail closed and require the retained native stdout for diagnosis, +they are not treated as successful capture. JSON mode accepts one CAMX report +with exactly one passed, zero failed and zero skipped tests. It does not require +GoogleTest-style markers that the JSON-backed native client may omit. If RUN/OK +markers are emitted, they must be a single matching pair, whose internal name +may differ from the JSON basename. Zero tests, multiple reports, partial markers +and reported failures remain FAIL. Process exit and fresh dump validation are +required in both modes, a summary alone is not a capture PASS. + +CAMX per-test lines such as `"NativeHalX.OmniTest" report -> [PASSED]` are +parsed separately from GoogleTest numeric totals such as `[ PASSED ] 1 test.`. +When present, exactly one successful per-test report is allowed and its name +must agree with any RUN/OK markers. Failed, skipped, malformed or duplicate +per-test reports still fail, even when the final summary claims success. + +Native capture also uses the existing fresh-file, nonempty-file and checksum +diversity validation. It permits untagged `.yuv`, `.raw`, `.bin`, `.jpg` and +`.jpeg` images in the selected output directory and its subdirectories, while +ignoring logs and stale files. Wrapper dump discovery and invocation are +unchanged. No packages are installed for native base runs. The existing Yocto +YAML interface remains unchanged. Its existing JSON selection also works when +runtime discovery finds only a native executable. + +The [Kodiak configuration notes](Kodiak/README.md) describe the stream settings +preserved by both JSON launch paths. + ```sh ./run.sh [--overlay] [--fit-dtb NAME] [--json JSON_FILE] [--target TARGET] [--help] ``` @@ -295,8 +568,9 @@ NHX JSON argument Options: ```text ---overlay Install the optional Camera NHX CAMX package set on Debian, - Ubuntu, or CentOS and ensure the CAMX FIT DTB is selected. +--overlay Verify the Camera NHX CAMX package set on Debian, Ubuntu, + or CentOS, install missing packages, and ensure the CAMX + FIT DTB is selected. --fit-dtb NAME Select NAME as the FIT DTB compatibility name for the next boot. Requires --overlay on a supported desktop distro. @@ -342,11 +616,17 @@ Created under the test directory: - Logs: - `logs/Camera_NHX_.log` + - `logs/Camera_NHX_dtc_.log` - `logs/dmesg_/dmesg_snapshot.log` - `logs/dmesg_/dmesg_errors.log` when matches exist - Out files: - `out/Camera_NHX_summary_.txt` + - `out/Camera_NHX_running_.dts`, when `dtc` is available + - `out/Camera_NHX_runtime_camera_.log`, when `dtc` is available + - `out/Camera_NHX_runtime_sensors_.log`, when `dtc` is available + - `out/Camera_NHX_enabled_camx_nodes_.log` + - `out/Camera_NHX_enabled_sensor_nodes_.log` - Dump list file, usually under `out/nhx_/` - Checksum file(s), when checksum tool/helper is available @@ -384,7 +664,9 @@ Run a selected NHX config: cat Camera_NHX.res ``` -This script is LAVA-friendly and exits `0` even on FAIL/SKIP; gating is via `.res`. +After environment initialization this script exits `0` on FAIL/SKIP, gating is +via `.res`. A missing repository bootstrap or result-initialization failure +exits nonzero instead of publishing stale results. --- @@ -456,26 +738,33 @@ run: - NHX `Final Report` parsed successfully - `FAILED=0` +- `PASSED` is greater than zero and the NHX launcher exited zero - Dump list is non-empty - All dump files exist and are non-zero - Dump checksum validation helper did not fail, when used +- Any camera server stopped by the test was successfully restored ### FAIL - Any PASS condition is not met +- Explicit `--overlay` package recovery fails or leaves the mapped package set incomplete +- EFI selects `camx`, but the active runtime DT has no enabled downstream CamX nodes +- EFI read/parse/write/readback fails, or a pending selection does not persist after reboot +- EFI selects `camx` and the camera module artifact exists, but the module is not loaded - NHX reports one or more failed cases - Final Report is missing/unparseable - No dumps are detected - Any dump is missing or zero bytes - Dump checksum validation helper fails +- The NHX launcher exits nonzero or reports no passing tests +- A camera server stopped by the test cannot be restored to active state ### SKIP - Missing CAMX prerequisites, such as DT patterns, camera module artifact/loaded state, ICP firmware, CAMX packages, or an executable NHX launcher (`nhx.sh` or `camera-nhx` in `PATH`, or `/usr/libexec/camx/nhx.sh`) - The CAMX FIT DTB selection was written and a manual reboot is required before validation -- EFI selects `camx`, but the board-specific camera module is not loaded, so a manual reboot is required - `VendorDtbOverlays` is unavailable when `--overlay` was requested -- `fdtdump` is not available or camera node evidence is inconclusive +- No enabled downstream CamX node exists when `camx` was not explicitly selected - Requested `--json` file is not found - Requested JSON filename is ambiguous and `--target` was not supplied - JSON staging under `/etc/camera/test/NHX` fails diff --git a/Runner/suites/Multimedia/Camera/Camera_NHX/run.sh b/Runner/suites/Multimedia/Camera/Camera_NHX/run.sh index c7f1cd78e..dc1456aba 100755 --- a/Runner/suites/Multimedia/Camera/Camera_NHX/run.sh +++ b/Runner/suites/Multimedia/Camera/Camera_NHX/run.sh @@ -3,8 +3,6 @@ # SPDX-License-Identifier: BSD-3-Clause # Camera NHX validation -TESTNAME="Camera_NHX" - SCRIPT_DIR="$( cd "$(dirname "$0")" || exit 1 pwd @@ -20,12 +18,9 @@ while [ "$SEARCH" != "/" ]; do SEARCH=$(dirname "$SEARCH") done -RES_FILE="$SCRIPT_DIR/${TESTNAME}.res" - if [ -z "${INIT_ENV:-}" ]; then echo "[ERROR] Could not find init_env (starting at $SCRIPT_DIR)" >&2 - echo "$TESTNAME SKIP" >"$RES_FILE" 2>/dev/null || true - exit 0 + exit 1 fi if [ -z "${__INIT_ENV_LOADED:-}" ]; then @@ -34,6 +29,8 @@ if [ -z "${__INIT_ENV_LOADED:-}" ]; then __INIT_ENV_LOADED=1 fi +# shellcheck disable=SC1090 +. "$INIT_ENV" # shellcheck disable=SC1090,SC1091 . "$TOOLS/functestlib.sh" @@ -48,6 +45,10 @@ if [ -r "$TOOLS/lib_pkg_provider.sh" ]; then . "$TOOLS/lib_pkg_provider.sh" fi +TESTNAME="Camera_NHX" +RES_FILE="$SCRIPT_DIR/${TESTNAME}.res" +test_result_init "$TESTNAME" "$RES_FILE" || exit 1 + LOG_DIR="$SCRIPT_DIR/logs" OUT_DIR="$SCRIPT_DIR/out" DUMP_DIR="/var/cache/camera/nativehaltest" @@ -60,6 +61,12 @@ SUMMARY_TXT="$OUT_DIR/${TESTNAME}_summary_${TS}.txt" DMESG_DIR="$LOG_DIR/dmesg_${TS}" CAMX_EFI_LOG="$LOG_DIR/${TESTNAME}_camx_efi_${TS}.log" CAMX_EFI_LIST_LOG="$LOG_DIR/${TESTNAME}_camx_efi_list_${TS}.log" +RUNNING_DTS="$OUT_DIR/${TESTNAME}_running_${TS}.dts" +RUNNING_DTS_WARNINGS="$LOG_DIR/${TESTNAME}_dtc_${TS}.log" +CAMERA_DTS_MATCHES="$OUT_DIR/${TESTNAME}_runtime_camera_${TS}.log" +SENSOR_DTS_MATCHES="$OUT_DIR/${TESTNAME}_runtime_sensors_${TS}.log" +CAMX_DT_NODES_LOG="$OUT_DIR/${TESTNAME}_enabled_camx_nodes_${TS}.log" +SENSOR_DT_NODES_LOG="$OUT_DIR/${TESTNAME}_enabled_sensor_nodes_${TS}.log" NHX_OUTDIR="$OUT_DIR/nhx_${TS}" @@ -76,11 +83,28 @@ fi CAM_SERVER_PRESENT=0 CAM_SERVER_SERVICE="" CAM_SERVER_STOPPED_FOR_TEST=0 +CAM_SERVER_RESTORE_FAILED=0 +NHX_CHECK_SETUP=0 NHX_JSON="${NHX_JSON:-}" NHX_TARGET="${NHX_TARGET:-}" NHX_RUNNER="" NHX_RUNNER_SOURCE="" +NHX_RUNNER_MODE="wrapper" +NATIVE_REQUESTED=0 +NATIVE_TEST="" +NATIVE_BIN="" +NATIVE_FRAMES=150 +NATIVE_FRAMES_EXPLICIT=0 +NATIVE_CAMERA="" +NATIVE_DUMP_OVERRIDE="" +NATIVE_SELECTOR="" +NATIVE_REPORT_VALID=1 +NATIVE_NO_TESTS=0 +NATIVE_MODE="test" +NATIVE_JSON_STATE="" +NATIVE_JSON_RESTORE_FAILED=0 +NATIVE_CAPTURE_ACTIVE=0 NHX_JSON_RESOLVED="" NHX_JSON_ARG="" OVERLAY_REQUESTED=0 @@ -90,29 +114,51 @@ CAMX_FIT_DTB_SELECTED=0 # shellcheck disable=SC2317 cleanup() { + trap - EXIT INT TERM + if [ "$NATIVE_CAPTURE_ACTIVE" -eq 1 ]; then + if ! nhx_stop_native_capture; then + test_result_finish "FAIL" "$TESTNAME interrupted native capture could not be stopped safely" + fi + fi + if [ -n "${NATIVE_JSON_STATE:-}" ]; then + if ! nhx_restore_native_json "$NATIVE_JSON_STATE"; then + NATIVE_JSON_RESTORE_FAILED=1 + fi + fi if command -v efi_restore_efivarfs_ro >/dev/null 2>&1; then efi_restore_efivarfs_ro >/dev/null 2>&1 || true fi if [ "$CAM_SERVER_STOPPED_FOR_TEST" -eq 1 ] && [ "$CAM_SERVER_PRESENT" -eq 1 ]; then - systemd_service_start_safe "$CAM_SERVER_SERVICE" >/dev/null 2>&1 || true + if ! nhx_restore_camera_service "$CAM_SERVER_SERVICE" "$LOG_DIR"; then + test_result_finish "FAIL" "$TESTNAME FAIL camera-server restoration failed: $CAM_SERVER_SERVICE" + fi fi if [ -n "${MARKER:-}" ]; then rm -f "$MARKER" fi + if [ "${NATIVE_JSON_RESTORE_FAILED:-0}" -ne 0 ]; then + test_result_finish "FAIL" "$TESTNAME native JSON cleanup failed, see $NATIVE_JSON_STATE" + fi } -trap 'cleanup' EXIT INT TERM +trap 'cleanup' EXIT +trap 'test_result_finish "FAIL" "$TESTNAME interrupted, restoring owned camera state"' INT TERM usage() { cat </dev/null 2>&1; then + test_result_finish "FAIL" "$TESTNAME requires the repository package-provider helper" + fi + if [ "$(pkg_detect_os_id)" = centos ]; then + nhx_check_rpm_setup "$LOG_DIR" + NHX_SETUP_RPM_STATUS=$? + case "$NHX_SETUP_RPM_STATUS" in + 0) + ;; + 2) + test_result_finish "SKIP" "$TESTNAME requires the image-provided CAMX core package set, inspect NHX-PACKAGE diagnostics" + ;; + *) + test_result_finish "FAIL" "$TESTNAME could not query CAMX packages, inspect NHX-PACKAGE diagnostics" + ;; + esac + if ! command -v efivar >/dev/null 2>&1; then + test_result_finish "SKIP" "$TESTNAME requires image-provided efivar and its matching libraries, private RPM installation is not automated" + fi + NHX_SETUP_EFI=$(efi_find_variable_by_name VendorDtbOverlays "$CAMX_EFI_LIST_LOG") + NHX_SETUP_EFI_STATUS=$? + if [ "$NHX_SETUP_EFI_STATUS" -ne 0 ] && [ "$NHX_SETUP_EFI_STATUS" -ne 2 ]; then + log_file_with_label "NHX-EFI" "$CAMX_EFI_LIST_LOG" + test_result_finish "FAIL" "$TESTNAME could not discover EFI variables, check efivar runtime libraries and EFI permissions" + fi + if [ "$NHX_SETUP_EFI_STATUS" -eq 2 ]; then + test_result_finish "SKIP" "$TESTNAME requires firmware exposing VendorDtbOverlays for CAMX selection" + fi + efi_text_variable_matches "$NHX_SETUP_EFI" camx "$CAMX_EFI_LOG" csv-token >/dev/null + NHX_SETUP_EFI_STATUS=$? + log_file_with_label NHX-EFI "$CAMX_EFI_LOG" + case "$NHX_SETUP_EFI_STATUS" in + 0) + ;; + 2) + test_result_finish "SKIP" "$TESTNAME requires camx in the EFI VendorDtbOverlays list, provision the overlay and reboot before checking runtime readiness" + ;; + *) + test_result_finish "FAIL" "$TESTNAME could not read or parse VendorDtbOverlays, inspect $CAMX_EFI_LOG. No EFI changes attempted" + ;; + esac + fi +fi if [ "$OVERLAY_REQUESTED" -eq 1 ]; then if [ -z "$FIT_DTB_NAME" ]; then @@ -216,6 +416,8 @@ fi if [ "$OVERLAY_REQUESTED" -eq 1 ]; then for required_helper in \ pkg_provider_init \ + pkg_detect_os_id \ + pkg_lookup_package_set \ pkg_ensure_optional_package_set_present \ pkg_verify_package_set_installed; do if ! command -v "$required_helper" >/dev/null 2>&1; then @@ -226,26 +428,52 @@ if [ "$OVERLAY_REQUESTED" -eq 1 ]; then done pkg_provider_init + CAMX_OVERLAY_OS_ID="$(pkg_detect_os_id 2>/dev/null || true)" + CAMX_APT_SOURCE="none" - if ! pkg_ensure_optional_package_set_present \ - camera-nhx \ - qli-staging \ - auto \ - --overlay; then - log_fail "$TESTNAME FAIL - failed to ensure Camera NHX CAMX package set" - echo "$TESTNAME FAIL" >"$RES_FILE" + if [ "$CAMX_OVERLAY_OS_ID" = "debian" ]; then + CAMX_APT_SOURCE="qli-staging" + fi + + CAMX_PACKAGES="$(pkg_lookup_package_set camera-nhx 2>/dev/null || true)" + if [ -z "$CAMX_PACKAGES" ]; then + log_skip "$TESTNAME SKIP - no Camera NHX package-set mapping is available, os=${CAMX_OVERLAY_OS_ID:-unknown} action=add-a-camera-nhx-package-set-mapping-for-this-os" + echo "$TESTNAME SKIP" >"$RES_FILE" exit 0 fi + if pkg_verify_package_set_installed camera-nhx; then + log_info "Reusing installed CAMX package set, no repository refresh or package installation required" + else + if [ "$CAMX_OVERLAY_OS_ID" = centos ]; then + if ! pkg_prepare_camx_rpm_repositories "$LOG_DIR"; then + test_result_finish "FAIL" "$TESTNAME CAMX repository preparation failed, inspect EPEL, Qualcomm and CLO diagnostics" + fi + fi + log_info "Ensuring complete Camera NHX package set, os=${CAMX_OVERLAY_OS_ID:-unknown} packages=$CAMX_PACKAGES" + if ! pkg_ensure_optional_package_set_present \ + camera-nhx \ + "$CAMX_APT_SOURCE" \ + auto \ + --overlay; then + test_result_finish "FAIL" "$TESTNAME --overlay package-set installation failed, check configured repositories for $CAMX_PACKAGES" + fi + if ! pkg_verify_package_set_installed camera-nhx; then + test_result_finish "FAIL" "$TESTNAME CAMX package recovery left an incomplete package set: $CAMX_PACKAGES" + fi + fi + log_pass "Camera NHX CAMX package set is ready" - CAMX_OVERLAY_OS_ID="$(pkg_detect_os_id 2>/dev/null || true)" case "$CAMX_OVERLAY_OS_ID" in debian|ubuntu|centos) for required_helper in \ + pkg_ensure_command \ + efi_ensure_mounted \ efi_find_variable_by_name \ efi_text_variable_matches \ efi_write_text_variable \ + camx_ensure_fit_selection \ efi_restore_efivarfs_ro; do if ! command -v "$required_helper" >/dev/null 2>&1; then log_fail "$TESTNAME FAIL - required EFI helper is unavailable: $required_helper" @@ -254,85 +482,130 @@ if [ "$OVERLAY_REQUESTED" -eq 1 ]; then fi done - if ! CHECK_DEPS_NO_EXIT=1 check_dependencies efivar mount mktemp od tr sed grep sync awk; then + if ! pkg_ensure_command efivar; then + test_result_finish "FAIL" "$TESTNAME could not provide efivar from the configured package repositories. Check the mapped package and matching runtime libraries, private RPM replacement is not automated" + fi + + if ! CHECK_DEPS_RECOVER=0 CHECK_DEPS_NO_EXIT=1 check_dependencies efivar mount mktemp od tr sed grep sync awk mv rm sort find; then log_skip "$TESTNAME SKIP - CAMX FIT DTB verification requires efivar and EFI runtime tools" echo "$TESTNAME SKIP" >"$RES_FILE" exit 0 fi + efi_ensure_mounted "$LOG_DIR/${TESTNAME}_efi_mount_${TS}.log" + CAMX_EFI_MOUNT_STATUS=$? + case "$CAMX_EFI_MOUNT_STATUS" in + 0) + ;; + 2) + test_result_finish "SKIP" "$TESTNAME requires EFI runtime support for CAMX overlay selection" + ;; + *) + test_result_finish "FAIL" "$TESTNAME could not prepare efivarfs for CAMX overlay selection" + ;; + esac + CAMX_DTB_OVERLAY_VARIABLE="$(efi_find_variable_by_name \ VendorDtbOverlays \ "$CAMX_EFI_LIST_LOG" 2>>"$CAMX_EFI_LOG")" - if [ -z "$CAMX_DTB_OVERLAY_VARIABLE" ]; then - log_skip "$TESTNAME SKIP - VendorDtbOverlays EFI variable was not found" - echo "$TESTNAME SKIP" >"$RES_FILE" - exit 0 - fi + CAMX_EFI_DISCOVERY_STATUS=$? + case "$CAMX_EFI_DISCOVERY_STATUS" in + 0) + ;; + 2) + test_result_finish "SKIP" "$TESTNAME requires firmware exposing the VendorDtbOverlays EFI variable" + ;; + *) + log_file_with_label "NHX-EFI" "$CAMX_EFI_LIST_LOG" + log_file_with_label "NHX-EFI" "$CAMX_EFI_LOG" + test_result_finish "FAIL" "$TESTNAME could not discover one VendorDtbOverlays variable, check efivar runtime libraries, EFI permissions and duplicate variables" + ;; + esac log_info "CAMX FIT DTB EFI variable selected: $CAMX_DTB_OVERLAY_VARIABLE" - if efi_text_variable_matches \ + camx_ensure_fit_selection \ "$CAMX_DTB_OVERLAY_VARIABLE" \ "$FIT_DTB_NAME" \ - "$CAMX_EFI_LOG"; then - CAMX_FIT_DTB_SELECTED=1 - log_pass "CAMX FIT DTB is selected in EFI, value=$FIT_DTB_NAME" - log_info "Continuing with runtime DT and camera module validation" - else - log_info "Selecting CAMX FIT DTB for the next boot, value=$FIT_DTB_NAME" - - if ! efi_write_text_variable \ - "$CAMX_DTB_OVERLAY_VARIABLE" \ - "$FIT_DTB_NAME" \ - "$CAMX_EFI_LOG"; then - log_fail "$TESTNAME FAIL - could not select CAMX FIT DTB in EFI" - echo "$TESTNAME FAIL" >"$RES_FILE" - exit 0 - fi - - log_skip "$TESTNAME SKIP - CAMX FIT DTB selected for the next boot, reboot the target manually and rerun Camera_NHX" - echo "$TESTNAME SKIP" >"$RES_FILE" - exit 0 - fi + "$CAMX_EFI_LOG" \ + "$LOG_DIR/camx-fit-selection.pending" + CAMX_SELECTION_STATUS=$? + case "$CAMX_SELECTION_STATUS" in + 0) + CAMX_FIT_DTB_SELECTED=1 + log_info "Continuing with camera runtime validation, no EFI setup changes needed" + ;; + 2) + test_result_finish "SKIP" "$TESTNAME CAMX FIT DTB selection awaits a manual reboot, then rerun the same command" + ;; + *) + test_result_finish "FAIL" "$TESTNAME CAMX FIT selection could not be verified, inspect EFI before/write/readback diagnostics and any pending-selection record. No automatic rewrite or reboot retry" + ;; + esac ;; *) log_info "CAMX device-tree overlay selection is not applicable, os=${CAMX_OVERLAY_OS_ID:-unknown}" ;; esac else - log_info "Camera NHX overlay package installation not requested" + log_info "Camera NHX overlay package recovery not requested" fi # ----------------------------------------------------------------------------- # Deps check # ----------------------------------------------------------------------------- -deps_list="date awk sed grep tee wc ls find stat rm tr head tail dmesg sort fdtdump mkfifo sha256sum md5sum cksum diff cp mkdir" -if ! check_dependencies "$deps_list"; then +deps_list="date awk sed grep tee wc ls find stat rm tr head tail sort mkfifo cp mkdir" +if ! CHECK_DEPS_RECOVER=0 check_dependencies "$deps_list"; then log_skip "$TESTNAME SKIP missing one or more dependencies: $deps_list" echo "$TESTNAME SKIP" >"$RES_FILE" exit 0 fi -NHX_RUNNER="$(command -v nhx.sh 2>/dev/null || true)" -if [ -n "$NHX_RUNNER" ] && [ -f "$NHX_RUNNER" ] && [ -x "$NHX_RUNNER" ]; then - NHX_RUNNER_SOURCE="PATH-nhx.sh" -else - NHX_RUNNER="$(command -v camera-nhx 2>/dev/null || true)" -fi - -if [ -z "$NHX_RUNNER_SOURCE" ] && \ - [ -n "$NHX_RUNNER" ] && [ -f "$NHX_RUNNER" ] && [ -x "$NHX_RUNNER" ]; then - NHX_RUNNER_SOURCE="PATH-camera-nhx" -elif [ -z "$NHX_RUNNER_SOURCE" ] && \ - [ -f "/usr/libexec/camx/nhx.sh" ] && [ -x "/usr/libexec/camx/nhx.sh" ]; then - NHX_RUNNER="/usr/libexec/camx/nhx.sh" - NHX_RUNNER_SOURCE="packaged-libexec" -elif [ -z "$NHX_RUNNER_SOURCE" ]; then - log_skip "$TESTNAME SKIP NHX launcher not found, provide executable nhx.sh or camera-nhx in PATH, or /usr/libexec/camx/nhx.sh" +CKSUM_TOOL="$(nhx_pick_cksum_tool)" +if [ -z "$CKSUM_TOOL" ]; then + log_skip "$TESTNAME SKIP checksum validation requires one image-provided utility: sha256sum, md5sum, or cksum" echo "$TESTNAME SKIP" >"$RES_FILE" exit 0 fi +if [ "$NATIVE_REQUESTED" -eq 0 ]; then + NHX_RUNNER=$(nhx_find_launcher wrapper "") + NHX_DISCOVERY_STATUS=$? +else + NHX_DISCOVERY_STATUS=2 +fi +if [ "$NHX_DISCOVERY_STATUS" -eq 2 ]; then + NHX_RUNNER_MODE=native + NHX_RUNNER=$(nhx_find_launcher native "$NATIVE_BIN") + NHX_DISCOVERY_STATUS=$? +fi +case "$NHX_DISCOVERY_STATUS" in + 0) + ;; + 2) + test_result_finish "SKIP" "$TESTNAME requires an executable nhx.sh, camera-nhx or nativehaltest from the image CAMX packages" + ;; + *) + test_result_finish "FAIL" "$TESTNAME could not select a unique executable, inspect discovery diagnostics or specify --native-bin" + ;; +esac +NHX_RUNNER_SOURCE="$NHX_RUNNER_MODE-runtime-discovery" + +if [ "$NHX_RUNNER_MODE" = native ]; then + if [ -n "$NHX_JSON" ]; then + NATIVE_MODE=json + elif [ -n "$NHX_TARGET" ]; then + test_result_finish "FAIL" "$TESTNAME native JSON selection requires --json with --target" + fi + if ! NATIVE_SELECTOR=$(nhx_native_check_cli "$NHX_RUNNER" "$LOG_DIR" "$NATIVE_CAMERA" "$NATIVE_MODE"); then + log_file_with_label NATIVE-HAL-HELP "$LOG_DIR/native-hal-help.log" + test_result_finish "FAIL" "$TESTNAME installed nativehaltest lacks the required capture CLI or its help probe failed" + fi + if [ "$NHX_CHECK_SETUP" -eq 0 ] && [ -z "$NATIVE_TEST$NHX_JSON" ]; then + test_result_finish "SKIP" "$TESTNAME nativehaltest found at $NHX_RUNNER. Select --json from the suite folder or --native-test Suite.Case supported by this client" + fi +fi + log_info "NHX runner selected, path=$NHX_RUNNER source=$NHX_RUNNER_SOURCE" # ----------------------------------------------------------------------------- @@ -340,51 +613,107 @@ log_info "NHX runner selected, path=$NHX_RUNNER source=$NHX_RUNNER_SOURCE" # ----------------------------------------------------------------------------- log_info "Checking CAMX proprietary prerequisites before running NHX" -log_info "DT check" - -PATTERNS="qcom,cam-sensor qcom,cam-gmsl-sensor qcom,cam-gmsl-deserializer qcom,eeprom qcom,cci qcom,csiphy qcom,cam-tpg1031 qcom,camera qcom,cam camera_kt cam-req-mgr cam-cpas cam-jpeg cam-ife cam-icp camera0-thermal" -found_any=0 -missing_list="" +log_info "Decoding the active runtime device tree" -for pat in $PATTERNS; do - out="$(dt_confirm_node_or_compatible "$pat" 2>/dev/null || true)" - if [ -n "$out" ]; then - printf '%s\n' "$out" - found_any=1 - else - if [ -n "$missing_list" ]; then - missing_list="$missing_list, $pat" - else - missing_list="$pat" +camx_dump_runtime_dts "$RUNNING_DTS" "$RUNNING_DTS_WARNINGS" +DTC_RC=$? +case "$DTC_RC" in + 0) + log_pass "Active runtime DT decoded to $RUNNING_DTS" + if [ -s "$RUNNING_DTS_WARNINGS" ]; then + log_warn "dtc reported non-fatal warnings, retained in $RUNNING_DTS_WARNINGS" fi - fi -done + ;; + 2) + : >"$RUNNING_DTS_WARNINGS" + : >"$CAMERA_DTS_MATCHES" + : >"$SENSOR_DTS_MATCHES" + log_warn "dtc is not available in the image, continuing with enabled runtime-node validation" + ;; + 3) + : >"$RUNNING_DTS_WARNINGS" + : >"$CAMERA_DTS_MATCHES" + : >"$SENSOR_DTS_MATCHES" + log_warn "No readable runtime device-tree root is exposed under procfs or sysfs" + ;; + *) + : >"$CAMERA_DTS_MATCHES" + : >"$SENSOR_DTS_MATCHES" + log_warn "Active runtime DT decoding failed, continuing with enabled runtime-node validation, inspect $RUNNING_DTS_WARNINGS" + ;; +esac -if [ "$found_any" -ne 1 ]; then - log_skip "$TESTNAME SKIP missing DT patterns $missing_list" - echo "$TESTNAME SKIP" >"$RES_FILE" - exit 0 +if [ "$DTC_RC" -eq 0 ]; then + camx_extract_runtime_dts_evidence \ + "$RUNNING_DTS" \ + "$CAMERA_DTS_MATCHES" \ + "$SENSOR_DTS_MATCHES" fi -log_info "fdtdump camera nodes sample" +RUNTIME_DT_ROOT="$(dt_runtime_root 2>/dev/null || true)" +RUNTIME_DT_MODEL="$(dt_property_text "$RUNTIME_DT_ROOT" model 2>/dev/null || true)" +RUNTIME_DT_COMPATIBLE="$(dt_property_text "$RUNTIME_DT_ROOT" compatible 2>/dev/null || true)" +log_info "Runtime DT model: ${RUNTIME_DT_MODEL:-unavailable}" +log_info "Runtime DT compatible: ${RUNTIME_DT_COMPATIBLE:-unavailable}" +if [ "$DTC_RC" -eq 0 ]; then + log_info "[CAMX-CDT-FIT] efi_camx_selected=$CAMX_FIT_DTB_SELECTED active_dt=$RUNNING_DTS" + log_info "Runtime camera DTS matches retained in $CAMERA_DTS_MATCHES" + log_info "Runtime sensor DTS matches retained in $SENSOR_DTS_MATCHES" +else + log_info "[CAMX-CDT-FIT] efi_camx_selected=$CAMX_FIT_DTB_SELECTED active_dt=unavailable" +fi -FDT_MATCHES="$(camx_fdtdump_has_cam_nodes 2>/dev/null)" -rc=$? -if [ "$rc" -ne 0 ]; then - if [ "$rc" -eq 2 ]; then - log_skip "$TESTNAME SKIP fdtdump not available" +CAMX_DT_NODES="$(camx_list_enabled_runtime_nodes)" +: >"$CAMX_DT_NODES_LOG" +if [ -n "$CAMX_DT_NODES" ]; then + printf '%s\n' "$CAMX_DT_NODES" >"$CAMX_DT_NODES_LOG" +fi +if [ -z "$CAMX_DT_NODES" ]; then + if [ "$CAMX_FIT_DTB_SELECTED" -eq 1 ]; then + log_fail "$TESTNAME FAIL - VendorDtbOverlays selects camx but the active runtime DT has no enabled downstream CamX nodes, verify the CDT-selected FIT configuration and rebooted image" + echo "$TESTNAME FAIL" >"$RES_FILE" else - log_skip "$TESTNAME SKIP fdtdump did not show conclusive camera nodes" + log_skip "$TESTNAME SKIP - the active runtime DT has no enabled downstream CamX nodes, provision a CAMX-capable FIT configuration selected by CDT" + echo "$TESTNAME SKIP" >"$RES_FILE" fi - echo "$TESTNAME SKIP" >"$RES_FILE" exit 0 fi -printf '%s\n' "$FDT_MATCHES" | while IFS= read -r l; do - if [ -n "$l" ]; then - log_info " $l" +printf '%s\n' "$CAMX_DT_NODES" | while IFS= read -r camx_node; do + if [ -n "$camx_node" ]; then + log_info "Enabled CamX DT node: $camx_node" fi done +log_pass "Active runtime DT exposes enabled downstream CamX nodes" + +SENSOR_DT_NODES="$(camx_list_enabled_sensor_nodes)" +: >"$SENSOR_DT_NODES_LOG" +if [ -n "$SENSOR_DT_NODES" ]; then + printf '%s\n' "$SENSOR_DT_NODES" >"$SENSOR_DT_NODES_LOG" + printf '%s\n' "$SENSOR_DT_NODES" | while IFS= read -r sensor_node; do + if [ -n "$sensor_node" ]; then + log_info "Enabled camera sensor DT node: $sensor_node" + fi + done +else + log_warn "No enabled IMX, OV, AR, CamX sensor, or GMSL sensor nodes were found, NHX execution remains authoritative" +fi + +log_info "Checking optional raw FDT camera evidence" + +FDT_MATCHES="$(camx_fdtdump_has_cam_nodes 2>/dev/null)" +rc=$? +if [ "$rc" -eq 0 ]; then + printf '%s\n' "$FDT_MATCHES" | while IFS= read -r fdt_match; do + if [ -n "$fdt_match" ]; then + log_info "Raw FDT camera match: $fdt_match" + fi + done +elif [ "$rc" -eq 2 ]; then + log_warn "Optional fdtdump command is unavailable, continuing with enabled runtime-node evidence" +else + log_warn "Optional raw FDT scan did not find camera nodes, continuing with enabled runtime DT evidence" +fi log_info "driver load checks" @@ -416,7 +745,7 @@ log_info "Camera module artifact found $CAM_KO" if ! is_module_loaded "$CAM_MOD" 2>/dev/null; then if [ "$CAMX_FIT_DTB_SELECTED" -eq 1 ]; then - log_skip "$TESTNAME SKIP - CAMX FIT DTB is selected in EFI but $CAM_MOD is not loaded, reboot the target manually and rerun Camera_NHX" + test_result_finish "FAIL" "$TESTNAME CAMX FIT DTB is selected in EFI but $CAM_MOD is not loaded, inspect the running-kernel CAMX module and probe diagnostics" else log_skip "$TESTNAME SKIP camera module not loaded $CAM_MOD" fi @@ -507,7 +836,7 @@ else done fi -log_info "sensor presence warn-only NHX may still work without cam sensors" +log_info "libcamera sensor inventory is advisory, it does not enumerate the proprietary CAMX HAL" SENSOR_COUNT="$(libcam_list_sensors_count 2>/dev/null || true)" case "$SENSOR_COUNT" in @@ -515,20 +844,25 @@ case "$SENSOR_COUNT" in esac log_info "cam list detected $SENSOR_COUNT cameras" if [ "$SENSOR_COUNT" -lt 1 ]; then - log_warn "No sensors reported by cam list continuing" + log_warn "No sensors reported by cam list, NHX capture must still prove the selected camera works" fi -CKSUM_TOOL="" -if command -v nhx_pick_cksum_tool >/dev/null 2>&1; then - CKSUM_TOOL="$(nhx_pick_cksum_tool)" -else - if command -v sha256sum >/dev/null 2>&1; then - CKSUM_TOOL="sha256sum" - elif command -v md5sum >/dev/null 2>&1; then - CKSUM_TOOL="md5sum" - elif command -v cksum >/dev/null 2>&1; then - CKSUM_TOOL="cksum" +if [ "$NHX_CHECK_SETUP" -eq 1 ]; then + test_result_finish "PASS" "$TESTNAME NHX runtime prerequisites are present. No capture, QMMF service or physical sensor functionality was tested" +fi + +if [ "$NHX_RUNNER_MODE" = native ] && [ "$NATIVE_MODE" = json ]; then + if ! NHX_JSON_RESOLVED=$(nhx_resolve_json_file "$SCRIPT_DIR" "$NHX_JSON" "$NHX_TARGET"); then + test_result_finish "FAIL" "$TESTNAME cannot resolve one JSON: $NHX_JSON, check the path and specify --target if ambiguous" + fi + if ! NATIVE_JSON_STATE=$(mktemp -d "$LOG_DIR/native-json.XXXXXX"); then + test_result_finish "FAIL" "$TESTNAME cannot create native JSON evidence directory in $LOG_DIR" + fi + if ! NHX_JSON_ARG=$(nhx_prepare_native_json "$SCRIPT_DIR" "$NHX_JSON_RESOLVED" "$NATIVE_JSON_STATE"); then + test_result_finish "FAIL" "$TESTNAME cannot safely stage native JSON $NHX_JSON_RESOLVED, inspect staging diagnostics" fi + NATIVE_TEST="$NHX_JSON_ARG" + log_info "Native JSON source=$NHX_JSON_RESOLVED argument=$NHX_JSON_ARG, retained in $NATIVE_JSON_STATE/selected.json" fi log_info "$TESTNAME starting" @@ -563,6 +897,7 @@ if [ -n "$CAM_SERVER_SERVICE" ]; then if systemd_service_is_active "$CAM_SERVER_SERVICE"; then log_info "Stopping active camera server before NHX launcher" + CAM_SERVER_STOPPED_FOR_TEST=1 if ! systemd_service_stop_safe "$CAM_SERVER_SERVICE"; then log_fail "$TESTNAME FAIL - unable to stop active $CAM_SERVER_SERVICE before NHX launcher" @@ -576,7 +911,6 @@ if [ -n "$CAM_SERVER_SERVICE" ]; then exit 0 fi - CAM_SERVER_STOPPED_FOR_TEST=1 CAM_SERVER_TS_AFTER_STOP="$(date '+%Y-%m-%d %H:%M:%S')" log_info "Camera server status after stop" @@ -598,7 +932,13 @@ fi # ----------------------------------------------------------------------------- : >"$MARKER" 2>/dev/null || touch "$MARKER" -if [ -n "$NHX_JSON" ]; then +if [ "$NHX_RUNNER_MODE" = native ]; then + NATIVE_CAPTURE_ACTIVE=1 + nhx_run_native "$NHX_RUNNER" "$NATIVE_SELECTOR" "$NATIVE_TEST" \ + "$NATIVE_FRAMES" "$NATIVE_CAMERA" "$RUN_LOG" "$LOG_DIR" "$NATIVE_MODE" + NHX_RC=$? + NATIVE_CAPTURE_ACTIVE=0 +elif [ -n "$NHX_JSON" ]; then if ! command -v nhx_resolve_json_file >/dev/null 2>&1; then log_skip "$TESTNAME SKIP nhx_resolve_json_file helper not available" echo "$TESTNAME SKIP" >"$RES_FILE" @@ -687,9 +1027,15 @@ fi # ----------------------------------------------------------------------------- # Restore camera server state after NHX # ----------------------------------------------------------------------------- +if [ -n "$NATIVE_JSON_STATE" ]; then + if ! nhx_restore_native_json "$NATIVE_JSON_STATE"; then + NATIVE_JSON_RESTORE_FAILED=1 + fi +fi + if [ "$CAM_SERVER_STOPPED_FOR_TEST" -eq 1 ]; then log_info "Restoring camera server after NHX launcher: $CAM_SERVER_SERVICE" - if systemd_service_start_safe "$CAM_SERVER_SERVICE"; then + if nhx_restore_camera_service "$CAM_SERVER_SERVICE" "$LOG_DIR"; then CAM_SERVER_STOPPED_FOR_TEST=0 CAM_SERVER_TS_AFTER_START="$(date '+%Y-%m-%d %H:%M:%S')" @@ -701,7 +1047,8 @@ if [ "$CAM_SERVER_STOPPED_FOR_TEST" -eq 1 ]; then systemd_service_stdout_since "Camera server AFTER restore (stdout since restore marker)" \ "$RUN_LOG" "$CAM_SERVER_TS_AFTER_START" "$CAM_SERVER_SERVICE" || true else - log_warn "Failed to restore camera server after NHX launcher: $CAM_SERVER_SERVICE" + CAM_SERVER_RESTORE_FAILED=1 + log_fail "Failed to restore camera server after NHX launcher: $CAM_SERVER_SERVICE" fi else log_info "Camera server state was not changed for NHX, skipping restore" @@ -712,10 +1059,32 @@ fi # ----------------------------------------------------------------------------- DUMP_VALIDATION_FAIL=0 -mkdir -p "$NHX_OUTDIR" 2>/dev/null || true +if ! { + mkdir -p "$NHX_OUTDIR" && + : >"$DUMPS_LIST" && + : >"$CHECKSUMS_TXT" +}; then + test_result_finish "FAIL" "$TESTNAME cannot initialize capture artifacts in $NHX_OUTDIR, check permissions and free space" +fi + +if [ "$NHX_RUNNER_MODE" = native ]; then + if NATIVE_COUNTS=$(nhx_native_result_counts "$RUN_LOG" "$NATIVE_TEST" "$NATIVE_MODE"); then + NATIVE_REPORT_VALID=1 + else + NATIVE_RESULT_RC=$? + NATIVE_REPORT_VALID=0 + if [ "$NATIVE_RESULT_RC" -eq 2 ]; then + NATIVE_NO_TESTS=1 + log_fail "Native HAL executed zero tests for $NATIVE_TEST with $NHX_RUNNER, verify the selected JSON or Suite.Case is supported by this client" + log_info "Native CLI evidence: $LOG_DIR/native-hal-help.log, no alternate testcase or full-suite run will be attempted" + fi + fi +fi -if command -v nhx_validate_dumps_and_checksums >/dev/null 2>&1; then - if ! nhx_validate_dumps_and_checksums "$DUMP_DIR" "$MARKER" "$NHX_OUTDIR" "$CHECKSUMS_TXT" "$CHECKSUMS_PREV"; then +if [ "$NATIVE_NO_TESTS" -eq 1 ]; then + log_info "Capture validation was not exercised because native HAL executed zero tests" +elif command -v nhx_validate_dumps_and_checksums >/dev/null 2>&1; then + if ! nhx_validate_dumps_and_checksums "$DUMP_DIR" "$MARKER" "$NHX_OUTDIR" "$CHECKSUMS_TXT" "$CHECKSUMS_PREV" "$NHX_RUNNER_MODE"; then DUMP_VALIDATION_FAIL=1 fi @@ -749,7 +1118,13 @@ else fi fi -DUMP_COUNT="$(wc -l <"$DUMPS_LIST" 2>/dev/null | awk '{print $1}')" +DUMP_COUNT=0 +if [ -r "$DUMPS_LIST" ]; then + DUMP_COUNT="$(wc -l <"$DUMPS_LIST" | awk '{print $1}')" +else + DUMP_VALIDATION_FAIL=1 + log_fail "Capture list is unavailable at $DUMPS_LIST, inspect dump validation diagnostics" +fi if [ -z "$DUMP_COUNT" ]; then DUMP_COUNT=0 fi @@ -764,6 +1139,15 @@ TOTAL_BYTES=0 echo "NHX launcher exit code: $NHX_RC" echo "NHX runner: $NHX_RUNNER" echo "NHX runner source: $NHX_RUNNER_SOURCE" + echo "Execution mode: $NHX_RUNNER_MODE" + echo "Native testcase: ${NATIVE_TEST:-}" + if [ "$NATIVE_MODE" = json ]; then + echo "Native capture settings: selected JSON" + echo "Native JSON evidence: $NATIVE_JSON_STATE/selected.json" + else + echo "Native frames: $NATIVE_FRAMES" + echo "Native camera: ${NATIVE_CAMERA:-}" + fi echo "NHX JSON requested: ${NHX_JSON:-}" echo "NHX target requested: ${NHX_TARGET:-}" echo "NHX JSON resolved: ${NHX_JSON_RESOLVED:-}" @@ -842,6 +1226,21 @@ if [ -n "$FINAL_LINE" ]; then SKIPPED="$(echo "$FINAL_LINE" | sed -n 's/.*\[\([0-9][0-9]*\) SKIPPED\].*/\1/p')" fi +if [ "$NHX_RUNNER_MODE" = native ]; then + if [ "$NATIVE_REPORT_VALID" -eq 1 ]; then + read -r PASSED FAILED SKIPPED <"$RES_FILE" -else - log_fail "$TESTNAME FAIL $REASON" - echo "$TESTNAME FAIL" >"$RES_FILE" -fi - -exit 0 +test_result_finish "$RESULT" "$TESTNAME $RESULT $REASON" diff --git a/Runner/suites/Multimedia/Camera/Libcamera_cam/libcamera_test_README.md b/Runner/suites/Multimedia/Camera/Libcamera_cam/libcamera_test_README.md index 62f82ad85..43cf80a1b 100644 --- a/Runner/suites/Multimedia/Camera/Libcamera_cam/libcamera_test_README.md +++ b/Runner/suites/Multimedia/Camera/Libcamera_cam/libcamera_test_README.md @@ -7,10 +7,8 @@ This repository contains a **POSIX shell** test harness for exercising `libcamer ## What this test does 1. **Discovers repo context** (finds `init_env`, sources `functestlib.sh`, and the camera helpers `Runner/utils/camera/lib_camera.sh`). -2. **Checks DT readiness** using `dt_confirm_node_or_compatible` for: - - Sensor compatible(s), e.g. `sony,imx577` - - ISP / camera blocks, e.g. `isp`, `cam`, `camss` -3. **Lists available cameras** with `cam -l` (warning/error lines are tolerated when camera listing succeeds). +2. **Checks DT applicability** by discovering enabled Qualcomm CAMSS pipeline nodes in the runtime device tree. Disabled nodes from inactive overlays are ignored. +3. **Lists available cameras** with `cam -l`, retains the complete output, and accepts both `[0] ...` and `0: ...` camera entry formats. 4. **Captures frames** with `cam` for one or multiple indices, storing artifacts per camera under `OUT_DIR`. 5. **Validates output**: sequence continuity, content sanity (PPM/BIN), duplicate detection, and log scanning with noise suppression. 6. **Summarizes per‑camera PASS/FAIL**, with overall suite verdict and exit code. @@ -19,18 +17,17 @@ This repository contains a **POSIX shell** test harness for exercising `libcamer ## Requirements -- `cam` (from the `libcamera-tools` package on Debian and Ubuntu) +- Image-provided `cam` utility from libcamera - Standard tools: `awk`, `sed`, `grep`, `sort`, `cut`, `tr`, `wc`, `find`, `stat`, `head`, `tail`, `dd` - Optional: `sha256sum` or `md5sum` (for duplicate BIN detection) - **BusyBox compatibility**: - We avoid `find -printf` and `od -A` options (not available on BusyBox). -When `cam` is missing on an APT-based target, the shared package provider -refreshes package metadata, installs `libcamera-tools`, and verifies the -command before capture starts. Other images continue to use their configured -provider or image-provided command and report the missing dependency cleanly. +The test does not install packages at runtime. If `cam` or another required +utility is absent from the image, it reports a clean SKIP with the missing +prerequisite. -> The harness tolerates noisy `cam -l` / `cam -I` output (WARN/ERROR lines). It only requires that cameras and/or stream info are ultimately reported. +> The harness tolerates noisy `cam -l` / `cam -I` output when cameras and stream information are ultimately reported. A nonzero `cam -l` status or zero enumerated cameras is a failure when an enabled CAMSS pipeline is present. --- @@ -80,49 +77,51 @@ Examples: ## Device‑tree checks -The runner verifies DT node presence **before** capture: +The runner determines applicability **before** capture by finding enabled +Qualcomm compatibles containing `camss` in the live device tree. The shared +device-tree helper resolves runtime root symlinks, searches both standard roots, +and ignores nodes whose status is `disabled`, `fail`, or `failed`. -- First it looks for known sensor compatibles (e.g. `sony,imx577`). -- If the sensor isn’t found, it looks for ISP / camera nodes (e.g. `isp`, `cam`, `camss`). -- Matching entries are printed cleanly (name, path, compatible). - -If neither sensor nor ISP/camera blocks are found, the test **SKIPs** with a message: -``` -SKIP – No ISP/camera node/compatible found in DT -``` - -> On large DTs, this scan can take time. The log prints “Verifying the availability of DT nodes, this process may take some time.” +- No enabled CAMSS pipeline: **SKIP** because the upstream libcamera path is not applicable to the active image or overlay. +- Enabled CAMSS pipeline and `cam -l` fails: **FAIL** and retain the command output. +- Enabled CAMSS pipeline and `cam -l` reports zero cameras: **FAIL** because the image needs matching libcamera pipeline-handler and sensor support. --- ## IPA file workaround (simple pipeline) -On some builds, allocation may fail if `uncalibrated.yaml` exists for the `simple` IPA. The runner guards this by **renaming** it pre‑run: +On some builds, allocation may fail if `uncalibrated.yaml` exists for the +`simple` IPA. The runner temporarily moves it to a test-owned backup path: ```sh if [ -f /usr/share/libcamera/ipa/simple/uncalibrated.yaml ]; then mv /usr/share/libcamera/ipa/simple/uncalibrated.yaml \ - /usr/share/libcamera/ipa/simple/uncalibrated.yaml.bk + /usr/share/libcamera/ipa/simple/uncalibrated.yaml.qcom-testkit-backup fi ``` -It’s restored automatically at the end (if it was present). +The EXIT trap restores only the file moved by the current test run. If the +backup path already exists, the test leaves both files unchanged. --- ## Output & artifacts +Directly under `OUT_DIR`: + +- `cam-list-.log` – retained initial `cam -l` output used for applicability validation and index selection +- `summary.txt` – per‑camera PASS/FAIL + Per‑camera subfolder under `OUT_DIR`: + - `cam-run--camX.log` – raw cam output - `cam-info--camX.log` – `cam -l` and `cam -I` info - `frame-...` files (`.bin` or `.ppm`) – captured frames - `.file_seq_map.txt`, `.bytesused.txt`, etc. – validation sidecar files -- `summary.txt` – per‑camera PASS/FAIL -Console prints a **per‑camera** and **overall** summary. Exit codes: -- `0` PASS -- `1` FAIL -- `2` SKIP +Console prints a **per‑camera** and **overall** summary. +The `.res` file is the authoritative result. The runner exits `0` after +publishing PASS, FAIL, or SKIP so LAVA can consume that result. --- @@ -145,15 +144,11 @@ You can relax strictness with `--no-strict` (skips contiguous sequence enforceme --- -## Environment overrides +## Repository integration -- `INIT_ENV`: If set, the runner uses it instead of walking upward to find `init_env`. -- `LIBCAM_PATH`: If set, the runner sources this path for `lib_camera.sh` helper functions. -- Otherwise, the runner searches typical repo locations: - - `Runner/utils/camera/lib_camera.sh` - - `Runner/utils/lib_camera.sh` - - `utils/camera/lib_camera.sh` - - `utils/lib_camera.sh` +The runner locates `init_env` by walking upward from its own directory and +loads `Runner/utils/camera/lib_camera.sh` through the repository `TOOLS` path. +No environment override is required. --- @@ -161,7 +156,8 @@ You can relax strictness with `--no-strict` (skips contiguous sequence enforceme - **`cam -l` prints WARN/ERROR but lists cameras**: This is tolerated. The runner parses indices from the “Available cameras” section. - **BusyBox `find`/`od` compatibility**: We avoid GNU-only flags; if you see issues, ensure BusyBox provides the required applets mentioned above. -- **No DT matches**: Ensure your DT exposes sensor compatibles (e.g. `sony,imx577`) or ISP/camera nodes (`isp`, `cam`, `camss`). On dev boards, DT overlays may need to be applied. +- **No enabled CAMSS node**: Ensure the active runtime DT selects the upstream CAMSS image or overlay. Camera nodes from disabled overlays do not make this test applicable. +- **Enabled CAMSS but zero cameras**: Inspect `cam-list-.log` and provide a libcamera build with the matching Qualcomm pipeline handler and sensor support. - **Content flagged “near‑constant”**: This typically indicates all-same bytes in sampled regions. Verify the lens cap, sensor mode, or try `--args` with a smaller resolution/role to confirm live changes. - **IPA config missing**: See the **IPA file workaround** above. diff --git a/Runner/suites/Multimedia/Camera/Libcamera_cam/run.sh b/Runner/suites/Multimedia/Camera/Libcamera_cam/run.sh index e9f6c146a..438a29bb9 100755 --- a/Runner/suites/Multimedia/Camera/Libcamera_cam/run.sh +++ b/Runner/suites/Multimedia/Camera/Libcamera_cam/run.sh @@ -1,59 +1,46 @@ #!/bin/sh # Copyright (c) Qualcomm Technologies, Inc. -# SPDX-License-Identifier: BSD-3-Clause# libcamera 'cam' runner with strong post-capture validation (CLI-config only) -# ---------- Repo env + helpers (single-pass) ---------- -SCRIPT_DIR=$(cd "$(dirname "$0")" || exit 1; pwd) +# SPDX-License-Identifier: BSD-3-Clause +# libcamera 'cam' runner with strong post-capture validation (CLI-config only) + +# ---------- Repo env + helpers ---------- +SCRIPT_DIR="$( + cd "$(dirname "$0")" || exit 1 + pwd +)" +INIT_ENV="" SEARCH="$SCRIPT_DIR" -INIT_ENV="${INIT_ENV:-}" -LIBCAM_PATH="${LIBCAM_PATH:-}" -# Find init_env quickly (single upward walk) -while [ "$SEARCH" != "/" ] && [ -z "$INIT_ENV" ]; do - [ -f "$SEARCH/init_env" ] && INIT_ENV="$SEARCH/init_env" && break - SEARCH=${SEARCH%/*} +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 - printf '%s\n' "[ERROR] Could not find init_env (starting at $SCRIPT_DIR)" >&2 + 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" -# Prefer direct repo-root locations for lib_camera.sh -if [ -z "$LIBCAM_PATH" ]; then - REPO_ROOT=$(dirname "$INIT_ENV") - for cand in \ - "$REPO_ROOT/Runner/utils/camera/lib_camera.sh" \ - "$REPO_ROOT/utils/camera/lib_camera.sh" - do - [ -f "$cand" ] && { LIBCAM_PATH="$cand"; break; } - done -fi +LIBCAM_PATH="$TOOLS/camera/lib_camera.sh" -# Fallback upward walk only if still not found -if [ -z "$LIBCAM_PATH" ]; then - SEARCH="$SCRIPT_DIR" - while [ "$SEARCH" != "/" ]; do - for cand in \ - "$SEARCH/Runner/utils/camera/lib_camera.sh" \ - "$SEARCH/utils/camera/lib_camera.sh" - do - [ -f "$cand" ] && { LIBCAM_PATH="$cand"; break 2; } - done - SEARCH=${SEARCH%/*} - done -fi - -if [ -z "$LIBCAM_PATH" ]; then - if command -v log_error >/dev/null 2>&1; then - log_error "lib_camera.sh not found (searched under Runner/utils/camera and utils/camera)" - else - printf '%s\n' "ERROR: lib_camera.sh not found (searched under Runner/utils/camera and utils/camera)" >&2 - fi +if [ ! -r "$LIBCAM_PATH" ]; then + log_error "lib_camera.sh is unavailable at $LIBCAM_PATH" exit 1 fi @@ -62,7 +49,9 @@ fi TESTNAME="Libcamera_cam" RES_FILE="./${TESTNAME}.res" -: > "$RES_FILE" +if ! test_result_init "$TESTNAME" "$RES_FILE"; then + exit 1 +fi # ---------- Defaults (override via CLI only) ---------- CAM_INDEX="auto" # --index |all|n,m,k ; auto = first from `cam -l` @@ -78,8 +67,22 @@ DUP_MAX_RATIO="0.5" # --dup-max-ratio <0..1> BIN_TOL_PCT="5" # --bin-tol-pct PPM_SAMPLE_BYTES="65536" BIN_SAMPLE_BYTES="65536" +UNCALIB="/usr/share/libcamera/ipa/simple/uncalibrated.yaml" +UNCALIB_BACKUP="${UNCALIB}.qcom-testkit-backup" +UNCALIB_MOVED=0 + +# Restore test-owned runtime changes. +cleanup() { + if [ "$UNCALIB_MOVED" -eq 1 ]; then + libcam_restore_ipa_config "$UNCALIB" "$UNCALIB_BACKUP" || true + UNCALIB_MOVED=0 + fi +} + +trap 'cleanup' EXIT +trap 'exit 130' INT TERM -print_usage() { +usage() { cat </dev/null || true)" - if [ -n "$out" ]; then - printf '%s\n' "$out" - found_any=1 - else - [ -n "$missing_list" ] && missing_list="$missing_list, $pat" || missing_list="$pat" - fi -done - -if [ "$found_any" -eq 1 ]; then - log_info "DT nodes present (see matches above)." -else - log_skip "$TESTNAME SKIP – missing DT patterns: $missing_list" - echo "$TESTNAME SKIP" >"$RES_FILE" - exit 0 +log_info "Checking the runtime device tree for an enabled Qualcomm CAMSS pipeline" +CAMSS_NODES="$(libcam_list_enabled_pipeline_nodes 2>/dev/null || true)" + +if [ -z "$CAMSS_NODES" ]; then + test_result_finish \ + "SKIP" \ + "$TESTNAME SKIP - no enabled Qualcomm CAMSS pipeline exists in the runtime device tree, select an upstream CAMSS image or overlay" fi +printf '%s\n' "$CAMSS_NODES" | + while IFS= read -r camss_node; do + [ -n "$camss_node" ] || continue + log_info "Enabled CAMSS pipeline node: $camss_node" + done + # ---------- Dependencies ---------- -log_info "Ensuring the libcamera cam utility is available." -CHECK_DEPS_NO_EXIT=1 check_dependencies cam || { - log_error "cam utility remains unavailable after package recovery" - echo "$TESTNAME FAIL" > "$RES_FILE" - exit 1 -} -# nice-to-haves for validation -check_dependencies awk sed grep sort cut tr wc find stat head tail dd || true -(check_dependencies sha256sum || check_dependencies md5sum) || true +log_info "Checking image-provided libcamera dependencies" +if ! CHECK_DEPS_RECOVER=0 CHECK_DEPS_NO_EXIT=1 check_dependencies cam; then + test_result_finish \ + "SKIP" \ + "$TESTNAME SKIP - enabled CAMSS is present but the image does not provide the libcamera cam utility" +fi + +if ! CHECK_DEPS_RECOVER=0 CHECK_DEPS_NO_EXIT=1 \ + check_dependencies awk sed grep sort cut tr wc find stat head tail dd; then + test_result_finish \ + "SKIP" \ + "$TESTNAME SKIP - required image-provided shell utilities are unavailable" +fi + +if ! command -v sha256sum >/dev/null 2>&1 && + ! command -v md5sum >/dev/null 2>&1; then + log_info "No optional SHA-256 or MD5 utility is available, duplicate hash checks will be skipped" +fi # ---------- Setup ---------- mkdir -p "$OUT_DIR" 2>/dev/null || true RUN_TS="$(date -u +%Y%m%d-%H%M%S)" +CAM_LIST_LOG="${OUT_DIR%/}/cam-list-${RUN_TS}.log" log_info "Test: $TESTNAME" log_info "OUT_DIR=$OUT_DIR | COUNT=$CAPTURE_COUNT | SAVE_AS_PPM=$SAVE_AS_PPM" log_info "Extra args: ${CAM_EXTRA_ARGS:-}" # ---- IPA workaround: disable simple/uncalibrated to avoid buffer allocation failures ---- -UNCALIB="/usr/share/libcamera/ipa/simple/uncalibrated.yaml" -if [ -f "$UNCALIB" ]; then - log_info "Renaming $UNCALIB -> ${UNCALIB}.bk to avoid IPA buffer allocation issues" - mv "$UNCALIB" "${UNCALIB}.bk" 2>/dev/null || log_warn "Failed to rename $UNCALIB (continuing)" -elif [ -f "${UNCALIB}.bk" ]; then - # Already renamed in a previous run; just log for clarity - log_info "IPA workaround already applied: ${UNCALIB}.bk present" +if libcam_disable_ipa_config "$UNCALIB" "$UNCALIB_BACKUP"; then + UNCALIB_MOVED=1 +else + IPA_CONFIG_RC=$? + if [ "$IPA_CONFIG_RC" -eq 2 ]; then + log_info "IPA workaround is not applicable, config is absent: $UNCALIB" + fi fi # ---------- Sensor presence ---------- -SENSOR_COUNT="$(libcam_list_sensors_count 2>/dev/null)" -# harden: ensure numeric -case "$SENSOR_COUNT" in ''|*[!0-9]*) SENSOR_COUNT=0 ;; esac +if cam -l >"$CAM_LIST_LOG" 2>&1; then + CAM_LIST_RC=0 +else + CAM_LIST_RC=$? +fi + +sed 's/^/[cam -l] /' "$CAM_LIST_LOG" + +if [ "$CAM_LIST_RC" -ne 0 ]; then + test_result_finish \ + "FAIL" \ + "$TESTNAME FAIL - cam -l exited with status $CAM_LIST_RC although an enabled CAMSS pipeline is present, see $CAM_LIST_LOG" +fi + +SENSOR_COUNT="$(libcam_count_sensors_from_file "$CAM_LIST_LOG" 2>/dev/null || true)" +case "$SENSOR_COUNT" in + ''|*[!0-9]*) SENSOR_COUNT=0 ;; +esac log_info "[cam -l] detected ${SENSOR_COUNT} camera(s)" - + if [ "$SENSOR_COUNT" -lt 1 ]; then - log_skip "No sensors reported by 'cam -l' - marking SKIP" - echo "$TESTNAME SKIP" > "$RES_FILE" - exit 0 + test_result_finish \ + "FAIL" \ + "$TESTNAME FAIL - enabled CAMSS pipeline detected but cam -l reported zero cameras, provide matching libcamera pipeline-handler and sensor support, see $CAM_LIST_LOG" fi - # ---------- Resolve indices (supports: auto | all | 0,2,5) ---------- -INDICES="$(libcam_resolve_indices "$CAM_INDEX")" +INDICES="$(libcam_resolve_indices "$CAM_INDEX" "$CAM_LIST_LOG")" if [ -z "$INDICES" ]; then - log_skip "No valid camera indices resolved (cam -l empty?) - SKIP" - echo "$TESTNAME SKIP" > "$RES_FILE" - exit 0 + test_result_finish \ + "FAIL" \ + "$TESTNAME FAIL - cam -l reported cameras but no index matched --index $CAM_INDEX, see $CAM_LIST_LOG" fi log_info "Resolved indices: $INDICES" @@ -210,7 +275,7 @@ for IDX in $INDICES; do log_info "---- Camera idx: $IDX ----" { echo "== cam -l ==" - cam -l || true + cat "$CAM_LIST_LOG" echo echo "== cam -I (index $IDX) ==" cam -c "$IDX" -I || true @@ -257,11 +322,11 @@ for IDX in $INDICES; do [ $RC -eq 0 ] || { ANY_RC_NONZERO=1; PASS=0; } if [ "$PASS" -eq 1 ]; then - log_pass "[idx $IDX] PASS" + test_result_record "PASS" "Camera index $IDX capture and validation passed" PASS_LIST="$PASS_LIST $IDX" echo "cam$IDX PASS" >> "$OUT_DIR/summary.txt" else - log_fail "[idx $IDX] FAIL" + test_result_record "FAIL" "Camera index $IDX capture or validation failed, see $RUN_LOG" FAIL_LIST="$FAIL_LIST $IDX" echo "cam$IDX FAIL" >> "$OUT_DIR/summary.txt" OVERALL_PASS=0 @@ -293,11 +358,11 @@ done # ---------- Final verdict ---------- if [ "$OVERALL_PASS" -eq 1 ] && [ $ANY_RC_NONZERO -eq 0 ]; then - echo "$TESTNAME PASS" > "$RES_FILE" - log_pass "$TESTNAME PASS" - exit 0 + test_result_finish \ + "PASS" \ + "$TESTNAME PASS - all selected camera captures passed" else - echo "$TESTNAME FAIL" > "$RES_FILE" - log_fail "$TESTNAME FAIL" - exit 1 + test_result_finish \ + "FAIL" \ + "$TESTNAME FAIL - one or more selected camera captures failed, see $OUT_DIR/summary.txt" fi