From a6e7c30d6e9b72534f4d3207317a5fbe24da7f46 Mon Sep 17 00:00:00 2001 From: Srikanth Muppandam Date: Sat, 25 Oct 2025 07:05:47 +0530 Subject: [PATCH 1/4] (dt): fast, BusyBox-safe DT search helpers - Replace heavy find-based traversal with low-cost grep scans over /proc/device-tree for both node names and compatible strings. - Add boundary-aware matching: * node: ^(@|$) * compatible: token-prefix with clear labeling of the hit. - Print results as: ": ./[:]" for readability. - Avoid subshell state loss; fix quoting (SC2295) and expansion issues. - Ensure portability to BusyBox grep/ash; no GNU-only flags. - Greatly reduces latency and avoids OOM on constrained devices. Signed-off-by: Srikanth Muppandam --- Runner/utils/functestlib.sh | 163 ++++++++++++++++++------------------ 1 file changed, 81 insertions(+), 82 deletions(-) diff --git a/Runner/utils/functestlib.sh b/Runner/utils/functestlib.sh index 84a2d1c9..2c3b9358 100755 --- a/Runner/utils/functestlib.sh +++ b/Runner/utils/functestlib.sh @@ -2843,94 +2843,93 @@ wait_for_path() { return 1 } -# Skip unwanted device tree node names during DT parsing (e.g., aliases, metadata). -# Used to avoid traversing irrelevant or special nodes in recursive scans. -dt_should_skip_node() { - case "$1" in - ''|__*|phandle|name|'#'*|aliases|chosen|reserved-memory|interrupt-controller|thermal-zones) - return 0 ;; - esac - return 1 -} - -# Recursively yield all directories (nodes) under a DT root path. -# Terminates early if a match result is found in the provided file. -dt_yield_node_paths() { - _root="$1" - _matchresult="$2" - # If we've already matched, stop recursion immediately! - [ -s "$_matchresult" ] && return - for entry in "$_root"/*; do - [ -d "$entry" ] || continue - [ -s "$_matchresult" ] && return - echo "$entry" - dt_yield_node_paths "$entry" "$_matchresult" - done -} - -# Recursively search for files named "compatible" in DT paths. -# Terminates early if a match result is already present. -dt_yield_compatible_files() { - _root="$1" - _matchresult="$2" - [ -s "$_matchresult" ] && return - for entry in "$_root"/*; do - [ -e "$entry" ] || continue - [ -s "$_matchresult" ] && return - if [ -f "$entry" ] && [ "$(basename "$entry")" = "compatible" ]; then - echo "$entry" - elif [ -d "$entry" ]; then - dt_yield_compatible_files "$entry" "$_matchresult" - fi - done -} - -# Searches /proc/device-tree for nodes or compatible strings matching input patterns. -# Returns success and matched path if any pattern is found; used in pre-test validation. -dt_confirm_node_or_compatible() { - root="/proc/device-tree" - matchflag=$(mktemp) || exit 1 - matchresult=$(mktemp) || { rm -f "$matchflag"; exit 1; } +# --------------------------------------------------------------------- +# Ultra-light DT matchers (no indexing; BusyBox-safe; O(first hit)) +# --------------------------------------------------------------------- +DT_ROOT="/proc/device-tree" + +# Print matches for EVERY pattern given; return 0 if any matched, else 1. +# Output format (unchanged): +# - node name match: ": ./path/to/node" +# - compatible file match:": ./path/to/compatible:vendor,chip[,...]" +dt_confirm_node_or_compatible_all() { + LC_ALL=C + any=0 for pattern in "$@"; do - # Node search: strict prefix (e.g., "isp" only matches "isp@...") - for entry in $(dt_yield_node_paths "$root" "$matchresult"); do - [ -s "$matchresult" ] && break - node=$(basename "$entry") - dt_should_skip_node "$node" && continue - printf '%s' "$node" | grep -iEq "^${pattern}(@|$)" || continue - log_info "[DTFIND] Node name strict prefix match: $entry (pattern: $pattern)" - if [ ! -s "$matchresult" ]; then - echo "${pattern}:${entry}" > "$matchresult" + pl=$(printf '%s' "$pattern" | tr '[:upper:]' '[:lower:]') + + # -------- Pass 1: node name strict match (^pat(@|$)) in .../name files -------- + # BusyBox grep: -r (recursive), -s (quiet errors), -i (CI), -a (treat binary as text), + # -E (ERE), -l (print file names), -m1 (stop after first match). + name_file="$(grep -r -s -i -a -E -l -m1 "^${pl}(@|$)" "$DT_ROOT" 2>/dev/null | grep '/name$' -m1 || true)" + if [ -n "$name_file" ]; then + rel="${name_file#"$DT_ROOT"/}" + rel="${rel%/name}" + log_info "[DTFIND] Node name strict match: /$rel (pattern: $pattern)" + printf '%s: ./%s\n' "$pattern" "$rel" + any=1 + continue + fi + + # -------- Pass 2: compatible matches -------- + # Heuristic: + # - If pattern has a comma (e.g. "sony,imx577"), treat it as a full vendor:part token. + # We just need the first compatible file that contains that exact substring. + # - Else (e.g. "isp", "cam", "camss"), treat as IP family: + # token == pat OR token ends with -pat OR token prefix pat + # We still find a small candidate set via grep and vet tokens precisely. + case "$pattern" in *,*) + # label as chip (the part after the last comma) to avoid "swapped" look + chip_label="${pattern##*,}" + comp_file="$(grep -r -s -i -F -l -m1 -- "$pattern" "$DT_ROOT" 2>/dev/null | grep '/compatible$' -m1 || true)" + if [ -n "$comp_file" ]; then + comp_print="$(tr '\0' ' ' <"$comp_file" 2>/dev/null)" + comp_csv="$(tr '\0' ',' <"$comp_file" 2>/dev/null)"; comp_csv="${comp_csv%,}" + rel="${comp_file#"$DT_ROOT"/}" + log_info "[DTFIND] Compatible strict match: /$rel (${comp_print}) (pattern: $pattern)" + # Label by chip (e.g., "imx577: ./…:sony,imx577") + printf '%s: ./%s:%s\n' "$chip_label" "$rel" "$comp_csv" + any=1 + continue fi - touch "$matchflag" - done - [ -s "$matchresult" ] && break - - # Compatible property search: strict (prefix or whole word) - for file in $(dt_yield_compatible_files "$root" "$matchresult"); do - [ -s "$matchresult" ] && break - compdir=$(dirname "$file") - node=$(basename "$compdir") - dt_should_skip_node "$node" && continue - compval=$(tr '\0' ' ' < "$file") - printf '%s' "$compval" | grep -iEq "(^|[ ,])${pattern}([ ,]|$)" || continue - log_info "[DTFIND] Compatible strict match: $file (${compval}) (pattern: $pattern)" - if [ ! -s "$matchresult" ]; then - echo "${pattern}:${file}" > "$matchresult" + ;; + *) + cand_list="$(grep -r -s -i -F -l -- "$pattern" "$DT_ROOT" 2>/dev/null | grep '/compatible$' | head -n 64 || true)" + if [ -n "$cand_list" ]; then + for comp_file in $cand_list; do + hit=0 + while IFS= read -r tok; do + tokl=$(printf '%s' "$tok" | tr '[:upper:]' '[:lower:]') + case "$tokl" in + "$pl"|*-"$pl"|"$pl"*) hit=1; break ;; + esac + done </dev/null) +EOF + if [ "$hit" -eq 1 ]; then + comp_print="$(tr '\0' ' ' <"$comp_file" 2>/dev/null)" + comp_csv="$(tr '\0' ',' <"$comp_file" 2>/dev/null)"; comp_csv="${comp_csv%,}" + rel="${comp_file#"$DT_ROOT"/}" + log_info "[DTFIND] Compatible strict match: /$rel (${comp_print}) (pattern: $pattern)" + # Label stays as the generic pattern (isp/cam/camss) + printf '%s: ./%s:%s\n' "$pattern" "$rel" "$comp_csv" + any=1 + break + fi + done fi - touch "$matchflag" - done - [ -s "$matchresult" ] && break + ;; + esac + # if neither pass matched, we stay silent for this pattern done - if [ -f "$matchflag" ] && [ -s "$matchresult" ]; then - cat "$matchresult" - rm -f "$matchflag" "$matchresult" - return 0 - fi - rm -f "$matchflag" "$matchresult" - return 1 + [ "$any" -eq 1 ] +} + +# Back-compat: single-pattern wrapper +dt_confirm_node_or_compatible() { + dt_confirm_node_or_compatible_all "$@" } # Detects and returns the first available media node (e.g., /dev/media0). From 1611d457bfd1073d076ec1d1bbe786ce8e0a39eb Mon Sep 17 00:00:00 2001 From: Srikanth Muppandam Date: Sat, 25 Oct 2025 07:07:53 +0530 Subject: [PATCH 2/4] libcamera utils: robust, BusyBox-safe validation & IPA workaround MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add IPA fallback workaround (move simple/uncalibrated.yaml → .bk) to prevent buffer allocation failures. - Make all checks BusyBox-friendly: * Drop GNU-only options (find -printf, od -A). * Use dd | tr | cut for sampling and lightweight uniqueness/entropy checks. - Improve BIN/PPM validation: * Contiguous sequence check from cam log and files. * bytesused tolerance check and duplicate-frame bucketing. * Constant/near-constant frame heuristics (warn only). - Noise-tolerant error scanning: suppress known benign WARN/ERROR lines from cam -l and cam -I; only fail on fatal terms after suppression. - Sensor enumeration & index resolution helpers (auto/all/CSV). - Hashing: prefer sha256sum, fall back to md5sum when missing. - ShellCheck cleanups: direct command checks (no `$?`), no subshell mutation pitfalls, consistent quoting and logging. Signed-off-by: Srikanth Muppandam --- Runner/utils/camera/lib_camera.sh | 418 ++++++++++++++++++++++++++++++ 1 file changed, 418 insertions(+) create mode 100755 Runner/utils/camera/lib_camera.sh diff --git a/Runner/utils/camera/lib_camera.sh b/Runner/utils/camera/lib_camera.sh new file mode 100755 index 00000000..29bc3306 --- /dev/null +++ b/Runner/utils/camera/lib_camera.sh @@ -0,0 +1,418 @@ +#!/bin/sh +# Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. +# SPDX-License-Identifier: BSD-3-Clause-Clear +# Shared libcamera helpers +# ---------- Sensor & index helpers ---------- + +# 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:]]*[0-9]+:[[:space:]]/ { if (inlist) c++ } + END { print c+0 } + ' +} + +# List all camera indices seen in `cam -l` (space-separated) +libcam_list_indices() { + command -v cam >/dev/null 2>&1 || { printf '\n'; return 1; } + cam -l 2>/dev/null \ + | sed -n -E 's/^[[:space:]]*\[([0-9]+)\].*$/\1/p; s/^[[:space:]]*([0-9]+):.*/\1/p' +} + +# Resolve requested indices: +# - "auto" (default): first index from cam -l +# - "all": all indices space-separated +# - "0,2,5": comma-separated list (validated against cam -l) +# Outputs space-separated indices to stdout. +libcam_resolve_indices() { + want="${1:-auto}" + all="$(libcam_list_indices | tr '\n' ' ' | sed 's/[[:space:]]\+/ /g;s/^ //;s/ $//')" + [ -n "$all" ] || { printf '\n'; return 1; } + + case "$want" in + ""|"auto") + printf '%s\n' "$(printf '%s' "$all" | awk '{print $1}')" + ;; + "all") + printf '%s\n' "$all" + ;; + *) + good="" + IFS=','; set -- "$want"; IFS=' ' + for idx in "$@"; do + if printf '%s\n' "$all" | grep -qx "$idx"; then + good="$good $idx" + fi + done + printf '%s\n' "$(printf '%s' "$good" | sed 's/^ //')" + ;; + esac +} + +# Pick first camera index (helper used by older paths) +libcam_pick_cam_index() { + want="${1:-auto}" + if [ "$want" != "auto" ]; then + printf '%s\n' "$want" + return 0 + fi + libcam_list_indices | head -n1 +} + +# ---------- Log parsing ---------- + +# Extract sequences from a cam run log +# Usage: libcam_log_seqs "" +libcam_log_seqs() { + sed -n 's/.*seq:[[:space:]]*\([0-9][0-9]*\).*/\1/p' "$1" +} + +# Extract bytesused from a cam run log +# Usage: libcam_log_bytesused "" +libcam_log_bytesused() { + sed -n 's/.*bytesused:[[:space:]]*\([0-9][0-9]*\).*/\1/p' "$1" +} + +# Check contiguous sequence numbers on stdin; prints a summary and sets exit code +# Usage: libcam_check_contiguous +libcam_check_contiguous() { + awk ' + { + v=$1+0 + a[v]=1 + if(n==0 || vmax) max=v + n++ + } + END{ + if(n==0){ print "EMPTY"; exit 1 } + missing=0 + for(i=min;i<=max;i++){ if(!(i in a)) missing++ } + printf "N=%d MIN=%06d MAX=%06d MISSING=%d\n", n, min, max, missing + exit (missing==0 ? 0 : 2) + }' +} + +# List captured files and their inferred sequence (from filename) +# Usage: libcam_file_list_and_seq "" +libcam_file_list_and_seq() { + find "$1" -maxdepth 1 -type f \( -name 'frame-*.bin' -o -name 'frame-*.ppm' \) -print \ + | awk ' + { + f=$0; seq=-1 + if (match(f, /[^0-9]([0-9]+)\.(bin|ppm)$/, m)) seq=m[1] + print f, seq + }' | sort +} + +# Sample the first N bytes and count unique values (to detect constant content) +# Usage: libcam_sample_uniques "" "" +libcam_sample_uniques() { + f="$1"; n="$2" + + # Prefer BusyBox-compatible od -b + if command -v od >/dev/null 2>&1; then + dd if="$f" bs=1 count="$n" status=none 2>/dev/null \ + | od -v -b 2>/dev/null \ + | awk ' + { + for (i=2;i<=NF;i++) { + if ($i ~ /^[0-7]{3}$/) { + v = strtonum("0"$i) + if (!(v in seen)) { seen[v]=1; cnt++ } + } + } + } + END { print (cnt+0) } + ' + return + fi + + # Fallback: hexdump (some BusyBox builds lack -e) + if command -v hexdump >/dev/null 2>&1; then + dd if="$f" bs=1 count="$n" status=none 2>/dev/null \ + | hexdump -v -C 2>/dev/null \ + | awk ' + { + # Parse canonical dump: hex bytes in columns 2..17 + for (i=2;i<=17;i++) { + h = $i + if (h ~ /^[0-9A-Fa-f]{2}$/) { + v = strtonum("0x"h) + if (!(v in seen)) { seen[v]=1; cnt++ } + } + } + } + END { print (cnt+0) } + ' + return + fi + + # Last-chance optimistic fallback + echo 256 +} + +# Return a hashing command if available (sha256sum preferred) +# Usage: cmd="$(libcam_hash_command)" +libcam_hash_command() { + if command -v sha256sum >/dev/null 2>&1; then + echo sha256sum + elif command -v md5sum >/dev/null 2>&1; then + echo md5sum + else + echo "" + fi +} + +# ---------- Files & sequence mapping ---------- + +# Build file→seq map and (optionally) check contiguous sequences across files. +# Usage: libcam_files_and_seq "" "" +# Side-effects: writes "$out_dir/.file_seq_map.txt" +# Returns: 0 = OK, 1 = failed strict check +libcam_files_and_seq() { + dir="$1"; strict="$2" + MAP="$dir/.file_seq_map.txt" + : > "$MAP" + + for f in "$dir"/frame-*.bin "$dir"/frame-*.ppm; do + [ -e "$f" ] || continue + base="${f##*/}" + seq="${base%.*}" + seq="${seq##*-}" + printf '%s %s\n' "$f" "$seq" >> "$MAP" + done + + if [ "$strict" = "yes" ]; then + awk '{print $2+0}' "$MAP" | sort -n | libcam_check_contiguous >/dev/null 2>&1 || { + log_warn "non-contiguous sequences in files" + return 1 + } + fi + return 0 +} + +# ---------- Content validation (PPM & BIN) ---------- + +# Validate PPM frames under OUT_DIR. +# Usage: libcam_validate_ppm "" "" +# Returns: 0 = OK, 1 = problems found +libcam_validate_ppm() { + out_dir="$1" + ppm_bytes="$2" + count=$(find "$out_dir" -maxdepth 1 -type f -name 'frame-*.ppm' | wc -l | tr -d ' ') + [ "$count" -gt 0 ] || return 0 + + BAD_PPM=0 + ppm_list="$out_dir/.ppm_list.txt" + find "$out_dir" -maxdepth 1 -type f -name 'frame-*.ppm' -print > "$ppm_list" + + while IFS= read -r f; do + [ -f "$f" ] || continue + + magic="$(LC_ALL=C head -c 2 "$f" 2>/dev/null || true)" + if [ "$magic" != "P6" ]; then + log_warn "PPM magic not P6: $f" + BAD_PPM=$((BAD_PPM+1)) + continue + fi + + hdr="$(dd if="$f" bs=1 count=256 status=none 2>/dev/null)" + hdr_clean="$(printf '%s' "$hdr" \ + | sed 's/#.*$//g' \ + | tr '\n' ' ' \ + | tr -s ' ' \ + | sed 's/^P6 *//')" + + IFS=' ' read -r W H M _ </dev/null || stat -f %z "$f" 2>/dev/null || echo 0) + if [ "$fsz" -lt "$datasz" ]; then + log_warn "PPM smaller than payload ($fsz < $datasz): $f" + BAD_PPM=$((BAD_PPM+1)) + continue + fi + + u=$(libcam_sample_uniques "$f" "$ppm_bytes") + if [ "${u:-0}" -le 4 ]; then + log_warn "PPM looks constant/near-constant (uniques=$u): $f" + BAD_PPM=$((BAD_PPM+1)) + fi + done < "$ppm_list" + + [ "${BAD_PPM:-0}" -eq 0 ] +} + +# Validate BIN frames under OUT_DIR using RUN_LOG for bytesused correlation. +# Usage: libcam_validate_bin "" "" "" "" "" +# Returns: 0 = OK, 1 = problems found +libcam_validate_bin() { + dir="$1"; run_log="$2"; bin_sample_bytes="$3"; BIN_TOL_PCT="$4"; DUP_MAX_RATIO="$5" + + BAD=0 + + # Extract bytesused lines from the run log (may be 0 lines; that's fine) + BU_TXT="$dir/.bytesused.txt" + sed -n 's/.*bytesused:[[:space:]]*\([0-9][0-9]*\).*/\1/p' "$run_log" > "$BU_TXT" 2>/dev/null || : + + # List BIN files & sizes (BusyBox-compatible: no -printf) + SIZES_TXT="$dir/.bin_sizes.txt" + : > "$SIZES_TXT" + find "$dir" -maxdepth 1 -type f -name 'frame-*.bin' -print 2>/dev/null \ + | while IFS= read -r f; do + sz="$(stat -c %s "$f" 2>/dev/null || stat -f %z "$f" 2>/dev/null || wc -c <"$f")" + printf '%s %s\n' "$f" "$sz" >> "$SIZES_TXT" + done + + # Size tolerance check vs. closest bytesused + if [ -s "$BU_TXT" ] && [ -s "$SIZES_TXT" ]; then + while IFS= read -r line; do + f=$(printf '%s\n' "$line" | cut -d' ' -f1) + sz=$(printf '%s\n' "$line" | cut -d' ' -f2) + [ -n "$sz" ] || continue + + target=$( + sort -n "$BU_TXT" 2>/dev/null \ + | awk -v S="$sz" ' + BEGIN{best=-1; diff=1e99} + { d=$1-S; if(d<0)d=-d; if(d/dev/null 2>&1; then + u="$(dd if="$f" bs=1 count="$bin_sample_bytes" status=none 2>/dev/null \ + | od -An -tu1 -v 2>/dev/null \ + | tr -s ' ' ' ' | tr ' ' '\n' | sed '/^$/d' \ + | sort -n | uniq | wc -l | tr -d ' ' )" + elif command -v hexdump >/dev/null 2>&1; then + u="$(dd if="$f" bs=1 count="$bin_sample_bytes" status=none 2>/dev/null \ + | hexdump -v -e '1/1 "%u\n"' 2>/dev/null \ + | sort -n | uniq | wc -l | tr -d ' ' )" + fi + # Warn only; do NOT increment BAD. + if [ "${u:-0}" -le 4 ] 2>/dev/null; then + log_warn "BIN looks constant/near-constant (uniques=${u:-0}): $f" + fi + done < "$SIZES_TXT" + fi + + # Duplicate detection by hash (optional, best-effort) + if [ -s "$SIZES_TXT" ]; then + hash_cmd="" + if command -v sha256sum >/dev/null 2>&1; then hash_cmd="sha256sum" + elif command -v md5sum >/dev/null 2>&1; then hash_cmd="md5sum" + fi + + if [ -n "$hash_cmd" ]; then + DUPS_TXT="$dir/.hashes.txt" + : > "$DUPS_TXT" + while IFS= read -r f; do + [ -f "$f" ] || continue + $hash_cmd "$f" 2>/dev/null | awk '{print $1}' >> "$DUPS_TXT" + done < "$SORTED" 2>/dev/null || cp "$DUPS_TXT" "$SORTED" + + total=$(wc -l <"$SORTED" 2>/dev/null | tr -d ' ') + maxdup=$(awk ' + { cnt[$1]++ } + END { + m=0; for (k in cnt) if (cnt[k]>m) m=cnt[k]; + print m+0 + }' "$SORTED") + + if [ "${total:-0}" -gt 0 ] && [ "${maxdup:-0}" -gt 0 ]; then + ratio=$(awk -v m="$maxdup" -v t="$total" 'BEGIN{ if(t==0) print "0"; else printf "%.3f", m/t }') + if awk -v r="$ratio" -v lim="$DUP_MAX_RATIO" 'BEGIN{ exit !(r>lim) }'; then + log_warn "High duplicate ratio in BIN frames (max bucket $maxdup / $total = $ratio > $DUP_MAX_RATIO)" + BAD=$((BAD+1)) + fi + fi + fi + fi + fi + + [ "$BAD" -eq 0 ] +} + +# Orchestrate both content checks +# Usage: libcam_validate_content "" "" "" "" "" "" +# Returns: 0 = OK, 1 = problems found +libcam_validate_content() { + out_dir="$1"; run_log="$2"; ppm_bytes="$3"; bin_bytes="$4"; bin_tol_pct="$5"; dup_max_ratio="$6" + ok=0 + if ! libcam_validate_ppm "$out_dir" "$ppm_bytes"; then ok=1; fi + if ! libcam_validate_bin "$out_dir" "$run_log" "$bin_bytes" "$bin_tol_pct" "$dup_max_ratio"; then ok=1; fi + [ $ok -eq 0 ] +} + +# ---------- Error scanning ---------- + +# Scan run log for serious errors if strict; return non-zero if any found. +# Usage: libcam_scan_errors "" "" +libcam_scan_errors() { + run_log="$1" + strict="$2" + + [ "$strict" = "yes" ] || { log_info "[scan] ERR_STRICT=no; skipping fatal scan"; return 0; } + + # Build a filtered view that removes known-benign noise we see on imx577 / simple pipeline. + # We keep this BusyBox/grep-basic-friendly (no PCRE features). + tmpf="$(mktemp)" || return 1 + # Lines to ignore (noisy but benign for listing/capture): + # - CameraSensor legacy WARN/ERROR geometry queries + # - IPAProxy config yaml warnings for imx577/simple + # - Software ISP / SimplePipeline warnings + # - Generic "WARN" lines + grep -viE \ + 'CameraSensor|PixelArray|ActiveAreas|crop rectangle|Rotation control|No sensor delays|CameraSensorProperties|IPAProxy|configuration file.*yaml|SoftwareIsp|IPASoft|SimplePipeline' \ + "$run_log" >"$tmpf" || true + + # Fatal patterns: keep simple & portable; focus on truly bad states. + # (No generic "error" catch-all here.) + if grep -Eiq \ + 'segmentation fault|assert|device[^[:alpha:]]*not[^[:alpha:]]*found|failed to open camera|cannot open camera|stream[^[:alpha:]]*configuration[^[:alpha:]]*failed|request[^[:alpha:]]*timeout|EPIPE|I/O error' \ + "$tmpf"; then + log_warn "Serious error keywords found in cam run log (fatal)" + rm -f "$tmpf" + return 1 + fi + + log_info "[scan] No fatal errors after noise suppression" + rm -f "$tmpf" + return 0 +} From baeabd32925459370be7dc7bf660c0cd629eb413 Mon Sep 17 00:00:00 2001 From: Srikanth Muppandam Date: Sat, 25 Oct 2025 07:11:04 +0530 Subject: [PATCH 3/4] docs(libcamera_cam): add README with usage, deps, and troubleshooting - Document prerequisites, known BusyBox constraints, and IPA workaround. - Provide command-line options, examples (single/multi camera), and artifact layout. - Include guidance for DT gating output, common libcamera WARN/ERROR messages, and how the runner interprets them (fatal vs informational). Signed-off-by: Srikanth Muppandam --- .../Libcamera_cam/libcamera_test_README.md | 170 ++++++++++++++++++ 1 file changed, 170 insertions(+) create mode 100644 Runner/suites/Multimedia/Camera/Libcamera_cam/libcamera_test_README.md diff --git a/Runner/suites/Multimedia/Camera/Libcamera_cam/libcamera_test_README.md b/Runner/suites/Multimedia/Camera/Libcamera_cam/libcamera_test_README.md new file mode 100644 index 00000000..4925a1bd --- /dev/null +++ b/Runner/suites/Multimedia/Camera/Libcamera_cam/libcamera_test_README.md @@ -0,0 +1,170 @@ +# Libcamera Camera Test Runner + +This repository contains a **POSIX shell** test harness for exercising `libcamera` via its `cam` utility, with robust post‑capture validation and device‑tree (DT) checks. It is designed to run on embedded Linux targets (BusyBox-friendly), including Qualcomm RB platforms. + +--- + +## 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). +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. + +--- + +## Requirements + +- `cam` (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). + +> The harness tolerates noisy `cam -l` / `cam -I` output (WARN/ERROR lines). It only requires that cameras and/or stream info are ultimately reported. + +--- + +## Quick start + +From the test directory (e.g. `Runner/suites/Multimedia/Camera/Libcamera_cam/`): + +```sh +./run.sh +``` + +Default behavior: +- Auto‑detect first camera index (`cam -l`). +- Capture **10 frames** per selected camera. +- Write outputs under `./cam_out/` (per‑camera subfolders `cam#`). +- Validate and print a summary. + +### Common options + +```text +--index N|all|n,m Camera index (default: auto from `cam -l`; `all` = run on every camera) +--count N Frames to capture (default: 10) +--out DIR Output directory (default: ./cam_out) +--ppm Save frames as PPM files (frame-#.ppm) +--bin Save frames as BIN files (default; frame-#.bin) +--args "STR" Extra args passed to `cam` +--strict Enforce strict validation (default) +--no-strict Relax validation (no seq/err strictness) +--dup-max-ratio R Fail if max duplicate bucket/total > R (default: 0.5) +--bin-tol-pct P BIN size tolerance vs bytesused in % (default: 5) +-h, --help Help +``` + +Examples: +```sh +# Run default capture (first detected camera, 10 frames) +./run.sh + +# Run on all cameras, 20 frames, save PPM +./run.sh --index all --count 20 --ppm + +# Run on cameras 0 and 2, pass explicit stream config to cam +./run.sh --index 0,2 --args "-s width=1920,height=1080,role=viewfinder" +``` + +--- + +## Device‑tree checks + +The runner verifies DT node presence **before** capture: + +- 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.” + +--- + +## 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: + +```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 +fi +``` + +It’s restored automatically at the end (if it was present). + +--- + +## Output & artifacts + +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 + +--- + +## Validation details + +- **Sequence integrity**: checks that frame sequence numbers are contiguous (unless `--no-strict`). +- **PPM sanity**: header/magic checks and basic content entropy (sampled). +- **BIN sanity**: size compared to `bytesused` (±`BIN_TOL_PCT`), entropy sample, duplicate detection via hashes. +- **Error scan**: scans `cam` logs for fatal indicators, then applies **noise suppression** to ignore known benign warnings from `simple` pipeline and sensors like `imx577`. + +You can relax strictness with `--no-strict` (skips contiguous sequence enforcement and strict error gating). + +--- + +## Multicamera behavior + +- `--index all`: detects all indices from `cam -l` and iterates. +- `--index 0,2,5`: runs each listed index. +- Each index is independently validated and reported: if **any** camera fails, the **overall** result is **FAIL**. The summary lists which indices passed/failed. + +--- + +## Environment overrides + +- `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` + +--- + +## Troubleshooting + +- **`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. +- **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. + +--- + +## Maintainers + +- Multimedia/Camera QA +- Platform Integration + +Please submit issues and PRs with logs from `cam-run-*.log`, `cam-info-*.log`, and `summary.txt`. From 7902cdec33de71da615b7abdba0d40653d0d0b88 Mon Sep 17 00:00:00 2001 From: Srikanth Muppandam Date: Sat, 25 Oct 2025 07:13:50 +0530 Subject: [PATCH 4/4] Libcamera_cam/run.sh: minimal runner, DT gating, clear summaries - Source camera utils; keep main script small and readable. - Fast DT gating: print per-pattern matches; skip only when none found. - Treat cam -l/-I WARN/ERROR noise as non-fatal when cameras enumerate. - Support indices: auto | all | comma-separated; per-camera PASS/FAIL with an overall verdict and summary file. - Use --file correctly for directory or explicit filename targets. - ShellCheck fixes (SC1090/SC2015): dynamic source directives and proper if/else usage. - Add structured logging and stable, deterministic exit codes. Signed-off-by: Srikanth Muppandam --- .../Multimedia/Camera/Libcamera_cam/run.sh | 304 ++++++++++++++++++ 1 file changed, 304 insertions(+) create mode 100755 Runner/suites/Multimedia/Camera/Libcamera_cam/run.sh diff --git a/Runner/suites/Multimedia/Camera/Libcamera_cam/run.sh b/Runner/suites/Multimedia/Camera/Libcamera_cam/run.sh new file mode 100755 index 00000000..d021c5a3 --- /dev/null +++ b/Runner/suites/Multimedia/Camera/Libcamera_cam/run.sh @@ -0,0 +1,304 @@ +#!/bin/sh +# Copyright (c) Qualcomm Technologies, Inc. +# SPDX-License-Identifier: BSD-3-Clause-Clear +# libcamera 'cam' runner with strong post-capture validation (CLI-config only) +# ---------- Repo env + helpers (single-pass) ---------- +SCRIPT_DIR=$(cd "$(dirname "$0")" || exit 1; pwd) +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%/*} +done + +if [ -z "$INIT_ENV" ]; then + printf '%s\n' "[ERROR] Could not find init_env (starting at $SCRIPT_DIR)" >&2 + exit 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 + +# 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 + exit 1 +fi + +# shellcheck source=../../../../utils/camera/lib_camera.sh disable=SC1091 +. "$LIBCAM_PATH" + +TESTNAME="Libcamera_cam" +RES_FILE="./${TESTNAME}.res" +: > "$RES_FILE" + +# ---------- Defaults (override via CLI only) ---------- +CAM_INDEX="auto" # --index |all|n,m,k ; auto = first from `cam -l` +CAPTURE_COUNT="10" # --count +OUT_DIR="./cam_out" # --out +SAVE_AS_PPM="no" # --ppm | --bin +CAM_EXTRA_ARGS="" # --args "" + +# Validation knobs +SEQ_STRICT="yes" # --no-strict to relax +ERR_STRICT="yes" # --no-strict to relax +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" + +print_usage() { + cat < R (default: 0.5) + --bin-tol-pct P BIN size tolerance vs bytesused in % (default: 5) + -h, --help Show this help +EOF +} + +# ---------- CLI ---------- +while [ $# -gt 0 ]; do + case "$1" in + --index) shift; CAM_INDEX="$1" ;; + --count) shift; CAPTURE_COUNT="$1" ;; + --out) shift; OUT_DIR="$1" ;; + --ppm) SAVE_AS_PPM="yes" ;; + --bin) SAVE_AS_PPM="no" ;; + --args) shift; CAM_EXTRA_ARGS="$1" ;; + --strict) SEQ_STRICT="yes"; ERR_STRICT="yes" ;; + --no-strict) SEQ_STRICT="no"; ERR_STRICT="no" ;; + --dup-max-ratio) shift; DUP_MAX_RATIO="$1" ;; + --bin-tol-pct) shift; BIN_TOL_PCT="$1" ;; + -h|--help) print_usage; exit 0 ;; + *) log_warn "Unknown option: $1"; print_usage; exit 2 ;; + esac + shift +done + +# ---------- DT / platform readiness ---------- +# Print both sensor and CAMSS/ISP matches if they exist; skip only if neither is present. +log_info "Verifying the availability of DT nodes, this process may take some time." + +PATTERNS="sony,imx577 imx577 isp cam camss" +found_any=0 +missing_list="" + +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 + [ -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 +fi + +# ---------- Dependencies ---------- +log_info "Reviewing the dependencies needed to run the cam test." +check_dependencies cam || { + log_error "cam utility not found" + 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 + +# ---------- Setup ---------- +mkdir -p "$OUT_DIR" 2>/dev/null || true +RUN_TS="$(date -u +%Y%m%d-%H%M%S)" + +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" +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 +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 +fi + + +# ---------- Resolve indices (supports: auto | all | 0,2,5) ---------- +INDICES="$(libcam_resolve_indices "$CAM_INDEX")" +if [ -z "$INDICES" ]; then + log_skip "No valid camera indices resolved (cam -l empty?) - SKIP" + echo "$TESTNAME SKIP" > "$RES_FILE" + exit 0 +fi +log_info "Resolved indices: $INDICES" + +OVERALL_PASS=1 +ANY_RC_NONZERO=0 +PASS_LIST="" +FAIL_LIST="" +: > "$OUT_DIR/summary.txt" + +for IDX in $INDICES; do + # Per-camera logs & output dir + CAM_DIR="${OUT_DIR%/}/cam${IDX}" + mkdir -p "$CAM_DIR" 2>/dev/null || true + RUN_LOG="${CAM_DIR%/}/cam-run-${RUN_TS}-cam${IDX}.log" + INFO_LOG="${CAM_DIR%/}/cam-info-${RUN_TS}-cam${IDX}.log" + + log_info "---- Camera idx: $IDX ----" + { + echo "== cam -l ==" + cam -l || true + echo + echo "== cam -I (index $IDX) ==" + cam -c "$IDX" -I || true + } >"$INFO_LOG" 2>&1 + + # Capture + FILE_TARGET="$CAM_DIR/" + [ "$SAVE_AS_PPM" = "yes" ] && FILE_TARGET="$CAM_DIR/frame-#.ppm" + + log_info "cmd:" + log_info " cam -c $IDX --capture=$CAPTURE_COUNT \\" + log_info " --file=\"$FILE_TARGET\" ${CAM_EXTRA_ARGS:+\\}" + [ -n "$CAM_EXTRA_ARGS" ] && log_info " $CAM_EXTRA_ARGS" + + # shellcheck disable=SC2086 + ( cam -c "$IDX" --capture="$CAPTURE_COUNT" --file="$FILE_TARGET" $CAM_EXTRA_ARGS ) \ + >"$RUN_LOG" 2>&1 + RC=$? + + tail -n 50 "$RUN_LOG" | sed "s/^/[cam idx $IDX] /" + + # Per-camera validation + BIN_COUNT=$(find "$CAM_DIR" -maxdepth 1 -type f -name 'frame-*.bin' | wc -l | tr -d ' ') + PPM_COUNT=$(find "$CAM_DIR" -maxdepth 1 -type f -name 'frame-*.ppm' | wc -l | tr -d ' ') + TOTAL=$((BIN_COUNT + PPM_COUNT)) + log_info "[idx $IDX] Produced files: bin=$BIN_COUNT ppm=$PPM_COUNT total=$TOTAL (requested $CAPTURE_COUNT)" + + PASS=1 + [ "$TOTAL" -ge "$CAPTURE_COUNT" ] || { log_warn "[idx $IDX] Fewer files than requested"; PASS=0; } + + SEQ_REPORT="$(libcam_log_seqs "$RUN_LOG" | wc -l | tr -d ' ')" + [ "$SEQ_REPORT" -ge "$CAPTURE_COUNT" ] || { log_warn "[idx $IDX] cam log shows fewer seq lines ($SEQ_REPORT) than requested ($CAPTURE_COUNT)"; PASS=0; } + + if [ "$SEQ_STRICT" = "yes" ]; then + CSUM="$(libcam_log_seqs "$RUN_LOG" | libcam_check_contiguous 2>&1)" + echo "$CSUM" | sed 's/^/[seq] /' + echo "$CSUM" | grep -q 'MISSING=0' || { log_warn "[idx $IDX] non-contiguous sequences in log"; PASS=0; } + fi + + libcam_files_and_seq "$CAM_DIR" "$SEQ_STRICT" || PASS=0 + libcam_validate_content "$CAM_DIR" "$RUN_LOG" "$PPM_SAMPLE_BYTES" "$BIN_SAMPLE_BYTES" "$BIN_TOL_PCT" "$DUP_MAX_RATIO" || PASS=0 + libcam_scan_errors "$RUN_LOG" "$ERR_STRICT" || PASS=0 + + [ $RC -eq 0 ] || { ANY_RC_NONZERO=1; PASS=0; } + + if [ "$PASS" -eq 1 ]; then + log_pass "[idx $IDX] PASS" + PASS_LIST="$PASS_LIST $IDX" + echo "cam$IDX PASS" >> "$OUT_DIR/summary.txt" + else + log_fail "[idx $IDX] FAIL" + FAIL_LIST="$FAIL_LIST $IDX" + echo "cam$IDX FAIL" >> "$OUT_DIR/summary.txt" + OVERALL_PASS=0 + fi +done + +# ---------- Per-camera summary (always printed) ---------- +pass_trim="$(printf '%s' "$PASS_LIST" | sed 's/^ //')" +fail_trim="$(printf '%s' "$FAIL_LIST" | sed 's/^ //')" +log_info "---------- Per-camera summary ----------" +if [ -n "$pass_trim" ]; then + log_info "PASS: $pass_trim" +else + log_info "PASS: (none)" +fi +if [ -n "$fail_trim" ]; then + log_info "FAIL: $fail_trim" +else + log_info "FAIL: (none)" +fi +log_info "Summary file: $OUT_DIR/summary.txt" + +# ---------- Final verdict ---------- +if [ "$OVERALL_PASS" -eq 1 ] && [ $ANY_RC_NONZERO -eq 0 ]; then + echo "$TESTNAME PASS" > "$RES_FILE" + log_pass "$TESTNAME PASS" + exit 0 +else + echo "$TESTNAME FAIL" > "$RES_FILE" + log_fail "$TESTNAME FAIL" + exit 1 +fi + +# ---------- Artifacts ---------- +log_info "Artifacts under: $OUT_DIR/" +for IDX in $INDICES; do + CAM_DIR="${OUT_DIR%/}/cam${IDX}" + log_info " - $CAM_DIR/" +done