From c478360fcad4135a6bb5f1db3790b43ec8b7c633 Mon Sep 17 00:00:00 2001 From: Srikanth Muppandam Date: Thu, 2 Jul 2026 16:55:34 +0530 Subject: [PATCH] camera: select max sensor mode for RDI yavta capture Query sensor subdev media-bus codes and frame sizes dynamically from v4l2-ctl, select the largest advertised mode, and map the media-bus code to the yavta fourcc used for raw Bayer capture. This avoids hardcoded sensor dimensions while allowing Camera_RDI_FrameCapture to use max resolution modes such as OV08X 3856x2416 SGRBG10_1X10/SGRBG10P. Signed-off-by: Srikanth Muppandam --- .../Camera/Camera_RDI_FrameCapture/run.sh | 387 +++++++----- Runner/utils/camera/lib_camera_rdi.sh | 551 ++++++++++++++++++ Runner/utils/camera/parse_media_topology.py | 282 ++++++++- 3 files changed, 1048 insertions(+), 172 deletions(-) create mode 100755 Runner/utils/camera/lib_camera_rdi.sh diff --git a/Runner/suites/Multimedia/Camera/Camera_RDI_FrameCapture/run.sh b/Runner/suites/Multimedia/Camera/Camera_RDI_FrameCapture/run.sh index dd939b560..5ede9ec2d 100755 --- a/Runner/suites/Multimedia/Camera/Camera_RDI_FrameCapture/run.sh +++ b/Runner/suites/Multimedia/Camera/Camera_RDI_FrameCapture/run.sh @@ -1,11 +1,16 @@ #!/bin/sh # Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. -# SPDX-License-Identifier: BSD-3-Clause# --- Robustly find and source init_env --------------------------- -# ---------- Repo env + helpers ---------- +# SPDX-License-Identifier: BSD-3-Clause + +# --- Robustly find and source init_env --------------------------- SCRIPT_DIR="$( cd "$(dirname "$0")" || exit 1 pwd )" + +TESTNAME="Camera_RDI_FrameCapture" +RES_FILE="./$TESTNAME.res" + INIT_ENV="" SEARCH="$SCRIPT_DIR" @@ -19,23 +24,46 @@ done if [ -z "$INIT_ENV" ]; then echo "[ERROR] Could not find init_env (starting at $SCRIPT_DIR)" >&2 + echo "$TESTNAME FAIL" >"$RES_FILE" 2>/dev/null || true exit 1 fi -# Only source once (idempotent) +REPO_ROOT="$(dirname "$INIT_ENV")" + +# Only source once. if [ -z "${__INIT_ENV_LOADED:-}" ]; then # shellcheck disable=SC1090 . "$INIT_ENV" __INIT_ENV_LOADED=1 fi -# shellcheck disable=SC1090 -. "$INIT_ENV" # shellcheck disable=SC1091 . "$TOOLS/functestlib.sh" -TESTNAME="Camera_RDI_FrameCapture" -RES_FILE="./$TESTNAME.res" +# --------- Camera RDI helper library --------- +CAMERA_RDI_LIB="" + +if [ -n "${TOOLS:-}" ] && [ -f "$TOOLS/camera/lib_camera_rdi.sh" ]; then + CAMERA_RDI_LIB="$TOOLS/camera/lib_camera_rdi.sh" +elif [ -n "${ROOT_DIR:-}" ] && [ -f "$ROOT_DIR/utils/camera/lib_camera_rdi.sh" ]; then + CAMERA_RDI_LIB="$ROOT_DIR/utils/camera/lib_camera_rdi.sh" +elif [ -n "${REPO_ROOT:-}" ] && [ -f "$REPO_ROOT/utils/camera/lib_camera_rdi.sh" ]; then + CAMERA_RDI_LIB="$REPO_ROOT/utils/camera/lib_camera_rdi.sh" +else + echo "[ERROR] Missing camera RDI helper library" >&2 + echo "[ERROR] Checked:" >&2 + echo "[ERROR] ${TOOLS:-}/camera/lib_camera_rdi.sh" >&2 + echo "[ERROR] ${ROOT_DIR:-}/utils/camera/lib_camera_rdi.sh" >&2 + echo "[ERROR] ${REPO_ROOT:-}/utils/camera/lib_camera_rdi.sh" >&2 + echo "$TESTNAME FAIL" >"$RES_FILE" + exit 1 +fi + +log_info "Using camera RDI helper library: $CAMERA_RDI_LIB" + +# shellcheck disable=SC1090 +. "$CAMERA_RDI_LIB" + test_path="$(find_test_case_by_name "$TESTNAME")" cd "$test_path" || exit 1 @@ -44,9 +72,13 @@ print_usage() { Usage: $0 [--format ] [--frames ] [--help] Options: - --format Test one or more comma-separated formats (e.g., UYVY,NV12) - --frames Number of frames to capture per pipeline (default: 10) + --format Test one or more comma-separated formats, for example: UYVY,NV12 + --frames Number of frames to capture per pipeline. Default: 10 --help Show this help message + +Environment: + CAPTURE_TIMEOUT_SECS YAVTA capture timeout per attempt. Default: 45 + YAVTA_CTRL_TIMEOUT_SECS YAVTA stream-control timeout. Default: 10 EOF } @@ -57,16 +89,45 @@ log_info "=== Test Initialization ===" # --------- Argument Parsing --------- USER_FORMAT="" FRAMES=10 + while [ $# -gt 0 ]; do case "$1" in - --format) shift; USER_FORMAT="$1" ;; - --frames) shift; FRAMES="$1" ;; - --help) print_usage; exit 0 ;; - *) log_error "Unknown argument: $1"; print_usage; echo "$TESTNAME FAIL" >"$RES_FILE"; exit 1 ;; + --format) + shift + USER_FORMAT="$1" + ;; + --frames) + shift + FRAMES="$1" + ;; + --help) + print_usage + exit 0 + ;; + *) + log_error "Unknown argument: $1" + print_usage + echo "$TESTNAME FAIL" >"$RES_FILE" + exit 1 + ;; esac shift done +case "$FRAMES" in + ''|*[!0-9]*) + log_warn "Invalid --frames value; using default 10" + FRAMES=10 + ;; +esac + +CAPTURE_TIMEOUT_SECS="${CAPTURE_TIMEOUT_SECS:-45}" +case "$CAPTURE_TIMEOUT_SECS" in + ''|*[!0-9]*) + CAPTURE_TIMEOUT_SECS=45 + ;; +esac + # --------- DT Precheck --------- if ! dt_confirm_node_or_compatible "isp" "cam" "camss"; then log_skip "$TESTNAME SKIP – No ISP/camera node/compatible found in DT" @@ -74,26 +135,29 @@ if ! dt_confirm_node_or_compatible "isp" "cam" "camss"; then exit 2 fi -# --------- Kernel config sanity (MANDATORY bits only gate if totally absent) --------- +# --------- Kernel config sanity --------- log_info "[CONFIG] Expect at least: CONFIG_VIDEO_QCOM_CAMSS=y (or =m)" check_kernel_config "CONFIG_VIDEO_QCOM_CAMSS CONFIG_MEDIA_CONTROLLER CONFIG_V4L2_FWNODE" \ || log_warn "[CONFIG] One or more options missing; will continue if CAMSS stack is otherwise present" -# Optional visibility: print any CAMCC entries (name varies by tree) -if ! check_kernel_config "CONFIG_QCOM_CAMCC_SC7280"; then - if command -v zgrep >/dev/null 2>&1; then - CAMCC_SYMS="$(zgrep -E '^CONFIG_.*CAMCC.*=(y|m)' /proc/config.gz 2>/dev/null || true)" - else - CAMCC_SYMS="$(gzip -dc /proc/config.gz 2>/dev/null | grep -E '^CONFIG_.*CAMCC.*=(y|m)' || true)" - fi - if [ -n "$CAMCC_SYMS" ]; then - printf '%s\n' "$CAMCC_SYMS" | while IFS= read -r s; do - [ -n "$s" ] && log_info "[CONFIG] $s" - done - fi +# Optional visibility: print platform CAMCC entries. +# CAMCC symbol names vary by platform, so do not hardcode SC7280 here. +if command -v zgrep >/dev/null 2>&1; then + CAMCC_SYMS="$(zgrep -E '^CONFIG_.*CAMCC.*=(y|m)' /proc/config.gz 2>/dev/null || true)" +else + CAMCC_SYMS="$(gzip -dc /proc/config.gz 2>/dev/null | grep -E '^CONFIG_.*CAMCC.*=(y|m)' || true)" fi -# --------- Broader readiness gate (module OR builtin OR nodes OR dmesg) --------- +if [ -n "$CAMCC_SYMS" ]; then + log_pass "[CONFIG] CAMCC config present:" + printf '%s\n' "$CAMCC_SYMS" | while IFS= read -r s; do + [ -n "$s" ] && log_info "[CONFIG] $s" + done +else + log_warn "[CONFIG] No CAMCC config symbol found; continuing if CAMSS/media nodes are present" +fi + +# --------- Broader readiness gate --------- DMESG_CACHE="$(dmesg 2>/dev/null || true)" if [ -e /dev/media0 ] || [ -e /dev/video0 ]; then @@ -107,19 +171,25 @@ elif is_module_loaded qcom_camss; then elif [ -d /sys/module/qcom_camss ]; then log_pass "[READY] qcom_camss present as builtin" elif printf '%s\n' "$DMESG_CACHE" | grep -qiE 'qcom[-_]camss'; then - log_info "[READY] CAMSS messages found in dmesg (likely builtin)" + log_info "[READY] CAMSS messages found in dmesg, likely builtin" else - log_skip "Camera_RDI_FrameCapture SKIP – CAMSS driver not present (module or built-in)" + log_skip "$TESTNAME SKIP – CAMSS driver not present, module or built-in" echo "$TESTNAME SKIP" >"$RES_FILE" exit 2 fi -# --------- Module inventory (visibility only; no gating) --------- -MODULES_LIST="qcom_camss camcc_sc7280 videodev mc v4l2_fwnode v4l2_async videobuf2_common videobuf2_v4l2 videobuf2_dma_contig videobuf2_dma_sg videobuf2_memops" +# --------- Module inventory --------- +MODULES_LIST="qcom_camss videodev mc v4l2_fwnode v4l2_async videobuf2_common videobuf2_v4l2 videobuf2_dma_contig videobuf2_dma_sg videobuf2_memops" + +CAMCC_MODULES="$(awk '{print $1}' /proc/modules 2>/dev/null | grep -E 'camcc|cam_cc|glymur_camcc|kaanapali_camcc|sc7280_camcc|camcc_sc7280' | tr '\n' ' ')" +if [ -n "$CAMCC_MODULES" ]; then + MODULES_LIST="$MODULES_LIST $CAMCC_MODULES" +fi present_mods="" builtin_mods="" missing_mods="" + for m in $MODULES_LIST; do if is_module_loaded "$m"; then present_mods="$present_mods $m" @@ -132,25 +202,39 @@ done if [ -n "$present_mods" ]; then log_pass "[MODULES] Loaded:" - for m in $present_mods; do [ -n "$m" ] && log_info " - $m"; done + for m in $present_mods; do + [ -n "$m" ] && log_info " - $m" + done fi + if [ -n "$builtin_mods" ]; then log_info "[MODULES] Built-in:" - for m in $builtin_mods; do [ -n "$m" ] && log_info " - $m"; done + for m in $builtin_mods; do + [ -n "$m" ] && log_info " - $m" + done fi + if [ -n "$missing_mods" ]; then log_warn "[MODULES] Not found:" - for m in $missing_mods; do [ -n "$m" ] && log_info " - $m"; done + for m in $missing_mods; do + [ -n "$m" ] && log_info " - $m" + done fi -# Sensor modules (best-effort) +# Sensor modules, best-effort. SENSOR_MODS="$(awk '{print $1}' /proc/modules 2>/dev/null | grep -E '^(imx|ov|gc|ar)[0-9]+' | tr '\n' ' ')" -[ -n "$SENSOR_MODS" ] && { log_info "[MODULES] Sensors:"; for s in $SENSOR_MODS; do log_info " - $s"; done; } +if [ -n "$SENSOR_MODS" ]; then + log_info "[MODULES] Sensors:" + for s in $SENSOR_MODS; do + log_info " - $s" + done +fi -# --------- Dmesg probe errors (non-benign filter) --------- +# --------- Dmesg probe errors --------- DRIVER_MOD="qcom_camss" DMESG_MODULES='qcom_camss|camss|isp' DMESG_EXCLUDE='dummy regulator|supply [^ ]+ not found|using dummy regulator|Failed to create device link|reboot-mode.*-EEXIST|can.t register reboot mode' + if scan_dmesg_errors "$SCRIPT_DIR" "$DMESG_MODULES" "$DMESG_EXCLUDE"; then log_skip "$TESTNAME SKIP – $DRIVER_MOD probe errors detected in dmesg" echo "$TESTNAME SKIP" >"$RES_FILE" @@ -172,198 +256,173 @@ if [ -z "$MEDIA_NODE" ]; then exit 2 fi log_info "Detected media node: $MEDIA_NODE" - + # Light-touch global reset once media-ctl -d "$MEDIA_NODE" -r >/dev/null 2>&1 || true log_info "Media graph reset (-r) done on $MEDIA_NODE" sleep 0.2 - + # --------- Pipeline Detection (Python) --------- TOPO_FILE="$(mktemp "/tmp/${TESTNAME}_topo.XXXXXX")" TMP_PIPELINES_FILE="$(mktemp "/tmp/${TESTNAME}_blocks.XXXXXX")" trap 'rm -f "$TOPO_FILE" "$TMP_PIPELINES_FILE"' EXIT - + media-ctl -p -d "$MEDIA_NODE" >"$TOPO_FILE" 2>/dev/null PYTHON_PIPELINES="$(run_camera_pipeline_parser "$TOPO_FILE")" + if [ -z "$PYTHON_PIPELINES" ]; then log_skip "$TESTNAME SKIP – No valid pipelines found" echo "$TESTNAME SKIP" >"$RES_FILE" exit 2 fi + printf '%s\n' "$PYTHON_PIPELINES" >"$TMP_PIPELINES_FILE" - + log_info "User format override: ${USER_FORMAT:-}" log_info "Frame count per pipeline: $FRAMES" +log_info "YAVTA capture timeout per attempt: ${CAPTURE_TIMEOUT_SECS:-45}s" -# --------- Pipeline Processing (core logic unchanged) --------- -PASS=0; FAIL=0; SKIP=0; COUNT=0 +# --------- Pipeline Processing --------- +PASS=0 +FAIL=0 +SKIP=0 +COUNT=0 block="" + while IFS= read -r line || [ -n "$line" ]; do if [ "$line" = "--" ]; then - COUNT=$((COUNT+1)) + COUNT=$((COUNT + 1)) TMP="/tmp/cam_block.$$.$COUNT" - printf '%s\n' "$block" > "$TMP" + printf '%s\n' "$block" >"$TMP" - # Parses block and sets SENSOR, VIDEO, YAVTA_DEV, YAVTA_FMT, MEDIA_CTL_* lists, etc. + # Parses block and sets SENSOR, VIDEO, YAVTA_DEV, YAVTA_FMT, + # MEDIA_CTL_V_LIST, MEDIA_CTL_L_LIST, YAVTA_W, YAVTA_H, etc. parse_pipeline_block "$TMP" rm -f "$TMP" - # -------- Multi-format support (POSIX style, no arrays) -------- + if [ -z "$VIDEO" ] || [ "$VIDEO" = "None" ] || [ -z "$YAVTA_DEV" ]; then + log_skip "${SENSOR:-unknown}: Invalid pipeline – skipping" + SKIP=$((SKIP + 1)) + block="" + continue + fi + FORMATS_LIST="$USER_FORMAT" [ -z "$FORMATS_LIST" ] && FORMATS_LIST="$YAVTA_FMT" - OLD_IFS="$IFS"; IFS=',' + OLD_IFS="$IFS" + IFS=',' + for FMT_OVERRIDE in $FORMATS_LIST; do - FMT_OVERRIDE="$(printf '%s' "$FMT_OVERRIDE" | sed 's/^ *//;s/ *$//')" - TARGET_FORMAT="$FMT_OVERRIDE"; [ -z "$TARGET_FORMAT" ] && TARGET_FORMAT="$YAVTA_FMT" + camera_rdi_prepare_format_iteration "$FMT_OVERRIDE" "$YAVTA_FMT" - # New banner: show pad mbus fmt and video pixfmt - PAD_MBUS_FMT="$(printf '%s\n' "$MEDIA_CTL_V_LIST" | sed -n 's/.*fmt:\([^/]]*\)\/.*/\1/p' | head -n1)" - [ -z "$PAD_MBUS_FMT" ] && PAD_MBUS_FMT="auto" log_info "----- Pipeline $COUNT: ${SENSOR:-unknown} $VIDEO [pads:$PAD_MBUS_FMT] [video:$TARGET_FORMAT] -----" - if [ -z "$VIDEO" ] || [ "$VIDEO" = "None" ] || [ -z "$YAVTA_DEV" ]; then - log_skip "$SENSOR: Invalid pipeline – skipping" - SKIP=$((SKIP+1)) - continue - fi + camera_rdi_begin_result_tracking - # CI debug: print the exact commands we will run print_planned_commands "$MEDIA_NODE" "$TARGET_FORMAT" - # Configure & capture (original helpers) configure_pipeline_block "$MEDIA_NODE" "$TARGET_FORMAT" - execute_capture_block "$FRAMES" "$TARGET_FORMAT" + camera_rdi_capture_attempt "$FRAMES" "$TARGET_FORMAT" RET=$? - # Safety net retry - if [ "$RET" -ne 0 ]; then + # Safety retry only for capture failure, not unsupported/missing/interrupted. + if [ "$RET" -eq 1 ]; then log_warn "First attempt failed; resetting media graph and retrying once" - log_info " media-ctl -d $MEDIA_NODE -r" - media-ctl -d "$MEDIA_NODE" -r >/dev/null 2>&1 || true - sleep 0.1 + camera_rdi_reset_media_graph "$MEDIA_NODE" + print_planned_commands "$MEDIA_NODE" "$TARGET_FORMAT" configure_pipeline_block "$MEDIA_NODE" "$TARGET_FORMAT" - execute_capture_block "$FRAMES" "$TARGET_FORMAT" + camera_rdi_capture_attempt "$FRAMES" "$TARGET_FORMAT" RET=$? fi - ######################## Format/Resolution fallbacks ######################## - if [ "$RET" -ne 0 ]; then + ######################## Format fallback ######################## + if [ "$RET" -eq 1 ]; then if printf '%s' "$TARGET_FORMAT" | grep -q 'P$'; then ALT_FMT_A="$(printf '%s' "$TARGET_FORMAT" | sed 's/P$//')" + ALT_MBUS_A="$(camera_rdi_video_fmt_to_mbus_fmt "$ALT_FMT_A")" + + SAVE_V="$MEDIA_CTL_V_LIST" + SAVE_W="$YAVTA_W" + SAVE_H="$YAVTA_H" - SAVE_V="$MEDIA_CTL_V_LIST"; SAVE_W="$YAVTA_W"; SAVE_H="$YAVTA_H" - MEDIA_CTL_V_LIST="$(printf '%s\n' "$MEDIA_CTL_V_LIST" | sed -E "s/fmt:[^/]+\//fmt:${ALT_FMT_A}\//g")" + MEDIA_CTL_V_LIST="$( + printf '%s\n' "$MEDIA_CTL_V_LIST" \ + | sed -E "s/fmt:[^/]+\//fmt:${ALT_MBUS_A}\//g" + )" - log_info "Applying format fallback (A1): $TARGET_FORMAT → $ALT_FMT_A" + log_info "Applying format fallback (A1): video $TARGET_FORMAT → $ALT_FMT_A, pads → $ALT_MBUS_A" print_planned_commands "$MEDIA_NODE" "$ALT_FMT_A" configure_pipeline_block "$MEDIA_NODE" "$ALT_FMT_A" - execute_capture_block "$FRAMES" "$ALT_FMT_A" + camera_rdi_capture_attempt "$FRAMES" "$ALT_FMT_A" RET=$? - if [ "$RET" -ne 0 ] && [ -n "$SAVE_W" ] && [ -n "$SAVE_H" ]; then - NEW_W=$(( (SAVE_W/2)*2 )) - NEW_H=$(( (SAVE_H/2)*2 )) - MEDIA_CTL_V_LIST="$(printf '%s\n' "$MEDIA_CTL_V_LIST" | sed -E "s/([0-9]+x[0-9]+)/${NEW_W}x${NEW_H}/g")" - YAVTA_W="$NEW_W"; YAVTA_H="$NEW_H" - log_info "Applying resolution fallback (A2): ${SAVE_W}x${SAVE_H} → ${NEW_W}x${NEW_H} (format $ALT_FMT_A)" - print_planned_commands "$MEDIA_NODE" "$ALT_FMT_A" - configure_pipeline_block "$MEDIA_NODE" "$ALT_FMT_A" - execute_capture_block "$FRAMES" "$ALT_FMT_A" - RET=$? - fi - - MEDIA_CTL_V_LIST="$SAVE_V"; YAVTA_W="$SAVE_W"; YAVTA_H="$SAVE_H" - fi - fi - ###################### end ############################################### - - ######################## Try other RDI/Video indices ##################### - if [ "$RET" -ne 0 ]; then - CUR_RDI="$(printf '%s\n%s\n' "$MEDIA_CTL_V_LIST" "$MEDIA_CTL_L_LIST" \ - | sed -n 's/.*msm_vfe[0-9]_rdi\([0-9]\).*/\1/p' | head -n1)" - CUR_VIDIDX="$(printf '%s\n%s\n' "$MEDIA_CTL_V_LIST" "$MEDIA_CTL_L_LIST" \ - | sed -n 's/.*msm_vfe[0-9]_video\([0-9]\).*/\1/p' | head -n1)" - [ -z "$CUR_VIDIDX" ] && CUR_VIDIDX="$(printf '%s' "$YAVTA_DEV" | sed -n 's#.*/video\([0-9]\+\)$#\1#p')" + if [ "$RET" -eq 1 ] && [ -n "$SAVE_W" ] && [ -n "$SAVE_H" ]; then + NEW_W=$(((SAVE_W / 2) * 2)) + NEW_H=$(((SAVE_H / 2) * 2)) - if [ -n "$CUR_RDI" ] && [ -n "$CUR_VIDIDX" ]; then - for ALT_IDX in 0 1 2; do - [ "$ALT_IDX" = "$CUR_RDI" ] && continue + if [ "$NEW_W" != "$SAVE_W" ] || [ "$NEW_H" != "$SAVE_H" ]; then + OLD_SIZE="${SAVE_W}x${SAVE_H}" + NEW_SIZE="${NEW_W}x${NEW_H}" - SAVE_V="$MEDIA_CTL_V_LIST" - SAVE_L="$MEDIA_CTL_L_LIST" - SAVE_DEV="$YAVTA_DEV" - SAVE_W="$YAVTA_W" - SAVE_H="$YAVTA_H" - - MEDIA_CTL_V_LIST="$(printf '%s\n' "$MEDIA_CTL_V_LIST" \ - | sed -E "s/(msm_vfe[0-9]_rdi)[0-2]/\1${ALT_IDX}/g; s/(msm_vfe[0-9]_video)[0-2]/\1${ALT_IDX}/g")" - MEDIA_CTL_L_LIST="$(printf '%s\n' "$MEDIA_CTL_L_LIST" \ - | sed -E "s/(msm_vfe[0-9]_rdi)[0-2]/\1${ALT_IDX}/g; s/(msm_vfe[0-9]_video)[0-2]/\1${ALT_IDX}/g")" - - if printf '%s' "$YAVTA_DEV" | grep -qE '/dev/video[0-9]+$'; then - YAVTA_DEV="$(printf '%s' "$YAVTA_DEV" | sed -E "s#/dev/video[0-9]+#/dev/video${ALT_IDX}#")" - fi - - log_info "Applying path fallback (B1): switch to RDI/video index ${ALT_IDX} with format $TARGET_FORMAT" - print_planned_commands "$MEDIA_NODE" "$TARGET_FORMAT" - configure_pipeline_block "$MEDIA_NODE" "$TARGET_FORMAT" - execute_capture_block "$FRAMES" "$TARGET_FORMAT" - RET=$? + MEDIA_CTL_V_LIST="$(camera_rdi_replace_pad_size "$MEDIA_CTL_V_LIST" "$OLD_SIZE" "$NEW_SIZE")" + YAVTA_W="$NEW_W" + YAVTA_H="$NEW_H" - if [ "$RET" -ne 0 ] && [ -n "$SAVE_W" ] && [ -n "$SAVE_H" ]; then - YAVTA_W=""; YAVTA_H="" - log_info "Retrying (B2) letting driver choose size on index ${ALT_IDX}" - print_planned_commands "$MEDIA_NODE" "$TARGET_FORMAT" - configure_pipeline_block "$MEDIA_NODE" "$TARGET_FORMAT" - execute_capture_block "$FRAMES" "$TARGET_FORMAT" + log_info "Applying resolution fallback (A2): ${OLD_SIZE} → ${NEW_SIZE} (format $ALT_FMT_A)" + print_planned_commands "$MEDIA_NODE" "$ALT_FMT_A" + configure_pipeline_block "$MEDIA_NODE" "$ALT_FMT_A" + camera_rdi_capture_attempt "$FRAMES" "$ALT_FMT_A" RET=$? + else + log_info "Skipping resolution fallback (A2): size already even (${SAVE_W}x${SAVE_H})" fi + fi - MEDIA_CTL_V_LIST="$SAVE_V" - MEDIA_CTL_L_LIST="$SAVE_L" - YAVTA_DEV="$SAVE_DEV" - YAVTA_W="$SAVE_W" - YAVTA_H="$SAVE_H" - - [ "$RET" -eq 0 ] && break - done + MEDIA_CTL_V_LIST="$SAVE_V" + YAVTA_W="$SAVE_W" + YAVTA_H="$SAVE_H" fi fi - ###################### end ############################################### + ###################### end ##################################### - ############### Inline device-supported format fallback ################## - if [ "$RET" -ne 0 ]; then + ############### Device-supported format fallback ############### + if [ "$RET" -eq 1 ]; then SUP_FMTS="$(v4l2-ctl -d "$YAVTA_DEV" --list-formats 2>/dev/null \ | sed -n "s/^[[:space:]]*'\([^']*\)'.*/\1/p")" if [ -n "$SUP_FMTS" ]; then ALT_FMT_C="" + if printf '%s\n' "$SUP_FMTS" | grep -qx "$TARGET_FORMAT"; then ALT_FMT_C="$TARGET_FORMAT" elif printf '%s\n' "$TARGET_FORMAT" | grep -q 'P$' && \ printf '%s\n' "$SUP_FMTS" | grep -qx "$(printf '%s' "$TARGET_FORMAT" | sed 's/P$//')"; then ALT_FMT_C="$(printf '%s' "$TARGET_FORMAT" | sed 's/P$//')" else - ALT_FMT_C="$(printf '%s\n' "$SUP_FMTS" | grep -E '^S[RGB]+[0-9]{2}P?$' | head -n1)" - [ -z "$ALT_FMT_C" ] && ALT_FMT_C="$(printf '%s\n' "$SUP_FMTS" | head -n1)" + ALT_FMT_C="$(printf '%s\n' "$SUP_FMTS" | grep -E '^S[RGB]+[0-9]{2}P?$' | head -n 1)" + [ -z "$ALT_FMT_C" ] && ALT_FMT_C="$(printf '%s\n' "$SUP_FMTS" | head -n 1)" fi if [ -n "$ALT_FMT_C" ]; then + ALT_MBUS_C="$(camera_rdi_video_fmt_to_mbus_fmt "$ALT_FMT_C")" + SAVE_V="$MEDIA_CTL_V_LIST" SAVE_W="$YAVTA_W" SAVE_H="$YAVTA_H" - MEDIA_CTL_V_LIST="$(printf '%s\n' "$MEDIA_CTL_V_LIST" \ - | sed -E "s/fmt:[^/]+\//fmt:${ALT_FMT_C}\//g")" + MEDIA_CTL_V_LIST="$( + printf '%s\n' "$MEDIA_CTL_V_LIST" \ + | sed -E "s/fmt:[^/]+\//fmt:${ALT_MBUS_C}\//g" + )" YAVTA_W="" YAVTA_H="" - log_info "Applying device-supported format fallback (C): $TARGET_FORMAT → $ALT_FMT_C (letting driver choose size)" + log_info "Applying device-supported format fallback (C): video $TARGET_FORMAT → $ALT_FMT_C, pads → $ALT_MBUS_C" print_planned_commands "$MEDIA_NODE" "$ALT_FMT_C" configure_pipeline_block "$MEDIA_NODE" "$ALT_FMT_C" - execute_capture_block "$FRAMES" "$ALT_FMT_C" + camera_rdi_capture_attempt "$FRAMES" "$ALT_FMT_C" RET=$? MEDIA_CTL_V_LIST="$SAVE_V" @@ -372,24 +431,50 @@ while IFS= read -r line || [ -n "$line" ]; do fi fi fi - ###################### end ############################################### + ###################### end ##################################### + + RET="$(camera_rdi_final_ret "$RET")" case "$RET" in - 0) log_pass "$SENSOR $VIDEO $TARGET_FORMAT PASS"; PASS=$((PASS+1)) ;; - 1) log_fail "$SENSOR $VIDEO $TARGET_FORMAT FAIL (capture failed)"; FAIL=$((FAIL+1)) ;; - 2) log_skip "$SENSOR $VIDEO $TARGET_FORMAT SKIP (unsupported format)"; SKIP=$((SKIP+1)) ;; - 3) log_skip "$SENSOR $VIDEO missing data – skipping"; SKIP=$((SKIP+1)) ;; + 0) + log_pass "$SENSOR $VIDEO $TARGET_FORMAT PASS" + PASS=$((PASS + 1)) + ;; + 1) + log_fail "$SENSOR $VIDEO $TARGET_FORMAT FAIL (capture failed)" + FAIL=$((FAIL + 1)) + ;; + 2) + log_skip "$SENSOR $VIDEO $TARGET_FORMAT SKIP (unsupported format)" + SKIP=$((SKIP + 1)) + ;; + 3) + log_skip "$SENSOR $VIDEO missing data – skipping" + SKIP=$((SKIP + 1)) + ;; + *) + log_fail "$SENSOR $VIDEO $TARGET_FORMAT FAIL (unknown return: $RET)" + FAIL=$((FAIL + 1)) + ;; esac + + camera_rdi_restore_format_iteration done + IFS="$OLD_IFS" block="" else - if [ -z "$block" ]; then block="$line"; else block="$block -$line"; fi + if [ -z "$block" ]; then + block="$line" + else + block="$block +$line" + fi fi -done < "$TMP_PIPELINES_FILE" +done <"$TMP_PIPELINES_FILE" log_info "Test Summary: Passed: $PASS, Failed: $FAIL, Skipped: $SKIP" + if [ "$PASS" -gt 0 ]; then echo "$TESTNAME PASS" >"$RES_FILE" exit 0 diff --git a/Runner/utils/camera/lib_camera_rdi.sh b/Runner/utils/camera/lib_camera_rdi.sh new file mode 100755 index 000000000..aa869019d --- /dev/null +++ b/Runner/utils/camera/lib_camera_rdi.sh @@ -0,0 +1,551 @@ +#!/bin/sh +# SPDX-License-Identifier: BSD-3-Clause +# Common helpers for Camera_RDI_FrameCapture. + +# This file is sourced by: +# Runner/suites/Multimedia/Camera/Camera_RDI_FrameCapture/run.sh +# +# Keep this file limited to RDI-specific helpers: +# - format preparation/restoration +# - V4L2 pixfmt to media-bus fmt conversion +# - yavta timeout/capture handling +# - stream-control cleanup +# - retry/result tracking helpers + +# shellcheck disable=SC2317 +# Functions in this sourced helper are invoked by run.sh. + +camera_rdi_trim() { + printf '%s' "${1:-}" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//' +} + +camera_rdi_current_mbus_fmt() { + printf '%s\n' "${MEDIA_CTL_V_LIST:-}" \ + | sed -n 's/.*fmt:\([^/]]*\)\/.*/\1/p' \ + | head -n 1 +} + +camera_rdi_video_fmt_to_mbus_fmt() { + v4l2_fmt="$(camera_rdi_trim "$1")" + current_mbus="$(camera_rdi_current_mbus_fmt)" + + if [ -z "$v4l2_fmt" ]; then + printf '%s\n' "${current_mbus:-auto}" + return 0 + fi + + # If caller already passed an mbus-style format, keep it. + case "$v4l2_fmt" in + *_1X[0-9]*|*_2X[0-9]*) + printf '%s\n' "$v4l2_fmt" + return 0 + ;; + esac + + # Generic RAW Bayer rule: + # SGRBG10P -> SGRBG10_1X10 + # SGRBG10 -> SGRBG10_1X10 + # SRGGB12P -> SRGGB12_1X12 + # SBGGR8 -> SBGGR8_1X8 + bayer_mbus="$( + printf '%s\n' "$v4l2_fmt" | awk ' + { + f = $1 + sub(/P$/, "", f) + + if (length(f) < 6) { + exit + } + + prefix = substr(f, 1, 1) + order = substr(f, 2, 4) + bits = substr(f, 6) + + if (prefix != "S") { + exit + } + + if (order != "RGGB" && order != "GRBG" && order != "GBRG" && order != "BGGR") { + exit + } + + if (bits != "8" && bits != "10" && bits != "12" && bits != "14" && bits != "16") { + exit + } + + printf "S%s%s_1X%s\n", order, bits, bits + } + ' + )" + + if [ -n "$bayer_mbus" ]; then + printf '%s\n' "$bayer_mbus" + return 0 + fi + + # Small generic YUV mappings. These are not sensor-specific. + case "$v4l2_fmt" in + UYVY) printf '%s\n' "UYVY8_1X16"; return 0 ;; + YUYV) printf '%s\n' "YUYV8_1X16"; return 0 ;; + YVYU) printf '%s\n' "YVYU8_1X16"; return 0 ;; + VYUY) printf '%s\n' "VYUY8_1X16"; return 0 ;; + esac + + # For formats like NV12, there may not be a direct sensor pad mbus code. + # Keep the discovered media-bus format instead of guessing wrongly. + if [ -n "$current_mbus" ]; then + printf '%s\n' "$current_mbus" + else + printf '%s\n' "$v4l2_fmt" + fi +} + +camera_rdi_prepare_format_iteration() { + fmt_override="$1" + default_yavta_fmt="$2" + + CAMERA_RDI_SAVE_MEDIA_CTL_V_LIST="${MEDIA_CTL_V_LIST:-}" + CAMERA_RDI_SAVE_YAVTA_W="${YAVTA_W:-}" + CAMERA_RDI_SAVE_YAVTA_H="${YAVTA_H:-}" + + fmt_override="$(camera_rdi_trim "$fmt_override")" + default_yavta_fmt="$(camera_rdi_trim "$default_yavta_fmt")" + + TARGET_FORMAT="$fmt_override" + [ -n "$TARGET_FORMAT" ] || TARGET_FORMAT="$default_yavta_fmt" + + PAD_MBUS_FMT="$(camera_rdi_video_fmt_to_mbus_fmt "$TARGET_FORMAT")" + [ -n "$PAD_MBUS_FMT" ] || PAD_MBUS_FMT="$(camera_rdi_current_mbus_fmt)" + [ -n "$PAD_MBUS_FMT" ] || PAD_MBUS_FMT="auto" + + # Keep the video node format as V4L2 pixfmt, but configure pads with mbus fmt. + if [ "$PAD_MBUS_FMT" != "auto" ]; then + MEDIA_CTL_V_LIST="$( + printf '%s\n' "${MEDIA_CTL_V_LIST:-}" \ + | sed -E "s/fmt:[^/]+\//fmt:${PAD_MBUS_FMT}\//g" + )" + fi + + export TARGET_FORMAT PAD_MBUS_FMT MEDIA_CTL_V_LIST +} + +camera_rdi_restore_format_iteration() { + if [ -n "${CAMERA_RDI_SAVE_MEDIA_CTL_V_LIST+x}" ]; then + MEDIA_CTL_V_LIST="$CAMERA_RDI_SAVE_MEDIA_CTL_V_LIST" + fi + + if [ -n "${CAMERA_RDI_SAVE_YAVTA_W+x}" ]; then + YAVTA_W="$CAMERA_RDI_SAVE_YAVTA_W" + fi + + if [ -n "${CAMERA_RDI_SAVE_YAVTA_H+x}" ]; then + YAVTA_H="$CAMERA_RDI_SAVE_YAVTA_H" + fi + + unset CAMERA_RDI_SAVE_MEDIA_CTL_V_LIST + unset CAMERA_RDI_SAVE_YAVTA_W + unset CAMERA_RDI_SAVE_YAVTA_H + + export MEDIA_CTL_V_LIST YAVTA_W YAVTA_H +} + +camera_rdi_shell_quote_arg() { + arg="${1:-}" + + case "$arg" in + "") + printf "''" + return 0 + ;; + *[!A-Za-z0-9_./:=,+%@#-]*) + printf "'%s'" "$(printf '%s' "$arg" | sed "s/'/'\\\\''/g")" + return 0 + ;; + *) + printf '%s' "$arg" + return 0 + ;; + esac +} + +camera_rdi_log_cmd() { + msg="" + + for arg in "$@"; do + qarg="$(camera_rdi_shell_quote_arg "$arg")" + + if [ -n "$msg" ]; then + msg="$msg $qarg" + else + msg="$qarg" + fi + done + + log_info "RUN: $msg" +} + +camera_rdi_timeout_cmd() { + timeout_secs="$1" + shift + + case "$timeout_secs" in + ''|*[!0-9]*) + timeout_secs=45 + ;; + esac + + if [ "$timeout_secs" -lt 1 ] 2>/dev/null; then + timeout_secs=45 + fi + + # Use our own watchdog instead of external timeout because BusyBox/coreutils + # timeout options differ across images. + timeout_tag="$(date +%s 2>/dev/null || echo 0)" + timed_out_file="${TMPDIR:-/tmp}/camera_rdi_timeout.$$.$timeout_tag" + rm -f "$timed_out_file" 2>/dev/null || true + + "$@" & + cmd_pid=$! + + ( + sleep "$timeout_secs" 2>/dev/null || true + + if kill -0 "$cmd_pid" 2>/dev/null; then + echo "1" >"$timed_out_file" 2>/dev/null || true + kill -TERM "$cmd_pid" 2>/dev/null || true + sleep 3 2>/dev/null || true + + if kill -0 "$cmd_pid" 2>/dev/null; then + kill -KILL "$cmd_pid" 2>/dev/null || true + fi + fi + ) & + watchdog_pid=$! + + wait "$cmd_pid" + rc=$? + + kill "$watchdog_pid" 2>/dev/null || true + wait "$watchdog_pid" 2>/dev/null || true + + if [ -f "$timed_out_file" ]; then + rm -f "$timed_out_file" 2>/dev/null || true + return 124 + fi + + rm -f "$timed_out_file" 2>/dev/null || true + return "$rc" +} + +camera_rdi_exit_if_interrupted() { + rc="$1" + + case "$rc" in + 130|131|143) + log_warn "Interrupted by user or signal, stopping ${TESTNAME:-Camera_RDI_FrameCapture}" + echo "${TESTNAME:-Camera_RDI_FrameCapture} FAIL" >"${RES_FILE:-./Camera_RDI_FrameCapture.res}" 2>/dev/null || true + exit 1 + ;; + esac + + return 0 +} + +camera_rdi_reset_media_graph() { + media_node="$1" + + [ -n "$media_node" ] || return 0 + + log_info " media-ctl -d $media_node -r" + media-ctl -d "$media_node" -r >/dev/null 2>&1 || true + sleep 0.1 + + return 0 +} + +camera_rdi_begin_result_tracking() { + CAMERA_RDI_SEEN_CAPTURE_FAIL=0 + CAMERA_RDI_SEEN_UNSUPPORTED=0 + CAMERA_RDI_SEEN_MISSING=0 + + export CAMERA_RDI_SEEN_CAPTURE_FAIL + export CAMERA_RDI_SEEN_UNSUPPORTED + export CAMERA_RDI_SEEN_MISSING +} + +camera_rdi_note_ret() { + ret="$1" + + case "$ret" in + 0) + return 0 + ;; + 1) + CAMERA_RDI_SEEN_CAPTURE_FAIL=1 + ;; + 2) + CAMERA_RDI_SEEN_UNSUPPORTED=1 + ;; + 3) + CAMERA_RDI_SEEN_MISSING=1 + ;; + esac + + export CAMERA_RDI_SEEN_CAPTURE_FAIL + export CAMERA_RDI_SEEN_UNSUPPORTED + export CAMERA_RDI_SEEN_MISSING + + return 0 +} + +camera_rdi_final_ret() { + last_ret="$1" + + if [ "$last_ret" = "0" ]; then + printf '0\n' + return 0 + fi + + if [ "${CAMERA_RDI_SEEN_CAPTURE_FAIL:-0}" = "1" ]; then + printf '1\n' + return 0 + fi + + if [ "${CAMERA_RDI_SEEN_UNSUPPORTED:-0}" = "1" ]; then + printf '2\n' + return 0 + fi + + if [ "${CAMERA_RDI_SEEN_MISSING:-0}" = "1" ]; then + printf '3\n' + return 0 + fi + + printf '%s\n' "${last_ret:-1}" +} + +camera_rdi_capture_attempt() { + frames="$1" + fmt="$2" + + execute_capture_block "$frames" "$fmt" + ret=$? + + camera_rdi_exit_if_interrupted "$ret" + camera_rdi_note_ret "$ret" + + return "$ret" +} + +camera_rdi_replace_pad_size() { + input="$1" + old_size="$2" + new_size="$3" + + if [ -z "$input" ]; then + printf '\n' + return 0 + fi + + if [ -z "$old_size" ] || [ -z "$new_size" ]; then + printf '%s\n' "$input" + return 0 + fi + + # Replace only media format sizes after "/", not entity names. + # This avoids corrupting sensor names such as "ov08x40". + printf '%s\n' "$input" | sed "s#/${old_size} #/${new_size} #g; s#/${old_size}]#/${new_size}]#g" +} + +camera_rdi_try_set_stream_control() { + subdev="$1" + value="$2" + + [ -n "$subdev" ] || return 0 + [ "$subdev" != "None" ] || return 0 + [ -e "$subdev" ] || return 0 + + timeout_secs="${YAVTA_CTRL_TIMEOUT_SECS:-10}" + + camera_rdi_log_cmd yavta --no-query -w "0x009f0903 $value" "$subdev" + camera_rdi_timeout_cmd "$timeout_secs" \ + yavta --no-query -w "0x009f0903 $value" "$subdev" + rc=$? + + case "$rc" in + 0) + return 0 + ;; + 124|137|143) + log_warn "yavta stream-control timed out on $subdev after ${timeout_secs}s" + return 1 + ;; + *) + log_warn "yavta stream-control failed on $subdev rc=$rc" + return 1 + ;; + esac +} + +camera_rdi_cleanup_stream_control() { + subdev="$1" + + [ -n "$subdev" ] || return 0 + [ "$subdev" != "None" ] || return 0 + [ -e "$subdev" ] || return 0 + + camera_rdi_try_set_stream_control "$subdev" 0 >/dev/null 2>&1 || true + return 0 +} + +camera_rdi_find_parser() { + parser="" + + if [ -n "${TOOLS:-}" ] && [ -f "$TOOLS/camera/parse_media_topology.py" ]; then + parser="$TOOLS/camera/parse_media_topology.py" + elif [ -n "${ROOT_DIR:-}" ] && [ -f "$ROOT_DIR/utils/camera/parse_media_topology.py" ]; then + parser="$ROOT_DIR/utils/camera/parse_media_topology.py" + elif [ -n "${REPO_ROOT:-}" ] && [ -f "$REPO_ROOT/utils/camera/parse_media_topology.py" ]; then + parser="$REPO_ROOT/utils/camera/parse_media_topology.py" + fi + + printf '%s\n' "$parser" +} + +run_camera_pipeline_parser() { + topo_file="${1:-}" + parser="$(camera_rdi_find_parser)" + + if [ -z "$parser" ]; then + log_warn "Camera topology parser not found: parse_media_topology.py" + return 1 + fi + + if [ -z "$topo_file" ]; then + log_warn "Topology file argument missing for parse_media_topology.py" + return 1 + fi + + if [ ! -f "$topo_file" ]; then + log_warn "Topology file not found: $topo_file" + return 1 + fi + + python3 "$parser" "$topo_file" +} + +execute_capture_block() { + frames="$1" + fmt="$2" + + case "$frames" in + ''|*[!0-9]*) + frames=10 + ;; + esac + + [ -n "$fmt" ] || fmt="${YAVTA_FMT:-}" + + if [ -z "${YAVTA_DEV:-}" ] || [ "$YAVTA_DEV" = "None" ]; then + log_skip "Missing yavta video device" + return 3 + fi + + if [ ! -e "$YAVTA_DEV" ]; then + log_skip "YAVTA device not found: $YAVTA_DEV" + return 3 + fi + + if [ -z "$fmt" ] || [ "$fmt" = "None" ]; then + log_skip "Missing YAVTA pixel format for $YAVTA_DEV" + return 2 + fi + + capture_timeout="${CAPTURE_TIMEOUT_SECS:-45}" + case "$capture_timeout" in + ''|*[!0-9]*) + capture_timeout=45 + ;; + esac + + if [ "$capture_timeout" -lt 1 ] 2>/dev/null; then + capture_timeout=45 + fi + + capture_dir="${CAMERA_RDI_CAPTURE_DIR:-./frames_${COUNT:-0}_$(basename "$YAVTA_DEV")_${fmt}}" + mkdir -p "$capture_dir" 2>/dev/null || true + capture_pattern="$capture_dir/frame-#.bin" + + # Avoid stale files from previous attempt/retry. + rm -f "$capture_dir"/frame-*.bin 2>/dev/null || true + + # Best-effort cleanup before capture. + camera_rdi_cleanup_stream_control "${YAVTA_SENSOR_SUBDEV:-}" + + if [ -n "${YAVTA_W:-}" ] && [ -n "${YAVTA_H:-}" ]; then + camera_rdi_log_cmd yavta -B capture-mplane -c -I -n "$frames" -f "$fmt" \ + -s "${YAVTA_W}x${YAVTA_H}" -F "$YAVTA_DEV" --capture="$frames" --file="$capture_pattern" + + camera_rdi_timeout_cmd "$capture_timeout" \ + yavta -B capture-mplane -c -I -n "$frames" -f "$fmt" \ + -s "${YAVTA_W}x${YAVTA_H}" -F "$YAVTA_DEV" --capture="$frames" --file="$capture_pattern" + rc=$? + else + camera_rdi_log_cmd yavta -B capture-mplane -c -I -n "$frames" -f "$fmt" \ + -F "$YAVTA_DEV" --capture="$frames" --file="$capture_pattern" + + camera_rdi_timeout_cmd "$capture_timeout" \ + yavta -B capture-mplane -c -I -n "$frames" -f "$fmt" \ + -F "$YAVTA_DEV" --capture="$frames" --file="$capture_pattern" + rc=$? + fi + + # Always try to leave the sensor/subdev in a sane state. + camera_rdi_cleanup_stream_control "${YAVTA_SENSOR_SUBDEV:-}" + + case "$rc" in + 0) + captured_count="$( + find "$capture_dir" -type f -name 'frame-*.bin' -size +0c 2>/dev/null \ + | wc -l \ + | awk '{print $1}' + )" + + log_info "Captured non-empty frame files in $capture_dir: ${captured_count:-0}" + + if [ "${captured_count:-0}" -gt 0 ]; then + return 0 + fi + + log_warn "yavta returned success but no non-empty frame files were found in $capture_dir" + return 1 + ;; + + 130) + log_warn "yavta capture interrupted by user on $YAVTA_DEV" + return 130 + ;; + + 124|137|143) + log_fail "yavta capture timed out after ${capture_timeout}s on $YAVTA_DEV fmt=$fmt size=${YAVTA_W:-auto}x${YAVTA_H:-auto}" + return 1 + ;; + + *) + log_warn "yavta capture failed rc=$rc on $YAVTA_DEV fmt=$fmt size=${YAVTA_W:-auto}x${YAVTA_H:-auto}" + + # Only mark unsupported if v4l2-ctl successfully lists formats and fmt is absent. + # If v4l2-ctl cannot list due to busy/bad state, keep it as capture failure. + formats_out="$(v4l2-ctl -d "$YAVTA_DEV" --list-formats 2>/dev/null || true)" + if [ -n "$formats_out" ]; then + if printf '%s\n' "$formats_out" | grep -q "'$fmt'"; then + return 1 + fi + + return 2 + fi + + return 1 + ;; + esac +} diff --git a/Runner/utils/camera/parse_media_topology.py b/Runner/utils/camera/parse_media_topology.py index ac10718a6..a3127077f 100755 --- a/Runner/utils/camera/parse_media_topology.py +++ b/Runner/utils/camera/parse_media_topology.py @@ -6,16 +6,66 @@ import subprocess import argparse +def normalize_mbus_fmt(fmt): + if not fmt: + return fmt + + fmt = fmt.strip() + fmt = fmt.replace("MEDIA_BUS_FMT_", "") + + alias = { + "RGGB8": "SRGGB8_1X8", + "GRBG8": "SGRBG8_1X8", + "GBRG8": "SGBRG8_1X8", + "BGGR8": "SBGGR8_1X8", + + "RGGB10": "SRGGB10_1X10", + "GRBG10": "SGRBG10_1X10", + "GBRG10": "SGBRG10_1X10", + "BGGR10": "SBGGR10_1X10", + + "SRGGB10": "SRGGB10_1X10", + "SGRBG10": "SGRBG10_1X10", + "SGBRG10": "SGBRG10_1X10", + "SBGGR10": "SBGGR10_1X10", + + "SRGGB12": "SRGGB12_1X12", + "SGRBG12": "SGRBG12_1X12", + "SGBRG12": "SGBRG12_1X12", + "SBGGR12": "SBGGR12_1X12", + } + + return alias.get(fmt, fmt) + + def fourcc_map(fmt): + fmt = normalize_mbus_fmt(fmt) + mapping = { - "SRGGB10_1X10": "SRGGB10P", - "RGGB8_1X8": "RGGB8", - "BGGR10_1X10": "BGGR10P", "SRGGB8_1X8": "SRGGB8", + "SGRBG8_1X8": "SGRBG8", + "SGBRG8_1X8": "SGBRG8", + "SBGGR8_1X8": "SBGGR8", + + "SRGGB10_1X10": "SRGGB10P", + "SGRBG10_1X10": "SGRBG10P", + "SGBRG10_1X10": "SGBRG10P", + "SBGGR10_1X10": "SBGGR10P", + + "SRGGB12_1X12": "SRGGB12P", + "SGRBG12_1X12": "SGRBG12P", + "SGBRG12_1X12": "SGBRG12P", + "SBGGR12_1X12": "SBGGR12P", + + "SRGGB14_1X14": "SRGGB14P", + "SGRBG14_1X14": "SGRBG14P", + "SGBRG14_1X14": "SGBRG14P", + "SBGGR14_1X14": "SBGGR14P", + "YUYV8_2X8": "YUYV", "UYVY8_2X8": "UYVY", - # Add more as needed } + return mapping.get(fmt, fmt) def parse_entities(lines): @@ -111,6 +161,139 @@ def collect_supported_modes(video_devnode): except Exception: return [] +def run_text_cmd(cmd): + try: + return subprocess.check_output( + cmd, + encoding="utf-8", + stderr=subprocess.DEVNULL + ) + except Exception: + return "" + + +def collect_sensor_mbus_codes(sensor_subdev): + """ + Query sensor subdev media-bus codes. + + Expected v4l2-ctl output example: + 0x300d: MEDIA_BUS_FMT_SGRBG10_1X10 + + Returns: + [(code_arg, mbus_fmt), ...] + """ + if not sensor_subdev or not sensor_subdev.startswith("/dev/"): + return [] + + out = run_text_cmd([ + "v4l2-ctl", + "--device", sensor_subdev, + "--list-subdev-mbus-codes", "pad=0" + ]) + + if not out: + return [] + + codes = [] + seen = set() + + for line in out.splitlines(): + line = line.strip() + + code_arg = "" + mbus_fmt = "" + + m = re.search(r"(0x[0-9a-fA-F]+).*MEDIA_BUS_FMT_([A-Za-z0-9_]+)", line) + if m: + code_arg = m.group(1) + mbus_fmt = normalize_mbus_fmt(m.group(2)) + else: + m = re.search(r"MEDIA_BUS_FMT_([A-Za-z0-9_]+)", line) + if m: + mbus_fmt = normalize_mbus_fmt(m.group(1)) + code_arg = mbus_fmt + + if mbus_fmt and mbus_fmt not in seen: + seen.add(mbus_fmt) + codes.append((code_arg, mbus_fmt)) + + return codes + + +def collect_sensor_framesizes(sensor_subdev, code_arg, mbus_fmt=None): + """ + Query frame sizes for one sensor media-bus code. + """ + if not sensor_subdev or not code_arg: + return [] + + code_candidates = [code_arg] + + if mbus_fmt: + mbus_fmt = normalize_mbus_fmt(mbus_fmt) + code_candidates.append(mbus_fmt) + code_candidates.append(f"MEDIA_BUS_FMT_{mbus_fmt}") + + sizes = set() + + for code in code_candidates: + out = run_text_cmd([ + "v4l2-ctl", + "--device", sensor_subdev, + "--list-subdev-framesizes", f"pad=0,code={code}" + ]) + + if not out: + continue + + for line in out.splitlines(): + for w, h in re.findall(r"(\d+)x(\d+)", line): + sizes.add((int(w), int(h))) + + if sizes: + break + + return sorted(sizes) + + +def collect_sensor_max_mode(sensor_subdev, preferred_fmt=None): + """ + Select the largest advertised sensor mode dynamically. + + Tie-break: + 1. Larger width * height wins. + 2. If same area, prefer the active topology format. + """ + preferred_fmt = normalize_mbus_fmt(preferred_fmt) + best = None + + for code_arg, mbus_fmt in collect_sensor_mbus_codes(sensor_subdev): + for w, h in collect_sensor_framesizes(sensor_subdev, code_arg, mbus_fmt): + area = w * h + prefer_score = 1 if preferred_fmt and mbus_fmt == preferred_fmt else 0 + + candidate = { + "mbus_fmt": mbus_fmt, + "fourcc": fourcc_map(mbus_fmt), + "w": str(w), + "h": str(h), + "area": area, + "prefer_score": prefer_score, + } + + if best is None: + best = candidate + continue + + if candidate["area"] > best["area"]: + best = candidate + continue + + if candidate["area"] == best["area"] and candidate["prefer_score"] > best["prefer_score"]: + best = candidate + + return best or {} + def emit_media_ctl_v(entity, fmt, w, h): cmds = [] for pad_num in [0, 1]: @@ -124,31 +307,54 @@ def emit_media_ctl_v(entity, fmt, w, h): def build_pipeline_cmds(sensor_ent, entities): results = [] + src_pad = sensor_ent['pads'].get(0) if not src_pad or not src_pad['links']: return results + for lnk in src_pad['links']: csiphy_ent = next((e for e in entities.values() if e['name'] == lnk['target']), None) - if not csiphy_ent: continue + if not csiphy_ent: + continue + csid_ent = next((e for l in csiphy_ent['pads'].get(1, {}).get('links', []) if (e := next((e for e in entities.values() if e['name'] == l['target']), None))), None) - if not csid_ent: continue + if not csid_ent: + continue + vfe_ent = next((e for l in csid_ent['pads'].get(1, {}).get('links', []) if (e := next((e for e in entities.values() if e['name'] == l['target']), None))), None) - if not vfe_ent: continue + if not vfe_ent: + continue + vid_ent = next((e for l in vfe_ent['pads'].get(1, {}).get('links', []) if (e := next((e for e in entities.values() if e['name'] == l['target']), None))), None) - if not vid_ent or not vid_ent.get('devnode'): continue - + if not vid_ent or not vid_ent.get('devnode'): + continue + video_node = vid_ent['devnode'] if not video_supports_format(video_node): continue - - fmt = src_pad.get('fmt', 'None') - short_fmt = fourcc_map(fmt) - w = src_pad.get('w') - h = src_pad.get('h') - + + active_fmt = normalize_mbus_fmt(src_pad.get('fmt', 'None')) + active_w = src_pad.get('w') + active_h = src_pad.get('h') + + max_mode = collect_sensor_max_mode(sensor_ent.get('devnode'), active_fmt) + + if max_mode: + fmt = max_mode.get("mbus_fmt", active_fmt) + short_fmt = max_mode.get("fourcc", fourcc_map(fmt)) + w = max_mode.get("w", active_w) + h = max_mode.get("h", active_h) + max_mode_source = "sensor-subdev" + else: + fmt = active_fmt + short_fmt = fourcc_map(fmt) + w = active_w + h = active_h + max_mode_source = "media-topology-active" + results.append({ 'SENSOR': sensor_ent['name'], 'ENTITY': sensor_ent['id'], @@ -160,6 +366,9 @@ def build_pipeline_cmds(sensor_ent, entities): 'W': w, 'H': h, 'SUBDEV': sensor_ent.get('devnode'), + 'MAX_MODE_SOURCE': max_mode_source, + 'MAX_MODE_MBUS': fmt, + 'MAX_MODE_FOURCC': short_fmt, 'SUPPORTED_FOURCCS': collect_supported_fourccs(vid_ent, entities, fmt), 'SUPPORTED_MODE': collect_supported_modes(video_node), 'MEDIA_CTL_V': ( @@ -182,6 +391,7 @@ def build_pipeline_cmds(sensor_ent, entities): 'YAVTA_CTRL': f"{sensor_ent.get('devnode')} 0x009f0903 9" if sensor_ent.get('devnode') else "", 'YAVTA_CTRL_POST': f"{sensor_ent.get('devnode')} 0x009f0903 0" if sensor_ent.get('devnode') else "" }) + return results def video_supports_format(video, fmt=None, w=None, h=None): @@ -208,34 +418,64 @@ def main(): parser = argparse.ArgumentParser() parser.add_argument("topo", help="media-ctl -p output text file") args = parser.parse_args() - + with open(args.topo) as f: lines = f.readlines() - + entities = parse_entities(lines) found = False + for eid, ent in entities.items(): if not ent.get('is_sensor'): continue + pipelines = build_pipeline_cmds(ent, entities) + for r in pipelines: found = True - for k in ['SENSOR','ENTITY','CSIPHY','CSID','VFE','VIDEO','FMT','W','H','SUBDEV','SUPPORTED_FOURCCS']: + + for k in [ + 'SENSOR', + 'ENTITY', + 'CSIPHY', + 'CSID', + 'VFE', + 'VIDEO', + 'FMT', + 'W', + 'H', + 'SUBDEV', + 'MAX_MODE_SOURCE', + 'MAX_MODE_MBUS', + 'MAX_MODE_FOURCC', + 'SUPPORTED_FOURCCS', + ]: print(f"{k}:{r[k]}") + for mode in r['SUPPORTED_MODE']: print(f"SUPPORTED_MODE:{mode}") + for v in r['MEDIA_CTL_V']: print(f"MEDIA_CTL_V:{v}") + for l in r['MEDIA_CTL_L']: print(f"MEDIA_CTL_L:{l}") - if r['YAVTA_CTRL_PRE']: print(f"YAVTA_CTRL_PRE:{r['YAVTA_CTRL_PRE']}") - if r['YAVTA_CTRL']: print(f"YAVTA_CTRL:{r['YAVTA_CTRL']}") - if r['YAVTA_CTRL_POST']: print(f"YAVTA_CTRL_POST:{r['YAVTA_CTRL_POST']}") + + if r['YAVTA_CTRL_PRE']: + print(f"YAVTA_CTRL_PRE:{r['YAVTA_CTRL_PRE']}") + + if r['YAVTA_CTRL']: + print(f"YAVTA_CTRL:{r['YAVTA_CTRL']}") + + if r['YAVTA_CTRL_POST']: + print(f"YAVTA_CTRL_POST:{r['YAVTA_CTRL_POST']}") + print(f"YAVTA_DEV:{r['YAVTA_DEV']}") print(f"YAVTA_FMT:{r['YAVTA_FMT']}") print(f"YAVTA_W:{r['YAVTA_W']}") print(f"YAVTA_H:{r['YAVTA_H']}") print("--") + if not found: print("SKIP: No valid camera pipelines found in topology.") sys.exit(2)