diff --git a/Runner/suites/Multimedia/Video/README_Video.md b/Runner/suites/Multimedia/Video/README_Video.md deleted file mode 100644 index 28a458c2..00000000 --- a/Runner/suites/Multimedia/Video/README_Video.md +++ /dev/null @@ -1,78 +0,0 @@ -# Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. -# SPDX-License-Identifier: BSD-3-Clause-Clear - -# Iris V4L2 Video Test Scripts for Qualcomm Linux based platform (Yocto) - -## Overview - -Video scripts automates the validation of video encoding and decoding capabilities on the Qualcomm Linux based platform running a Yocto-based Linux system. It utilizes iri_v4l2_test test app which is publicly available @https://github.com/quic/v4l-video-test-app - -## Features - -- V4L2 driver level test -- Encoding YUV to H264 bitstream -- Decoding H264 bitstream to YUV -- Compatible with Yocto-based root filesystem - -## Prerequisites - -Ensure the following components are present in the target Yocto build: - -- `iris_v4l2_test` (available in /usr/bin/) - this test app can be compiled from https://github.com/quic/v4l-video-test-app -- input json file for iris_v4l2_test app -- input bitstream for decode script -- input YUV for encode script -- Write access to root filesystem (for environment setup) - -## Directory Structure - -```bash -Runner/ -├── suites/ -│ ├── Multimedia/ -│ │ ├── Video/ -│ │ │ ├── iris_v4l2_video_encode/ -│ │ │ │ ├── H264Encoder.json -│ │ │ │ ├── run.sh -│ │ │ ├── iris_v4l2_video_decode/ -│ │ │ │ ├── H264Decoder.json -│ │ │ │ ├── run.sh -``` - -## Usage - -1. Copy repo to Target Device: Use scp to transfer the scripts from the host to the target device. The scripts should be copied to any directory on the target device. - -2. Verify Transfer: Ensure that the repo have been successfully copied to any directory on the target device. - -3. Run Scripts: Navigate to the directory where these files are copied on the target device and execute the scripts as needed. - -Run a specific test using: ---- -Quick Example -``` -git clone -cd -scp -r Runner user@target_device_ip: -ssh user@target_device_ip -cd /Runner && ./run-test.sh iris_v4l2_video_encode -``` -Sample output: -``` -sh-5.2# cd /Runner && ./run-test.sh iris_v4l2_video_encode -[Executing test case: /Runner/suites/Multimedia/Video/iris_v4l2_video_encode] 1980-01-08 22:22:15 - -[INFO] 1980-01-08 22:22:15 - ----------------------------------------------------------------------------------------- -[INFO] 1980-01-08 22:22:15 - -------------------Starting iris_v4l2_video_encode Testcase---------------------------- -[INFO] 1980-01-08 22:22:15 - Checking if dependency binary is available -[PASS] 1980-01-08 22:22:15 - Test related dependencies are present. -... -[PASS] 1980-01-08 22:22:17 - iris_v4l2_video_encode : Test Passed -[INFO] 1980-01-08 22:22:17 - -------------------Completed iris_v4l2_video_encode Testcase---------------------------- -``` -3. Results will be available in the `Runner/suites/Multimedia/Video/` directory under each usecase folder. - -## Notes - -- The script does not take any arguments. -- It validates the presence of required libraries before executing tests. -- If any critical tool is missing, the script exits with an error message. \ No newline at end of file diff --git a/Runner/suites/Multimedia/Video/Video_V4L2_Runner/README_Video.md b/Runner/suites/Multimedia/Video/Video_V4L2_Runner/README_Video.md new file mode 100644 index 00000000..56194737 --- /dev/null +++ b/Runner/suites/Multimedia/Video/Video_V4L2_Runner/README_Video.md @@ -0,0 +1,265 @@ +# Iris V4L2 Video Test Scripts for Qualcomm Linux (Yocto) + +**Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.** +**SPDX-License-Identifier: BSD-3-Clause-Clear** + +--- + +## Overview + +These scripts automate validation of video **encoding** and **decoding** on Qualcomm Linux platforms running a Yocto-based rootfs. +They drive the public `iris_v4l2_test` app: . + +The suite includes a **reboot-free video stack switcher** (upstream ↔ downstream), a **Kodiak (QCS6490/RB3gen2) firmware swap flow**, and robust **pre-flight checks** (rootfs size, network bootstrap, module sanity, device nodes). + +--- + +## What’s New (since 2025‑09‑26) + +- **Pre-download rootfs auto‑resize** + - New `ensure_rootfs_min_size` (now in `functestlib.sh`) verifies `/` has at least **2 GiB** available and, if the root partition is `/dev/disk/by-partlabel/rootfs`, runs: + ```sh + resize2fs /dev/disk/by-partlabel/rootfs + ``` + - Invoked **before** any clip bundle download. + +- **Kodiak upstream: auto‑install backup firmware before switching** + - `video_kodiak_install_firmware` (in `lib_video.sh`) looks for a recent backup blob under **`$VIDEO_FW_BACKUP_DIR`** (defaults to `/opt/video-fw-backups`) **and legacy `/opt` patterns**, then copies it to: + ``` + /lib/firmware/qcom/vpu/vpu20_p1_gen2.mbn + ``` + - Attempts **remoteproc stop → firmware swap → start**, with fallback to **module reload** and **platform unbind/bind**. + - Automatically runs when `--platform kodiak --stack upstream`. + +- **Network bootstrap before downloads (Ethernet → Wi‑Fi)** + - `ensure_network_online` first tries wired DHCP; if still offline and **Wi‑Fi credentials are available**, it attempts: + 1) `nmcli dev wifi connect` (with a **key‑mgmt fallback** that creates a PSK connection if NM complains: `802-11-wireless-security.key-mgmt: property is missing`), then + 2) `wpa_supplicant + udhcpc` as a final fallback. + - Credentials are taken from environment **`SSID`/`PASSWORD`** or an optional `./ssid_list.txt` (first line: `ssid password`). + +- **App path hardening** + - If `--app` points to a file that exists but is not executable, the runner does a best‑effort `chmod +x` and proceeds. + +- **Platform‑aware hard gates & clearer logging** + - Upstream/Downstream validation is **platform specific** (lemans/monaco vs. kodiak). + - udev refresh + stale node pruning for `/dev/video*` and `/dev/media*` after any stack change. + +--- + +## Features + +- Pure **V4L2** driver-level tests using `iris_v4l2_test` +- **Encode** (YUV → H.264/H.265) and **Decode** (H.264/H.265/VP9 → YUV) +- **Yocto**-friendly, POSIX shell with BusyBox-safe paths +- Parse & run multiple JSON configs; auto-detect **encode/decode** +- **Auto-fetch** missing input clips (retries, BusyBox `wget` compatible) +- **Rootfs size guard** (auto‑resize) **before** fetching assets +- **Network bootstrap** (Ethernet → Wi‑Fi via `nmcli`/`wpa_supplicant`) when needed for downloads +- Timeout, repeat, dry-run, JUnit XML, dmesg triage +- **Stack switcher**: upstream ↔ downstream without reboot +- **Kodiak firmware live swap** with backup/restore helpers +- **udev refresh + prune** of stale device nodes + +--- + +## Directory Layout + +```bash +Runner/ +├── suites/ +│ └── Multimedia/ +│ └── Video/ +│ ├── README_Video.md +│ └── Video_V4L2_Runner/ +│ ├── h264Decoder.json +│ ├── h265Decoder.json +│ ├── vp9Decoder.json +│ ├── h264Encoder.json +│ ├── h265Encoder.json +│ └── run.sh +└── utils/ + ├── functestlib.sh + └── lib_video.sh +``` + +--- + +## Quick Start + +```bash +git clone +cd + +# Copy to target +scp -r Runner user@: +ssh user@ + +cd /Runner +./run-test.sh Video_V4L2_Runner +``` + +> Results land under: `Runner/suites/Multimedia/Video/Video_V4L2_Runner/` + +--- + +## Runner CLI (run.sh) + +| Option | Description | +|---|---| +| `--config path.json` | Run a specific config file | +| `--dir DIR` | Directory to search for configs | +| `--pattern GLOB` | Filter configs by glob pattern | +| `--extract-input-clips true|false` | Auto-fetch missing clips (default: `true`) | +| `--timeout S` | Timeout per test (default: `60`) | +| `--strict` | Treat dmesg warnings as failures | +| `--no-dmesg` | Disable dmesg scanning | +| `--max N` | Run at most `N` tests | +| `--stop-on-fail` | Abort suite on first failure | +| `--loglevel N` | Log level passed to `iris_v4l2_test` | +| `--repeat N` | Repeat each test `N` times | +| `--repeat-delay S` | Delay between repeats | +| `--repeat-policy all|any` | PASS if all runs pass, or any run passes | +| `--junit FILE` | Write JUnit XML | +| `--dry-run` | Print commands only | +| `--verbose` | Verbose runner logs | +| `--app /path/to/iris_v4l2_test` | Override test app path | +| `--stack auto|upstream|downstream|base|overlay|up|down` | Select target stack | +| `--platform lemans|monaco|kodiak` | Force platform (else auto-detect) | +| `--downstream-fw PATH` | **Kodiak**: path to DS firmware (e.g. `vpu20_1v.mbn`) | + +--- + +## Pre‑Flight: Rootfs Size & Network + +### Auto‑resize before downloads +The runner calls `ensure_rootfs_min_size 2` **before** any download. If `/` is on `/dev/disk/by-partlabel/rootfs` and total size is below ~2 GiB, it executes: +```sh +resize2fs /dev/disk/by-partlabel/rootfs +``` + +### Network bootstrap (if offline) +If the target is offline when a clip bundle is needed: + +1. Tries Ethernet DHCP (safe retries) +2. If **Wi‑Fi creds** are available, tries: + - `nmcli dev wifi connect "" password "" ifname ` + If NetworkManager complains about missing key‑mgmt, it auto‑falls back to: + ```sh + nmcli con add type wifi ifname con-name auto- ssid "" \ + wifi-sec.key-mgmt wpa-psk wifi-sec.psk "" + nmcli con up auto- + ``` + - If still offline, uses `wpa_supplicant + udhcpc` + +**Provide credentials via:** +```sh +export SSID="Hydra" +export PASSWORD="K5x48Vz3" +# or create ./ssid_list.txt with: Hydra K5x48Vz3 +``` + +--- + +## Stack Selection & Validation + +- **lemans/monaco** + - **Upstream**: `qcom_iris` + `iris_vpu` + - **Downstream**: `iris_vpu` only (and *no* `qcom_iris`) + +- **kodiak** + - **Upstream**: `venus_core`, `venus_dec`, `venus_enc` + - **Downstream**: `iris_vpu` + +The runner: +1. Prints **pre/post** module snapshots and any runtime/persistent modprobe blocks +2. Switches stacks without reboot (uses runtime blacklists under `/run/modprobe.d`) +3. **Refreshes** `/dev/video*` & `/dev/media*` with udev and **prunes** stale nodes + +--- + +## Kodiak Firmware Flows + +### Downstream (custom blob) +When `--stack downstream` and you pass `--downstream-fw /path/to/vpu20_1v.mbn`: +1. The blob is copied to: `/lib/firmware/qcom/vpu/vpu20_p1_gen2.mbn` +2. Previous image is backed up to: `/opt/video-fw-backups/vpu20_p1_gen2.mbn..bak` +3. Runner tries **remoteproc restart**, then **module reload**, then **unbind/bind** + +### Upstream (restore a backup before switch) +When `--stack upstream` on **kodiak**, the runner tries to **restore a known‑good backup** to `/lib/firmware/qcom/vpu/vpu20_p1_gen2.mbn` **before** switching: +- Search order: + 1. `$VIDEO_FW_BACKUP_DIR` (default `/opt/video-fw-backups`), newest `vpu20_p1_gen2.mbn.*.bak` + 2. Legacy `/opt` patterns (e.g., `vpu20_p1_gen2.mbn.*.bak`) +- Then it attempts **remoteproc restart**; falls back as needed. + +**Tip:** If you maintain backups under a custom path: +```sh +export VIDEO_FW_BACKUP_DIR=/opt +./run.sh --platform kodiak --stack upstream +``` + +--- + +## Examples + +### Run all configs with auto stack +```sh +./run.sh +``` + +### Force downstream on lemans/monaco +```sh +./run.sh --stack downstream +``` + +### Force upstream on lemans/monaco +```sh +./run.sh --stack upstream +``` + +### Kodiak: downstream with custom firmware (live swap) +```sh +./run.sh --platform kodiak --stack downstream --downstream-fw /data/fw/vpu20_1v.mbn +``` + +### Kodiak: upstream with automatic backup restore +```sh +./run.sh --platform kodiak --stack upstream +# optionally pin the backup directory +VIDEO_FW_BACKUP_DIR=/opt ./run.sh --platform kodiak --stack upstream +``` + +### Ensure Wi‑Fi is used for downloads (if needed) +```sh +export SSID="Hydra" +export PASSWORD="K5x48Vz3" +./run.sh --extract-input-clips true +``` + +### Use a specific app binary +```sh +./run.sh --app /data/vendor/iris_test_app/iris_v4l2_test --stack upstream +``` + +### Run only H.265 decode +```sh +./run.sh --pattern '*h265*Decoder.json' +``` + +--- + +## Troubleshooting + +- **“No backup firmware found” on kodiak upstream switch** + Ensure your backups exist under `/opt/video-fw-backups` **or** legacy `/opt` with a name like `vpu20_p1_gen2.mbn..bak`, or set `VIDEO_FW_BACKUP_DIR`. + +- **Upstream/Downstream mismatch** + Check the **pre/post module snapshots**; the runner will explicitly log which modules are present/absent. + +- **No `/dev/video*`** + The runner triggers udev and prunes stale nodes; verify udev is available and rules are active. + +- **Download fails** + Ensure time is sane (TLS), network is reachable, and provide Wi‑Fi creds via env or `ssid_list.txt`. The downloader uses BusyBox‑compatible flags with retries. + +--- \ No newline at end of file diff --git a/Runner/suites/Multimedia/Video/iris_v4l2_video_decode/h264Decoder.json b/Runner/suites/Multimedia/Video/Video_V4L2_Runner/h264Decoder.json similarity index 67% rename from Runner/suites/Multimedia/Video/iris_v4l2_video_decode/h264Decoder.json rename to Runner/suites/Multimedia/Video/Video_V4L2_Runner/h264Decoder.json index 2afb90f4..a2ff932b 100755 --- a/Runner/suites/Multimedia/Video/iris_v4l2_video_decode/h264Decoder.json +++ b/Runner/suites/Multimedia/Video/Video_V4L2_Runner/h264Decoder.json @@ -1,20 +1,20 @@ -{ - "ExecutionMode": "Sequential", - "TestCases": [ - { - "Name" : "Decoder Testcase", - "TestConfigs" : { - "Domain": "Decoder", - "InputPath": "./simple_AVC_720p_10fps_90frames.264", - "NumFrames": -1, - "CodecName": "AVC", - "PixelFormat": "NV12", - "Width": 1280, - "Height": 720, - "Outputpath": "./output_simple_nv12_720p_90frms.yuv", - "InputBufferCount": 16, - "OutputBufferCount": 16 - } - } - ] -} +{ + "ExecutionMode": "Sequential", + "TestCases": [ + { + "Name" : "H264 Decoder Testcase", + "TestConfigs" : { + "Domain": "Decoder", + "InputPath": "./720p_AVC.h264", + "NumFrames": -1, + "CodecName": "AVC", + "PixelFormat": "NV12", + "Width": 1280, + "Height": 720, + "Outputpath": "./720p_AVC.yuv", + "InputBufferCount": 16, + "OutputBufferCount": 16 + } + } + ] +} diff --git a/Runner/suites/Multimedia/Video/iris_v4l2_video_encode/h264Encoder.json b/Runner/suites/Multimedia/Video/Video_V4L2_Runner/h264Encoder.json similarity index 87% rename from Runner/suites/Multimedia/Video/iris_v4l2_video_encode/h264Encoder.json rename to Runner/suites/Multimedia/Video/Video_V4L2_Runner/h264Encoder.json index 4855b5bd..709b7bfe 100755 --- a/Runner/suites/Multimedia/Video/iris_v4l2_video_encode/h264Encoder.json +++ b/Runner/suites/Multimedia/Video/Video_V4L2_Runner/h264Encoder.json @@ -1,30 +1,30 @@ -{ - "ExecutionMode": "Sequential", - "TestCases": [ - { - "Name" : "Encoder Testcase", - "TestConfigs" : { - "Domain": "Encoder", - "InputPath": "./simple_nv12_720p_90frms.yuv", - "NumFrames": -1, - "CodecName": "AVC", - "PixelFormat": "NV12", - "Width": 1280, - "Height": 720, - "Outputpath": "./output_simple_AVC_720p_10fps.h264", - "InputBufferCount": 32, - "OutputBufferCount": 32, - "OperatingRate": 10, - "FrameRate": 10, - "StaticControls": [ - {"Id": "Profile", "Vtype": "String", "Value": "BASELINE"}, - {"Id": "Level", "Vtype": "String", "Value": "5.0"}, - {"Id": "FrameRC", "Vtype": "Int", "Value": 1}, - {"Id": "BitRate", "Vtype": "Int", "Value": 18000000}, - {"Id": "BitRateMode", "Vtype": "String", "Value": "CBR"}, - {"Id": "PrefixHeaderMode", "Vtype": "String", "Value": "JOINED"} - ] - } - } - ] -} +{ + "ExecutionMode": "Sequential", + "TestCases": [ + { + "Name" : "Encoder Testcase", + "TestConfigs" : { + "Domain": "Encoder", + "InputPath": "./90frames_yuv.yuv", + "NumFrames": -1, + "CodecName": "AVC", + "PixelFormat": "NV12", + "Width": 1280, + "Height": 720, + "Outputpath": "./Enc_AVC_720p.h264", + "InputBufferCount": 32, + "OutputBufferCount": 32, + "OperatingRate": 10, + "FrameRate": 10, + "StaticControls": [ + {"Id": "Profile", "Vtype": "String", "Value": "BASELINE"}, + {"Id": "Level", "Vtype": "String", "Value": "5.0"}, + {"Id": "FrameRC", "Vtype": "Int", "Value": 1}, + {"Id": "BitRate", "Vtype": "Int", "Value": 18000000}, + {"Id": "BitRateMode", "Vtype": "String", "Value": "CBR"}, + {"Id": "PrefixHeaderMode", "Vtype": "String", "Value": "JOINED"} + ] + } + } + ] +} diff --git a/Runner/suites/Multimedia/Video/Video_V4L2_Runner/h265Decoder.json b/Runner/suites/Multimedia/Video/Video_V4L2_Runner/h265Decoder.json new file mode 100755 index 00000000..fa9bb581 --- /dev/null +++ b/Runner/suites/Multimedia/Video/Video_V4L2_Runner/h265Decoder.json @@ -0,0 +1,21 @@ +{ + "ExecutionMode": "Sequential", + "TestCases": [ + { + "Name": "HEVC Decoder TestCase", + "TestConfigs": { + "Domain": "Decoder", + "InputPath": "./720x1280_hevc.h265", + "NumFrames": -1, + "CodecName": "HEVC", + "PixelFormat": "NV12", + "Width": 1280, + "Height": 720, + "Outputpath": "./720x1280_hevc.yuv", + "InputBufferCount": 16, + "OutputBufferCount": 16, + "UseMinBufferCtrl": false + } + } + ] +} diff --git a/Runner/suites/Multimedia/Video/Video_V4L2_Runner/h265Encoder.json b/Runner/suites/Multimedia/Video/Video_V4L2_Runner/h265Encoder.json new file mode 100755 index 00000000..56446230 --- /dev/null +++ b/Runner/suites/Multimedia/Video/Video_V4L2_Runner/h265Encoder.json @@ -0,0 +1,102 @@ +{ + "ExecutionMode": "Sequential", + "TestCases": [ + { + "Name": "HEVC Encode TestCase", + "TestConfigs": { + "VideoDevice": "/dev/video1", + "Domain": "Encoder", + "InputPath": "./60frames_yuv.yuv", + "NumFrames": 60, + "CodecName": "HEVC", + "PixelFormat": "NV12", + "Width": 1280, + "Height": 720, + "Outputpath": "./Enc_HEVC__Main_1280_720.265", + "UseMinBufferCtrl": false, + "InputBufferCount": 32, + "OutputBufferCount": 32, + "OperatingRate": 30, + "FrameRate": 30, + "StaticControls": [ + { + "Id": "Profile", + "Vtype": "String", + "Value": "MAIN" + }, + { + "Id": "Level", + "Vtype": "String", + "Value": "5.0" + }, + { + "Id": "FrameRC", + "Vtype": "Int", + "Value": 1 + }, + { + "Id": "BitRate", + "Vtype": "Int", + "Value": 3662400 + }, + { + "Id": "BitRateMode", + "Vtype": "String", + "Value": "VBR" + }, + { + "Id": "PrefixHeaderMode", + "Vtype": "String", + "Value": "JOINED" + }, + { + "Id": "Tier", + "Vtype": "String", + "Value": "HIGH" + }, + { + "Id": "GOPSize", + "Vtype": "Int", + "Value": 59 + }, + { + "Id": "MultiSliceMode", + "Vtype": "String", + "Value": "SINGLE" + }, + { + "Id": "MinIQP", + "Vtype": "Int", + "Value": 10 + }, + { + "Id": "MinPQP", + "Vtype": "Int", + "Value": 10 + }, + { + "Id": "MinBQP", + "Vtype": "Int", + "Value": 10 + }, + { + "Id": "MaxIQP", + "Vtype": "Int", + "Value": 51 + }, + { + "Id": "MaxPQP", + "Vtype": "Int", + "Value": 51 + }, + { + "Id": "MaxBQP", + "Vtype": "Int", + "Value": 51 + } + ], + "DynamicControls": [] + } + } + ] +} diff --git a/Runner/suites/Multimedia/Video/Video_V4L2_Runner/run.sh b/Runner/suites/Multimedia/Video/Video_V4L2_Runner/run.sh new file mode 100755 index 00000000..de576911 --- /dev/null +++ b/Runner/suites/Multimedia/Video/Video_V4L2_Runner/run.sh @@ -0,0 +1,517 @@ +#!/bin/sh +# Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. +# SPDX-License-Identifier: BSD-3-Clause-Clear +# IRIS Video V4L2 runner with stack selection via utils/lib_video.sh +# ---------- Repo env + helpers ---------- +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 +# Only source once (idempotent) +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" +# shellcheck disable=SC1091 +. "$TOOLS/lib_video.sh" + +TESTNAME="Video_V4L2_Runner" +RES_FILE="./${TESTNAME}.res" + +: "${TAR_URL:=https://github.com/qualcomm-linux/qcom-linux-testkit/releases/download/IRIS-Video-Files-v1.0/video_clips_iris.tar.gz}" + +# --- Defaults / knobs --- +TIMEOUT="${TIMEOUT:-60}" +STRICT="${STRICT:-0}" +DMESG_SCAN="${DMESG_SCAN:-1}" +PATTERN="" +MAX="${MAX:-0}" +STOP_ON_FAIL="${STOP_ON_FAIL:-0}" +DRY=0 +EXTRACT_INPUT_CLIPS="${EXTRACT_INPUT_CLIPS:-true}" +SUCCESS_RE="${SUCCESS_RE:-SUCCESS}" +LOGLEVEL="${LOGLEVEL:-15}" +REPEAT="${REPEAT:-1}" +REPEAT_DELAY="${REPEAT_DELAY:-0}" +REPEAT_POLICY="${REPEAT_POLICY:-all}" +JUNIT_OUT="" +VERBOSE=0 + +VIDEO_STACK="${VIDEO_STACK:-auto}" +VIDEO_PLATFORM="${VIDEO_PLATFORM:-}" +VIDEO_FW_DS="${VIDEO_FW_DS:-}" +VIDEO_FW_BACKUP_DIR="${VIDEO_FW_BACKUP_DIR:-}" +VIDEO_NO_REBOOT="${VIDEO_NO_REBOOT:-0}" +VIDEO_FORCE="${VIDEO_FORCE:-0}" +VIDEO_APP="${VIDEO_APP:-/usr/bin/iris_v4l2_test}" + +usage() { + cat </dev/null || true + if [ ! -x "$VIDEO_APP" ]; then + log_warn "App $VIDEO_APP is not executable and chmod failed; attempting to run anyway." + fi +fi + +# Decide final app path: if --app given, require it; otherwise try default path, else PATH +final_app="" +if [ -n "$VIDEO_APP" ] && [ -x "$VIDEO_APP" ]; then + final_app="$VIDEO_APP" +else + if [ "$VIDEO_APP" = "/usr/bin/iris_v4l2_test" ] && command -v iris_v4l2_test >/dev/null 2>&1; then + final_app="$(command -v iris_v4l2_test)" + fi +fi +if [ -z "$final_app" ]; then + log_skip "$TESTNAME SKIP - iris_v4l2_test not available (VIDEO_APP=$VIDEO_APP). Provide --app or install the binary." + printf '%s\n' "$TESTNAME SKIP" >"$RES_FILE" + exit 0 +fi +VIDEO_APP="$final_app" +export VIDEO_APP + +# Core tools we still need +check_dependencies grep sed awk find sort || { + log_skip "$TESTNAME SKIP - required tools missing" + printf '%s\n' "$TESTNAME SKIP" >"$RES_FILE" + exit 0 +} + +# --- Resolve testcase path and cd so outputs land here --- +test_path="$(find_test_case_by_name "$TESTNAME" 2>/dev/null || echo "$SCRIPT_DIR")" +cd "$test_path" || { log_error "cd failed: $test_path"; printf '%s\n' "$TESTNAME FAIL" >"$RES_FILE"; exit 1; } + +LOG_DIR="./logs_${TESTNAME}" +mkdir -p "$LOG_DIR" +export LOG_DIR + +# Ensure rootfs meets minimum size (2GiB) BEFORE any downloads +ensure_rootfs_min_size 2 + +# If we're going to fetch, ensure network is online first (use SSID/PASSWORD if provided) +if [ "$EXTRACT_INPUT_CLIPS" = "true" ] && [ -z "$CFG" ] && [ -z "$DIR" ]; then + net_rc=1 + if command -v check_network_status_rc >/dev/null 2>&1; then + check_network_status_rc; net_rc=$? + elif command -v check_network_status >/dev/null 2>&1; then + check_network_status >/dev/null 2>&1; net_rc=$? + fi + if [ "$net_rc" -ne 0 ]; then + video_step "" "Bring network online (Wi-Fi credentials if provided)" + ensure_network_online || true + fi +fi + +# --- Optional early fetch of bundle (best-effort) +# Skip if explicit --config/--dir is provided, or EXTRACT_INPUT_CLIPS=false +if [ "$EXTRACT_INPUT_CLIPS" = "true" ] && [ -z "$CFG" ] && [ -z "$DIR" ]; then + video_step "" "Early bundle fetch (best-effort)" + extract_tar_from_url "$TAR_URL" || true +else + log_info "Skipping early bundle fetch (explicit --config/--dir provided or EXTRACT_INPUT_CLIPS=false)." +fi + +log_info "----------------------------------------------------------------------" +log_info "---------------------- Starting $TESTNAME (modular) -------------------" +log_info "STACK=$VIDEO_STACK PLATFORM=${VIDEO_PLATFORM:-auto} STRICT=$STRICT DMESG_SCAN=$DMESG_SCAN" +log_info "TIMEOUT=${TIMEOUT}s LOGLEVEL=$LOGLEVEL REPEAT=$REPEAT REPEAT_POLICY=$REPEAT_POLICY" +log_info "APP=$VIDEO_APP" +[ -n "$VIDEO_FW_DS" ] && log_info "Downstream FW override: $VIDEO_FW_DS" +[ -n "$VIDEO_FW_BACKUP_DIR" ] && log_info "FW backup override: $VIDEO_FW_BACKUP_DIR" +[ "$VERBOSE" -eq 1 ] && log_info "CWD=$(pwd) | SCRIPT_DIR=$SCRIPT_DIR | test_path=$test_path" + +# Warn if not root (module/blacklist ops may fail) +video_warn_if_not_root + +# --- Ensure desired video stack (hot switch best-effort) --- +plat="$VIDEO_PLATFORM" +[ -n "$plat" ] || plat=$(video_detect_platform) +log_info "Detected platform: $plat" + +VIDEO_STACK=$(video_normalize_stack "$VIDEO_STACK") +pre_stack=$(video_stack_status "$plat") +log_info "Current video stack (pre): $pre_stack" + +# Kodiak + upstream → install backup firmware to /lib/firmware before switching +if [ "$plat" = "kodiak" ]; then + case "$VIDEO_STACK" in + upstream|up|base) + video_step "" "Kodiak upstream firmware install" + video_kodiak_install_firmware || true + ;; + esac +fi + +video_dump_stack_state "pre" + +video_step "" "Apply desired stack = $VIDEO_STACK" +post_stack=$(video_ensure_stack "$VIDEO_STACK" "$plat" || true) +if [ -z "$post_stack" ] || [ "$post_stack" = "unknown" ]; then + log_warn "Could not fully switch to requested stack=$VIDEO_STACK (platform=$plat). Blacklist updated; reboot may be required." + post_stack=$(video_stack_status "$plat") +fi +log_info "Video stack (post): $post_stack" + +video_dump_stack_state "post" + +# Always refresh/prune device nodes (even if no switch occurred) +video_step "" "Refresh V4L device nodes (udev trigger + prune stale)" +video_clean_and_refresh_v4l || true + +# --- Hard gate: if requested stack not in effect, abort immediately (platform-aware) +case "$VIDEO_STACK" in + upstream|up|base) + if ! video_validate_upstream_loaded "$plat"; then + case "$plat" in + lemans|monaco) msg="qcom_iris+iris_vpu not both present";; + kodiak) msg="venus_core/dec/enc not all present";; + *) msg="required upstream modules not present for platform $plat";; + esac + log_fail "[STACK] Upstream requested but $msg; aborting." + printf '%s\n' "$TESTNAME FAIL" >"$RES_FILE" + exit 1 + fi + ;; + downstream|overlay|down) + if ! video_validate_downstream_loaded "$plat"; then + case "$plat" in + lemans|monaco) msg="iris_vpu missing or qcom_iris still loaded";; + kodiak) msg="iris_vpu missing or venus_core still loaded";; + *) msg="required downstream modules not present for platform $plat";; + esac + log_fail "[STACK] Downstream requested but $msg; aborting." + printf '%s\n' "$TESTNAME FAIL" >"$RES_FILE" + exit 1 + fi + ;; +esac + +# Per-platform module validation (informational) +case "$plat" in + lemans|monaco) + if [ "$post_stack" = "upstream" ]; then + if video_has_module_loaded qcom_iris && video_has_module_loaded iris_vpu; then + log_pass "Upstream validated: qcom_iris + iris_vpu present" + else + log_warn "Upstream expected but modules mismatch (need qcom_iris and iris_vpu)" + fi + elif [ "$post_stack" = "downstream" ]; then + if video_has_module_loaded iris_vpu && ! video_has_module_loaded qcom_iris; then + log_pass "Downstream validated: only iris_vpu present" + else + log_warn "Downstream expected but qcom_iris still loaded or iris_vpu missing" + fi + fi + ;; + kodiak) + if [ "$post_stack" = "upstream" ]; then + if video_has_module_loaded venus_core && video_has_module_loaded venus_dec && video_has_module_loaded venus_enc; then + log_pass "Upstream validated: venus_core/dec/enc present" + else + log_warn "Upstream expected but venus modules mismatch" + fi + elif [ "$post_stack" = "downstream" ]; then + if video_has_module_loaded iris_vpu; then + log_pass "Downstream validated: iris_vpu present (Kodiak)" + else + log_warn "Downstream expected but iris_vpu not present (Kodiak)" + fi + fi + ;; + *) log_warn "Unknown platform; skipping strict module validation" ;; +esac + +# Validate numeric loglevel +case "$LOGLEVEL" in + ''|*[!0-9]* ) log_warn "Non-numeric --loglevel '$LOGLEVEL'; using 15"; LOGLEVEL=15 ;; +esac + +# --- Discover config list --- +CFG_LIST="$LOG_DIR/.cfgs"; : > "$CFG_LIST" + +if [ -n "$CFG" ] && [ -d "$CFG" ]; then + DIR="$CFG" + CFG="" +fi + +if [ -z "$CFG" ]; then + if [ -n "$DIR" ]; then + base_dir="$DIR" + if [ -n "$PATTERN" ]; then + find "$base_dir" -type f -name "$PATTERN" 2>/dev/null | sort > "$CFG_LIST" + else + find "$base_dir" -type f -name "*.json" 2>/dev/null | sort > "$CFG_LIST" + fi + log_info "Using custom config directory: $base_dir" + else + log_info "No --config passed, searching for JSON under testcase dir: $test_path" + find "$test_path" -type f -name "*.json" 2>/dev/null | sort > "$CFG_LIST" + fi +else + printf '%s\n' "$CFG" > "$CFG_LIST" +fi + +if [ ! -s "$CFG_LIST" ]; then + log_skip "$TESTNAME SKIP - no JSON configs found" + printf '%s\n' "$TESTNAME SKIP" >"$RES_FILE" + exit 0 +fi + +cfg_count=$(wc -l < "$CFG_LIST" 2>/dev/null | tr -d ' ') +log_info "Discovered $cfg_count JSON config(s) to run" + +# --- JUnit prep / results files --- +JUNIT_TMP="$LOG_DIR/.junit_cases.xml" +: > "$JUNIT_TMP" +printf '%s\n' "mode,id,result,name,elapsed,pass_runs,fail_runs" > "$LOG_DIR/results.csv" +: > "$LOG_DIR/summary.txt" + +# --- Suite loop --- +total=0; pass=0; fail=0; skip=0; suite_rc=0 + +while IFS= read -r cfg; do + [ -n "$cfg" ] || continue + total=$((total + 1)) + + if video_is_decode_cfg "$cfg"; then mode="decode"; else mode="encode"; fi + + name_and_id=$(video_pretty_name_from_cfg "$cfg") + pretty=$(printf '%s' "$name_and_id" | cut -d'|' -f1) + raw_codec=$(video_guess_codec_from_cfg "$cfg") + codec=$(video_canon_codec "$raw_codec") + safe_codec=$(printf '%s' "$codec" | tr ' /' '__') + base_noext=$(basename "$cfg" .json) + id="${mode}-${safe_codec}-${base_noext}" + + log_info "[$id] START — mode=$mode codec=$codec name=\"$pretty\" cfg=\"$cfg\"" + + video_step "$id" "Check /dev/video* presence" + if ! video_devices_present; then + log_skip "[$id] SKIP - no /dev/video* nodes" + printf '%s\n' "$id SKIP $pretty" >> "$LOG_DIR/summary.txt" + printf '%s\n' "$mode,$id,SKIP,$pretty,0,0,0" >> "$LOG_DIR/results.csv" + skip=$((skip + 1)) + continue + fi + + # Fetch only when not explicitly provided a config/dir and feature enabled + if [ "$EXTRACT_INPUT_CLIPS" = "true" ] && [ -z "$CFG" ] && [ -z "$DIR" ]; then + video_step "$id" "Ensure clips present or fetch" + video_ensure_clips_present_or_fetch "$cfg" "$TAR_URL" + ce=$? + if [ "$ce" -eq 2 ] 2>/dev/null; then + if [ "$mode" = "decode" ]; then + log_skip "[$id] SKIP - offline and clips missing (decode case)" + printf '%s\n' "$id SKIP $pretty" >> "$LOG_DIR/summary.txt" + printf '%s\n' "$mode,$id,SKIP,$pretty,0,0,0" >> "$LOG_DIR/results.csv" + skip=$((skip + 1)) + continue + fi + elif [ "$ce" -eq 1 ] 2>/dev/null; then + log_fail "[$id] FAIL - fetch/extract failed while online" + printf '%s\n' "$id FAIL $pretty" >> "$LOG_DIR/summary.txt" + printf '%s\n' "$mode,$id,FAIL,$pretty,0,0,0" >> "$LOG_DIR/results.csv" + fail=$((fail + 1)); suite_rc=1 + [ "$STOP_ON_FAIL" -eq 1 ] && break + continue + fi + else + log_info "[$id] Fetch disabled (explicit --config/--dir)." + fi + + # Strict clip existence check after optional fetch + video_step "$id" "Verify required clips exist" + missing_case=0 + clips_file="$LOG_DIR/.clips.$$" + video_extract_input_clips "$cfg" > "$clips_file" + if [ -s "$clips_file" ]; then + while IFS= read -r pth; do + [ -z "$pth" ] && continue + case "$pth" in + /*) abs="$pth" ;; + *) abs=$(cd "$(dirname "$cfg")" 2>/dev/null && pwd)/$pth ;; + esac + [ -f "$abs" ] || missing_case=1 + done < "$clips_file" + fi + rm -f "$clips_file" 2>/dev/null || true + if [ "$missing_case" -eq 1 ] 2>/dev/null; then + log_fail "[$id] Required input clip(s) not present — $pretty" + printf '%s\n' "$id FAIL $pretty" >> "$LOG_DIR/summary.txt" + printf '%s\n' "$mode,$id,FAIL,$pretty,$elapsed,0,0" >> "$LOG_DIR/results.csv" + fail=$((fail + 1)); suite_rc=1 + [ "$STOP_ON_FAIL" -eq 1 ] && break + continue + fi + + if [ "$DRY" -eq 1 ]; then + video_step "$id" "DRY RUN - print command" + log_info "[dry] [$id] $VIDEO_APP --config \"$cfg\" --loglevel $LOGLEVEL — $pretty" + printf '%s\n' "$id DRY-RUN $pretty" >> "$LOG_DIR/summary.txt" + printf '%s\n' "$mode,$id,DRY-RUN,$pretty,0,0,0" >> "$LOG_DIR/results.csv" + continue + fi + + pass_runs=0; fail_runs=0; rep=1 + start_case=$(date +%s 2>/dev/null || printf '%s' 0) + logf="$LOG_DIR/${id}.log" + + while [ "$rep" -le "$REPEAT" ]; do + [ "$REPEAT" -gt 1 ] && log_info "[$id] repeat $rep/$REPEAT — $pretty" + video_step "$id" "Execute app" + # Print the exact iris command for debugging + log_info "[$id] CMD: $VIDEO_APP --config \"$cfg\" --loglevel $LOGLEVEL" + if video_run_once "$cfg" "$logf" "$TIMEOUT" "$SUCCESS_RE" "$LOGLEVEL"; then + pass_runs=$((pass_runs + 1)) + else + # Crash triage (read rc from log footer) + rc_val="$(awk -F'=' '/^END-RUN rc=/{print $2}' "$logf" 2>/dev/null | tail -n1 | tr -d ' ')" + if [ -n "$rc_val" ] 2>/dev/null; then + case "$rc_val" in + 139) log_warn "[$id] App exited rc=139 (SIGSEGV).";; + 134) log_warn "[$id] App exited rc=134 (SIGABRT).";; + 137) log_warn "[$id] App exited rc=137 (SIGKILL/OOM?).";; + *) : ;; + esac + fi + fail_runs=$((fail_runs + 1)) + fi + if [ "$rep" -lt "$REPEAT" ] && [ "$REPEAT_DELAY" -gt 0 ]; then sleep "$REPEAT_DELAY"; fi + rep=$((rep + 1)) + done + + end_case=$(date +%s 2>/dev/null || printf '%s' 0) + elapsed=$((end_case - start_case)); [ "$elapsed" -lt 0 ] 2>/dev/null && elapsed=0 + + final="FAIL" + case "$REPEAT_POLICY" in + any) [ "$pass_runs" -ge 1 ] && final="PASS" ;; + all|*) [ "$fail_runs" -eq 0 ] && final="PASS" ;; + esac + + video_step "$id" "DMESG triage" + video_scan_dmesg_if_enabled "$DMESG_SCAN" "$LOG_DIR" + dmesg_rc=$? + if [ "$dmesg_rc" -eq 0 ]; then + log_warn "[$id] dmesg reported errors (STRICT=$STRICT)" + [ "$STRICT" -eq 1 ] && final="FAIL" + fi + + { + printf 'RESULT id=%s mode=%s pretty="%s" final=%s pass_runs=%s fail_runs=%s elapsed=%s\n' \ + "$id" "$mode" "$pretty" "$final" "$pass_runs" "$fail_runs" "$elapsed" + } >> "$logf" 2>&1 + + video_junit_append_case "$JUNIT_TMP" "Video.$mode" "$pretty" "$elapsed" "$final" "$logf" + + case "$final" in + PASS) log_pass "[$id] PASS ($pass_runs/$REPEAT ok) — $pretty" ;; + FAIL) log_fail "[$id] FAIL (pass=$pass_runs fail=$fail_runs) — $pretty" ;; + SKIP) log_skip "[$id] SKIP — $pretty" ;; + esac + + printf '%s\n' "$id $final $pretty" >> "$LOG_DIR/summary.txt" + printf '%s\n' "$mode,$id,$final,$pretty,$elapsed,$pass_runs,$fail_runs" >> "$LOG_DIR/results.csv" + + if [ "$final" = "PASS" ]; then + pass=$((pass + 1)) + else + fail=$((fail + 1)); suite_rc=1 + [ "$STOP_ON_FAIL" -eq 1 ] && break + fi + + if [ "$MAX" -gt 0 ] && [ "$total" -ge "$MAX" ]; then + log_info "Reached MAX=$MAX tests; stopping" + break + fi +done < "$CFG_LIST" + +log_info "Summary: total=$total pass=$pass fail=$fail skip=$skip" + +# --- JUnit finalize --- +if [ -n "$JUNIT_OUT" ]; then + tests=$((pass + fail + skip)) + failures="$fail" + skipped="$skip" + { + printf '\n' "$TESTNAME" "$tests" "$failures" "$skipped" + cat "$JUNIT_TMP" + printf '\n' + } > "$JUNIT_OUT" + log_info "Wrote JUnit: $JUNIT_OUT" +fi + +if [ "$suite_rc" -eq 0 ] 2>/dev/null; then + log_pass "$TESTNAME: PASS"; printf '%s\n' "$TESTNAME PASS" >"$RES_FILE" +else + log_fail "$TESTNAME: FAIL"; printf '%s\n' "$TESTNAME FAIL" >"$RES_FILE" +fi +exit "$suite_rc" diff --git a/Runner/suites/Multimedia/Video/Video_V4L2_Runner/vp9Decoder.json b/Runner/suites/Multimedia/Video/Video_V4L2_Runner/vp9Decoder.json new file mode 100755 index 00000000..e38d4f95 --- /dev/null +++ b/Runner/suites/Multimedia/Video/Video_V4L2_Runner/vp9Decoder.json @@ -0,0 +1,21 @@ +{ + "ExecutionMode": "Sequential", + "TestCases": [ + { + "Name": "VP9 Decode TestCase", + "TestConfigs": { + "Domain": "Decoder", + "InputPath": "./720x1280_vp9.ivf", + "NumFrames": -1, + "CodecName": "VP9", + "PixelFormat": "NV12", + "Width": 1280, + "Height": 720, + "Outputpath": "./720x1280_vp9.yuv", + "InputBufferCount": 16, + "OutputBufferCount": 16, + "UseMinBufferCtrl": false + } + } + ] +} diff --git a/Runner/suites/Multimedia/Video/iris_v4l2_video_decode/run.sh b/Runner/suites/Multimedia/Video/iris_v4l2_video_decode/run.sh deleted file mode 100755 index 88e6f7bc..00000000 --- a/Runner/suites/Multimedia/Video/iris_v4l2_video_decode/run.sh +++ /dev/null @@ -1,60 +0,0 @@ -#!/bin/sh - -# Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. -# SPDX-License-Identifier: BSD-3-Clause-Clear - -# Robustly find and source init_env -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 - -# Only source if not already loaded (idempotent) -if [ -z "$__INIT_ENV_LOADED" ]; then - # shellcheck disable=SC1090 - . "$INIT_ENV" -fi -# Always source functestlib.sh, using $TOOLS exported by init_env -# shellcheck disable=SC1090,SC1091 -. "$TOOLS/functestlib.sh" - -TESTNAME="iris_v4l2_video_decode" -test_path=$(find_test_case_by_name "$TESTNAME") -cd "$test_path" || exit 1 -# shellcheck disable=SC2034 -res_file="./$TESTNAME.res" -TAR_URL="https://github.com/qualcomm-linux/qcom-linux-testkit/releases/download/IRIS-Video-Files-v1.0/video_clips_iris.tar.gz" - -log_info "-----------------------------------------------------------------------------------------" -log_info "-------------------Starting $TESTNAME Testcase----------------------------" -log_info "=== Test Initialization ===" - -log_info "Checking if dependency binary is available" -check_dependencies iris_v4l2_test -extract_tar_from_url "$TAR_URL" - -# Run the first test -iris_v4l2_test --config "${test_path}/h264Decoder.json" --loglevel 15 >> "${test_path}/video_dec.txt" - -if grep -q "SUCCESS" "${test_path}/video_dec.txt"; then - log_pass "$TESTNAME : Test Passed" - echo "$TESTNAME PASS" > "$test_path/$TESTNAME.res" - exit 0 -else - log_fail "$TESTNAME : Test Failed" - echo "$TESTNAME FAIL" > "$test_path/$TESTNAME.res" - exit 1 -fi - -log_info "-------------------Completed $TESTNAME Testcase----------------------------" diff --git a/Runner/suites/Multimedia/Video/iris_v4l2_video_encode/run.sh b/Runner/suites/Multimedia/Video/iris_v4l2_video_encode/run.sh deleted file mode 100755 index 6de21775..00000000 --- a/Runner/suites/Multimedia/Video/iris_v4l2_video_encode/run.sh +++ /dev/null @@ -1,60 +0,0 @@ -#!/bin/sh - -# Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. -# SPDX-License-Identifier: BSD-3-Clause-Clear - -# Robustly find and source init_env -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 - -# Only source if not already loaded (idempotent) -if [ -z "$__INIT_ENV_LOADED" ]; then - # shellcheck disable=SC1090 - . "$INIT_ENV" -fi -# Always source functestlib.sh, using $TOOLS exported by init_env -# shellcheck disable=SC1090,SC1091 -. "$TOOLS/functestlib.sh" - -TESTNAME="iris_v4l2_video_encode" -test_path=$(find_test_case_by_name "$TESTNAME") -cd "$test_path" || exit 1 -# shellcheck disable=SC2034 -res_file="./$TESTNAME.res" -TAR_URL="https://github.com/qualcomm-linux/qcom-linux-testkit/releases/download/IRIS-Video-Files-v1.0/video_clips_iris.tar.gz" - -log_info "-----------------------------------------------------------------------------------------" -log_info "-------------------Starting $TESTNAME Testcase----------------------------" -log_info "=== Test Initialization ===" - -log_info "Checking if dependency binary is available" -check_dependencies iris_v4l2_test -extract_tar_from_url "$TAR_URL" - -# Run the first test -iris_v4l2_test --config "${test_path}/h264Encoder.json" --loglevel 15 >> "${test_path}/video_enc.txt" - -if grep -q "SUCCESS" "${test_path}/video_enc.txt"; then - log_pass "$TESTNAME : Test Passed" - echo "$TESTNAME PASS" > "$test_path/$TESTNAME.res" - exit 0 -else - log_fail "$TESTNAME : Test Failed" - echo "$TESTNAME FAIL" > "$test_path/$TESTNAME.res" - exit 1 -fi - -log_info "-------------------Completed $TESTNAME Testcase----------------------------" diff --git a/Runner/utils/functestlib.sh b/Runner/utils/functestlib.sh index eeba2a3f..9bce1312 100755 --- a/Runner/utils/functestlib.sh +++ b/Runner/utils/functestlib.sh @@ -1,4 +1,5 @@ #!/bin/sh +#!/bin/sh # Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. # SPDX-License-Identifier: BSD-3-Clause-Clear @@ -289,267 +290,269 @@ check_network_status() { fi } - -# ---- Connectivity probe (0 OK, 2 IP/no-internet, 1 no IP) ---- -# Env overrides: -# NET_PROBE_ROUTE_IP=1.1.1.1 -# NET_PING_HOST=8.8.8.8 -check_network_status_rc() { - net_probe_route_ip="${NET_PROBE_ROUTE_IP:-1.1.1.1}" - net_ping_host="${NET_PING_HOST:-8.8.8.8}" +# --- Make sure system time is sane (TLS needs a sane clock) --- +ensure_reasonable_clock() { + now="$(date +%s 2>/dev/null || echo 0)" + cutoff="$(date -d '2020-01-01 UTC' +%s 2>/dev/null || echo 1577836800)" + [ -z "$cutoff" ] && cutoff=1577836800 + [ "$now" -ge "$cutoff" ] 2>/dev/null && return 0 - if command -v ip >/dev/null 2>&1; then - net_ip_addr="$(ip -4 route get "$net_probe_route_ip" 2>/dev/null \ - | awk 'NR==1{for(i=1;i<=NF;i++) if($i=="src"){print $(i+1); exit}}')" - if [ -z "$net_ip_addr" ]; then - net_ip_addr="$(ip -o -4 addr show scope global up 2>/dev/null \ - | awk 'NR==1{split($4,a,"/"); print a[1]}')" - fi - else - net_ip_addr="" + log_warn "System clock looks invalid (epoch=$now). Attempting quick time sync..." + if command -v timedatectl >/dev/null 2>&1; then + timedatectl set-ntp true 2>/dev/null || true + sleep 1 fi - - if [ -n "$net_ip_addr" ]; then - if command -v ping >/dev/null 2>&1; then - if ping -c 1 -W 2 "$net_ping_host" >/dev/null 2>&1 \ - || ping -c 1 -w 2 "$net_ping_host" >/dev/null 2>&1; then - unset net_probe_route_ip net_ping_host net_ip_addr - return 0 - fi - unset net_probe_route_ip net_ping_host net_ip_addr - return 2 - else - unset net_probe_route_ip net_ping_host net_ip_addr - return 2 - fi + # If no NTP client, we cannot do much more here. + now2="$(date +%s 2>/dev/null || echo 0)" + if [ "$now2" -ge "$cutoff" ] 2>/dev/null; then + log_pass "Clock synchronized." + return 0 fi - - unset net_probe_route_ip net_ping_host net_ip_addr + log_warn "Clock still invalid; TLS downloads may fail. Treating as limited network." return 1 } -# ---- Interface snapshot (INFO log only) ---- -net_log_iface_snapshot() { - net_ifc="$1" - [ -n "$net_ifc" ] || { unset net_ifc; return 0; } - - net_admin="DOWN" - net_oper="unknown" - net_carrier="0" - net_ip="none" - - if command -v ip >/dev/null 2>&1 && ip -o link show "$net_ifc" >/dev/null 2>&1; then - if ip -o link show "$net_ifc" | awk -F'[<>]' '{print $2}' | grep -qw UP; then - net_admin="UP" - fi - fi - [ -r "/sys/class/net/$net_ifc/operstate" ] && net_oper="$(cat "/sys/class/net/$net_ifc/operstate" 2>/dev/null)" - [ -r "/sys/class/net/$net_ifc/carrier" ] && net_carrier="$(cat "/sys/class/net/$net_ifc/carrier" 2>/dev/null)" - - if command -v get_ip_address >/dev/null 2>&1; then - net_ip="$(get_ip_address "$net_ifc" 2>/dev/null)" - [ -n "$net_ip" ] || net_ip="none" - fi - - log_info "[NET] ${net_ifc}: admin=${net_admin} oper=${net_oper} carrier=${net_carrier} ip=${net_ip}" - - unset net_ifc net_admin net_oper net_carrier net_ip -} -# ---- Bring the system online if possible (0 OK, 2 IP/no-internet, 1 no IP) ---- -ensure_network_online() { - check_network_status_rc; net_rc=$? - [ "$net_rc" -eq 0 ] && { unset net_rc; return 0; } - - if command -v systemctl >/dev/null 2>&1 && command -v check_systemd_services >/dev/null 2>&1; then - check_systemd_services NetworkManager systemd-networkd connman || true - fi - - net_had_any_ip=0 - - # -------- Ethernet pass -------- - net_ifaces="" - if command -v get_ethernet_interfaces >/dev/null 2>&1; then - net_ifaces="$(get_ethernet_interfaces 2>/dev/null)" - fi - - for net_ifc in $net_ifaces; do - net_log_iface_snapshot "$net_ifc" +# If the tar file already exists,then function exit. Otherwise function to check the network connectivity and it will download tar from internet. +extract_tar_from_url() { + url="$1" + outdir="${LOG_DIR:-.}" + mkdir -p "$outdir" 2>/dev/null || true + + case "$url" in + /*) tarfile="$url" ;; + file://*) tarfile="${url#file://}" ;; + *) tarfile="$outdir/$(basename "$url")" ;; + esac + markfile="${tarfile}.extracted" + + # --- small helper: decide if this archive already looks extracted in $outdir + tar_already_extracted() { + tf="$1" + od="$2" + + # Fast path: explicit marker from a previous successful extract. + [ -f "${tf}.extracted" ] && return 0 + + # Fall back: sniff a few entries from the archive and see if they exist on disk. + # (No reliance on any one filename; majority-of-first-10 rule.) + # NOTE: we intentionally avoid pipes->subshell to keep counters reliable in POSIX sh. + tmp_list="${od}/.tar_ls.$$" + if tar -tf "$tf" 2>/dev/null | head -n 20 >"$tmp_list"; then + total=0; present=0 + # shellcheck disable=SC2039 + while IFS= read -r ent; do + [ -z "$ent" ] && continue + total=$((total + 1)) + # Normalize (strip trailing slash for directories) + ent="${ent%/}" + # Check both full relative path and basename (covers archives with nested roots) + if [ -e "$od/$ent" ] || [ -e "$od/$(basename "$ent")" ]; then + present=$((present + 1)) + fi + done < "$tmp_list" + rm -f "$tmp_list" 2>/dev/null || true - if command -v is_link_up >/dev/null 2>&1; then - if ! is_link_up "$net_ifc"; then - log_info "[NET] ${net_ifc}: link=down → skipping DHCP" - continue + # If we have at least 3 hits or ≥50% of probed entries, assume it's already extracted. + if [ "$present" -ge 3 ] || { [ "$total" -gt 0 ] && [ $((present * 100 / total)) -ge 50 ]; }; then + return 0 fi fi + return 1 + } - log_info "[NET] ${net_ifc}: bringing up and requesting DHCP..." - if command -v bringup_interface >/dev/null 2>&1; then - bringup_interface "$net_ifc" 2 2 || true - fi - - if command -v run_dhcp_client >/dev/null 2>&1; then - run_dhcp_client "$net_ifc" 10 >/dev/null 2>&1 || true - elif command -v try_dhcp_client_safe >/dev/null 2>&1; then - try_dhcp_client_safe "$net_ifc" 8 || true + if command -v check_tar_file >/dev/null 2>&1; then + # Your site-specific validator (returns 0=extracted ok, 2=exists but not extracted, 1=missing/bad) + check_tar_file "$url"; status=$? + else + # Fallback: if archive exists and looks extracted -> status=0; if exists not-extracted -> 2; else 1 + if [ -f "$tarfile" ]; then + if tar_already_extracted "$tarfile" "$outdir"; then + status=0 + else + status=2 + fi + else + status=1 fi + fi - net_log_iface_snapshot "$net_ifc" + ensure_reasonable_clock || { log_warn "Proceeding in limited-network mode."; limited_net=1; } - check_network_status_rc; net_rc=$? - case "$net_rc" in - 0) log_pass "[NET] ${net_ifc}: internet reachable"; unset net_ifaces net_ifc net_rc net_had_any_ip; return 0 ;; - 2) log_warn "[NET] ${net_ifc}: IP assigned but internet not reachable"; net_had_any_ip=1 ;; - 1) log_info "[NET] ${net_ifc}: still no IP after DHCP attempt" ;; - esac - done + # ---- helpers ------------------------------------------------------------ + is_busybox_wget() { command -v wget >/dev/null 2>&1 && wget --help 2>&1 | head -n1 | grep -qi busybox; } - # -------- Wi-Fi pass -------- - net_wifi="" - if command -v get_wifi_interface >/dev/null 2>&1; then - net_wifi="$(get_wifi_interface 2>/dev/null || echo "")" - fi - if [ -n "$net_wifi" ]; then - net_log_iface_snapshot "$net_wifi" - log_info "[NET] ${net_wifi}: bringing up Wi-Fi..." + try_download() { + src="$1"; dst="$2" + part="${dst}.part.$$" + ca="" + for cand in /etc/ssl/certs/ca-certificates.crt /etc/ssl/cert.pem /system/etc/security/cacerts/ca-certificates.crt; do + [ -r "$cand" ] && ca="$cand" && break + done - if command -v bringup_interface >/dev/null 2>&1; then - bringup_interface "$net_wifi" 2 2 || true + # 1) curl first (IPv4, retries, redirects) + if command -v curl >/dev/null 2>&1; then + if [ -n "$ca" ]; then + curl -4 -L --fail --retry 3 --retry-delay 2 --connect-timeout 10 \ + -o "$part" --cacert "$ca" "$src" + else + curl -4 -L --fail --retry 3 --retry-delay 2 --connect-timeout 10 \ + -o "$part" "$src" + fi + rc=$? + if [ $rc -eq 0 ]; then mv -f "$part" "$dst" 2>/dev/null || true; return 0; fi + rm -f "$part" 2>/dev/null || true + case "$rc" in 60|35|22) return 60 ;; esac # TLS-ish / HTTP fail fi - net_creds="" - if command -v get_wifi_credentials >/dev/null 2>&1; then - net_creds="$(get_wifi_credentials "" "" 2>/dev/null || true)" + # 2) aria2c (if available) + if command -v aria2c >/dev/null 2>&1; then + aria2c -x4 -s4 -m3 --connect-timeout=10 \ + -o "$(basename "$part")" --dir="$(dirname "$part")" "$src" + rc=$? + if [ $rc -eq 0 ]; then mv -f "$part" "$dst" 2>/dev/null || true; return 0; fi + rm -f "$part" 2>/dev/null || true fi - if [ -n "$net_creds" ]; then - net_ssid="$(printf '%s\n' "$net_creds" | awk '{print $1}')" - net_pass="$(printf '%s\n' "$net_creds" | awk '{print $2}')" - log_info "[NET] ${net_wifi}: trying nmcli/wpa_supplicant for SSID='${net_ssid}'" - if command -v wifi_connect_nmcli >/dev/null 2>&1; then - wifi_connect_nmcli "$net_wifi" "$net_ssid" "$net_pass" || true - fi - if command -v wifi_connect_wpa_supplicant >/dev/null 2>&1; then - wifi_connect_wpa_supplicant "$net_wifi" "$net_ssid" "$net_pass" || true - fi - else - log_info "[NET] ${net_wifi}: no credentials provided → DHCP only" - if command -v run_dhcp_client >/dev/null 2>&1; then - run_dhcp_client "$net_wifi" 10 >/dev/null 2>&1 || true + # 3) wget: handle BusyBox vs GNU + if command -v wget >/dev/null 2>&1; then + if is_busybox_wget; then + # BusyBox wget has: -O, -T, --no-check-certificate (no -4, no --tries) + wget -O "$part" -T 15 "$src"; rc=$? + if [ $rc -ne 0 ]; then + log_warn "BusyBox wget failed (rc=$rc); final attempt with --no-check-certificate." + wget -O "$part" -T 15 --no-check-certificate "$src"; rc=$? + fi + if [ $rc -eq 0 ]; then mv -f "$part" "$dst" 2>/dev/null || true; return 0; fi + rm -f "$part" 2>/dev/null || true + return 60 + else + # GNU wget: can use IPv4 and tries + if [ -n "$ca" ]; then + wget -4 --timeout=15 --tries=3 --ca-certificate="$ca" -O "$part" "$src"; rc=$? + else + wget -4 --timeout=15 --tries=3 -O "$part" "$src"; rc=$? + fi + if [ $rc -ne 0 ]; then + log_warn "wget failed (rc=$rc); final attempt with --no-check-certificate." + wget -4 --timeout=15 --tries=1 --no-check-certificate -O "$part" "$src"; rc=$? + fi + if [ $rc -eq 0 ] ; then mv -f "$part" "$dst" 2>/dev/null || true; return 0; fi + rm -f "$part" 2>/dev/null || true + [ $rc -eq 5 ] && return 60 + return $rc fi fi - net_log_iface_snapshot "$net_wifi" - check_network_status_rc; net_rc=$? - case "$net_rc" in - 0) log_pass "[NET] ${net_wifi}: internet reachable"; unset net_wifi net_ifaces net_ifc net_rc net_had_any_ip net_creds net_ssid net_pass; return 0 ;; - 2) log_warn "[NET] ${net_wifi}: IP assigned but internet not reachable"; net_had_any_ip=1 ;; - 1) log_info "[NET] ${net_wifi}: still no IP after connect/DHCP attempt" ;; - esac - fi - - # -------- DHCP/route/DNS fixup (udhcpc script) -------- - net_script_path="" - if command -v ensure_udhcpc_script >/dev/null 2>&1; then - net_script_path="$(ensure_udhcpc_script 2>/dev/null || echo "")" - fi - if [ -n "$net_script_path" ]; then - log_info "[NET] udhcpc default.script present → refreshing leases" - for net_ifc in $net_ifaces $net_wifi; do - [ -n "$net_ifc" ] || continue - if command -v run_dhcp_client >/dev/null 2>&1; then - run_dhcp_client "$net_ifc" 8 >/dev/null 2>&1 || true - fi - done - check_network_status_rc; net_rc=$? - case "$net_rc" in - 0) log_pass "[NET] connectivity restored after udhcpc fixup"; unset net_script_path net_ifaces net_wifi net_ifc net_rc net_had_any_ip; return 0 ;; - 2) log_warn "[NET] IP present but still no internet after udhcpc fixup"; net_had_any_ip=1 ;; - esac - fi + return 127 + } + # ------------------------------------------------------------------------ - if [ "$net_had_any_ip" -eq 1 ] 2>/dev/null; then - unset net_script_path net_ifaces net_wifi net_ifc net_rc net_had_any_ip - return 2 - fi - unset net_script_path net_ifaces net_wifi net_ifc net_rc net_had_any_ip - return 1 -} - - -# If the tar file already exists,then function exit. Otherwise function to check the network connectivity and it will download tar from internet. -extract_tar_from_url() { - url=$1 - filename=$(basename "$url") - - check_tar_file "$url" - status=$? if [ "$status" -eq 0 ]; then log_info "Already extracted. Skipping download." return 0 - elif [ "$status" -eq 1 ]; then - log_info "File missing or invalid. Will download and extract." - check_network_status || return 1 - log_info "Downloading $url..." - wget -O "$filename" "$url" || { - log_fail "Failed to download $filename" - return 1 - } - log_info "Extracting $filename..." - tar -xvf "$filename" || { - log_fail "Failed to extract $filename" - return 1 - } elif [ "$status" -eq 2 ]; then log_info "File exists and is valid, but not yet extracted. Proceeding to extract." - tar -xvf "$filename" || { - log_fail "Failed to extract $filename" - return 1 - } + else + case "$url" in + /*|file://*) + if [ ! -f "$tarfile" ]; then log_fail "Local tar file not found: $tarfile"; return 1; fi + ;; + *) + if [ -n "$limited_net" ]; then + log_warn "Limited network; cannot fetch media bundle. Will SKIP decode cases." + return 2 + fi + log_info "Downloading $url -> $tarfile" + if ! try_download "$url" "$tarfile"; then + rc=$? + if [ $rc -eq 60 ]; then + log_warn "TLS/handshake problem while downloading (cert/clock/firewall). Treating as limited network." + return 2 + fi + log_fail "Failed to download $(basename "$url")" + return 1 + fi + ;; + esac fi - - # Optionally, check that extraction succeeded - first_entry=$(tar -tf "$filename" 2>/dev/null | head -n1 | cut -d/ -f1) - if [ -n "$first_entry" ] && [ -e "$first_entry" ]; then - log_pass "Files extracted successfully ($first_entry exists)." + + log_info "Extracting $(basename "$tarfile")..." + if tar -xvf "$tarfile"; then + # If we got here, assume success; write marker for future quick skips. + : > "$markfile" 2>/dev/null || true + + # Best-effort sanity: verify at least one entry now exists. + first_entry=$(tar -tf "$tarfile" 2>/dev/null | head -n1 | sed 's#/$##') + if [ -n "$first_entry" ] && { [ -e "$first_entry" ] || [ -e "$outdir/$first_entry" ]; }; then + log_pass "Files extracted successfully ($(basename "$first_entry") present)." + return 0 + fi + log_warn "Extraction finished but couldn't verify entries. Assuming success." return 0 - else - log_fail "Extraction did not create expected entry: $first_entry" - return 1 fi + + log_fail "Failed to extract $(basename "$tarfile")" + return 1 } # Function to check if a tar file exists check_tar_file() { - url=$1 - filename=$(basename "$url") - - # 1. Check file exists - if [ ! -f "$filename" ]; then - log_error "File $filename does not exist." + url="$1" + outdir="${LOG_DIR:-.}" + mkdir -p "$outdir" 2>/dev/null || true + + case "$url" in + /*) tarfile="$url" ;; + file://*) tarfile="${url#file://}" ;; + *) tarfile="$outdir/$(basename "$url")" ;; + esac + markfile="${tarfile}.extracted" + + # 1) Existence & basic validity + if [ ! -f "$tarfile" ]; then + log_info "File $(basename "$tarfile") does not exist in $outdir." return 1 fi - - # 2. Check file is non-empty - if [ ! -s "$filename" ]; then - log_error "File $filename exists but is empty." + if [ ! -s "$tarfile" ]; then + log_warn "File $(basename "$tarfile") exists but is empty." return 1 fi - - # 3. Check file is a valid tar archive - if ! tar -tf "$filename" >/dev/null 2>&1; then - log_error "File $filename is not a valid tar archive." + if ! tar -tf "$tarfile" >/dev/null 2>&1; then + log_warn "File $(basename "$tarfile") is not a valid tar archive." return 1 fi - - # 4. Check if already extracted: does the first entry in the tar exist? - first_entry=$(tar -tf "$filename" 2>/dev/null | head -n1 | cut -d/ -f1) - if [ -n "$first_entry" ] && [ -e "$first_entry" ]; then - log_pass "$filename has already been extracted ($first_entry exists)." + + # 2) Already extracted? (marker first) + if [ -f "$markfile" ]; then + log_pass "$(basename "$tarfile") has already been extracted (marker present)." return 0 fi - - log_info "$filename exists and is valid, but not yet extracted." + + # 3) Heuristic: check multiple entries from the tar exist on disk + tmp_list="${outdir}/.tar_ls.$$" + if tar -tf "$tarfile" 2>/dev/null | head -n 20 >"$tmp_list"; then + total=0; present=0 + while IFS= read -r ent; do + [ -z "$ent" ] && continue + total=$((total + 1)) + ent="${ent%/}" + # check exact relative path and also basename (covers archives with a top-level dir) + if [ -e "$outdir/$ent" ] || [ -e "$outdir/$(basename "$ent")" ]; then + present=$((present + 1)) + fi + done < "$tmp_list" + rm -f "$tmp_list" 2>/dev/null || true + + # If we find a reasonable portion of entries, assume it's extracted + if [ "$present" -ge 3 ] || { [ "$total" -gt 0 ] && [ $((present * 100 / total)) -ge 50 ]; }; then + log_pass "$(basename "$tarfile") already extracted ($present/$total entries found)." + return 0 + fi + fi + + # 4) Exists and valid, but not yet extracted + log_info "$(basename "$tarfile") exists and is valid, but not yet extracted." return 2 } @@ -703,25 +706,14 @@ get_ip_address() { # Run a command with a timeout (in seconds) run_with_timeout() { timeout="$1"; shift - [ -z "$timeout" ] && { "$@"; return $?; } - - if command -v timeout >/dev/null 2>&1; then - timeout "$timeout" "$@" - return $? - fi - - # fallback if coreutils timeout is missing - ( - "$@" & - pid=$! - ( sleep "$timeout"; kill "$pid" 2>/dev/null ) & - watcher=$! - wait $pid 2>/dev/null - status=$? - kill $watcher 2>/dev/null - exit $status - ) - return $? + ( "$@" ) & + pid=$! + ( sleep "$timeout"; kill "$pid" 2>/dev/null ) & + watcher=$! + wait $pid 2>/dev/null + status=$? + kill $watcher 2>/dev/null + return $status } # DHCP client logic (dhclient and udhcpc with timeouts) @@ -1332,45 +1324,41 @@ start_remoteproc() { printf 'start\n' >"$statef" 2>/dev/null || return 1 wait_remoteproc_state "$path" running 6 } - -# Returns 0 if every rproc using $1 firmware is running; non-zero otherwise. +# Validate remoteproc running state with retries and logging validate_remoteproc_running() { - fw="$1" - - # Fast skip: if DT doesn't advertise this firmware, there may be nothing to validate - if command -v dt_has_remoteproc_fw >/dev/null 2>&1; then - if ! dt_has_remoteproc_fw "$fw"; then - log_skip "DT does not list '$fw' remoteproc; skipping rproc state check" - return 0 - fi - fi - - # functestlib helper prints "path|state|firmware|name" (one line per instance) - entries="$(get_remoteproc_by_firmware "$fw" "" all 2>/dev/null)" || entries="" - - if [ -z "$entries" ]; then - log_fail "No /sys/class/remoteproc entry found for firmware '$fw'" + fw_name="$1" + log_file="${2:-/dev/null}" + max_wait_secs="${3:-10}" + delay_per_try_secs="${4:-1}" + + rproc_path=$(get_remoteproc_path_by_firmware "$fw_name") + if [ -z "$rproc_path" ]; then + echo "[ERROR] Remoteproc for '$fw_name' not found" >> "$log_file" + { + echo "---- Last 20 remoteproc dmesg logs ----" + dmesg | grep -i "remoteproc" | tail -n 20 + echo "----------------------------------------" + } >> "$log_file" return 1 fi - - fail=0 - while IFS='|' read -r rpath rstate rfirm rname; do - [ -n "$rpath" ] || continue - inst="$(basename "$rpath")" - log_info "remoteproc entry: path=$rpath state=$rstate firmware=$rfirm name=$rname" - if [ "$rstate" = "running" ]; then - log_pass "$inst: running" - else - # If you prefer to re-query, uncomment next line: - # rstate="$(get_remoteproc_state "$rpath" 2>/dev/null || echo "$rstate")" - log_fail "$inst: not running (state=$rstate)" - fail=$((fail + 1)) + + total_waited=0 + while [ "$total_waited" -lt "$max_wait_secs" ]; do + state=$(get_remoteproc_state "$rproc_path") + if [ "$state" = "running" ]; then + return 0 fi - done <<__RPROC_LIST__ -$entries -__RPROC_LIST__ - - [ "$fail" -eq 0 ] + sleep "$delay_per_try_secs" + total_waited=$((total_waited + delay_per_try_secs)) + done + + echo "[ERROR] $fw_name remoteproc did not reach 'running' state within ${max_wait_secs}s (last state: $state)" >> "$log_file" + { + echo "---- Last 20 remoteproc dmesg logs ----" + dmesg | grep -i "remoteproc" | tail -n 20 + echo "----------------------------------------" + } >> "$log_file" + return 1 } # acquire_test_lock @@ -1935,7 +1923,7 @@ wait_for_path() { [ -e "$_p" ] && return 0 sleep 1 i=$((i+1)) - done + done return 1 } @@ -2131,41 +2119,24 @@ $val" ;; # 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" - - # Keep arg2 for API compatibility: pads use MBUS formats here; the - # requested video pixfmt is applied later by execute_capture_block(). - # Mark as intentionally read to silence ShellCheck SC2034. - # shellcheck disable=SC2034 - USER_FORMAT="${2:-}" - : "${USER_FORMAT:-}" # intentional no-op read - - # Clean slate, like your manual 'reset' - log_info "[CMD] media-ctl -d \"$MEDIA_NODE\" --reset" - media-ctl -d "$MEDIA_NODE" --reset - - # 1) Apply pad formats exactly as parsed. If a line fails with _1X10, - # retry once with the short token (SRGGB10) — some trees prefer it. - printf '%s\n' "$MEDIA_CTL_V_LIST" | while IFS= read -r vline; do - [ -n "$vline" ] || continue - log_info "[CMD] media-ctl -d \"$MEDIA_NODE\" -V \"$vline\"" - if ! media-ctl -d "$MEDIA_NODE" -V "$vline"; then - # fallback: *_1X10 → SRGGB10 for Bayer 10; extend if needed later - vline_fallback="$(printf '%s' "$vline" | sed -E 's/fmt:SRGGB10_1X10\//fmt:SRGGB10\//g')" - if [ "$vline_fallback" != "$vline" ]; then - log_warn " -V failed, retrying with: $vline_fallback" - log_info "[CMD] media-ctl -d \"$MEDIA_NODE\" -V \"$vline_fallback\"" - media-ctl -d "$MEDIA_NODE" -V "$vline_fallback" || true - fi + 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 - - # 2) Apply links exactly as parsed (same order as your manual sequence). - printf '%s\n' "$MEDIA_CTL_L_LIST" | while IFS= read -r lline; do - [ -n "$lline" ] || continue - log_info "[CMD] media-ctl -d \"$MEDIA_NODE\" -l \"$lline\"" - media-ctl -d "$MEDIA_NODE" -l "$lline" + + # 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 } @@ -2217,272 +2188,60 @@ execute_capture_block() { done } -# ---- Wayland/Weston helpers ----------------------- -# Ensure a private XDG runtime directory exists and is usable (0700). -ensure_private_runtime_dir() { - d="$1" - [ -n "$d" ] || return 1 - [ -d "$d" ] || mkdir -p "$d" 2>/dev/null || return 1 - chmod 0700 "$d" 2>/dev/null || return 1 - : >"$d/.xdg-test" 2>/dev/null || return 1 - rm -f "$d/.xdg-test" 2>/dev/null - return 0 -} - -# Return first Wayland socket under a base dir (prints path or fails). -find_wayland_socket_in() { - base="$1" - [ -d "$base" ] || return 1 - for s in "$base"/wayland-*; do - [ -S "$s" ] || continue - printf '%s\n' "$s" - return 0 - done - return 1 -} - -# Best-effort discovery of a usable Wayland socket anywhere. -discover_wayland_socket_anywhere() { - uid="$(id -u 2>/dev/null || echo 0)" - bases="" - [ -n "$XDG_RUNTIME_DIR" ] && bases="$bases $XDG_RUNTIME_DIR" - bases="$bases /dev/socket/weston /run/user/$uid /tmp/wayland-$uid /dev/shm" - for b in $bases; do - ensure_private_runtime_dir "$b" >/dev/null 2>&1 || true - if s="$(find_wayland_socket_in "$b")"; then - printf '%s\n' "$s" - return 0 - fi - done - return 1 -} - -# Adopt env from a Wayland socket path like /run/user/0/wayland-0 -# Sets XDG_RUNTIME_DIR and WAYLAND_DISPLAY. Returns 0 on success. -adopt_wayland_env_from_socket() { - sock="$1" - [ -n "$sock" ] || { log_warn "adopt_wayland_env_from_socket: no socket path given"; return 1; } - [ -S "$sock" ] || { log_warn "adopt_wayland_env_from_socket: not a socket: $sock"; return 1; } - - # Derive components without external dirname/basename - XDG_RUNTIME_DIR=${sock%/*} - WAYLAND_DISPLAY=${sock##*/} +# --- Ensure rootfs has minimum size (defaults to 2GiB) ----------------------- +# Usage: ensure_rootfs_min_size [min_gib] +# - Checks / size (df -P /) in KiB. If < min, runs resize2fs on the rootfs device. +# - Logs to $LOG_DIR/resize2fs.log if LOG_DIR is set, else /tmp/resize2fs.log. +ensure_rootfs_min_size() { + min_gib="${1:-2}" + min_kb=$((min_gib*1024*1024)) - export XDG_RUNTIME_DIR - export WAYLAND_DISPLAY + total_kb="$(df -P / 2>/dev/null | awk 'NR==2{print $2}')" + [ -n "$total_kb" ] || { log_warn "df check failed; skipping resize."; return 0; } - log_info "Selected Wayland socket: $sock" - log_info "Wayland env: XDG_RUNTIME_DIR=$XDG_RUNTIME_DIR WAYLAND_DISPLAY=$WAYLAND_DISPLAY" - return 0 -} - -# Try to connect to Wayland. Returns 0 on OK. -wayland_can_connect() { - if command -v weston-info >/dev/null 2>&1; then - weston-info >/dev/null 2>&1 - return $? - fi - # fallback: quick client probe - ( env -i XDG_RUNTIME_DIR="$XDG_RUNTIME_DIR" WAYLAND_DISPLAY="$WAYLAND_DISPLAY" true ) >/dev/null 2>&1 - return $? -} - -# Ensure a Weston socket exists; if not, stop+start Weston and adopt helper socket. -weston_pick_env_or_start() { - sock="$(discover_wayland_socket_anywhere 2>/dev/null || true)" - if [ -n "$sock" ]; then - adopt_wayland_env_from_socket "$sock" - log_info "Selected Wayland socket: $sock" + if [ "$total_kb" -ge "$min_kb" ] 2>/dev/null; then + log_info "Rootfs size OK (>=${min_gib}GiB)." return 0 fi - - if weston_is_running; then - log_info "Stopping Weston..." - weston_stop - i=0; while weston_is_running && [ "$i" -lt 5 ]; do i=$((i+1)); sleep 1; done - fi - - log_info "Starting Weston..." - weston_start - i=0; sock="" - while [ "$i" -lt 6 ]; do - sock="$(find_wayland_socket_in /dev/socket/weston 2>/dev/null || true)" - [ -n "$sock" ] && break - sleep 1; i=$((i+1)) - done - if [ -z "$sock" ]; then - log_fail "Could not find Wayland socket after starting Weston." - return 1 + + # Pick root device: prefer by-partlabel/rootfs, else actual source of / + root_dev="/dev/disk/by-partlabel/rootfs" + if [ ! -e "$root_dev" ]; then + if command -v findmnt >/dev/null 2>&1; then + root_dev="$(findmnt -n -o SOURCE / 2>/dev/null | head -n1)" + else + root_dev="$(awk '$2=="/"{print $1; exit}' /proc/mounts 2>/dev/null)" + fi fi - adopt_wayland_env_from_socket "$sock" - log_info "Weston started; socket: $sock" - return 0 -} - -# Find candidate Wayland sockets in common locations. -# Prints absolute socket paths, one per line, most-preferred first. -find_wayland_sockets() { - uid="$(id -u 2>/dev/null || echo 0)" - - # Start with $XDG_RUNTIME_DIR if set. - if [ -n "$XDG_RUNTIME_DIR" ] && [ -d "$XDG_RUNTIME_DIR" ]; then - if [ -e "$XDG_RUNTIME_DIR/wayland-1" ]; then - printf '%s\n' "$XDG_RUNTIME_DIR/wayland-1" - fi - if [ -e "$XDG_RUNTIME_DIR/wayland-0" ]; then - printf '%s\n' "$XDG_RUNTIME_DIR/wayland-0" - fi - fi - - # Qualcomm/Yocto common path. - if [ -e /dev/socket/weston/wayland-1 ]; then - printf '%s\n' /dev/socket/weston/wayland-1 - fi - if [ -e /dev/socket/weston/wayland-0 ]; then - printf '%s\n' /dev/socket/weston/wayland-0 - fi - - # XDG spec default per-user location. - if [ -e "/run/user/$uid/wayland-1" ]; then - printf '%s\n' "/run/user/$uid/wayland-1" - fi - if [ -e "/run/user/$uid/wayland-0" ]; then - printf '%s\n' "/run/user/$uid/wayland-0" - fi -} - -# Ensure XDG_RUNTIME_DIR has owner=current-user and mode 0700. -# Returns 0 if OK (or fixed), non-zero if still not compliant. -ensure_wayland_runtime_dir_perms() { - dir="$1" - [ -n "$dir" ] && [ -d "$dir" ] || return 1 - - cur_uid="$(id -u 2>/dev/null || echo 0)" - cur_gid="$(id -g 2>/dev/null || echo 0)" - - # Best-effort fixups first (don’t error if chown/chmod fail) - chown "$cur_uid:$cur_gid" "$dir" 2>/dev/null || true - chmod 0700 "$dir" 2>/dev/null || true - - # Verify using stat (GNU first, then BSD). If stat is unavailable, - # we can’t verify—assume OK to avoid SC2012 (ls) usage. - if command -v stat >/dev/null 2>&1; then - # Mode: GNU: %a ; BSD: %Lp - mode="$(stat -c '%a' "$dir" 2>/dev/null || stat -f '%Lp' "$dir" 2>/dev/null || echo '')" - # Owner uid: GNU: %u ; BSD: %u - uid="$(stat -c '%u' "$dir" 2>/dev/null || stat -f '%u' "$dir" 2>/dev/null || echo '')" - - [ "$mode" = "700" ] && [ "$uid" = "$cur_uid" ] && return 0 - return 1 - fi - # No stat available: directory exists and we attempted to fix perms/owner. - # Treat as success so clients can try; avoids SC2012 warnings. - return 0 -} - -# Quick Wayland handshake check. -# Prefers `wayland-info` with a short timeout; otherwise validates socket presence. -# Also enforces/fixes XDG_RUNTIME_DIR permissions so clients won’t reject it. -wayland_connection_ok() { - if [ -n "$XDG_RUNTIME_DIR" ]; then - ensure_wayland_runtime_dir_perms "$XDG_RUNTIME_DIR" || return 1 - fi + if [ -z "$root_dev" ] || [ ! -e "$root_dev" ]; then + log_warn "Could not determine root device; skipping resize." + return 0 + fi - if command -v wayland-info >/dev/null 2>&1; then - if command -v timeout >/dev/null 2>&1; then - timeout 3s wayland-info >/dev/null 2>&1 - return $? + # Optional: only run on ext* filesystems (resize2fs) + if command -v blkid >/dev/null 2>&1; then + fstype="$(blkid -o value -s TYPE "$root_dev" 2>/dev/null | head -n1)" + case "$fstype" in + ext2|ext3|ext4) : ;; + *) log_warn "Rootfs type '$fstype' not ext*, skipping resize."; return 0 ;; + esac fi - wayland-info >/dev/null 2>&1 - return $? - fi - # Fallback: env variables and socket must exist. - if [ -n "$XDG_RUNTIME_DIR" ] && [ -n "$WAYLAND_DISPLAY" ] && [ -e "$XDG_RUNTIME_DIR/$WAYLAND_DISPLAY" ]; then - return 0 - fi - return 1 -} - -# Print concise metadata for a path (portable). -# Prefers stat(1) (GNU or BSD); falls back to ls(1) only if needed. -# Usage: print_path_meta "/some/path" -print_path_meta() { - p=$1 - if [ -z "$p" ]; then - return 1 - fi - # GNU stat - if stat -c '%A %U %G %a %n' "$p" >/dev/null 2>&1; then - stat -c '%A %U %G %a %n' "$p" - return 0 - fi - # BSD/Mac stat - if stat -f '%Sp %Su %Sg %OLp %N' "$p" >/dev/null 2>&1; then - stat -f '%Sp %Su %Sg %OLp %N' "$p" - return 0 - fi - # shellcheck disable=SC2012 - ls -ld -- "$p" 2>/dev/null -} - -soc_id() { - if [ -r /sys/devices/soc0/soc_id ]; then - cat /sys/devices/soc0/soc_id 2>/dev/null - elif [ -r /proc/device-tree/compatible ]; then - tr -d '\0' /dev/null | head -n 1 + log_dir="${LOG_DIR:-/tmp}" + mkdir -p "$log_dir" 2>/dev/null || true + if command -v resize2fs >/dev/null 2>&1; then + log_info "Rootfs <${min_gib}GiB ($(awk '{print int($1/1024)}' <>"$log_dir/resize2fs.log" 2>&1; then + log_pass "resize2fs completed on $root_dev (see $log_dir/resize2fs.log)." + else + log_warn "resize2fs failed on $root_dev (see $log_dir/resize2fs.log)." + fi else - uname -r + log_warn "resize2fs not available; skipping resize." fi -} - -# --- BusyBox-safe helper: convert "15s" -> 15, pass-through if already integer --- -timeout_to_seconds() { - case "$1" in *s) echo "${1%s}";; *) echo "$1";; esac -} - -# --- POSIX timeout without GNU coreutils --- -sh_timeout() { - dur="$1"; shift - [ "$1" = "--" ] && shift - case "$dur" in *s) sec=${dur%s} ;; *) sec=$dur ;; esac - [ -z "$sec" ] && sec=0 - - "$@" & - pid=$! - - t=0 - while kill -0 "$pid" 2>/dev/null; do - if [ "$t" -ge "$sec" ] 2>/dev/null; then - kill "$pid" 2>/dev/null || true - sleep 1 - kill -9 "$pid" 2>/dev/null || true - wait "$pid" 2>/dev/null - return 124 - fi - sleep 1 - t=$((t+1)) - done - wait "$pid"; return $? -} - -# Map test duration keywords to seconds -duration_to_secs() { - case "$1" in - short) echo 5 ;; - medium) echo 15 ;; - long) echo 30 ;; - *) echo 5 ;; - esac -} - -log_soc_info() { - m=""; s=""; pv="" - [ -r /sys/devices/soc0/machine ] && m="$(cat /sys/devices/soc0/machine 2>/dev/null)" - [ -r /sys/devices/soc0/soc_id ] && s="$(cat /sys/devices/soc0/soc_id 2>/dev/null)" - [ -r /sys/devices/soc0/platform_version ] && pv="$(cat /sys/devices/soc0/platform_version 2>/dev/null)" - [ -n "$m" ] && log_info "SoC.machine: $m" - [ -n "$s" ] && log_info "SoC.soc_id: $s" - [ -n "$pv" ] && log_info "SoC.platform_version: $pv" -} + return 0 +} \ No newline at end of file diff --git a/Runner/utils/lib_video.sh b/Runner/utils/lib_video.sh new file mode 100755 index 00000000..fab11351 --- /dev/null +++ b/Runner/utils/lib_video.sh @@ -0,0 +1,1257 @@ +#!/bin/sh +# Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. +# SPDX-License-Identifier: BSD-3-Clause-Clear +# Common, POSIX-compliant helpers for Qualcomm video stack selection and V4L2 testing. +# Requires functestlib.sh: log_info/log_warn/log_pass/log_fail/log_skip, +# check_dependencies, extract_tar_from_url, (optional) run_with_timeout, ensure_network_online. + +# ----------------------------------------------------------------------------- +# Public env knobs (may be exported by caller or set via CLI in run.sh) +# ----------------------------------------------------------------------------- +# VIDEO_STACK auto|upstream|downstream|base|overlay|up|down (default: auto) +# VIDEO_PLATFORM lemans|monaco|kodiak|"" (auto-detect) +# TAR_URL bundle for input clips (used by video_ensure_clips_present_or_fetch) +# VIDEO_APP path to iris_v4l2_test (default /usr/bin/iris_v4l2_test) + +# ----------------------------------------------------------------------------- +# Constants / tool paths +# ----------------------------------------------------------------------------- +IRIS_UP_MOD="qcom_iris" +IRIS_VPU_MOD="iris_vpu" +VENUS_CORE_MOD="venus_core" +VENUS_DEC_MOD="venus_dec" +VENUS_ENC_MOD="venus_enc" + +# We purposely avoid persistent blacklist here (no /etc changes). +# Session-only blocks live under /run/modprobe.d +RUNTIME_BLOCK_DIR="/run/modprobe.d" + +# Firmware path for Kodiak downstream blob +FW_PATH_KODIAK="/lib/firmware/qcom/vpu/vpu20_p1_gen2.mbn" +: "${FW_BACKUP_DIR:=/opt}" + +MODPROBE="$(command -v modprobe 2>/dev/null || printf '%s' /sbin/modprobe)" +LSMOD="$(command -v lsmod 2>/dev/null || printf '%s' /sbin/lsmod)" + +# Default app path (caller may override via env) +VIDEO_APP="${VIDEO_APP:-/usr/bin/iris_v4l2_test}" + +# ----------------------------------------------------------------------------- +# Tiny utils +# ----------------------------------------------------------------------------- +video_exist_cmd() { command -v "$1" >/dev/null 2>&1; } + +video_warn_if_not_root() { + uid="$(id -u 2>/dev/null || printf '%s' 1)" + if [ "$uid" -ne 0 ] 2>/dev/null; then + log_warn "Not running as root; module/blacklist operations may fail." + fi +} + +video_has_module_loaded() { + "$LSMOD" 2>/dev/null | awk '{print $1}' | grep -q "^$1$" +} + +video_devices_present() { + set -- /dev/video* 2>/dev/null + [ -e "$1" ] +} + +video_step() { + id="$1"; msg="$2" + if [ -n "$id" ]; then log_info "[$id] STEP: $msg"; else log_info "STEP: $msg"; fi +} + +# ----------------------------------------------------------------------------- +# Optional: log firmware hint after reload +# ----------------------------------------------------------------------------- +video_log_fw_hint() { + if video_exist_cmd dmesg; then + out="$(dmesg 2>/dev/null | tail -n 200 | grep -Ei 'firmware|iris_vpu|venus' | tail -n 10)" + if [ -n "$out" ]; then + printf '%s\n' "$out" | while IFS= read -r ln; do log_info "dmesg: $ln"; done + fi + fi +} + +# ----------------------------------------------------------------------------- +# Shared module introspection helpers (kept; used by dump) +# ----------------------------------------------------------------------------- +video_insmod_with_deps() { + # Takes a logical module name (e.g. qcom_iris), resolves the path, then insmods deps+module + m="$1" + + # Resolve file path first (handles hyphen/underscore and updates/) + path="$(video_find_module_file "$m")" || path="" + if [ -z "$path" ] || [ ! -f "$path" ]; then + log_warn "insmod fallback: could not locate module file for $m" + return 1 + fi + + # Load dependencies from the FILE (not the modname), then the module itself + deps="" + if video_exist_cmd modinfo; then + deps="$(modinfo -F depends "$path" 2>/dev/null | tr ',' ' ' | tr -s ' ')" + fi + + for d in $deps; do + [ -z "$d" ] && continue + video_has_module_loaded "$d" && continue + # Try modprobe first (cheap), then insmod fallback by resolving its path + if ! "$MODPROBE" -q "$d" 2>/dev/null; then + dpath="$(video_find_module_file "$d")" || dpath="" + if [ -n "$dpath" ] && [ -f "$dpath" ]; then + insmod "$dpath" 2>/dev/null || true + fi + fi + done + + insmod "$path" 2>/dev/null && return 0 + log_warn "insmod failed for $path" + return 1 +} + +video_modprobe_dryrun() { + m="$1" + out=$("$MODPROBE" -n "$m" 2>/dev/null) + if [ -n "$out" ]; then + printf '%s\n' "$out" | while IFS= read -r ln; do log_info "modprobe -n $m: $ln"; done + else + log_info "modprobe -n $m:" + fi +} + +video_list_runtime_blocks() { + found=0 + for f in "$RUNTIME_BLOCK_DIR"/*.conf; do + [ -e "$f" ] || continue + if grep -Eiq '(^|[[:space:]])(blacklist|install)[[:space:]]+(qcom[-_]iris|iris[-_]vpu|venus[-_](core|dec|enc))' "$f" 2>/dev/null; then + log_info "$f" + found=1 + fi + done + [ "$found" -eq 0 ] && log_info "(none)" +} + +video_dump_stack_state() { + when="$1" # pre|post + log_info "Modules ($when):" + log_info "lsmod (iris/venus):" + "$LSMOD" 2>/dev/null | awk 'NR==1 || $1 ~ /^(iris_vpu|qcom_iris|venus_core|venus_dec|venus_enc)$/ {print}' + video_modprobe_dryrun qcom_iris + video_modprobe_dryrun iris_vpu + video_modprobe_dryrun venus_core + video_modprobe_dryrun venus_dec + video_modprobe_dryrun venus_enc + log_info "runtime blocks:" + video_list_runtime_blocks +} + +video_find_module_file() { + # Resolve a module file path for a logical mod name (handles _ vs -). + # Prefers: modinfo -n, then .../updates/, then general search. + m="$1" + kr="$(uname -r 2>/dev/null)" + if [ -z "$kr" ]; then + kr="$(find /lib/modules -mindepth 1 -maxdepth 1 -type d -printf '%f\n' 2>/dev/null | head -n1)" + fi + + # Try modinfo database first + if video_exist_cmd modinfo; then + p="$(modinfo -n "$m" 2>/dev/null)" + if [ -n "$p" ] && [ -f "$p" ]; then + printf '%s\n' "$p" + return 0 + fi + fi + + # Fall back to name variants + m_us="$m" + m_hy="$(printf '%s' "$m" | tr '_' '-')" + m_alt="$(printf '%s' "$m" | tr '-' '_')" + + # Prefer updates directory if present + for pat in "$m_us" "$m_hy" "$m_alt"; do + p="$(find "/lib/modules/$kr/updates" -type f -name "${pat}.ko*" 2>/dev/null | head -n 1)" + [ -n "$p" ] && { printf '%s\n' "$p"; return 0; } + done + + # General search under this kernel’s tree + for pat in "$m_us" "$m_hy" "$m_alt"; do + p="$(find "/lib/modules/$kr" -type f -name "${pat}.ko*" 2>/dev/null | head -n 1)" + [ -n "$p" ] && { printf '%s\n' "$p"; return 0; } + done + + # Very last-ditch: fuzzy search for *iris* matches to help logs + p="$(find "/lib/modules/$kr" -type f -name '*iris*.ko*' 2>/dev/null | head -n 1)" + [ -n "$p" ] && { printf '%s\n' "$p"; return 0; } + + return 1 +} + +# Back-compat aliases so existing callers keep working +modprobe_dryrun() { video_modprobe_dryrun "$@"; } +list_runtime_blocks() { video_list_runtime_blocks "$@"; } +dump_stack_state() { video_dump_stack_state "$@"; } + +# ----------------------------------------------------------------------------- +# Runtime-only (session) block/unblock: lives under /run/modprobe.d +# ----------------------------------------------------------------------------- +video_block_mod_now() { + # usage: video_block_mod_now qcom_iris [iris_vpu ...] + mkdir -p "$RUNTIME_BLOCK_DIR" 2>/dev/null || true + for m in "$@"; do + printf 'install %s /bin/false\n' "$m" > "$RUNTIME_BLOCK_DIR/${m}-block.conf" + done + depmod -a 2>/dev/null || true +} + +video_unblock_mod_now() { + # usage: video_unblock_mod_now qcom_iris [iris_vpu ...] + for m in "$@"; do rm -f "$RUNTIME_BLOCK_DIR/${m}-block.conf"; done + depmod -a 2>/dev/null || true +} + +# ----------------------------------------------------------------------------- +# Persistent de-blacklist for a module (handles qcom_iris ↔ qcom-iris) +# Removes: +# - "blacklist " lines +# - "install /bin/false" lines +# in /etc/modprobe.d and /lib/modprobe.d, for both _ and - spellings. +# Also clears runtime /run/modprobe.d/-block.conf files. +# ----------------------------------------------------------------------------- +video_persistent_unblock_module() { + m="$1" + [ -n "$m" ] || return 0 + + m_us="$(printf '%s' "$m" | tr '-' '_')" + m_hy="$(printf '%s' "$m" | tr '_' '-')" + + # Clear runtime install-blocks (best effort) + if command -v video_unblock_mod_now >/dev/null 2>&1; then + video_unblock_mod_now "$m_us" "$m_hy" + else + rm -f "/run/modprobe.d/${m_us}-block.conf" "/run/modprobe.d/${m_hy}-block.conf" 2>/dev/null || true + fi + + # Scrub persistent blacklists and install-/bin/false overrides + for d in /etc/modprobe.d /lib/modprobe.d; do + [ -d "$d" ] || continue + for f in "$d"/*.conf; do + [ -e "$f" ] || continue + tmp="$f.tmp.$$" + # Keep every line that does NOT match our blacklist/install patterns + awk -v a="$m_us" -v b="$m_hy" ' + BEGIN { IGNORECASE=1 } + { + line = $0 + bl1 = "^[[:space:]]*blacklist[[:space:]]*" a "([[:space:]]|$)" + bl2 = "^[[:space:]]*blacklist[[:space:]]*" b "([[:space:]]|$)" + in1 = "^[[:space:]]*install[[:space:]]*" a "([[:space:]]|$).*[/]bin[/]false" + in2 = "^[[:space:]]*install[[:space:]]*" b "([[:space:]]|$).*[/]bin[/]false" + if (line ~ bl1 || line ~ bl2 || line ~ in1 || line ~ in2) next + print line + } + ' "$f" >"$tmp" 2>/dev/null || { rm -f "$tmp" >/dev/null 2>&1; continue; } + mv "$tmp" "$f" + done + done + + # Clean up empty, module-specific blacklist files if any + for f in \ + "/etc/modprobe.d/blacklist-${m_us}.conf" \ + "/etc/modprobe.d/blacklist-${m_hy}.conf" + do + [ -e "$f" ] || continue + # Remove if file has no non-comment, non-blank lines + if ! grep -Eq '^[[:space:]]*[^#[:space:]]' "$f" 2>/dev/null; then + rm -f "$f" 2>/dev/null || true + fi + done + + # Refresh module dependency cache and udev rules + depmod -a 2>/dev/null || true + if command -v udevadm >/dev/null 2>&1; then + udevadm control --reload-rules 2>/dev/null || true + fi + + log_info "persistent unblock done for $m (and aliases: $m_us, $m_hy)" + return 0 +} + +# ----------------------------------------------------------------------------- +# modprobe with insmod fallback (standalone-like resilience) +# ----------------------------------------------------------------------------- +video_modprobe_or_insmod() { + m="$1" + + # 1) Try modprobe + "$MODPROBE" "$m" 2>/dev/null && return 0 + + # 2) If modprobe failed, scrub persistent blocks (blacklist + install /bin/false) and retry + log_warn "modprobe $m failed, attempting de-blacklist + retry" + video_persistent_unblock_module "$m" + "$MODPROBE" "$m" 2>/dev/null && return 0 + + # 3) As a final fallback, insmod the actual file (handles hyphen/underscore) + if video_insmod_with_deps "$m"; then + return 0 + fi + + # 4) One last nudge: try the hyphen/underscore alias through modprobe again + malt="$(printf '%s' "$m" | tr '_-' '-_')" + if [ "$malt" != "$m" ]; then + "$MODPROBE" "$malt" 2>/dev/null && return 0 + if video_insmod_with_deps "$malt"; then + return 0 + fi + fi + + log_warn "modprobe $m failed, and insmod fallback did not succeed" + return 1 +} + +# ----------------------------------------------------------------------------- +# Stack normalization & auto preference +# ----------------------------------------------------------------------------- +video_normalize_stack() { + s="$(printf '%s' "$1" | tr '[:upper:]' '[:lower:]')" + case "$s" in + upstream|base|up) printf '%s\n' "upstream" ;; + downstream|overlay|down) printf '%s\n' "downstream" ;; + auto|"") printf '%s\n' "auto" ;; + *) printf '%s\n' "$s" ;; + esac +} + +# ----------------------------------------------------------------------------- +# Platform detect → lemans|monaco|kodiak|unknown +# ----------------------------------------------------------------------------- +video_detect_platform() { + model=""; compat="" + if [ -r /proc/device-tree/model ]; then model=$(tr -d '\000' /dev/null); fi + if [ -r /proc/device-tree/compatible ]; then compat=$(tr -d '\000' /dev/null); fi + s=$(printf '%s\n%s\n' "$model" "$compat" | tr '[:upper:]' '[:lower:]') + + echo "$s" | grep -q "qcs9100" && { printf '%s\n' "lemans"; return 0; } + echo "$s" | grep -q "qcs8300" && { printf '%s\n' "monaco"; return 0; } + echo "$s" | grep -q "qcs6490" && { printf '%s\n' "kodiak"; return 0; } + echo "$s" | grep -q "ride-sx" && echo "$s" | grep -q "9100" && { printf '%s\n' "lemans"; return 0; } + echo "$s" | grep -q "ride-sx" && echo "$s" | grep -q "8300" && { printf '%s\n' "monaco"; return 0; } + echo "$s" | grep -q "rb3" && echo "$s" | grep -q "6490" && { printf '%s\n' "kodiak"; return 0; } + + printf '%s\n' "unknown" +} + +# ----------------------------------------------------------------------------- +# Validation helpers +# ----------------------------------------------------------------------------- +video_validate_upstream_loaded() { + plat="$1" + case "$plat" in + lemans|monaco) + video_has_module_loaded "$IRIS_UP_MOD" && video_has_module_loaded "$IRIS_VPU_MOD" + return $? + ;; + kodiak) + video_has_module_loaded "$VENUS_CORE_MOD" && video_has_module_loaded "$VENUS_DEC_MOD" && video_has_module_loaded "$VENUS_ENC_MOD" + return $? + ;; + *) return 1 ;; + esac +} + +video_validate_downstream_loaded() { + plat="$1" + case "$plat" in + # On lemans/monaco, downstream == iris_vpu present AND qcom_iris ABSENT + lemans|monaco) + if video_has_module_loaded "$IRIS_VPU_MOD" && ! video_has_module_loaded "$IRIS_UP_MOD"; then + return 0 + fi + return 1 + ;; + # On kodiak, downstream == iris_vpu present AND venus core ABSENT + kodiak) + if video_has_module_loaded "$IRIS_VPU_MOD" && ! video_has_module_loaded "$VENUS_CORE_MOD"; then + return 0 + fi + return 1 + ;; + *) return 1 ;; + esac +} + +# Verifies the requested stack after switching, per platform. +# Usage: video_assert_stack +# Returns 0 if OK; prints a clear reason and returns 1 otherwise. +video_assert_stack() { + plat="$1"; want="$(video_normalize_stack "$2")" + + case "$want" in + upstream|up|base) + if video_validate_upstream_loaded "$plat"; then + return 0 + fi + case "$plat" in + lemans|monaco) + log_fail "[STACK] Upstream requested but qcom_iris + iris_vpu are not both present." + ;; + kodiak) + log_fail "[STACK] Upstream requested but venus_core/dec/enc are not all present." + ;; + *) + log_fail "[STACK] Upstream requested but platform '$plat' is unknown." + ;; + esac + return 1 + ;; + downstream|overlay|down) + if video_validate_downstream_loaded "$plat"; then + return 0 + fi + case "$plat" in + lemans|monaco) + log_fail "[STACK] Downstream requested but iris_vpu not present or qcom_iris still loaded." + ;; + kodiak) + log_fail "[STACK] Downstream requested but iris_vpu not present or venus_core still loaded." + ;; + *) + log_fail "[STACK] Downstream requested but platform '$plat' is unknown." + ;; + esac + return 1 + ;; + *) + log_fail "[STACK] Unknown target stack '$want'." + return 1 + ;; + esac +} + +video_stack_status() { + plat="$1" + if video_validate_downstream_loaded "$plat"; then + printf '%s\n' "downstream" + elif video_validate_upstream_loaded "$plat"; then + printf '%s\n' "upstream" + else + printf '%s\n' "unknown" + fi +} + +# ----------------------------------------------------------------------------- +# Unload all video modules relevant to platform (standalone-like) +# ----------------------------------------------------------------------------- +video_unload_all_video_modules() { + plat="$1" + + tryrmmod() { + mod="$1" + if video_has_module_loaded "$mod"; then + if "$MODPROBE" -r "$mod" 2>/dev/null; then + log_info "Removed module: $mod" + else + log_warn "Could not remove $mod via modprobe -r; retrying after short delay" + sleep 0.2 + if "$MODPROBE" -r "$mod" 2>/dev/null; then + log_info "Removed module after retry: $mod" + else + log_warn "Still could not remove: $mod" + fi + fi + fi + } + + case "$plat" in + lemans|monaco) + # Upstream first, then vpu (and retry upstream once vpu is gone) + tryrmmod "$IRIS_UP_MOD" + tryrmmod "$IRIS_VPU_MOD" + tryrmmod "$IRIS_UP_MOD" + ;; + kodiak) + # Upstream (venus*) and downstream (iris_vpu) — remove all + tryrmmod "$VENUS_ENC_MOD" + tryrmmod "$VENUS_DEC_MOD" + tryrmmod "$VENUS_CORE_MOD" + tryrmmod "$IRIS_VPU_MOD" + ;; + *) : ;; + esac +} + +# ----------------------------------------------------------------------------- +# Hot switch (best-effort, no reboot) — mirrors standalone flow +# ----------------------------------------------------------------------------- +video_hot_switch_modules() { + plat="$1"; stack="$2"; rc=0 + + case "$plat" in + lemans|monaco) + if [ "$stack" = "downstream" ]; then + # Block upstream; allow downstream + video_block_upstream_strict + video_unblock_mod_now "$IRIS_VPU_MOD" + + # Clean slate + video_unload_all_video_modules "$plat" + + # Load only iris_vpu + if ! video_modprobe_or_insmod "$IRIS_VPU_MOD"; then + rc=1 + fi + + # Final guard: upstream must not be loaded + if video_has_module_loaded "$IRIS_UP_MOD"; then + log_warn "$IRIS_UP_MOD reloaded unexpectedly; unloading and keeping block in place" + "$MODPROBE" -r "$IRIS_UP_MOD" 2>/dev/null || rc=1 + fi + + else # upstream + # Unblock both sides + video_unblock_mod_now "$IRIS_UP_MOD" "$IRIS_VPU_MOD" + + # Clean slate + video_unload_all_video_modules "$plat" + + # Load upstream core first, then vpu + if ! video_modprobe_or_insmod "$IRIS_UP_MOD"; then + log_warn "modprobe $IRIS_UP_MOD failed; printing current runtime blocks & retrying" + video_list_runtime_blocks + video_unblock_mod_now "$IRIS_UP_MOD" + video_modprobe_or_insmod "$IRIS_UP_MOD" || rc=1 + fi + video_modprobe_or_insmod "$IRIS_VPU_MOD" || true + fi + ;; + + kodiak) + if [ "$stack" = "downstream" ]; then + # Block upstream venus; allow downstream + video_block_mod_now "$VENUS_CORE_MOD" "$VENUS_DEC_MOD" "$VENUS_ENC_MOD" + video_unblock_mod_now "$IRIS_VPU_MOD" + + # Unload everything + "$MODPROBE" -r "$IRIS_VPU_MOD" 2>/dev/null || true + "$MODPROBE" -r "$VENUS_ENC_MOD" 2>/dev/null || true + "$MODPROBE" -r "$VENUS_DEC_MOD" 2>/dev/null || true + "$MODPROBE" -r "$VENUS_CORE_MOD" 2>/dev/null || true + + # Load downstream + if ! video_modprobe_or_insmod "$IRIS_VPU_MOD"; then + log_warn "Kodiak: failed to load $IRIS_VPU_MOD" + rc=1 + fi + + # Automatic FW swap + live reload (does real work only if VIDEO_FW_DS is set & file exists) + log_info "Kodiak: invoking firmware swap/reload helper (if VIDEO_FW_DS provided)" + if ! video_kodiak_swap_and_reload "${VIDEO_FW_DS}"; then + log_warn "Kodiak: swap/reload helper reported failure (continuing)" + fi + + video_log_fw_hint + + else # upstream + video_unblock_mod_now "$VENUS_CORE_MOD" "$VENUS_DEC_MOD" "$VENUS_ENC_MOD" "$IRIS_VPU_MOD" + "$MODPROBE" -r "$IRIS_VPU_MOD" 2>/dev/null || true + video_modprobe_or_insmod "$VENUS_CORE_MOD" || rc=1 + video_modprobe_or_insmod "$VENUS_DEC_MOD" || true + video_modprobe_or_insmod "$VENUS_ENC_MOD" || true + video_log_fw_hint + fi + ;; + + *) rc=1 ;; + esac + return $rc +} + +# ----------------------------------------------------------------------------- +# Entry point: ensure desired stack +# ----------------------------------------------------------------------------- +video_ensure_stack() { + want_raw="$1" # upstream|downstream|auto|base|overlay|up|down + plat="$2" + + if [ -z "$plat" ]; then plat=$(video_detect_platform); fi + want="$(video_normalize_stack "$want_raw")" + + if [ "$want" = "auto" ]; then + pref="$(video_auto_preference_from_blacklist "$plat")" + if [ "$pref" != "unknown" ]; then + want="$pref" + else + cur="$(video_stack_status "$plat")" + if [ "$cur" != "unknown" ]; then want="$cur"; else want="upstream"; fi + fi + log_info "AUTO stack selection => $want" + fi + + # ----- Fast path only when it is safe to skip switching ----- + if [ "$want" = "upstream" ]; then + if video_validate_upstream_loaded "$plat"; then + printf '%s\n' "upstream" + return 0 + fi + else # downstream + if video_validate_downstream_loaded "$plat"; then + # + # IMPORTANT: On Kodiak, if a downstream FW override is provided, we still + # need to perform the firmware swap + live reload even if iris_vpu is already loaded. + # + if [ "$plat" = "kodiak" ] && [ -n "$VIDEO_FW_DS" ]; then + log_info "Kodiak: downstream already loaded, but FW override provided — performing FW swap + live reload." + # fall through to the hot-switch path below + else + printf '%s\n' "downstream" + return 0 + fi + fi + fi + + # Apply persistent blacklist for the requested stack (no reboot needed; runtime blocks handle the session) + video_apply_blacklist_for_stack "$plat" "$want" || return 1 + + # Do the hot switch (unload opposite side, install FW if needed, load target side, attempt live FW apply where applicable) + video_hot_switch_modules "$plat" "$want" || true + + # Ensure Kodiak FW swap even after a successful switch (defensive) + if [ "$plat" = "kodiak" ] && [ "$want" = "downstream" ] && [ -n "$VIDEO_FW_DS" ] && [ -f "$VIDEO_FW_DS" ]; then + if video_validate_downstream_loaded "$plat"; then + log_info "Kodiak: downstream active and FW override detected — ensuring FW swap + live reload." + video_kodiak_swap_and_reload "$VIDEO_FW_DS" || log_warn "Kodiak: post-switch FW swap/reload failed (continuing)" + fi + fi + + # Verify again + if [ "$want" = "upstream" ]; then + if video_validate_upstream_loaded "$plat"; then printf '%s\n' "upstream"; return 0; fi + else + if video_validate_downstream_loaded "$plat"; then printf '%s\n' "downstream"; return 0; fi + fi + + printf '%s\n' "unknown" + return 1 +} + +video_block_upstream_strict() { + # Prefer runtime block first + video_block_mod_now "$IRIS_UP_MOD" + + # If it keeps reappearing, persist a lightweight install rule + if [ -w /etc/modprobe.d ]; then + if ! grep -q "install[[:space:]]\+${IRIS_UP_MOD}[[:space:]]\+/bin/false" /etc/modprobe.d/qcom_iris-block.conf 2>/dev/null; then + printf 'install %s /bin/false\n' "$IRIS_UP_MOD" >> /etc/modprobe.d/qcom_iris-block.conf 2>/dev/null || true + fi + depmod -a 2>/dev/null || true + fi +} + +# ----------------------------------------------------------------------------- +# udev refresh + prune stale /dev/video* and /dev/media* nodes +# ----------------------------------------------------------------------------- +video_clean_and_refresh_v4l() { + # Try to have udev repopulate nodes for current drivers + if video_exist_cmd udevadm; then + log_info "udev trigger: video4linux/media" + udevadm trigger --subsystem-match=video4linux --action=change 2>/dev/null || true + udevadm trigger --subsystem-match=media --action=change 2>/dev/null || true + udevadm settle 2>/dev/null || true + fi + + # Prune /dev/video* nodes that have no sysfs backing + for n in /dev/video*; do + [ -e "$n" ] || continue + bn=$(basename "$n") + if [ ! -e "/sys/class/video4linux/$bn" ]; then + log_info "Pruning stale node: $n" + rm -f "$n" 2>/dev/null || true + fi + done + + # Prune /dev/media* nodes that have no sysfs backing + for n in /dev/media*; do + [ -e "$n" ] || continue + bn=$(basename "$n") + if [ ! -e "/sys/class/media/$bn" ]; then + log_info "Pruning stale node: $n" + rm -f "$n" 2>/dev/null || true + fi + done +} + +# ----------------------------------------------------------------------------- +# DMESG triage +# ----------------------------------------------------------------------------- +video_scan_dmesg_if_enabled() { + dm="$1"; logdir="$2" + if [ "$dm" -ne 1 ] 2>/dev/null; then return 2; fi + MODS='oom|memory|BUG|hung task|soft lockup|hard lockup|rcu|page allocation failure|I/O error' + EXCL='using dummy regulator|not found|EEXIST|probe deferred' + if scan_dmesg_errors "$logdir" "$MODS" "$EXCL"; then return 0; fi + return 1 +} + +# ----------------------------------------------------------------------------- +# JSON helpers (jq-free) — robust multi-key scan +# ----------------------------------------------------------------------------- +video_is_decode_cfg() { + cfg="$1" + b=$(basename "$cfg" | tr '[:upper:]' '[:lower:]') + case "$b" in *dec*.json) return 0 ;; *enc*.json) return 1 ;; esac + dom=$(sed -n 's/.*"Domain"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' "$cfg" 2>/dev/null | head -n 1) + dom_l=$(printf '%s' "$dom" | tr '[:upper:]' '[:lower:]') + case "$dom_l" in decoder|decode) return 0 ;; encoder|encode) return 1 ;; esac + return 0 +} + +video_extract_scalar() { k="$1"; cfg="$2"; sed -n "s/.*\"$k\"[[:space:]]*:[[:space:]]*\"\\([^\"\r\n]*\\)\".*/\\1/p" "$cfg"; } +video_extract_array() { k="$1"; cfg="$2"; sed -n "s/.*\"$k\"[[:space:]]*:\\s*\\[\\(.*\\)\\].*/\\1/p" "$cfg" | tr ',' '\n' | sed -n 's/.*"\([^"]*\)".*/\1/p'; } + +video_extract_array_ml() { + k="$1"; cfg="$2" + awk -v k="$k" ' + $0 ~ "\""k"\"[[:space:]]*:" {in=1} + in {print; if ($0 ~ /\]/) exit} + ' "$cfg" | sed -n 's/.*"\([^"]*\)".*/\1/p' | grep -vx "$k" +} + +video_strings_from_array_key() { + k="$1"; cfg="$2" + { + video_extract_array_ml "$k" "$cfg" + video_extract_array "$k" "$cfg" + } 2>/dev/null | sed '/^$/d' | awk '!seen[$0]++' +} + +video_extract_base_dirs() { + cfg="$1" + for k in InputDir InputDirectory InputFolder BasePath BaseDir; do + video_extract_scalar "$k" "$cfg" + done | sed '/^$/d' | head -n 1 +} + +video_guess_codec_from_cfg() { + cfg="$1" + for k in Codec codec CodecName codecName VideoCodec videoCodec DecoderName EncoderName Name name; do + v=$(video_extract_scalar "$k" "$cfg" | head -n 1) + if [ -n "$v" ]; then printf '%s\n' "$v"; return 0; fi + done + for tok in hevc h265 h264 av1 vp9 vp8 mpeg4 mpeg2 h263 avc; do + if grep -qiE "(^|[^A-Za-z0-9])${tok}([^A-Za-z0-9]|$)" "$cfg" 2>/dev/null; then printf '%s\n' "$tok"; return 0; fi + done + b=$(basename "$cfg" | tr '[:upper:]' '[:lower:]') + for tok in hevc h265 h264 av1 vp9 vp8 mpeg4 mpeg2 h263 avc; do case "$b" in *"$tok"*) printf '%s\n' "$tok"; return 0 ;; esac; done + printf '%s\n' "unknown" + return 0 +} + +video_canon_codec() { + c=$(printf '%s' "$1" | tr '[:upper:]' '[:lower:]') + case "$c" in + h265|hevc) printf '%s\n' "hevc" ;; + h264|avc) printf '%s\n' "h264" ;; + vp9) printf '%s\n' "vp9" ;; + vp8) printf '%s\n' "vp8" ;; + av1) printf '%s\n' "av1" ;; + mpeg4) printf '%s\n' "mpeg4" ;; + mpeg2) printf '%s\n' "mpeg2" ;; + h263) printf '%s\n' "h263" ;; + *) printf '%s\n' "$c" ;; + esac +} + +video_pretty_name_from_cfg() { + cfg="$1"; base=$(basename "$cfg" .json) + nm=$(video_extract_scalar "name" "$cfg"); [ -z "$nm" ] && nm=$(video_extract_scalar "Name" "$cfg") + if video_is_decode_cfg "$cfg"; then cd_op="Decode"; else cd_op="Encode"; fi + codec=$(video_extract_scalar "codec" "$cfg"); [ -z "$codec" ] && codec=$(video_extract_scalar "Codec" "$cfg") + nice="$cd_op:$base" + if [ -n "$nm" ]; then nice="$nm"; elif [ -n "$codec" ]; then nice="$cd_op:$codec ($base)"; fi + safe=$(printf '%s' "$nice" | tr ' ' '_' | tr -cd 'A-Za-z0-9._-') + printf '%s|%s\n' "$nice" "$safe" +} + +# Collect input clip paths from varied schemas (expanded keys) +video_extract_input_clips() { + cfg="$1" + + { + # 1) direct scalar-ish + for k in \ + InputPath inputPath Inputpath input InputFile input_file infile InFile InFileName \ + FilePath Source Clip File Bitstream BitstreamFile YUV YUVFile YuvFileName Path RawFile RawInput RawYUV \ + Sequence + do + video_extract_scalar "$k" "$cfg" + done + } 2>/dev/null | sed '/^$/d' + + { + # 2) common array keys + for k in Inputs InputFiles input_files Clips Files FileList Streams Bitstreams FileNames InputStreams; do + video_strings_from_array_key "$k" "$cfg" + done + } 2>/dev/null | sed '/^$/d' + + # 3) arrays of file names with a base directory hint + basedir=$(video_extract_base_dirs "$cfg") + if [ -n "$basedir" ]; then + for arr in Files Inputs Clips InputFiles FileNames; do + video_strings_from_array_key "$arr" "$cfg" | sed '/^\//! s_^_'"$basedir"'/_' + done + fi +} + +# ----------------------------------------------------------------------------- +# Network-aware clip ensure/fetch +# ----------------------------------------------------------------------------- +video_ensure_clips_present_or_fetch() { + cfg="$1"; tu="$2" + clips=$(video_extract_input_clips "$cfg") + if [ -z "$clips" ]; then return 0; fi + + tmp_list="${LOG_DIR:-.}/.video_missing.$$" + : > "$tmp_list" + printf '%s\n' "$clips" | while IFS= read -r p; do + [ -z "$p" ] && continue + case "$p" in /*) abs="$p" ;; *) abs=$(cd "$(dirname "$cfg")" 2>/dev/null && pwd)/$p ;; esac + [ -f "$abs" ] || printf '%s\n' "$abs" >> "$tmp_list" + done + + if [ ! -s "$tmp_list" ]; then + rm -f "$tmp_list" 2>/dev/null || true + return 0 + fi + + log_warn "Some input clips are missing (list: $tmp_list)" + [ -z "$tu" ] && tu="$TAR_URL" + + if command -v ensure_network_online >/dev/null 2>&1; then + if ! ensure_network_online; then + log_warn "Network offline/limited; cannot fetch media bundle" + rm -f "$tmp_list" 2>/dev/null || true + return 2 + fi + fi + + if [ -n "$tu" ]; then + log_info "Attempting fetch via TAR_URL=$tu" + if extract_tar_from_url "$tu"; then + rm -f "$tmp_list" 2>/dev/null || true + return 0 + fi + log_warn "Fetch/extract failed for TAR_URL" + rm -f "$tmp_list" 2>/dev/null || true + return 1 + fi + + log_warn "No TAR_URL provided; cannot fetch media bundle." + rm -f "$tmp_list" 2>/dev/null || true + return 1 +} + +# ----------------------------------------------------------------------------- +# JUnit helper +# ----------------------------------------------------------------------------- +video_junit_append_case() { + of="$1"; class="$2"; name="$3"; t="$4"; st="$5"; logf="$6" + [ -n "$of" ] || return 0 + tailtxt="" + if [ -f "$logf" ]; then + tailtxt=$(tail -n 50 "$logf" 2>/dev/null | sed 's/&/\&/g;s//\>/g') + fi + { + printf ' \n' "$class" "$name" "$t" + case "$st" in + PASS) : ;; + SKIP) printf ' \n' ;; + FAIL) printf ' \n' "failed"; printf '%s\n' "$tailtxt"; printf ' \n' ;; + esac + printf ' \n' + } >> "$of" +} + +# ----------------------------------------------------------------------------- +# Timeout wrapper availability + single run +# ----------------------------------------------------------------------------- +video_have_run_with_timeout() { video_exist_cmd run_with_timeout; } + +# Ensure the test app is executable; if not, try to fix it (or stage a temp copy) +video_prepare_app() { + app="${VIDEO_APP:-/usr/bin/iris_v4l2_test}" + + if [ -z "$app" ] || [ ! -e "$app" ]; then + log_fail "App not found: $app" + return 1 + fi + + # If it's already executable, we're done. + if [ -x "$app" ]; then + return 0 + fi + + # Try to chmod in place. + if chmod +x "$app" 2>/dev/null; then + log_info "Set executable bit on $app" + return 0 + fi + + # If chmod failed (e.g., RO filesystem), try a temp copy in /tmp and exec that. + tmp="/tmp/$(basename "$app").$$" + if cp -f "$app" "$tmp" 2>/dev/null && chmod +x "$tmp" 2>/dev/null; then + VIDEO_APP="$tmp" + log_info "Using temp executable copy: $VIDEO_APP" + return 0 + fi + + log_warn "Could not make app executable (chmod/copy failed). Execution may fail." + return 1 +} + +video_run_once() { + cfg="$1"; logf="$2"; tmo="$3"; suc="$4"; lvl="$5" +# NEW: ensure the app is executable (or stage a temp copy) + video_prepare_app || true + : > "$logf" + { + iso_now="$(date -u +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || echo now)" + printf 'BEGIN-RUN %s\n' "$iso_now" + printf 'APP=%s\n' "$VIDEO_APP" + printf 'CFG=%s\n' "$cfg" + printf 'LOGLEVEL=%s TIMEOUT=%s\n' "$lvl" "${tmo:-none}" + printf 'CMD=%s %s %s %s\n' "$VIDEO_APP" "--config" "$cfg" "--loglevel $lvl" + } >>"$logf" 2>&1 + + if video_have_run_with_timeout; then + if run_with_timeout "$tmo" "$VIDEO_APP" --config "$cfg" --loglevel "$lvl" >>"$logf" 2>&1; then :; else + rc=$? + if [ "$rc" -eq 124 ] 2>/dev/null; then log_fail "[run] timeout after ${tmo}s"; else log_fail "[run] $VIDEO_APP exited rc=$rc"; fi + printf 'END-RUN rc=%s\n' "$rc" >>"$logf" + grep -Eq "$suc" "$logf" + return $? + fi + else + if "$VIDEO_APP" --config "$cfg" --loglevel "$lvl" >>"$logf" 2>&1; then :; else + rc=$? + log_fail "[run] $VIDEO_APP exited rc=$rc (no timeout enforced)" + printf 'END-RUN rc=%s\n' "$rc" >>"$logf" + grep -Eq "$suc" "$logf" + return $? + fi + fi + + printf 'END-RUN rc=0\n' >>"$logf" + grep -Eq "$suc" "$logf" +} + +# ----------------------------------------------------------------------------- +# Kodiak firmware swap + live reload (no reboot) +# ----------------------------------------------------------------------------- +# Always use the final on-device firmware name +video_kodiak_fw_basename() { + printf '%s\n' "vpu20_p1_gen2.mbn" +} + +# Install (rename) firmware: source (e.g. vpuw20_1v.mbn) -> /lib/firmware/qcom/vpu/vpu20_p1_gen2.mbn +video_kodiak_install_fw() { + # usage: video_kodiak_install_fw /path/to/vpuw20_1v.mbn + src="$1" + if [ -z "$src" ] || [ ! -f "$src" ]; then + log_warn "Kodiak FW src missing: $src" + return 1 + fi + + dst="$FW_PATH_KODIAK" # /lib/firmware/qcom/vpu/vpu20_p1_gen2.mbn + tmp="${dst}.new.$$" + + mkdir -p "$(dirname "$dst")" 2>/dev/null || true + + # Backup any existing firmware into /opt (timestamped) + if [ -f "$dst" ]; then + mkdir -p "$FW_BACKUP_DIR" 2>/dev/null || true + ts=$(date +%Y%m%d%H%M%S 2>/dev/null || printf '%s' "now") + cp -f "$dst" "$FW_BACKUP_DIR/vpu20_p1_gen2.mbn.$ts.bak" 2>/dev/null || true + fi + + # Copy source to the exact destination filename (rename happens here) + cp -f "$src" "$tmp" 2>/dev/null || { log_warn "FW copy to temp failed: $tmp"; return 1; } + chmod 0644 "$tmp" 2>/dev/null || true + chown root:root "$tmp" 2>/dev/null || true + mv -f "$tmp" "$dst" 2>/dev/null || { log_warn "FW mv into place failed: $dst"; rm -f "$tmp" 2>/dev/null || true; return 1; } + sync || true + if command -v restorecon >/dev/null 2>&1; then + restorecon "$dst" 2>/dev/null || true + fi + + log_info "Kodiak FW installed at: $dst (from: $(basename "$src"))" + return 0 +} + +# Install latest Kodiak upstream firmware from backups into the kernel firmware path. +# Looks under ${VIDEO_FW_BACKUP_DIR:-/opt} for multiple filename styles: +# vpu20_p1_gen2_*.mbn +# vpu20_p1_gen2.mbn.* +# vpu20_p1_gen2*.mbn.bak +# vpu20_p1_gen2*.bak +# vpu20_p1_gen2*.mbn.* +video_kodiak_install_firmware() { + src_dir="${VIDEO_FW_BACKUP_DIR:-/opt}" + dest="/lib/firmware/qcom/vpu/vpu20_p1_gen2.mbn" + + [ -d "$src_dir" ] || { log_warn "Backup dir not found: $src_dir"; return 0; } + mkdir -p "$(dirname "$dest")" 2>/dev/null || true + + candidates="" + for g in \ + "$src_dir"/vpu20_p1_gen2_*.mbn \ + "$src_dir"/vpu20_p1_gen2.mbn.* \ + "$src_dir"/vpu20_p1_gen2*.mbn.bak \ + "$src_dir"/vpu20_p1_gen2*.bak \ + "$src_dir"/vpu20_p1_gen2*.mbn.* ; do + for f in $g; do + [ -f "$f" ] && candidates="$candidates +$f" + done + done + + if [ -z "$candidates" ]; then + log_warn "No backup firmware found under $src_dir (tried patterns: vpu20_p1_gen2_*.mbn, vpu20_p1_gen2.mbn.*, *.mbn.bak, *.bak)" + return 0 + fi + + # Pick newest by modification time without 'ls' + newest="" + # shellcheck disable=SC2048,SC2086 + for f in $candidates; do + [ -f "$f" ] || continue + if [ -z "$newest" ] || [ -n "$(find "$f" -prune -newer "$newest" -print -quit 2>/dev/null)" ]; then + newest="$f" + fi + done + [ -n "$newest" ] || newest="$(printf '%s\n' "$candidates" | head -n1)" + + log_info "Using backup firmware: $newest → $dest" + if cp -f "$newest" "$dest" 2>/dev/null; then + chmod 0644 "$dest" 2>/dev/null || true + log_pass "Installed Kodiak upstream firmware to $dest" + return 0 + fi + + log_warn "cp failed; trying install -D" + if install -m 0644 -D "$newest" "$dest" 2>/dev/null; then + log_pass "Installed Kodiak upstream firmware to $dest" + return 0 + fi + + log_error "Failed to install firmware from $newest to $dest" + return 1 +} + +# remoteproc reload must reference the *destination* basename +video_kodiak_try_remoteproc_reload() { + bn="$(video_kodiak_fw_basename)" # always "vpu20_p1_gen2.mbn" + did=0 + for rp in /sys/class/remoteproc/remoteproc*; do + [ -d "$rp" ] || continue + name="$(tr '[:upper:]' '[:lower:]' < "$rp/name" 2>/dev/null)" + case "$name" in + *vpu*|*video*|*iris* ) + log_info "remoteproc: $rp (name=$name)" + if [ -r "$rp/state" ]; then log_info "remoteproc state (pre): $(cat "$rp/state" 2>/dev/null)"; fi + if [ -w "$rp/state" ]; then echo stop > "$rp/state" 2>/dev/null || true; fi + if [ -w "$rp/firmware" ]; then printf '%s' "$bn" > "$rp/firmware" 2>/dev/null || true; fi + if [ -w "$rp/state" ]; then echo start > "$rp/state" 2>/dev/null || true; fi + sleep 1 + if [ -r "$rp/state" ]; then log_info "remoteproc state (post): $(cat "$rp/state" 2>/dev/null)"; fi + did=1 + ;; + esac + done + [ $did -eq 1 ] && { log_info "remoteproc reload attempted with $bn"; return 0; } + return 1 +} + +video_kodiak_try_module_reload() { + # Reload iris_vpu so request_firmware() grabs the new blob + rc=1 + if video_has_module_loaded "$IRIS_VPU_MOD"; then + log_info "module reload: rmmod $IRIS_VPU_MOD" + "$MODPROBE" -r "$IRIS_VPU_MOD" 2>/dev/null || true + sleep 1 + fi + log_info "module reload: modprobe $IRIS_VPU_MOD" + if "$MODPROBE" "$IRIS_VPU_MOD" 2>/dev/null; then + rc=0 + else + ko=$("$MODPROBE" -n -v "$IRIS_VPU_MOD" 2>/dev/null | awk '/(^| )insmod( |$)/ {print $2; exit}') + if [ -n "$ko" ] && [ -f "$ko" ]; then + log_info "module reload: insmod $ko" + insmod "$ko" 2>/dev/null && rc=0 || rc=1 + fi + fi + [ $rc -eq 0 ] && log_info "iris_vpu module reloaded" + return $rc +} + +video_kodiak_try_unbind_bind() { + # Last resort: platform unbind/bind + did=0 + for drv in /sys/bus/platform/drivers/*; do + [ -d "$drv" ] || continue + case "$(basename "$drv")" in *iris*|*vpu*|*video* ) + for dev in "$drv"/*; do + [ -L "$dev" ] || continue + dn="$(basename "$dev")" + log_info "platform: unbind $dn from $(basename "$drv")" + if [ -w "$drv/unbind" ]; then echo "$dn" > "$drv/unbind" 2>/dev/null || true; fi + sleep 1 + log_info "platform: bind $dn to $(basename "$drv")" + if [ -w "$drv/bind" ]; then echo "$dn" > "$drv/bind" 2>/dev/null || true; fi + did=1 + done + ;; esac + done + [ $did -eq 1 ] && { log_info "platform unbind/bind attempted"; return 0; } + return 1 +} + +video_kodiak_swap_and_reload() { + # usage: video_kodiak_swap_and_reload /path/to/newfw.mbn + newsrc="$1" + if [ -z "$newsrc" ] || [ ! -f "$newsrc" ]; then + log_warn "No FW source to install (VIDEO_FW_DS unset or missing)" + return 1 + fi + video_kodiak_install_fw "$newsrc" || return 1 + video_kodiak_try_remoteproc_reload && { video_clean_and_refresh_v4l; return 0; } + video_kodiak_try_module_reload && { video_clean_and_refresh_v4l; return 0; } + video_kodiak_try_unbind_bind && { video_clean_and_refresh_v4l; return 0; } + log_warn "FW reload attempts did not confirm success (remoteproc/module/unbind)." + return 1 +} + +# Fallback: try modprobe first, then insmod the path printed by "modprobe -n -v" +if ! command -v video_try_modprobe_then_insmod >/dev/null 2>&1; then +video_try_modprobe_then_insmod() { + m="$1" + # Try modprobe + if "$MODPROBE" "$m" 2>/dev/null; then + return 0 + fi + # Try to parse an insmod line from a dry-run + p="$("$MODPROBE" -n -v "$m" 2>/dev/null | awk '/(^| )insmod( |$)/ {print $2; exit}')" + if [ -n "$p" ] && [ -f "$p" ]; then + insmod "$p" 2>/dev/null && return 0 + fi + return 1 +} +fi + +# --- Persistent blacklist storage (if not already defined) --- +: "${BLACKLIST_DIR:=/etc/modprobe.d}" +: "${BLACKLIST_FILE:=$BLACKLIST_DIR/blacklist.conf}" + +# Return 0 if token is present in blacklist.conf, else 1 +video_is_blacklisted() { + tok="$1" + [ -f "$BLACKLIST_FILE" ] || return 1 + grep -q "^blacklist[[:space:]]\+$tok$" "$BLACKLIST_FILE" 2>/dev/null +} + +# Ensure a "blacklist " line exists (idempotent) +video_ensure_blacklist() { + tok="$1" + mkdir -p "$BLACKLIST_DIR" 2>/dev/null || true + if ! video_is_blacklisted "$tok"; then + printf 'blacklist %s\n' "$tok" >>"$BLACKLIST_FILE" + log_info "Added persistent blacklist for: $tok" + fi +} + +# Remove any matching "blacklist " lines (idempotent) +video_remove_blacklist() { + tok="$1" + [ -f "$BLACKLIST_FILE" ] || return 0 + tmp="$BLACKLIST_FILE.tmp.$$" + # delete lines like: blacklist (with optional surrounding spaces) + sed "/^[[:space:]]*blacklist[[:space:]]\+${tok}[[:space:]]*$/d" \ + "$BLACKLIST_FILE" >"$tmp" 2>/dev/null && mv "$tmp" "$BLACKLIST_FILE" + log_info "Removed persistent blacklist for: $tok" +} + +# ----------------------------------------------------------------------------- +# Blacklist desired stack (persistent, cross-boot) +# ----------------------------------------------------------------------------- +video_apply_blacklist_for_stack() { + plat="$1"; stack="$2" + case "$plat" in + lemans|monaco) + if [ "$stack" = "downstream" ]; then + # Block upstream; allow downstream + video_ensure_blacklist "qcom-iris" + video_ensure_blacklist "qcom_iris" + video_remove_blacklist "iris-vpu" + video_remove_blacklist "iris_vpu" + else # upstream + # Unblock everything (we rely on runtime blocks for session control) + video_remove_blacklist "qcom-iris" + video_remove_blacklist "qcom_iris" + video_remove_blacklist "iris-vpu" + video_remove_blacklist "iris_vpu" + fi + ;; + kodiak) + if [ "$stack" = "downstream" ]; then + # Block upstream venus; allow downstream iris_vpu + video_ensure_blacklist "venus-core" + video_ensure_blacklist "venus_core" + video_ensure_blacklist "venus-dec" + video_ensure_blacklist "venus_dec" + video_ensure_blacklist "venus-enc" + video_ensure_blacklist "venus_enc" + video_remove_blacklist "iris-vpu" + video_remove_blacklist "iris_vpu" + else # upstream + # Unblock venus and iris_vpu + video_remove_blacklist "venus-core" + video_remove_blacklist "venus_core" + video_remove_blacklist "venus-dec" + video_remove_blacklist "venus_dec" + video_remove_blacklist "venus-enc" + video_remove_blacklist "venus_enc" + video_remove_blacklist "iris-vpu" + video_remove_blacklist "iris_vpu" + fi + ;; + *) + return 1 + ;; + esac + return 0 +} + +# ----------------------------------------------------------------------------- +# AUTO preference helper using persistent blacklists +# ----------------------------------------------------------------------------- +video_auto_preference_from_blacklist() { + plat="$1" + case "$plat" in + lemans|monaco) + if video_is_blacklisted "qcom-iris" || video_is_blacklisted "qcom_iris"; then + printf '%s\n' "downstream"; return 0 + fi + ;; + kodiak) + if video_is_blacklisted "venus-core" || video_is_blacklisted "venus_core" \ + || video_is_blacklisted "venus-dec" || video_is_blacklisted "venus_dec" \ + || video_is_blacklisted "venus-enc" || video_is_blacklisted "venus_enc"; then + printf '%s\n' "downstream"; return 0 + fi + ;; + esac + printf '%s\n' "unknown" + return 0 +}