diff --git a/Runner/suites/Kernel/Baseport/IPA/run.sh b/Runner/suites/Kernel/Baseport/IPA/run.sh index ea2cf9b9..7486f5d4 100755 --- a/Runner/suites/Kernel/Baseport/IPA/run.sh +++ b/Runner/suites/Kernel/Baseport/IPA/run.sh @@ -2,6 +2,9 @@ # Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. # SPDX-License-Identifier: BSD-3-Clause-Clear +# +# Test for IPA driver: skip if CONFIG_QCOM_IPA not enabled, then +# builtin vs module, verify /dev/ipa, functional sysfs & dmesg checks. # Robustly find and source init_env SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" @@ -30,40 +33,61 @@ fi TESTNAME="IPA" test_path=$(find_test_case_by_name "$TESTNAME") cd "$test_path" || exit 1 -res_file="./$TESTNAME.res" +RES_FILE="./$TESTNAME.res" log_info "-----------------------------------------------------------------------------------------" log_info "-------------------Starting $TESTNAME Testcase----------------------------" log_info "=== Test Initialization ===" -IPA_MODULE_PATH=$(find_kernel_module "ipa") - -if [ -z "$IPA_MODULE_PATH" ]; then - log_error "ipa.ko module not found in filesystem." - echo "$TESTNAME FAIL" > "$res_file" - exit 1 +# Kernel config gate +if ! check_kernel_config "CONFIG_QCOM_IPA"; then + log_skip "$TESTNAME SKIP - CONFIG_QCOM_IPA not enabled" + echo "$TESTNAME SKIP" >"$RES_FILE" + exit 0 fi -log_info "Found ipa.ko at: $IPA_MODULE_PATH" +MODNAME="ipa" +PRE_LOADED=0 -if ! load_kernel_module "$IPA_MODULE_PATH"; then - echo "$TESTNAME FAIL" > "$res_file" - exit 1 +# Ensure module is loaded +if is_module_loaded "$MODNAME"; then + PRE_LOADED=1 + log_info "Module $MODNAME already loaded" +else + MODPATH=$(find_kernel_module "$MODNAME") + [ -n "$MODPATH" ] || log_fail "$MODNAME.ko not found in filesystem" + load_kernel_module "$MODPATH" || log_fail "Failed to load $MODNAME" + log_pass "$MODNAME module loaded" fi -if is_module_loaded "ipa"; then - log_info "ipa module is loaded" - log_pass "$TESTNAME : Test Passed" - echo "$TESTNAME PASS" > "$res_file" - exit 0 +# /dev node check (warn only, don't fail/skip) +log_info "Verifying /dev/ipa node" +if wait_for_path "/dev/ipa" 3; then + log_pass "/dev/ipa node is present" else - log_error "ipa module not listed in lsmod" - log_fail "$TESTNAME : Test Failed" - echo "$TESTNAME FAIL" > "$res_file" - exit 1 + log_warn "No /dev/ipa node found (platform/use-case may not expose it)" fi -log_info "=== Cleanup ===" -unload_kernel_module "ipa" true +# dmesg scan: errors + success pattern +# scan_dmesg_errors(label, out_dir, extra_err, ok_kw) +scan_dmesg_errors "ipa" "." "handshake_complete.*error|stall|abort" "IPA Q6 handshake completed" +rc=$? +case "$rc" in + 0) log_warn "IPA-related errors found in dmesg (see ipa_dmesg_errors.log)" ;; + 1) log_info "No IPA-related errors in dmesg" ;; + 2) log_warn "Success pattern 'IPA Q6 handshake completed' not found in dmesg" ;; + 3) log_warn "scan_dmesg_errors misuse (label missing?)" ;; +esac + +#Cleanup: unload only if we loaded it +if [ "$PRE_LOADED" -eq 0 ]; then + log_info "Unloading $MODNAME (loaded by this test)" + unload_kernel_module "$MODNAME" false || log_warn "Unload $MODNAME failed" +else + log_info "$MODNAME was pre-loaded; leaving it loaded" +fi log_info "-------------------Completed $TESTNAME Testcase----------------------------" +log_pass "$TESTNAME PASS" +echo "$TESTNAME PASS" >"$RES_FILE" +exit 0 diff --git a/Runner/suites/Kernel/Baseport/RMNET/run.sh b/Runner/suites/Kernel/Baseport/RMNET/run.sh index 145f3f8a..c850cd2f 100755 --- a/Runner/suites/Kernel/Baseport/RMNET/run.sh +++ b/Runner/suites/Kernel/Baseport/RMNET/run.sh @@ -2,6 +2,9 @@ # Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. # SPDX-License-Identifier: BSD-3-Clause-Clear +# +# Test for RMNET driver: skip if CONFIG_RMNET not enabled, then +# builtin vs module, verify /dev/rmnet, functional sysfs & dmesg checks. # Robustly find and source init_env SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" @@ -30,40 +33,79 @@ fi TESTNAME="RMNET" test_path=$(find_test_case_by_name "$TESTNAME") cd "$test_path" || exit 1 -res_file="./$TESTNAME.res" +RES_FILE="./$TESTNAME.res" log_info "-----------------------------------------------------------------------------------------" log_info "-------------------Starting $TESTNAME Testcase----------------------------" log_info "=== Test Initialization ===" -RMNET_MODULE_PATH=$(find_kernel_module "rmnet") -if [ -z "$RMNET_MODULE_PATH" ]; then - log_error "rmnet.ko module not found in filesystem." - echo "$TESTNAME FAIL" > "$res_file" - exit 1 +# Kernel config gate +if ! check_kernel_config "CONFIG_RMNET"; then + log_skip "$TESTNAME SKIP - CONFIG_RMNET not enabled" + echo "$TESTNAME SKIP" >"$RES_FILE" + exit 0 fi -log_info "Found rmnet.ko at: $RMNET_MODULE_PATH" +# Load module if needed +MODNAME="rmnet" +PRE_LOADED=0 +if is_module_loaded "$MODNAME"; then + PRE_LOADED=1 + log_info "Module $MODNAME already loaded" +else + MODPATH=$(find_kernel_module "$MODNAME") + [ -n "$MODPATH" ] || log_fail "$MODNAME.ko not found in filesystem" + load_kernel_module "$MODPATH" || log_fail "Failed to load $MODNAME" + log_pass "$MODNAME module loaded" +fi -if ! load_kernel_module "$RMNET_MODULE_PATH"; then - echo "$TESTNAME FAIL" > "$res_file" - exit 1 +# /dev/rmnet* nodes (authoritative), but do NOT FAIL if absent (common until modem data call) +log_info "Verifying /dev/rmnet* node(s)" +first_node="" +for n in /dev/rmnet*; do + case "$n" in + /dev/rmnet*) [ -e "$n" ] && { first_node="$n"; break; } ;; + esac +done + +if [ -n "$first_node" ]; then + if wait_for_path "$first_node" 3; then + log_pass "rmnet node $first_node is present" + else + log_fail "rmnet node $first_node did not appear within timeout" + fi +else + log_warn "No /dev/rmnet* nodes found" fi -if is_module_loaded "rmnet"; then - log_info "rmnet module is loaded" - log_pass "$TESTNAME : Test Passed" - echo "$TESTNAME PASS" > "$res_file" - exit 0 +# dmesg scan for rmnet errors (no positive 'OK' pattern required here) +scan_dmesg_errors "rmnet" "." "panic|oops|fault|stall|abort" "" +if [ -s "./rmnet_dmesg_errors.log" ]; then + log_fail "rmnet-related errors found in dmesg" else - log_error "rmnet module not listed in lsmod" - log_fail "$TESTNAME : Test Failed" - echo "$TESTNAME FAIL" > "$res_file" - exit 1 + log_info "No rmnet-related errors in dmesg" +fi + +# Optional informational check: ip link (do not fail if absent) +if command -v ip >/dev/null 2>&1; then + if ip -o link show | grep -qi rmnet; then + log_info "ip(8): rmnet interface(s) present" + else + log_info "ip(8): no rmnet interface yet (probably no data call)" + fi fi -log_info "=== Cleanup ===" -unload_kernel_module "rmnet" true +# Cleanup if we loaded it +if [ "$PRE_LOADED" -eq 0 ]; then + log_info "Unloading $MODNAME (loaded by this test)" + unload_kernel_module "$MODNAME" false || log_warn "Unload $MODNAME failed" +else + log_info "$MODNAME was pre-loaded; leaving it loaded" +fi log_info "-------------------Completed $TESTNAME Testcase----------------------------" +log_pass "$TESTNAME PASS" +echo "$TESTNAME PASS" >"$RES_FILE" +exit 0 + diff --git a/Runner/suites/Multimedia/Camera/Camera_RDI_FrameCapture/README_Camera_RDI_FrameCapture.md b/Runner/suites/Multimedia/Camera/Camera_RDI_FrameCapture/README_Camera_RDI_FrameCapture.md new file mode 100644 index 00000000..90b1724c --- /dev/null +++ b/Runner/suites/Multimedia/Camera/Camera_RDI_FrameCapture/README_Camera_RDI_FrameCapture.md @@ -0,0 +1,92 @@ +# Camera RDI Frame Capture Test + +This test validates functional camera RDI (Raw Dump Interface) pipelines by: + +- Dynamically detecting all camera pipelines using `media-ctl` +- Parsing valid RDI pipelines with a Python helper script +- Streaming frames using `yavta` from detected working pipelines +- Supporting manual override of video format and frame count + +## ๐Ÿ“ Test Directory Structure + +``` +Camera_RDI_FrameCapture/ +โ”œโ”€โ”€ run.sh +โ”œโ”€โ”€ parse_media_topology.py +โ””โ”€โ”€ init_env +``` + +## ๐Ÿง  How It Works + +1. Detects media device node dynamically +2. Dumps the topology to a temporary file +3. Parses pipeline details using `parse_media_topology.py` +4. For each detected pipeline: + - Applies correct media-ctl `-V` and `-l` configuration + - Sets V4L2 controls pre-/post-streaming via `yavta` + - Attempts frame capture using `yavta` +5. Logs PASS/FAIL/SKIP per pipeline +6. Generates a `.res` file with final test result + +## โš™๏ธ Dependencies + +Make sure the following tools are available in the target filesystem: + +- `media-ctl` +- `yavta` +- `v4l2-ctl` +- `python3` +- Kernel module: `qcom_camss` +- Required DT nodes for `camss`, `isp`, or `camera` compatible strings + +## ๐Ÿงช Usage + +```sh +./run.sh [--format ] [--frames ] +``` + +### Examples: + +- Auto-detect and capture 10 frames per working RDI pipeline: + ```sh + ./run.sh + ``` + +- Force UYVY format and capture 5 frames: + ```sh + ./run.sh --format UYVY --frames 5 + ``` + +## ๐Ÿ“ฆ Output + +- Captured frame files: `frame-#.bin` in current directory +- Result summary: `Camera_RDI_FrameCapture.res` +- Detailed logs through `functestlib.sh`-based `log_info`, `log_pass`, etc. + +## โœ… Pass Criteria + +- At least one pipeline successfully captures frames +- Logs include `"Captured frames"` for at least one working video node + +## โŒ Fail/Skip Criteria + +- If pipeline configuration fails or no frames captured, it is marked FAIL +- If no working pipelines are found or prerequisites are unmet, test is SKIPPED + +## ๐Ÿงผ Cleanup + +Temporary files created: +- `/tmp/v4l2_camera_RDI_dump_topo.*` +- `/tmp/v4l2_camera_RDI_dump_pipelines.*` + +They are auto-removed at the end of the test. + +## ๐Ÿ“ Notes + +- The test is dynamic and supports multiple pipelines per board +- Python script only outputs **valid** working pipelines (validated via `v4l2-ctl`) +- `run.sh` is robust, CI-ready, and skips flaky or unsupported configurations gracefully + +--- + +ยฉ Qualcomm Technologies, Inc. โ€“ All rights reserved \ No newline at end of file diff --git a/Runner/suites/Multimedia/Camera/Camera_RDI_FrameCapture/run.sh b/Runner/suites/Multimedia/Camera/Camera_RDI_FrameCapture/run.sh new file mode 100755 index 00000000..f679af82 --- /dev/null +++ b/Runner/suites/Multimedia/Camera/Camera_RDI_FrameCapture/run.sh @@ -0,0 +1,199 @@ +#!/bin/sh +# Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. +# SPDX-License-Identifier: BSD-3-Clause-Clear + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +INIT_ENV="" +SEARCH="$SCRIPT_DIR" +while [ "$SEARCH" != "/" ]; do + if [ -f "$SEARCH/init_env" ]; then + INIT_ENV="$SEARCH/init_env" + break + fi + SEARCH=$(dirname "$SEARCH") +done + +if [ -z "$INIT_ENV" ]; then + echo "[ERROR] Could not find init_env (starting at $SCRIPT_DIR)" >&2 + exit 1 +fi + +if [ -z "$__INIT_ENV_LOADED" ]; then + # shellcheck disable=SC1090 + . "$INIT_ENV" +fi +# shellcheck disable=SC1090,SC1091 +. "$TOOLS/functestlib.sh" + +TESTNAME="Camera_RDI_FrameCapture" +RES_FILE="./$TESTNAME.res" +test_path=$(find_test_case_by_name "$TESTNAME") +cd "$test_path" || exit 1 + +print_usage() { + cat <] [--frames ] [--help] + +Options: + --format Override capture format (e.g., UYVY, NV12) + --frames Number of frames to capture per pipeline (default: 10) + --help Show this help message +EOF +} + + +log_info "----------------------------------------------------------------------" +log_info "------------------- Starting $TESTNAME Testcase ----------------------" +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 + exit 1 + ;; + esac + shift +done + +# --------- Prechecks --------- +if ! dt_confirm_node_or_compatible "isp" "cam" "camss"; then + log_skip "$TESTNAME SKIP โ€“ No ISP/camera node/compatible found in DT" + echo "$TESTNAME SKIP" >"$RES_FILE" + exit 0 +fi + +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 ! is_module_loaded "$DRIVER_MOD"; then + log_skip "$TESTNAME SKIP โ€“ Driver module $DRIVER_MOD not loaded" + echo "$TESTNAME SKIP" >"$RES_FILE" + exit 0 +fi + +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" + exit 0 +fi + +# --------- Dependency Checks --------- +check_dependencies media-ctl yavta python3 v4l2-ctl || { + log_skip "$TESTNAME SKIP โ€“ Required tools missing" + echo "$TESTNAME SKIP" >"$RES_FILE" + exit 0 +} + +# --------- Media Node Detection --------- +MEDIA_NODE=$(detect_media_node) +if [ -z "$MEDIA_NODE" ]; then + log_skip "$TESTNAME SKIP โ€“ Media node not found" + echo "$TESTNAME SKIP" >"$RES_FILE" + exit 0 +fi +log_info "Detected media node: $MEDIA_NODE" + +# --------- Pipeline Detection --------- +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 0 +fi + +printf "%s\n" "$PYTHON_PIPELINES" > "$TMP_PIPELINES_FILE" + +log_info "User format override: ${USER_FORMAT:-}" +log_info "Frame count per pipeline: $FRAMES" + +# --------- 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)) + TMP="/tmp/cam_block.$$.$COUNT" + printf "%s\n" "$block" > "$TMP" + + # Parses block and sets SENSOR, VIDEO, YAVTA_DEV, FMT, etc + parse_pipeline_block "$TMP" + rm -f "$TMP" + + TARGET_FORMAT="${USER_FORMAT:-$YAVTA_FMT}" + + log_info "----- Pipeline $COUNT: ${SENSOR:-unknown} $VIDEO $TARGET_FORMAT -----" + + if [ -z "$VIDEO" ] || [ "$VIDEO" = "None" ] || [ -z "$YAVTA_DEV" ]; then + log_skip "$SENSOR: Invalid pipeline โ€“ skipping" + SKIP=$((SKIP+1)) + block="" + continue + fi + + configure_pipeline_block "$MEDIA_NODE" "$USER_FORMAT" + execute_capture_block "$FRAMES" "$TARGET_FORMAT" + 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)) + ;; + esac + block="" + else + if [ -z "$block" ]; then + block="$line" + else + block="$block +$line" + fi + fi +done < "$TMP_PIPELINES_FILE" + +log_info "Test Summary: Passed: $PASS, Failed: $FAIL, Skipped: $SKIP" +if [ "$PASS" -gt 0 ]; then + echo "$TESTNAME PASS" >"$RES_FILE" +elif [ "$FAIL" -gt 0 ]; then + echo "$TESTNAME FAIL" >"$RES_FILE" +else + echo "$TESTNAME SKIP" >"$RES_FILE" +fi + +exit 0 diff --git a/Runner/utils/camera/parse_media_topology.py b/Runner/utils/camera/parse_media_topology.py new file mode 100755 index 00000000..bd30c5b9 --- /dev/null +++ b/Runner/utils/camera/parse_media_topology.py @@ -0,0 +1,245 @@ +#!/usr/bin/env python3 +# Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. +# SPDX-License-Identifier: BSD-3-Clause-Clear + +import sys +import re +import subprocess +import argparse + +def fourcc_map(fmt): + mapping = { + "SRGGB10_1X10": "SRGGB10P", + "RGGB8_1X8": "RGGB8", + "BGGR10_1X10": "BGGR10P", + "SRGGB8_1X8": "SRGGB8", + "YUYV8_2X8": "YUYV", + "UYVY8_2X8": "UYVY", + # Add more as needed + } + return mapping.get(fmt, fmt) + +def parse_entities(lines): + entities = {} + entity_pat = re.compile(r"^- entity (\d+): ([^\(]+)") + device_pat = re.compile(r"device node name (.+)") + type_pat = re.compile(r"type V4L2 subdev subtype (\w+)") + pad_pat = re.compile(r"^\s*pad(\d+): (\w+)") + link_pat = re.compile(r'-> "([^"]+)":(\d+) \[([^\]]*)\]') + fmt_pat = re.compile(r"\[stream:(\d+) fmt:([^\s/]+)/(\d+)x(\d+)") + fmt2_pat = re.compile(r"\[fmt:([^\s/]+)/(\d+)x(\d+)") + sensor_pat = re.compile(r"type V4L2 subdev subtype Sensor flags") + cur_entity = None + cur_pad = None + for line in lines: + line = line.rstrip('\n') + m = entity_pat.match(line) + if m: + idx, name = int(m.group(1)), m.group(2).strip() + cur_entity = { + 'id': idx, 'name': name, 'pads': {}, 'devnode': None, + 'type': None, 'is_sensor': False + } + entities[idx] = cur_entity + cur_pad = None + continue + if cur_entity is None: + continue + m = device_pat.search(line) + if m: + cur_entity['devnode'] = m.group(1).strip() + m = type_pat.search(line) + if m: + cur_entity['type'] = m.group(1).strip() + if sensor_pat.search(line): + cur_entity['is_sensor'] = True + m = pad_pat.match(line) + if m: + pad_idx, pad_type = int(m.group(1)), m.group(2) + cur_entity['pads'][pad_idx] = {'type': pad_type, 'links': [], 'fmt': None, 'w': None, 'h': None} + cur_pad = pad_idx + m = link_pat.search(line) + if m and cur_pad is not None: + target, pad_idx, flags = m.group(1), int(m.group(2)), m.group(3) + cur_entity['pads'][cur_pad]['links'].append({'target': target, 'pad': pad_idx, 'flags': flags}) + m = fmt_pat.search(line) + if m and cur_pad is not None: + _, fmt, w, h = m.groups() + cur_entity['pads'][cur_pad].update({'fmt': fmt, 'w': w, 'h': h}) + m = fmt2_pat.search(line) + if m and cur_pad is not None: + fmt, w, h = m.groups() + cur_entity['pads'][cur_pad].update({'fmt': fmt, 'w': w, 'h': h}) + return entities + +def collect_supported_fourccs(video_ent, entities, fallback_fmt=None): + fourccs = set() + for pad in video_ent.get('pads', {}).values(): + if pad.get('fmt'): + fourccs.add(pad['fmt']) + if not fourccs: + video_name = video_ent['name'] + for ent in entities.values(): + for pad in ent.get('pads', {}).values(): + for link in pad.get('links', []): + if link['target'] == video_name and pad.get('fmt'): + fourccs.add(pad['fmt']) + if not fourccs and fallback_fmt: + fourccs.add(fallback_fmt) + return ','.join(sorted(fourccs)) if fourccs else 'None' + +def collect_supported_modes(video_devnode): + modes = set() + if not video_devnode or not video_devnode.startswith('/dev/video'): + return [] + try: + output = subprocess.check_output( + ["v4l2-ctl", "--device", video_devnode, "--list-formats-ext"], + encoding="utf-8", stderr=subprocess.DEVNULL + ) + current_fmt = None + for line in output.splitlines(): + line = line.strip() + if line.startswith('['): + m = re.match(r"\[\d+\]:\s+'(\w+)'", line) + if m: + current_fmt = m.group(1) + elif "Size:" in line and current_fmt: + matches = re.findall(r"(\d+)x(\d+)", line) + for (w, h) in matches: + modes.add(f"{current_fmt}/{w}x{h}") + return sorted(modes) + except Exception: + return [] + +def emit_media_ctl_v(entity, fmt, w, h): + cmds = [] + for pad_num in [0, 1]: + if pad_num in entity['pads']: + pad = entity['pads'][pad_num] + _fmt = fmt if fmt else pad.get('fmt', 'None') + _w = w if w else pad.get('w', 'None') + _h = h if h else pad.get('h', 'None') + cmds.append(f'"{entity["name"]}":{pad_num}[fmt:{_fmt}/{_w}x{_h} field:none]') + return cmds + +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 + 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 + 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 + 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 + + 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') + + results.append({ + 'SENSOR': sensor_ent['name'], + 'ENTITY': sensor_ent['id'], + 'CSIPHY': csiphy_ent['name'], + 'CSID': csid_ent['name'], + 'VFE': vfe_ent['name'], + 'VIDEO': video_node, + 'FMT': fmt, + 'W': w, + 'H': h, + 'SUBDEV': sensor_ent.get('devnode'), + 'SUPPORTED_FOURCCS': collect_supported_fourccs(vid_ent, entities, fmt), + 'SUPPORTED_MODE': collect_supported_modes(video_node), + 'MEDIA_CTL_V': ( + emit_media_ctl_v(sensor_ent, fmt, w, h) + + emit_media_ctl_v(csiphy_ent, fmt, w, h) + + emit_media_ctl_v(csid_ent, fmt, w, h) + + emit_media_ctl_v(vfe_ent, fmt, w, h) + ), + 'MEDIA_CTL_L': [ + f'"{sensor_ent["name"]}":0->"{csiphy_ent["name"]}":0[1]', + f'"{csiphy_ent["name"]}":1->"{csid_ent["name"]}":0[1]', + f'"{csid_ent["name"]}":1->"{vfe_ent["name"]}":0[1]', + f'"{vfe_ent["name"]}":1->"{vid_ent["name"]}":0[1]', + ], + 'YAVTA_DEV': video_node, + 'YAVTA_FMT': short_fmt, + 'YAVTA_W': w, + 'YAVTA_H': h, + 'YAVTA_CTRL_PRE': f"{sensor_ent.get('devnode')} 0x009f0903 0" if sensor_ent.get('devnode') else "", + '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): + if not video or video == "None": + return False + try: + out = subprocess.check_output(["v4l2-ctl", "--device", video, "--list-formats-ext"], + encoding="utf-8", stderr=subprocess.DEVNULL) + if fmt: + found_fmt = False + for line in out.splitlines(): + if fmt in line: + found_fmt = True + if found_fmt and w and h and f"{w}x{h}" in line: + return True + if found_fmt and (not w or not h): + return True + return False + return True + except Exception: + return False + +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']: + 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']}") + 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) + +if __name__ == "__main__": + main() diff --git a/Runner/utils/functestlib.sh b/Runner/utils/functestlib.sh index a9075ebb..69f3db22 100755 --- a/Runner/utils/functestlib.sh +++ b/Runner/utils/functestlib.sh @@ -61,12 +61,13 @@ is_module_loaded() { /sbin/lsmod | awk '{print $1}' | grep -q "^${module_name}$" } -# Insert a kernel module with optional parameters +# load_kernel_module [params...] +# 1) If already loaded, no-op +# 2) Try insmod [params] +# 3) If that fails, try modprobe [params] load_kernel_module() { - module_path="$1" - shift + module_path="$1"; shift params="$*" - module_name=$(basename "$module_path" .ko) if is_module_loaded "$module_name"; then @@ -76,15 +77,24 @@ load_kernel_module() { if [ ! -f "$module_path" ]; then log_error "Module file not found: $module_path" - return 1 + # still try modprobe if it exists in modules directory + else + log_info "Loading module via insmod: $module_path $params" + if /sbin/insmod "$module_path" "$params" 2>insmod_err.log; then + log_info "Module $module_name loaded successfully via insmod" + return 0 + else + log_warn "insmod failed: $(cat insmod_err.log)" + fi fi - log_info "Loading module: $module_path $params" - if /sbin/insmod "$module_path" "$params" 2>insmod_err.log; then - log_info "Module $module_name loaded successfully" + # fallback to modprobe + log_info "Falling back to modprobe $module_name $params" + if /sbin/modprobe "$module_name" "$params" 2>modprobe_err.log; then + log_info "Module $module_name loaded successfully via modprobe" return 0 else - log_error "insmod failed: $(cat insmod_err.log)" + log_error "modprobe failed: $(cat modprobe_err.log)" return 1 fi } @@ -171,14 +181,25 @@ find_test_case_script_by_name() { find "$base_dir" -type d -iname "$test_name" -print -quit 2>/dev/null } +# Check each given kernel config is set to y/m in /proc/config.gz, logs result, returns 0/1. check_kernel_config() { - configs="$1" - for config_key in $configs; do - if zcat /proc/config.gz | grep -qE "^$config_key=(y|m)"; then - log_pass "Kernel config $config_key is enabled" + cfgs=$1 + for config_key in $cfgs; do + if command -v zgrep >/dev/null 2>&1; then + if zgrep -qE "^${config_key}=(y|m)" /proc/config.gz 2>/dev/null; then + log_pass "Kernel config $config_key is enabled" + else + log_fail "Kernel config $config_key is missing or not enabled" + return 1 + fi else - log_fail "Kernel config $config_key is missing or not enabled" - return 1 + # Fallback if zgrep is unavailable + if gzip -dc /proc/config.gz 2>/dev/null | grep -qE "^${config_key}=(y|m)"; then + log_pass "Kernel config $config_key is enabled" + else + log_fail "Kernel config $config_key is missing or not enabled" + return 1 + fi fi done return 0 @@ -1653,41 +1674,318 @@ detect_ufs_partition_block() { return 1 } -# Check for dmesg I/O errors and log summary +# wait_for_path [timeout_sec] +wait_for_path() { + _p="$1"; _t="${2:-3}" + i=0 + while [ "$i" -lt "$_t" ]; do + [ -e "$_p" ] && return 0 + sleep 1 + i=$((i+1)) + done + return 1 +} + +############################################################################### +# scan_dmesg_errors +# +# Only scans *new* dmesg lines for true error patterns (since last test run). +# Keeps a timestamped error log history for each run. +# Handles dmesg with/without timestamps. Cleans up markers/logs if test dir is gone. +# Usage: scan_dmesg_errors "$SCRIPT_DIR" [optional_extra_keywords...] +############################################################################### scan_dmesg_errors() { - label="$1" # Example: "ufs", "emmc", "wifi" - output_dir="$2" # Directory to store logs - extra_keywords="$3" # (Optional) Space-separated keywords for this label + prefix="$1" + module_regex="$2" # e.g. 'qcom_camss|camss|isp' + exclude_regex="${3:-"dummy regulator|supply [^ ]+ not found|using dummy regulator"}" + shift 3 + + mkdir -p "$prefix" - [ -z "$label" ] && return 1 - [ -z "$output_dir" ] && output_dir="." + DMESG_SNAPSHOT="$prefix/dmesg_snapshot.log" + DMESG_ERRORS="$prefix/dmesg_errors.log" + DATE_STAMP=$(date +%Y%m%d-%H%M%S) + DMESG_HISTORY="$prefix/dmesg_errors_$DATE_STAMP.log" - snapshot_file="$output_dir/${label}_dmesg_snapshot.log" - error_file="$output_dir/${label}_dmesg_errors.log" + # Error patterns (edit as needed for your test coverage) + err_patterns='Unknown symbol|probe failed|fail(ed)?|error|timed out|not found|invalid|corrupt|abort|panic|oops|unhandled|can.t (start|init|open|allocate|find|register)' - log_info "Scanning dmesg for recent $label-related I/O errors..." + rm -f "$DMESG_SNAPSHOT" "$DMESG_ERRORS" + dmesg > "$DMESG_SNAPSHOT" 2>/dev/null - dmesg | tail -n 300 > "$snapshot_file" + # 1. Match lines with correct module and error pattern + # 2. Exclude lines with harmless patterns (using dummy regulator etc) + grep -iE "^\[[^]]+\][[:space:]]+($module_regex):.*($err_patterns)" "$DMESG_SNAPSHOT" \ + | grep -vEi "$exclude_regex" > "$DMESG_ERRORS" || true - # Common error indicators - common_keywords="error fail timeout reset crc abort fault invalid fatal warn" + cp "$DMESG_ERRORS" "$DMESG_HISTORY" - # Build keyword pattern (common + optional extra) - pattern="($common_keywords" - if [ -n "$extra_keywords" ]; then - pattern="$pattern|$extra_keywords" + if [ -s "$DMESG_ERRORS" ]; then + log_info "dmesg scan: found non-benign module errors in $DMESG_ERRORS (history: $DMESG_HISTORY)" + return 0 fi - pattern="$pattern)" + log_info "No relevant, non-benign errors for modules [$module_regex] in recent dmesg." + return 1 +} - # Filter: lines must match label and error pattern - grep -iE "$label" "$snapshot_file" | grep -iE "$pattern" > "$error_file" +# 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; } + + 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" + 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" + fi + touch "$matchflag" + done + [ -s "$matchresult" ] && break + done - if [ -s "$error_file" ]; then - log_warn "Detected potential $label-related errors in dmesg:" - while IFS= read -r line; do - log_warn " dmesg: $line" - done < "$error_file" + if [ -f "$matchflag" ] && [ -s "$matchresult" ]; then + cat "$matchresult" + rm -f "$matchflag" "$matchresult" + return 0 + fi + rm -f "$matchflag" "$matchresult" + return 1 +} + +# Detects and returns the first available media node (e.g., /dev/media0). +# Returns failure if no media nodes are present. +detect_media_node() { + for node in /dev/media*; do + if [ -e "$node" ]; then + printf '%s\n' "$node" + return 0 + fi + done + return 1 +} + +# Runs the Python pipeline parser with a topology file to emit camera blocks. +# Returns structured per-pipeline output for further processing. +run_camera_pipeline_parser() { + # Input: path to topo file (e.g., /tmp/cam_topo.txt) + # Output: emits parse output to stdout + TOPO_FILE="$1" + + if [ ! -f "$TOPO_FILE" ]; then + log_error "Topology file $TOPO_FILE not found" + return 1 + fi + +PYTHON_SCRIPT="$TOOLS/camera/parse_media_topology.py" + if [ ! -x "$PYTHON_SCRIPT" ]; then + log_error "Camera pipeline parser script not found at $PYTHON_SCRIPT" + return 1 + fi + + log_info "Invoking camera pipeline parser with $TOPO_FILE" + python3 "$PYTHON_SCRIPT" "$TOPO_FILE" +} + +# Shortcut to detect a media node, dump topology, and run the parser on it. +# Intended for quick standalone camera pipeline detection. +get_camera_pipeline_blocks() { + TOPO_FILE="/tmp/parse_topo.$$" + MEDIA_NODE=$(detect_media_node) + if [ -z "$MEDIA_NODE" ]; then + echo "[ERROR] Failed to detect media node" >&2 + return 1 + fi + media-ctl -p -d "$MEDIA_NODE" >"$TOPO_FILE" 2>/dev/null + PARSE_SCRIPT="${TOOLS}/camera/parse_media_topology.py" + if [ ! -f "$PARSE_SCRIPT" ]; then + echo "[ERROR] Python script $PARSE_SCRIPT not found" >&2 + rm -f "$TOPO_FILE" + return 1 + fi + python3 "$PARSE_SCRIPT" "$TOPO_FILE" + ret=$? + rm -f "$TOPO_FILE" + return "$ret" +} + +# Parses a single pipeline block file into shell variables and lists. +# Each key-value line sets up variables for media-ctl or yavta operations. +parse_pipeline_block() { + block_file="$1" + + # Unset and export all variables that will be assigned + unset SENSOR VIDEO FMT YAVTA_DEV YAVTA_FMT YAVTA_W YAVTA_H + unset MEDIA_CTL_V_LIST MEDIA_CTL_L_LIST + unset YAVTA_CTRL_PRE_LIST YAVTA_CTRL_LIST YAVTA_CTRL_POST_LIST + + export SENSOR VIDEO FMT YAVTA_DEV YAVTA_FMT YAVTA_W YAVTA_H + export MEDIA_CTL_V_LIST MEDIA_CTL_L_LIST + export YAVTA_CTRL_PRE_LIST YAVTA_CTRL_LIST YAVTA_CTRL_POST_LIST + + while IFS= read -r bline || [ -n "$bline" ]; do + key=$(printf "%s" "$bline" | awk -F':' '{print $1}') + val=$(printf "%s" "$bline" | sed 's/^[^:]*://') + case "$key" in + SENSOR) SENSOR=$val ;; + VIDEO) VIDEO=$val ;; + YAVTA_DEV) YAVTA_DEV=$val ;; + YAVTA_W) YAVTA_W=$val ;; + YAVTA_H) YAVTA_H=$val ;; + YAVTA_FMT) YAVTA_FMT=$val ;; + MEDIA_CTL_V) MEDIA_CTL_V_LIST="$MEDIA_CTL_V_LIST +$val" ;; + MEDIA_CTL_L) MEDIA_CTL_L_LIST="$MEDIA_CTL_L_LIST +$val" ;; + YAVTA_CTRL_PRE) YAVTA_CTRL_PRE_LIST="$YAVTA_CTRL_PRE_LIST +$val" ;; + YAVTA_CTRL) YAVTA_CTRL_LIST="$YAVTA_CTRL_LIST +$val" ;; + YAVTA_CTRL_POST) YAVTA_CTRL_POST_LIST="$YAVTA_CTRL_POST_LIST +$val" ;; + esac + done < "$block_file" + + # Fallback: if VIDEO is not emitted, use YAVTA_DEV + if [ -z "$VIDEO" ] && [ -n "$YAVTA_DEV" ]; then + VIDEO="$YAVTA_DEV" + fi +} + +# Applies media configuration (format and links) using media-ctl from parsed pipeline block. +# Resets media graph first and applies user-specified format override if needed. +configure_pipeline_block() { + MEDIA_NODE="$1" USER_FORMAT="$2" + + media-ctl -d "$MEDIA_NODE" --reset >/dev/null 2>&1 + + # Apply MEDIA_CTL_V (override format if USER_FORMAT set) + printf "%s\n" "$MEDIA_CTL_V_LIST" | while IFS= read -r vline || [ -n "$vline" ]; do + [ -z "$vline" ] && continue + vline_new="$vline" + if [ -n "$USER_FORMAT" ]; then + vline_new=$(printf "%s" "$vline_new" | sed -E "s/fmt:[^/]+\/([0-9]+x[0-9]+)/fmt:${USER_FORMAT}\/\1/g") + fi + media-ctl -d "$MEDIA_NODE" -V "$vline_new" >/dev/null 2>&1 + done + + # Apply MEDIA_CTL_L + printf "%s\n" "$MEDIA_CTL_L_LIST" | while IFS= read -r lline || [ -n "$lline" ]; do + [ -z "$lline" ] && continue + media-ctl -d "$MEDIA_NODE" -l "$lline" >/dev/null 2>&1 + done +} + +# Executes yavta pipeline controls and captures frames from a video node. +# Handles pre-capture and post-capture register writes, with detailed result code +execute_capture_block() { + FRAMES="$1" FORMAT="$2" + + # Pre-stream controls + printf "%s\n" "$YAVTA_CTRL_PRE_LIST" | while IFS= read -r ctrl; do + [ -z "$ctrl" ] && continue + dev=$(echo "$ctrl" | awk '{print $1}') + reg=$(echo "$ctrl" | awk '{print $2}') + val=$(echo "$ctrl" | awk '{print $3}') + yavta --no-query -w "$reg $val" "$dev" >/dev/null 2>&1 + done + + # Stream-on controls + printf "%s\n" "$YAVTA_CTRL_LIST" | while IFS= read -r ctrl; do + [ -z "$ctrl" ] && continue + dev=$(echo "$ctrl" | awk '{print $1}') + reg=$(echo "$ctrl" | awk '{print $2}') + val=$(echo "$ctrl" | awk '{print $3}') + yavta --no-query -w "$reg $val" "$dev" >/dev/null 2>&1 + done + + # Capture + if [ -n "$YAVTA_DEV" ] && [ -n "$FORMAT" ] && [ -n "$YAVTA_W" ] && [ -n "$YAVTA_H" ]; then + OUT=$(yavta -B capture-mplane -c -I -n "$FRAMES" -f "$FORMAT" -s "${YAVTA_W}x${YAVTA_H}" -F "$YAVTA_DEV" --capture="$FRAMES" --file="frame-#.bin" 2>&1) + RET=$? + if echo "$OUT" | grep -qi "Unsupported video format"; then + return 2 + elif [ $RET -eq 0 ] && echo "$OUT" | grep -q "Captured [1-9][0-9]* frames"; then + return 0 + else + return 1 + fi else - log_info "No $label-related errors found in recent dmesg logs." + return 3 fi + + # Post-stream controls + printf "%s\n" "$YAVTA_CTRL_POST_LIST" | while IFS= read -r ctrl; do + [ -z "$ctrl" ] && continue + dev=$(echo "$ctrl" | awk '{print $1}') + reg=$(echo "$ctrl" | awk '{print $2}') + val=$(echo "$ctrl" | awk '{print $3}') + yavta --no-query -w "$reg $val" "$dev" >/dev/null 2>&1 + done } +