diff --git a/Runner/plans/video_pre-merge.yaml b/Runner/plans/video_pre-merge.yaml new file mode 100755 index 00000000..cd97182f --- /dev/null +++ b/Runner/plans/video_pre-merge.yaml @@ -0,0 +1,55 @@ +metadata: + format: Lava-Test Test Definition 1.0 + name: Video_V4L2_All + description: "Single YAML: pass TARGET, STACK, iris path and downstream FW; run.sh handles the rest" + maintainer: + - smuppand@qti.qualcomm.com + os: + - openembedded + scope: + - functional + +run: + steps: + - cd Runner + - export REPO_ROOT="$PWD" + + # --- Job-level inputs (set per device/job) --- + - export TARGET="${TARGET:-}" # e.g. Kodiak|LeMans|Monaco|Talos + - export STACK="${STACK:-both}" # base|overlay|auto|both or synonyms: upstream|downstream + - export IRIS_BIN="${IRIS_BIN:-}" # optional: /path/to/iris_v4l2_test + - export DOWNSTREAM_FW="${DOWNSTREAM_FW:-}" # optional: firmware blob for overlay + + # Normalize STACK -> MODE (one-liner synonyms) + - export MODE_NORM="$(printf '%s' "$STACK" | tr '[:upper:]' '[:lower:]' | sed -e 's/^upstream$/base/' -e 's/^downstream$/overlay/')" + - | + case "$MODE_NORM" in base|overlay|auto|both) : ;; *) MODE_NORM="both" ;; esac + echo "TARGET=$TARGET STACK=$STACK MODE_NORM=$MODE_NORM" + + # Build args for run.sh (match your CLI exactly) + - | + ARGS="" + [ -n "$TARGET" ] && ARGS="$ARGS --target \"$TARGET\"" + + if [ -n "$IRIS_BIN" ]; then + ARGS="$ARGS --iris-bin \"$IRIS_BIN\"" + else + IB="$(command -v iris_v4l2_test 2>/dev/null || true)" + [ -n "$IB" ] && ARGS="$ARGS --iris-bin \"$IB\"" + fi + + [ -n "$DOWNSTREAM_FW" ] && ARGS="$ARGS --downstream-fw \"$DOWNSTREAM_FW\"" + + echo "ARGS=$ARGS" + + # Single call: run.sh handles base/overlay/auto and 'both' via its reexec shim + - | + cd "$REPO_ROOT/suites/Multimedia/Video/Video_V4L2_Runner" + # shellcheck disable=SC2086 + sh -lc "./run.sh $ARGS --mode \"$MODE_NORM\"" || true + + # Report result (AUSanity style) + - "$REPO_ROOT/utils/send-to-lava.sh" "$REPO_ROOT/suites/Multimedia/Video/Video_V4L2_Runner/Video_V4L2_Runner.res" || true + + # Optional roll-up (ignored if absent) + - "$REPO_ROOT/utils/result_parse.sh" || true diff --git a/Runner/suites/Multimedia/Video/Video_V4L2_Runner/README_Video.md b/Runner/suites/Multimedia/Video/Video_V4L2_Runner/README_Video.md index dc3ad62f..2fe82573 100644 --- a/Runner/suites/Multimedia/Video/Video_V4L2_Runner/README_Video.md +++ b/Runner/suites/Multimedia/Video/Video_V4L2_Runner/README_Video.md @@ -14,35 +14,28 @@ The suite includes a **reboot-free video stack switcher** (upstream ↔ downstre --- -## 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. +## What’s New (since 2025‑10‑03) + +- **Network stabilization delay (post-connect)** + After an interface comes up (DHCP/DNS), the runner now sleeps for a short grace period before the first TLS download to avoid immediate failures. + - Env knob: `NET_STABILIZE_SLEEP` (default **5** seconds). + +- **Downloader timeouts & retries (BusyBox‑friendly)** + Clip bundle downloads honor BusyBox wget timeouts/retries and perform a final TLS‑lenient attempt when the clock is not yet sane. + - Env knobs: `WGET_TIMEOUT_SECS` (default **120**), `WGET_TRIES` (default **2**). + +- **SKIP instead of FAIL when offline** + If the network is unreachable (or time is invalid for TLS) *and* required media clips are missing, **decode** cases are *SKIPPED* rather than failed. Encode cases continue to run. + +- **App launch & inter‑test pacing** + To reduce flakiness from back‑to‑back runs, the runner adds small sleeps **before** launching `iris_v4l2_test` and **between** tests. + - Env knobs: `VIDEO_APP_LAUNCH_SLEEP` (default **1** second), `VIDEO_INTER_TEST_SLEEP` (default **1** second). + +- **Module operations: gentle waits & retries** + Module unload/load and blacklist scrubbing paths include short sleeps and a retry pass (`modprobe -r` retry with a small delay, 1s delays around remoteproc/module reloads). No new CLI needed. + +- **CLI parity** + `--stack both` is supported to run the suite twice in one invocation (BASE/upstream pass then OVERLAY/downstream pass). --- @@ -51,7 +44,7 @@ The suite includes a **reboot-free video stack switcher** (upstream ↔ downstre - 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** +- 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 @@ -59,6 +52,25 @@ The suite includes a **reboot-free video stack switcher** (upstream ↔ downstre - **Stack switcher**: upstream ↔ downstream without reboot - **Kodiak firmware live swap** with backup/restore helpers - **udev refresh + prune** of stale device nodes +- **Waits/retries/sleeps** integrated across networking, downloads, module ops, and app launches (see next section) + +--- + +## Stability waits, retries & timeouts (defaults & overrides) + +These are **environment variables** (not user‑visible CLI flags) so your LAVA job YAML can stay minimal. All are **optional**—defaults are sane. + +| Env Var | Default | Purpose | +|---|---:|---| +| `NET_STABILIZE_SLEEP` | `5` | Sleep (seconds) after link/IP assignment before first download. Applied also when already online, to debounce DNS/routes. | +| `WGET_TIMEOUT_SECS` | `120` | BusyBox wget timeout per attempt when fetching the clip bundle. | +| `WGET_TRIES` | `2` | BusyBox wget retry count for clip bundle. | +| `VIDEO_APP_LAUNCH_SLEEP` | `1` | Sleep (seconds) right before launching `iris_v4l2_test` for each case. | +| `VIDEO_INTER_TEST_SLEEP` | `1` | Sleep (seconds) between cases to allow device/udev to settle. | + +> Notes +> - If download **stalls** or the system clock is invalid for TLS, the runner re-checks network health and treats it as **offline** → decode cases **SKIP** (not FAIL). +> - Module management includes small internal waits (e.g., `modprobe -r` retry after 200ms, 1s delays around remoteproc/module reloads). These are built‑in, no extra env required. --- @@ -123,7 +135,7 @@ cd /Runner | `--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 | +| `--stack auto|upstream|downstream|base|overlay|up|down|both` | Select target stack (use `both` for BASE→OVERLAY two-pass) | | `--platform lemans|monaco|kodiak` | Force platform (else auto-detect) | | `--downstream-fw PATH` | **Kodiak**: path to DS firmware (e.g. `vpu20_1v.mbn`) | @@ -151,13 +163,21 @@ If the target is offline when a clip bundle is needed: ``` - If still offline, uses `wpa_supplicant + udhcpc` +After connectivity, a **debounce wait** is applied: +```sh +# Default 5s (override via NET_STABILIZE_SLEEP) +sleep "${NET_STABILIZE_SLEEP:-5}" +``` + **Provide credentials via:** ```sh export SSID="WIFI_SSID" export PASSWORD="WIFI_PASSWORD" -# or create ./ssid_list.txt with: WIFI_PASSWORD WIFI_PASSWORD +# or create ./ssid_list.txt with: WIFI_SSID WIFI_PASSWORD ``` +When network remains unreachable and clips are missing, **decode cases are SKIPPED** (not failed). + --- ## Stack Selection & Validation @@ -174,6 +194,7 @@ 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 +4. Applies small **waits/retries** around unload/load and de‑blacklist/blacklist paths --- @@ -183,14 +204,14 @@ The runner: 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** +3. Runner tries **remoteproc restart**, then **module reload**, then **unbind/bind** (with short waits between steps) ### 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. +- Then it attempts **remoteproc restart**; falls back as needed (with 1s waits). **Tip:** If you maintain backups under a custom path: ```sh @@ -202,11 +223,16 @@ export VIDEO_FW_BACKUP_DIR=/opt ## Examples -### Run all configs with auto stack +### Minimal: run all configs with sane defaults ```sh ./run.sh ``` +### Use `both` to run BASE then OVERLAY in one job +```sh +./run.sh --stack both +``` + ### Force downstream on lemans/monaco ```sh ./run.sh --stack downstream @@ -246,6 +272,22 @@ export PASSWORD="WIFI_PASSWORD" ./run.sh --pattern '*h265*Decoder.json' ``` +### Override waits/timeouts (optional) +```sh +# Debounce network right after IP assignment +export NET_STABILIZE_SLEEP=8 + +# BusyBox wget tuning +export WGET_TIMEOUT_SECS=180 +export WGET_TRIES=3 + +# Pacing iris app & tests +export VIDEO_APP_LAUNCH_SLEEP=2 +export VIDEO_INTER_TEST_SLEEP=3 + +./run.sh --stack upstream +``` + --- ## Troubleshooting @@ -260,6 +302,6 @@ export PASSWORD="WIFI_PASSWORD" 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. + 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 and a final TLS‑lenient attempt if needed. When the network remains unreachable, the runner **SKIPs** decode cases. ---- +--- \ No newline at end of file diff --git a/Runner/suites/Multimedia/Video/Video_V4L2_Runner/run.sh b/Runner/suites/Multimedia/Video/Video_V4L2_Runner/run.sh index de576911..2f379f61 100755 --- a/Runner/suites/Multimedia/Video/Video_V4L2_Runner/run.sh +++ b/Runner/suites/Multimedia/Video/Video_V4L2_Runner/run.sh @@ -2,10 +2,15 @@ # 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)" +SCRIPT_DIR="$( + cd "$(dirname "$0")" || exit 1 + pwd +)" INIT_ENV="" SEARCH="$SCRIPT_DIR" + while [ "$SEARCH" != "/" ]; do if [ -f "$SEARCH/init_env" ]; then INIT_ENV="$SEARCH/init_env" @@ -18,12 +23,14 @@ 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 @@ -34,32 +41,46 @@ fi 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}" +if [ -z "${TAR_URL:-}" ]; then + TAR_URL="https://github.com/qualcomm-linux/qcom-linux-testkit/releases/download/IRIS-Video-Files-v1.0/video_clips_iris.tar.gz" +fi # --- Defaults / knobs --- -TIMEOUT="${TIMEOUT:-60}" -STRICT="${STRICT:-0}" -DMESG_SCAN="${DMESG_SCAN:-1}" +if [ -z "${TIMEOUT:-}" ]; then TIMEOUT="60"; fi +if [ -z "${STRICT:-}" ]; then STRICT="0"; fi +if [ -z "${DMESG_SCAN:-}" ]; then DMESG_SCAN="1"; fi 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}" +if [ -z "${MAX:-}" ]; then MAX="0"; fi +if [ -z "${STOP_ON_FAIL:-}" ]; then STOP_ON_FAIL="0"; fi +DRY="0" +if [ -z "${EXTRACT_INPUT_CLIPS:-}" ]; then EXTRACT_INPUT_CLIPS="true"; fi +if [ -z "${SUCCESS_RE:-}" ]; then SUCCESS_RE="SUCCESS"; fi +if [ -z "${LOGLEVEL:-}" ]; then LOGLEVEL="15"; fi +if [ -z "${REPEAT:-}" ]; then REPEAT="1"; fi +if [ -z "${REPEAT_DELAY:-}" ]; then REPEAT_DELAY="0"; fi +if [ -z "${REPEAT_POLICY:-}" ]; then REPEAT_POLICY="all"; fi JUNIT_OUT="" -VERBOSE=0 +VERBOSE="0" + +if [ -z "${VIDEO_STACK:-}" ]; then VIDEO_STACK="auto"; fi +if [ -z "${VIDEO_PLATFORM:-}" ]; then VIDEO_PLATFORM=""; fi +if [ -z "${VIDEO_FW_DS:-}" ]; then VIDEO_FW_DS=""; fi +if [ -z "${VIDEO_FW_BACKUP_DIR:-}" ]; then VIDEO_FW_BACKUP_DIR=""; fi +if [ -z "${VIDEO_NO_REBOOT:-}" ]; then VIDEO_NO_REBOOT="0"; fi +if [ -z "${VIDEO_FORCE:-}" ]; then VIDEO_FORCE="0"; fi +if [ -z "${VIDEO_APP:-}" ]; then VIDEO_APP="/usr/bin/iris_v4l2_test"; fi + +# --- Net/DL tunables (no-op if helpers ignore them) --- +if [ -z "${NET_STABILIZE_SLEEP:-}" ]; then NET_STABILIZE_SLEEP="5"; fi +if [ -z "${WGET_TIMEOUT_SECS:-}" ]; then WGET_TIMEOUT_SECS="120"; fi +if [ -z "${WGET_TRIES:-}" ]; then WGET_TRIES="2"; fi + +# --- Stability sleeps --- +if [ -z "${APP_LAUNCH_SLEEP:-}" ]; then APP_LAUNCH_SLEEP="1"; fi +if [ -z "${INTER_TEST_SLEEP:-}" ]; then INTER_TEST_SLEEP="2"; fi -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}" +# --- New: log flavor for --stack both sub-runs --- +LOG_FLAVOR="" usage() { cat </dev/null 2>&1; then - final_app="$(command -v iris_v4l2_test)" + if [ "$VIDEO_APP" = "/usr/bin/iris_v4l2_test" ]; then + if command -v iris_v4l2_test >/dev/null 2>&1; then + final_app="$(command -v iris_v4l2_test)" + fi 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 || { +# --- Resolve testcase path and cd so outputs land here --- +if ! check_dependencies grep sed awk find sort; then log_skip "$TESTNAME SKIP - required tools missing" printf '%s\n' "$TESTNAME SKIP" >"$RES_FILE" exit 0 -} +fi -# --- 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}" +if ! cd "$test_path"; then + log_error "cd failed: $test_path" + printf '%s\n' "$TESTNAME FAIL" >"$RES_FILE" + exit 1 +fi + +# --- New: split logs by flavor, share bundle cache at root --- +LOG_ROOT="./logs_${TESTNAME}" +LOG_DIR="$LOG_ROOT" + +if [ -n "$LOG_FLAVOR" ]; then + LOG_DIR="$LOG_ROOT/$LOG_FLAVOR" +fi + mkdir -p "$LOG_DIR" export LOG_DIR +export LOG_ROOT -# Ensure rootfs meets minimum size (2GiB) BEFORE any downloads -ensure_rootfs_min_size 2 +# --- Detect top-level vs sub-run (when --stack both re-execs itself) --- +TOP_LEVEL_RUN="1" +if [ -n "$LOG_FLAVOR" ]; then + TOP_LEVEL_RUN="0" +fi -# 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 +# Ensure rootfs meets minimum size (2GiB) BEFORE any downloads — only once +if [ "$TOP_LEVEL_RUN" -eq 1 ]; then + ensure_rootfs_min_size 2 +else + log_info "Sub-run: skipping rootfs size check (already performed)." +fi + +# If we're going to fetch, ensure network is online first — only once +if [ "$TOP_LEVEL_RUN" -eq 1 ]; then + 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 + sleep "${NET_STABILIZE_SLEEP:-5}" + else + sleep "${NET_STABILIZE_SLEEP:-5}" + fi fi +else + log_info "Sub-run: skipping initial network bring-up." +fi + +# --- Early guard: bail out BEFORE any download if Kodiak-downstream lacks --downstream-fw --- +early_plat="$VIDEO_PLATFORM" +if [ -z "$early_plat" ]; then + early_plat="$(video_detect_platform)" 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 +early_stack="$(video_normalize_stack "$VIDEO_STACK")" + +if [ "$early_plat" = "kodiak" ] && [ "$early_stack" = "downstream" ] && [ -z "${VIDEO_FW_DS:-}" ]; then + log_skip "On Kodiak, downstream/overlay requires --downstream-fw ; skipping run." + printf '%s\n' "$TESTNAME SKIP" >"$RES_FILE" + exit 0 +fi + +# --- Optional early fetch of bundle (best-effort, ALWAYS in LOG_ROOT) — only once +if [ "$TOP_LEVEL_RUN" -eq 1 ]; then + if [ "$EXTRACT_INPUT_CLIPS" = "true" ] && [ -z "$CFG" ] && [ -z "$DIR" ]; then + video_step "" "Early bundle fetch (best-effort)" + + saved_log_dir="$LOG_DIR" + LOG_DIR="$LOG_ROOT" + export LOG_DIR + + if command -v check_network_status_rc >/dev/null 2>&1; then + if ! check_network_status_rc; then + log_info "Network unreachable; skipping early media bundle fetch." + else + extract_tar_from_url "$TAR_URL" || true + fi + else + extract_tar_from_url "$TAR_URL" || true + fi + + LOG_DIR="$saved_log_dir" + export LOG_DIR + else + log_info "Skipping early bundle fetch (explicit --config/--dir provided or EXTRACT_INPUT_CLIPS=false)." + fi else - log_info "Skipping early bundle fetch (explicit --config/--dir provided or EXTRACT_INPUT_CLIPS=false)." + log_info "Sub-run: skipping early bundle fetch." +fi + +# --- If user asked for both stacks, re-invoke ourselves for base and overlay --- +if [ "${VIDEO_STACK}" = "both" ]; then + build_reexec_args() { + args="" + + if [ -n "${CFG:-}" ]; then + esc_cfg="$(printf %s "$CFG" | sed "s/'/'\\\\''/g")" + args="$args --config '$esc_cfg'" + fi + + if [ -n "${DIR:-}" ]; then + esc_dir="$(printf %s "$DIR" | sed "s/'/'\\\\''/g")" + args="$args --dir '$esc_dir'" + fi + + if [ -n "${PATTERN:-}" ]; then + esc_pat="$(printf %s "$PATTERN" | sed "s/'/'\\\\''/g")" + args="$args --pattern '$esc_pat'" + fi + + if [ -n "${TIMEOUT:-}" ]; then + args="$args --timeout $(printf %s "$TIMEOUT")" + fi + + if [ "${STRICT:-0}" -eq 1 ]; then + args="$args --strict" + fi + + if [ "${DMESG_SCAN:-1}" -eq 0 ]; then + args="$args --no-dmesg" + fi + + if [ -n "${MAX:-}" ] && [ "$MAX" -gt 0 ] 2>/dev/null; then + args="$args --max $MAX" + fi + + if [ "${STOP_ON_FAIL:-0}" -eq 1 ]; then + args="$args --stop-on-fail" + fi + + if [ -n "${LOGLEVEL:-}" ]; then + args="$args --loglevel $(printf %s "$LOGLEVEL")" + fi + + if [ -n "${REPEAT:-}" ]; then + args="$args --repeat $(printf %s "$REPEAT")" + fi + + if [ -n "${REPEAT_DELAY:-}" ]; then + args="$args --repeat-delay $(printf %s "$REPEAT_DELAY")" + fi + + if [ -n "${REPEAT_POLICY:-}" ]; then + esc_pol="$(printf %s "$REPEAT_POLICY" | sed "s/'/'\\\\''/g")" + args="$args --repeat-policy '$esc_pol'" + fi + + if [ -n "${JUNIT_OUT:-}" ]; then + esc_junit="$(printf %s "$JUNIT_OUT" | sed "s/'/'\\\\''/g")" + args="$args --junit '$esc_junit'" + fi + + if [ "${DRY:-0}" -eq 1 ]; then + args="$args --dry-run" + fi + + if [ -n "${EXTRACT_INPUT_CLIPS:-}" ] && [ "$EXTRACT_INPUT_CLIPS" != "true" ]; then + args="$args --extract-input-clips $(printf %s "$EXTRACT_INPUT_CLIPS")" + fi + + if [ "${VERBOSE:-0}" -eq 1 ]; then + args="$args --verbose" + fi + + if [ -n "${VIDEO_PLATFORM:-}" ]; then + esc_plat="$(printf %s "$VIDEO_PLATFORM" | sed "s/'/'\\\\''/g")" + args="$args --platform '$esc_plat'" + fi + + if [ -n "${VIDEO_FW_DS:-}" ]; then + esc_fw="$(printf %s "$VIDEO_FW_DS" | sed "s/'/'\\\\''/g")" + args="$args --downstream-fw '$esc_fw'" + fi + + if [ "${VIDEO_FORCE:-0}" -eq 1 ]; then + args="$args --force" + fi + + if [ -n "${VIDEO_APP:-}" ]; then + esc_app="$(printf %s "$VIDEO_APP" | sed "s/'/'\\\\''/g")" + args="$args --app '$esc_app'" + fi + + if [ -n "${SSID:-}" ]; then + esc_ssid="$(printf %s "$SSID" | sed "s/'/'\\\\''/g")" + args="$args --ssid '$esc_ssid'" + fi + + if [ -n "${PASSWORD:-}" ]; then + esc_pwd="$(printf %s "$PASSWORD" | sed "s/'/'\\\\''/g")" + args="$args --password '$esc_pwd'" + fi + + if [ -n "${APP_LAUNCH_SLEEP:-}" ]; then + args="$args --app-launch-sleep $(printf %s "$APP_LAUNCH_SLEEP")" + fi + + if [ -n "${INTER_TEST_SLEEP:-}" ]; then + args="$args --inter-test-sleep $(printf %s "$INTER_TEST_SLEEP")" + fi + + printf "%s" "$args" + } + + reexec_args="$(build_reexec_args)" + + log_info "[both] starting BASE (upstream) pass" + # shellcheck disable=SC2086 + sh -c "'$0' --stack base --log-flavor upstream $reexec_args" + rc_base=$? + + base_res_line="" + if [ -f "$RES_FILE" ]; then + base_res_line="$(cat "$RES_FILE" 2>/dev/null || true)" + fi + + log_info "[both] starting OVERLAY (downstream) pass" + # shellcheck disable=SC2086 + sh -c "'$0' --stack overlay --log-flavor downstream $reexec_args" + rc_overlay=$? + + overlay_res_line="" + if [ -f "$RES_FILE" ]; then + overlay_res_line="$(cat "$RES_FILE" 2>/dev/null || true)" + fi + + base_status="$(printf '%s\n' "$base_res_line" | awk '{print $2}')" + overlay_status="$(printf '%s\n' "$overlay_res_line" | awk '{print $2}')" + + overlay_reason="" + plat_for_reason="$VIDEO_PLATFORM" + if [ -z "$plat_for_reason" ]; then + plat_for_reason="$(video_detect_platform)" + fi + if [ "$overlay_status" = "SKIP" ] && [ "$plat_for_reason" = "kodiak" ] && [ -z "${VIDEO_FW_DS:-}" ]; then + overlay_reason="missing --downstream-fw" + fi + + if [ "$rc_base" -eq 0 ] && [ "$rc_overlay" -eq 0 ]; then + if [ "$base_status" = "PASS" ] && [ "$overlay_status" = "SKIP" ]; then + if [ -n "$overlay_reason" ]; then + log_info "[both] upstream/base executed and PASS; downstream/overlay SKIP ($overlay_reason). Overall PASS." + else + log_info "[both] upstream/base executed and PASS; downstream/overlay SKIP. Overall PASS." + fi + elif [ "$base_status" = "SKIP" ] && [ "$overlay_status" = "PASS" ]; then + log_info "[both] downstream/overlay executed and PASS; upstream/base SKIP. Overall PASS." + else + log_pass "[both] both passes succeeded" + fi + + printf '%s\n' "$TESTNAME PASS" > "$RES_FILE" + exit 0 + else + log_fail "[both] one or more passes failed (base rc=$rc_base, overlay rc=$rc_overlay; base=$base_status overlay=$overlay_status)" + printf '%s\n' "$TESTNAME FAIL" >"$RES_FILE" + exit 1 + fi fi log_info "----------------------------------------------------------------------" @@ -184,20 +545,29 @@ 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" +if [ -n "$VIDEO_FW_DS" ]; then + log_info "Downstream FW override: $VIDEO_FW_DS" +fi +if [ -n "$VIDEO_FW_BACKUP_DIR" ]; then + log_info "FW backup override: $VIDEO_FW_BACKUP_DIR" +fi +if [ "$VERBOSE" -eq 1 ]; then + log_info "CWD=$(pwd) | SCRIPT_DIR=$SCRIPT_DIR | test_path=$test_path" +fi +log_info "SLEEPS: app-launch=${APP_LAUNCH_SLEEP}s, inter-test=${INTER_TEST_SLEEP}s" # 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) +if [ -z "$plat" ]; then + plat=$(video_detect_platform) +fi log_info "Detected platform: $plat" -VIDEO_STACK=$(video_normalize_stack "$VIDEO_STACK") -pre_stack=$(video_stack_status "$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 @@ -210,14 +580,46 @@ if [ "$plat" = "kodiak" ]; then esac fi +# ---- Enforce --downstream-fw on Kodiak when requesting downstream/overlay (SKIP if unmet) ---- +if [ "$plat" = "kodiak" ]; then + case "$VIDEO_STACK" in + downstream|overlay|down) + if [ -z "$VIDEO_FW_DS" ] || [ ! -f "$VIDEO_FW_DS" ]; then + log_skip "On Kodiak, downstream/overlay requires --downstream-fw ; skipping run." + printf '%s\n' "$TESTNAME SKIP" >"$RES_FILE" + exit 0 + fi + ;; + esac +fi + +# --- Optional cleanup: robust capture + normalization of post-stack value --- video_dump_stack_state "pre" video_step "" "Apply desired stack = $VIDEO_STACK" -post_stack=$(video_ensure_stack "$VIDEO_STACK" "$plat" || true) + +stack_tmp="$LOG_DIR/.ensure_stack.$$.out" +: > "$stack_tmp" + +video_ensure_stack "$VIDEO_STACK" "$plat" >"$stack_tmp" 2>&1 || true + +if [ -s "$stack_tmp" ]; then + total_lines="$(wc -l < "$stack_tmp" 2>/dev/null | tr -d ' ')" + if [ -n "$total_lines" ] && [ "$total_lines" -gt 1 ] 2>/dev/null; then + head -n $((total_lines - 1)) "$stack_tmp" + fi + post_stack="$(tail -n 1 "$stack_tmp" | tr -d '\r')" +else + post_stack="" +fi + +rm -f "$stack_tmp" 2>/dev/null || 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") + post_stack="$(video_stack_status "$plat")" fi + log_info "Video stack (post): $post_stack" video_dump_stack_state "post" @@ -231,9 +633,15 @@ 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";; + lemans|monaco) + msg="qcom_iris 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" @@ -243,9 +651,15 @@ case "$VIDEO_STACK" in 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";; + 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" @@ -260,8 +674,10 @@ case "$plat" in 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" + elif video_has_module_loaded qcom_iris && ! video_has_module_loaded iris_vpu; then + log_pass "Upstream validated: qcom_iris present (pure upstream build)" else - log_warn "Upstream expected but modules mismatch (need qcom_iris and iris_vpu)" + log_warn "Upstream expected but qcom_iris not present" fi elif [ "$post_stack" = "downstream" ]; then if video_has_module_loaded iris_vpu && ! video_has_module_loaded qcom_iris; then @@ -275,8 +691,10 @@ case "$plat" in 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" + elif video_has_module_loaded qcom_iris && ! video_has_module_loaded iris_vpu; then + log_pass "Upstream validated: qcom_iris present (pure upstream build on Kodiak)" else - log_warn "Upstream expected but venus modules mismatch" + log_warn "Upstream expected but neither Venus trio nor pure qcom_iris path validated" fi elif [ "$post_stack" = "downstream" ]; then if video_has_module_loaded iris_vpu; then @@ -286,16 +704,22 @@ case "$plat" in fi fi ;; - *) log_warn "Unknown platform; skipping strict module validation" ;; + *) + 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 ;; + ''|*[!0-9]* ) + log_warn "Non-numeric --loglevel '$LOGLEVEL'; using 15" + LOGLEVEL=15 + ;; esac # --- Discover config list --- -CFG_LIST="$LOG_DIR/.cfgs"; : > "$CFG_LIST" +CFG_LIST="$LOG_DIR/.cfgs" +: > "$CFG_LIST" if [ -n "$CFG" ] && [ -d "$CFG" ]; then DIR="$CFG" @@ -325,32 +749,63 @@ if [ ! -s "$CFG_LIST" ]; then exit 0 fi -cfg_count=$(wc -l < "$CFG_LIST" 2>/dev/null | tr -d ' ') +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 +total="0" +pass="0" +fail="0" +skip="0" +suite_rc="0" +first_case="1" while IFS= read -r cfg; do - [ -n "$cfg" ] || continue + if [ -z "$cfg" ]; then + continue + fi + + # Inter-test pause (skip before the very first case) + if [ "$first_case" -eq 0 ] 2>/dev/null; then + case "$INTER_TEST_SLEEP" in + ''|*[!0-9]* ) + : + ;; + 0) + : + ;; + *) + log_info "Inter-test sleep ${INTER_TEST_SLEEP}s" + sleep "$INTER_TEST_SLEEP" + ;; + esac + fi + first_case="0" + total=$((total + 1)) - if video_is_decode_cfg "$cfg"; then mode="decode"; else mode="encode"; fi + 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) + 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 "----------------------------------------------------------------------" log_info "[$id] START — mode=$mode codec=$codec name=\"$pretty\" cfg=\"$cfg\"" video_step "$id" "Check /dev/video* presence" @@ -365,8 +820,32 @@ while IFS= read -r cfg; do # 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" + + saved_log_dir_case="$LOG_DIR" + LOG_DIR="$LOG_ROOT" + export LOG_DIR + video_ensure_clips_present_or_fetch "$cfg" "$TAR_URL" ce=$? + + LOG_DIR="$saved_log_dir_case" + export LOG_DIR + + # Map generic download errors to "offline" if link just flapped + if [ "$ce" -eq 1 ] 2>/dev/null; then + sleep "${NET_STABILIZE_SLEEP:-5}" + + if command -v check_network_status_rc >/dev/null 2>&1; then + if ! check_network_status_rc; then + ce=2 + fi + elif command -v check_network_status >/dev/null 2>&1; then + if ! check_network_status >/dev/null 2>&1; then + ce=2 + fi + fi + fi + if [ "$ce" -eq 2 ] 2>/dev/null; then if [ "$mode" = "decode" ]; then log_skip "[$id] SKIP - offline and clips missing (decode case)" @@ -379,8 +858,13 @@ while IFS= read -r cfg; do 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 + fail=$((fail + 1)) + suite_rc=1 + + if [ "$STOP_ON_FAIL" -eq 1 ]; then + break + fi + continue fi else @@ -389,26 +873,45 @@ while IFS= read -r cfg; do # Strict clip existence check after optional fetch video_step "$id" "Verify required clips exist" - missing_case=0 + 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 + if [ -z "$pth" ]; then + continue + fi + case "$pth" in - /*) abs="$pth" ;; - *) abs=$(cd "$(dirname "$cfg")" 2>/dev/null && pwd)/$pth ;; + /*) + abs="$pth" + ;; + *) + abs="$(cd "$(dirname "$cfg")" 2>/dev/null && pwd)/$pth" + ;; esac - [ -f "$abs" ] || missing_case=1 + + if [ ! -f "$abs" ]; then + missing_case=1 + fi 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 + fail=$((fail + 1)) + suite_rc=1 + + if [ "$STOP_ON_FAIL" -eq 1 ]; then + break + fi + continue fi @@ -420,49 +923,84 @@ while IFS= read -r cfg; do continue fi - pass_runs=0; fail_runs=0; rep=1 - start_case=$(date +%s 2>/dev/null || printf '%s' 0) + 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" + if [ "$REPEAT" -gt 1 ]; then + log_info "[$id] repeat $rep/$REPEAT — $pretty" + fi + video_step "$id" "Execute app" - # Print the exact iris command for debugging log_info "[$id] CMD: $VIDEO_APP --config \"$cfg\" --loglevel $LOGLEVEL" + + case "$APP_LAUNCH_SLEEP" in + ''|*[!0-9]* ) + : + ;; + 0) + : + ;; + *) + log_info "[$id] pre-launch sleep ${APP_LAUNCH_SLEEP}s" + sleep "$APP_LAUNCH_SLEEP" + ;; + esac + 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?).";; + 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 + + 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 + end_case="$(date +%s 2>/dev/null || printf '%s' 0)" + elapsed=$((end_case - start_case)) + if [ "$elapsed" -lt 0 ] 2>/dev/null; then + elapsed=0 + fi final="FAIL" case "$REPEAT_POLICY" in - any) [ "$pass_runs" -ge 1 ] && final="PASS" ;; - all|*) [ "$fail_runs" -eq 0 ] && final="PASS" ;; + any) + if [ "$pass_runs" -ge 1 ]; then + final="PASS" + fi + ;; + all|*) + if [ "$fail_runs" -eq 0 ]; then + final="PASS" + fi + ;; 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" + if [ "$STRICT" -eq 1 ]; then + final="FAIL" + fi fi { @@ -473,9 +1011,15 @@ while IFS= read -r cfg; do 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" ;; + 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" @@ -484,8 +1028,12 @@ while IFS= read -r cfg; do if [ "$final" = "PASS" ]; then pass=$((pass + 1)) else - fail=$((fail + 1)); suite_rc=1 - [ "$STOP_ON_FAIL" -eq 1 ] && break + fail=$((fail + 1)) + suite_rc=1 + + if [ "$STOP_ON_FAIL" -eq 1 ]; then + break + fi fi if [ "$MAX" -gt 0 ] && [ "$total" -ge "$MAX" ]; then @@ -496,22 +1044,73 @@ done < "$CFG_LIST" log_info "Summary: total=$total pass=$pass fail=$fail skip=$skip" +# --- End-of-run detailed per-test results --- +if [ -s "$LOG_DIR/summary.txt" ]; then + log_info "----------------------------------------------------------------------" + log_info "Per-test results (id result):" + while IFS= read -r line; do + id_field=$(printf '%s\n' "$line" | awk '{print $1}') + res_field=$(printf '%s\n' "$line" | awk '{print $2}') + if [ -n "$id_field" ] && [ -n "$res_field" ]; then + log_info "$id_field $res_field" + fi + done < "$LOG_DIR/summary.txt" +fi + +# --- Aggregate breakdown by mode/codec --- +if [ -s "$LOG_DIR/results.csv" ]; then + log_info "----------------------------------------------------------------------" + log_info "Mode/codec breakdown (total/pass/fail/skip):" + awk -F',' 'NR>1 { + id=$2; res=$3; + split(id,a,"-"); mode=a[1]; codec=a[2]; + key=mode "-" codec; + total[key]++ + if (res=="PASS") pass[key]++ + else if (res=="FAIL") fail[key]++ + else if (res=="SKIP") skip[key]++ + } + END { + for (k in total) { + printf " %s: total=%d pass=%d fail=%d skip=%d\n", k, total[k], pass[k]+0, fail[k]+0, skip[k]+0 + } + }' "$LOG_DIR/results.csv" | while IFS= read -r ln; do + if [ -n "$ln" ]; then + log_info "$ln" + fi + done +fi + # --- 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 +# Overall suite result (single-stack path): +# If ALL testcases were skipped (pass=0, fail=0, skip>0) => overall SKIP. +# Otherwise: suite_rc==0 -> PASS, else FAIL. +if [ "$pass" -eq 0 ] && [ "$fail" -eq 0 ] && [ "$skip" -gt 0 ]; then + log_skip "$TESTNAME: SKIP (all $skip test(s) skipped)" + printf '%s\n' "$TESTNAME SKIP" >"$RES_FILE" + exit 0 +fi + if [ "$suite_rc" -eq 0 ] 2>/dev/null; then - log_pass "$TESTNAME: PASS"; printf '%s\n' "$TESTNAME PASS" >"$RES_FILE" + log_pass "$TESTNAME: PASS" + printf '%s\n' "$TESTNAME PASS" >"$RES_FILE" else - log_fail "$TESTNAME: FAIL"; printf '%s\n' "$TESTNAME FAIL" >"$RES_FILE" + log_fail "$TESTNAME: FAIL" + printf '%s\n' "$TESTNAME FAIL" >"$RES_FILE" fi + exit "$suite_rc" diff --git a/Runner/utils/functestlib.sh b/Runner/utils/functestlib.sh index 7b1a6c0e..76f744cb 100755 --- a/Runner/utils/functestlib.sh +++ b/Runner/utils/functestlib.sh @@ -1,6 +1,4 @@ #!/bin/sh -#!/bin/sh - # Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. # SPDX-License-Identifier: BSD-3-Clause-Clear @@ -300,19 +298,26 @@ ensure_reasonable_clock() { 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 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 + grace=25 + start="$(date +%s 2>/dev/null || echo 0)" + end=$((start + grace)) + while :; do + cur="$(date +%s 2>/dev/null || echo 0)" + if [ "$cur" -ge "$cutoff" ] 2>/dev/null; then + log_pass "Clock synchronized." + return 0 + fi + if [ "$cur" -ge "$end" ] 2>/dev/null; then + break + fi + sleep 1 + done + log_warn "Clock still invalid; TLS downloads may fail. Treating as limited network." 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" @@ -390,7 +395,7 @@ extract_tar_from_url() { [ -r "$cand" ] && ca="$cand" && break done - # 1) curl first (IPv4, retries, redirects) + # 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 \ @@ -405,7 +410,7 @@ extract_tar_from_url() { case "$rc" in 60|35|22) return 60 ;; esac # TLS-ish / HTTP fail fi - # 2) aria2c (if available) + # 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" @@ -414,7 +419,7 @@ extract_tar_from_url() { rm -f "$part" 2>/dev/null || true fi - # 3) wget: handle BusyBox vs GNU + # 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) @@ -447,7 +452,6 @@ extract_tar_from_url() { return 127 } # ------------------------------------------------------------------------ - if [ "$status" -eq 0 ]; then log_info "Already extracted. Skipping download." return 0 @@ -2272,64 +2276,6 @@ execute_capture_block() { done } -# --- 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)) - - total_kb="$(df -P / 2>/dev/null | awk 'NR==2{print $2}')" - [ -n "$total_kb" ] || { log_warn "df check failed; skipping resize."; return 0; } - - if [ "$total_kb" -ge "$min_kb" ] 2>/dev/null; then - log_info "Rootfs size OK (>=${min_gib}GiB)." - return 0 - fi - - # 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 - - if [ -z "$root_dev" ] || [ ! -e "$root_dev" ]; then - log_warn "Could not determine root device; skipping resize." - return 0 - fi - - # 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 - - 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 - log_warn "resize2fs not available; skipping resize." - fi - return 0 -} - log_soc_info() { m=""; s=""; pv="" [ -r /sys/devices/soc0/machine ] && m="$(cat /sys/devices/soc0/machine 2>/dev/null)" @@ -2495,7 +2441,7 @@ tryremountrw() { # FASTRPC_AUTOREMOUNT=yes|no (default: no) allow remount rw if needed # FASTRPC_AUTOREMOUNT_RO=yes|no (default: yes) remount ro after linking ensure_usr_lib_dsp_symlinks() { - [ "${FASTRPC_DSP_AUTOLINK:-yes}" = "yes" ] || { log_info "DSP autolink disabled" return 0; } + [ "${FASTRPC_DSP_AUTOLINK:-yes}" = "yes" ] || { log_info "DSP autolink disabled"; return 0; } dsptgt="/usr/lib/dsp" @@ -2602,7 +2548,12 @@ ensure_usr_lib_dsp_symlinks() { # Final visibility + sanity if find "$dsptgt" -mindepth 1 -maxdepth 1 -print -quit 2>/dev/null | grep -q .; then log_info "DSP autolink complete ($linked link(s))" - ls -l "$dsptgt" 2>/dev/null | sed 's/^/[INFO] /' || true + find "$dsptgt" \ + -mindepth 1 \ + -maxdepth 1 \ + -printf '%M %u %g %6s %TY-%Tm-%Td %TH:%TM %p\n' 2>/dev/null \ + | sed 's/^/[INFO] /' \ + || true else log_warn "DSP autolink finished but $dsptgt is still empty, source may contain only nested content or FS is RO." fi @@ -2612,3 +2563,296 @@ ensure_usr_lib_dsp_symlinks() { mount -o remount,ro "$mountpt" 2>/dev/null || true fi } + +# --- 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)) + + total_kb="$(df -P / 2>/dev/null | awk 'NR==2{print $2}')" + [ -n "$total_kb" ] || { log_warn "df check failed; skipping resize."; return 0; } + + if [ "$total_kb" -ge "$min_kb" ] 2>/dev/null; then + log_info "Rootfs size OK (>=${min_gib}GiB)." + return 0 + fi + + # 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 + + # Detect filesystem type robustly + fstype="" + 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 + *TYPE=*) + fstype="$(printf '%s' "$fstype" | sed -n 's/.*TYPE="\([^"]*\)".*/\1/p')" + ;; + esac + if [ -z "$fstype" ] && command -v lsblk >/dev/null 2>&1; then + fstype="$(lsblk -no FSTYPE "$root_dev" 2>/dev/null | head -n1)" + fi + if [ -z "$fstype" ]; then + fstype="$(blkid "$root_dev" 2>/dev/null | sed -n 's/.*TYPE="\([^"]*\)".*/\1/p')" + fi + case "$fstype" in + ext2|ext3|ext4) : ;; + *) + log_warn "Rootfs type '${fstype:-unknown}' not ext*, skipping resize." + return 0 + ;; + esac + fi + + log_dir="${LOG_DIR:-/tmp}" + mkdir -p "$log_dir" 2>/dev/null || true + + if command -v resize2fs >/dev/null 2>&1; then + mib="$(printf '%s\n' "$total_kb" | awk '{printf "%d",$1/1024}')" + log_info "Rootfs <${min_gib}GiB (${mib} MiB). Resizing filesystem on $root_dev ..." + if resize2fs "$root_dev" >>"$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 + log_warn "resize2fs not available; skipping resize." + fi + return 0 +} +# ---- 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}" + + 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="" + 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 + fi + + unset net_probe_route_ip net_ping_host net_ip_addr + 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=$? + if [ "$net_rc" -eq 0 ]; then + ensure_reasonable_clock || log_warn "Proceeding in limited-network mode." + unset net_rc + return 0 + fi + + 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 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 + fi + fi + + 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 + fi + + net_log_iface_snapshot "$net_ifc" + + check_network_status_rc; net_rc=$? + case "$net_rc" in + 0) + log_pass "[NET] ${net_ifc}: internet reachable" + ensure_reasonable_clock || log_warn "Proceeding in limited-network mode." + 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 + + # -------- 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..." + + if command -v bringup_interface >/dev/null 2>&1; then + bringup_interface "$net_wifi" 2 2 || true + fi + + net_creds="" + if command -v get_wifi_credentials >/dev/null 2>&1; then + net_creds="$(get_wifi_credentials "" "" 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 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 nmcli brought us up, do NOT fall back to wpa_supplicant + check_network_status_rc; net_rc=$? + if [ "$net_rc" -ne 0 ]; then + log_info "[NET] ${net_wifi}: falling back to wpa_supplicant + DHCP" + if command -v wifi_connect_wpa_supplicant >/dev/null 2>&1; then + wifi_connect_wpa_supplicant "$net_wifi" "$net_ssid" "$net_pass" || true + fi + if command -v run_dhcp_client >/dev/null 2>&1; then + run_dhcp_client "$net_wifi" 10 >/dev/null 2>&1 || true + fi + 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 + 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" + ensure_reasonable_clock || log_warn "Proceeding in limited-network mode." + 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" + ensure_reasonable_clock || log_warn "Proceeding in limited-network mode." + 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 + + 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 +} diff --git a/Runner/utils/lib_video.sh b/Runner/utils/lib_video.sh index fab11351..e5535508 100755 --- a/Runner/utils/lib_video.sh +++ b/Runner/utils/lib_video.sh @@ -36,10 +36,28 @@ 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}" +# ----------------------------------------------------------------------------- +# NEW: settle / retry tunables (env-only; no CLI) +# ----------------------------------------------------------------------------- +: "${MOD_RETRY_COUNT:=3}" +: "${MOD_RETRY_SLEEP:=0.4}" +: "${MOD_SETTLE_SLEEP:=0.5}" + # ----------------------------------------------------------------------------- # Tiny utils # ----------------------------------------------------------------------------- -video_exist_cmd() { command -v "$1" >/dev/null 2>&1; } +video_exist_cmd() { + command -v "$1" >/dev/null 2>&1 +} + +video_usleep() { + # Safe sleep wrapper (accepts integers or decimals) + dur="$1" + if [ -z "$dur" ]; then + dur=0 + fi + sleep "$dur" 2>/dev/null || true +} video_warn_if_not_root() { uid="$(id -u 2>/dev/null || printf '%s' 1)" @@ -58,8 +76,13 @@ video_devices_present() { } video_step() { - id="$1"; msg="$2" - if [ -n "$id" ]; then log_info "[$id] STEP: $msg"; else log_info "STEP: $msg"; fi + id="$1" + msg="$2" + if [ -n "$id" ]; then + log_info "[$id] STEP: $msg" + else + log_info "STEP: $msg" + fi } # ----------------------------------------------------------------------------- @@ -69,7 +92,9 @@ 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 + printf '%s\n' "$out" | while IFS= read -r ln; do + log_info "dmesg: $ln" + done fi fi } @@ -81,23 +106,29 @@ 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/) + if video_has_module_loaded "$m"; then + log_info "module already loaded (insmod path skipped): $m" + return 0 + fi + 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 [ -z "$d" ]; then + continue + fi + if video_has_module_loaded "$d"; then + continue + fi if ! "$MODPROBE" -q "$d" 2>/dev/null; then dpath="$(video_find_module_file "$d")" || dpath="" if [ -n "$dpath" ] && [ -f "$dpath" ]; then @@ -106,7 +137,10 @@ video_insmod_with_deps() { fi done - insmod "$path" 2>/dev/null && return 0 + if insmod "$path" 2>/dev/null; then + return 0 + fi + log_warn "insmod failed for $path" return 1 } @@ -115,7 +149,9 @@ 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 + printf '%s\n' "$out" | while IFS= read -r ln; do + log_info "modprobe -n $m: $ln" + done else log_info "modprobe -n $m:" fi @@ -130,19 +166,24 @@ video_list_runtime_blocks() { found=1 fi done - [ "$found" -eq 0 ] && log_info "(none)" + if [ "$found" -eq 0 ]; then + log_info "(none)" + fi } 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 } @@ -152,11 +193,11 @@ video_find_module_file() { # 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 @@ -165,34 +206,40 @@ video_find_module_file() { 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; } + if [ -n "$p" ]; then + printf '%s\n' "$p" + return 0 + fi 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; } + if [ -n "$p" ]; then + printf '%s\n' "$p" + return 0 + fi 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 "$@"; } +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 @@ -200,47 +247,65 @@ dump_stack_state() { video_dump_stack_state "$@"; } 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 + + if command -v udevadm >/dev/null 2>&1; then + udevadm control --reload-rules 2>/dev/null || true + udevadm settle 2>/dev/null || true + fi + + video_usleep "${MOD_SETTLE_SLEEP}" } 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 + for m in "$@"; do + rm -f "$RUNTIME_BLOCK_DIR/${m}-block.conf" + done + depmod -a 2>/dev/null || true + + if command -v udevadm >/dev/null 2>&1; then + udevadm control --reload-rules 2>/dev/null || true + udevadm settle 2>/dev/null || true + fi + + video_usleep "${MOD_SETTLE_SLEEP}" } # ----------------------------------------------------------------------------- # 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 + if [ -z "$m" ]; then + return 0 + fi 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 + depmod -a 2>/dev/null || true + if command -v udevadm >/dev/null 2>&1; then + udevadm control --reload-rules 2>/dev/null || true + udevadm settle 2>/dev/null || true + fi 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 } { @@ -252,56 +317,104 @@ video_persistent_unblock_module() { 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; } + ' "$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 + 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 + udevadm settle 2>/dev/null || true fi + video_usleep "${MOD_SETTLE_SLEEP}" + log_info "persistent unblock done for $m (and aliases: $m_us, $m_hy)" return 0 } +# ----------------------------------------------------------------------------- +# Retry wrapper for modprobe +# ----------------------------------------------------------------------------- +video_retry_modprobe() { + m="$1" + n="${MOD_RETRY_COUNT}" + i=0 + + if video_has_module_loaded "$m"; then + log_info "module already loaded (retry path skipped): $m" + return 0 + fi + + while [ "$i" -lt "$n" ]; do + i=$((i+1)) + log_info "modprobe attempt $i/$n: $m" + + if video_has_module_loaded "$m"; then + log_info "module became present before attempt $i: $m" + return 0 + fi + + if "$MODPROBE" "$m" 2>/dev/null; then + log_info "modprobe succeeded on attempt $i: $m" + return 0 + fi + + video_usleep "${MOD_RETRY_SLEEP}" + done + + if video_has_module_loaded "$m"; then + log_info "module present after retries: $m" + return 0 + fi + + log_warn "modprobe failed after $n attempts: $m" + return 1 +} + # ----------------------------------------------------------------------------- # modprobe with insmod fallback (standalone-like resilience) # ----------------------------------------------------------------------------- video_modprobe_or_insmod() { m="$1" - # 1) Try modprobe - "$MODPROBE" "$m" 2>/dev/null && return 0 + if video_has_module_loaded "$m"; then + log_info "module already loaded (modprobe/insmod path skipped): $m" + return 0 + fi + + if video_retry_modprobe "$m"; then + return 0 + fi - # 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 + video_usleep "${MOD_SETTLE_SLEEP}" + + if video_retry_modprobe "$m"; then + return 0 + fi - # 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_retry_modprobe "$malt"; then + return 0 + fi if video_insmod_with_deps "$malt"; then return 0 fi @@ -317,10 +430,18 @@ video_modprobe_or_insmod() { 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" ;; + upstream|base|up) + printf '%s\n' "upstream" + ;; + downstream|overlay|down) + printf '%s\n' "downstream" + ;; + auto|"") + printf '%s\n' "auto" + ;; + *) + printf '%s\n' "$s" + ;; esac } @@ -328,17 +449,43 @@ video_normalize_stack() { # 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 + 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; } + 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" } @@ -348,97 +495,157 @@ video_detect_platform() { # ----------------------------------------------------------------------------- video_validate_upstream_loaded() { plat="$1" + + if [ -z "$plat" ]; then + plat="$(video_detect_platform)" + fi + case "$plat" in lemans|monaco) - video_has_module_loaded "$IRIS_UP_MOD" && video_has_module_loaded "$IRIS_VPU_MOD" - return $? + # Any upstream build has qcom_iris present + if video_has_module_loaded qcom_iris; then + return 0 + fi + return 1 ;; + kodiak) - video_has_module_loaded "$VENUS_CORE_MOD" && video_has_module_loaded "$VENUS_DEC_MOD" && video_has_module_loaded "$VENUS_ENC_MOD" - return $? + # Upstream valid if Venus trio present OR pure qcom_iris-only present + if video_has_module_loaded venus_core && video_has_module_loaded venus_dec && video_has_module_loaded venus_enc; then + return 0 + fi + + if video_has_module_loaded qcom_iris && ! video_has_module_loaded iris_vpu; then + return 0 + fi + + return 1 ;; - *) return 1 ;; esac + + return 1 } 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 ;; + *) + 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")" + 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." + 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 ;; - kodiak) - log_fail "[STACK] Downstream requested but iris_vpu not present or venus_core still loaded." + 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] Downstream requested but platform '$plat' is unknown." + *) + log_fail "[STACK] Unknown target stack '$want'." + return 1 ;; - 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" + + if [ -z "$plat" ]; then + plat="$(video_detect_platform)" fi + + case "$plat" in + lemans|monaco) + # Upstream accepted if: + # - pure upstream build: qcom_iris present and iris_vpu absent + # - base+overlay build: qcom_iris and iris_vpu both present + if video_has_module_loaded qcom_iris && ! video_has_module_loaded iris_vpu; then + printf '%s\n' "upstream" + return 0 + fi + + if video_has_module_loaded qcom_iris && video_has_module_loaded iris_vpu; then + printf '%s\n' "upstream" + return 0 + fi + + # Downstream if only iris_vpu is present (no qcom_iris) + if video_has_module_loaded iris_vpu && ! video_has_module_loaded qcom_iris; then + printf '%s\n' "downstream" + return 0 + fi + ;; + + kodiak) + # Upstream accepted if: + # - Venus trio present (canonical upstream on Kodiak), OR + # - pure upstream build: qcom_iris present and iris_vpu absent + if video_has_module_loaded venus_core && video_has_module_loaded venus_dec && video_has_module_loaded venus_enc; then + printf '%s\n' "upstream" + return 0 + fi + + if video_has_module_loaded qcom_iris && ! video_has_module_loaded iris_vpu; then + printf '%s\n' "upstream" + return 0 + fi + + # Downstream if iris_vpu present and Venus core not loaded + if video_has_module_loaded iris_vpu && ! video_has_module_loaded venus_core; then + printf '%s\n' "downstream" + return 0 + fi + ;; + esac + + printf '%s\n' "unknown" + return 1 } # ----------------------------------------------------------------------------- @@ -454,7 +661,7 @@ video_unload_all_video_modules() { log_info "Removed module: $mod" else log_warn "Could not remove $mod via modprobe -r; retrying after short delay" - sleep 0.2 + video_usleep 0.2 if "$MODPROBE" -r "$mod" 2>/dev/null; then log_info "Removed module after retry: $mod" else @@ -465,106 +672,115 @@ video_unload_all_video_modules() { } 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" - ;; - *) : ;; + lemans|monaco) + tryrmmod "$IRIS_UP_MOD" + tryrmmod "$IRIS_VPU_MOD" + tryrmmod "$IRIS_UP_MOD" + ;; + kodiak) + tryrmmod "$VENUS_ENC_MOD" + tryrmmod "$VENUS_DEC_MOD" + tryrmmod "$VENUS_CORE_MOD" + tryrmmod "$IRIS_VPU_MOD" + tryrmmod "$IRIS_UP_MOD" + ;; + *) + : + ;; esac + + depmod -a 2>/dev/null || true + if command -v udevadm >/dev/null 2>&1; then + udevadm settle 2>/dev/null || true + fi + video_usleep "${MOD_SETTLE_SLEEP}" } # ----------------------------------------------------------------------------- # Hot switch (best-effort, no reboot) — mirrors standalone flow # ----------------------------------------------------------------------------- video_hot_switch_modules() { - plat="$1"; stack="$2"; rc=0 + 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" + lemans|monaco) + if [ "$stack" = "downstream" ]; then + video_block_upstream_strict + video_unblock_mod_now "$IRIS_VPU_MOD" + video_usleep "${MOD_SETTLE_SLEEP}" - # Load only iris_vpu - if ! video_modprobe_or_insmod "$IRIS_VPU_MOD"; then - rc=1 - fi + video_unload_all_video_modules "$plat" - # 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" + if ! video_modprobe_or_insmod "$IRIS_VPU_MOD"; then + rc=1 + fi + video_usleep "${MOD_SETTLE_SLEEP}" - # Clean slate - video_unload_all_video_modules "$plat" + 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 + video_usleep 0.2 + fi + else + video_unblock_mod_now "$IRIS_UP_MOD" "$IRIS_VPU_MOD" + video_usleep "${MOD_SETTLE_SLEEP}" - # 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 + video_unload_all_video_modules "$plat" - # 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)" + 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_usleep "${MOD_SETTLE_SLEEP}" + video_modprobe_or_insmod "$IRIS_UP_MOD" || rc=1 + fi + video_modprobe_or_insmod "$IRIS_VPU_MOD" || true + video_usleep "${MOD_SETTLE_SLEEP}" fi + ;; + kodiak) + if [ "$stack" = "downstream" ]; then + video_block_mod_now "$VENUS_CORE_MOD" "$VENUS_DEC_MOD" "$VENUS_ENC_MOD" "$IRIS_UP_MOD" + video_unblock_mod_now "$IRIS_VPU_MOD" + video_usleep "${MOD_SETTLE_SLEEP}" + + "$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 + "$MODPROBE" -r "$IRIS_UP_MOD" 2>/dev/null || true + video_usleep "${MOD_SETTLE_SLEEP}" + + if ! video_modprobe_or_insmod "$IRIS_VPU_MOD"; then + log_warn "Kodiak: failed to load $IRIS_VPU_MOD" + rc=1 + 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 - ;; + 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_usleep "${MOD_SETTLE_SLEEP}" - *) rc=1 ;; + video_log_fw_hint + else + 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_usleep "${MOD_SETTLE_SLEEP}" + 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_usleep "${MOD_SETTLE_SLEEP}" + video_log_fw_hint + fi + ;; + *) + rc=1 + ;; esac + return $rc } @@ -572,80 +788,132 @@ video_hot_switch_modules() { # Entry point: ensure desired stack # ----------------------------------------------------------------------------- video_ensure_stack() { - want_raw="$1" # upstream|downstream|auto|base|overlay|up|down + want_raw="$1" # upstream|downstream|auto|base|overlay|up|down plat="$2" - - if [ -z "$plat" ]; then plat=$(video_detect_platform); fi + + 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 + cur_aut="$(video_stack_status "$plat")" + if [ "$cur_aut" != "unknown" ]; then + want="$cur_aut" + 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 + + # ---------------------------------------------------------------------- + # Early no-op: if current state already equals desired, do NOT hot switch. + # This covers: + # - Build #1 (pure upstream: qcom_iris only) on lemans/monaco/kodiak + # - Build #2 (base+overlay: qcom_iris + iris_vpu) when upstream is requested + # - Downstream already active (e.g., iris_vpu only on lemans/monaco, or kodiak downstream) + # Still allow Kodiak downstream FW swap without touching modules. + # ---------------------------------------------------------------------- + cur_state="$(video_stack_status "$plat")" + + if [ "$cur_state" = "$want" ]; then + if [ "$plat" = "kodiak" ] && [ "$want" = "downstream" ] && [ -n "$VIDEO_FW_DS" ] && [ -f "$VIDEO_FW_DS" ]; then + log_info "Kodiak: downstream already active; applying FW swap + live reload without hot switch." + video_kodiak_swap_and_reload "$VIDEO_FW_DS" || log_warn "Kodiak: FW swap/reload failed (continuing)" 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' "$cur_state" + return 0 + fi + + # Fast paths (retain existing logic; these also help when cur_state was unknown) + case "$want" in + upstream|up|base) + case "$plat" in + lemans|monaco) + if video_has_module_loaded qcom_iris && ! video_has_module_loaded iris_vpu; then + printf '%s\n' "upstream" + return 0 + fi + ;; + kodiak) + if video_has_module_loaded venus_core && video_has_module_loaded venus_dec && video_has_module_loaded venus_enc; then + printf '%s\n' "upstream" + return 0 + fi + if video_has_module_loaded qcom_iris && ! video_has_module_loaded iris_vpu; then + printf '%s\n' "upstream" + return 0 + fi + ;; + esac + if video_validate_upstream_loaded "$plat"; then + printf '%s\n' "upstream" + return 0 + fi + ;; + downstream|overlay|down) + if video_validate_downstream_loaded "$plat"; then + if [ "$plat" = "kodiak" ] && [ -n "$VIDEO_FW_DS" ]; then + log_info "Kodiak: downstream already loaded, FW override provided — performing FW swap + live reload." + video_kodiak_swap_and_reload "$VIDEO_FW_DS" || log_warn "Kodiak: post-switch FW swap/reload failed (continuing)" + fi printf '%s\n' "downstream" return 0 fi - fi - fi - - # Apply persistent blacklist for the requested stack (no reboot needed; runtime blocks handle the session) + ;; + esac + + # Only reach here if a switch is actually required. 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_usleep "${MOD_SETTLE_SLEEP}" + 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 + 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 + 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 + + if command -v udevadm >/dev/null 2>&1; then + udevadm control --reload-rules 2>/dev/null || true + udevadm settle 2>/dev/null || true + fi + + video_usleep "${MOD_SETTLE_SLEEP}" fi } @@ -653,7 +921,6 @@ video_block_upstream_strict() { # 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 @@ -661,7 +928,6 @@ video_clean_and_refresh_v4l() { 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") @@ -671,7 +937,6 @@ video_clean_and_refresh_v4l() { fi done - # Prune /dev/media* nodes that have no sysfs backing for n in /dev/media*; do [ -e "$n" ] || continue bn=$(basename "$n") @@ -686,11 +951,16 @@ video_clean_and_refresh_v4l() { # DMESG triage # ----------------------------------------------------------------------------- video_scan_dmesg_if_enabled() { - dm="$1"; logdir="$2" - if [ "$dm" -ne 1 ] 2>/dev/null; then return 2; fi + 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 + if scan_dmesg_errors "$logdir" "$MODS" "$EXCL"; then + return 0 + fi return 1 } @@ -700,50 +970,104 @@ video_scan_dmesg_if_enabled() { video_is_decode_cfg() { cfg="$1" b=$(basename "$cfg" | tr '[:upper:]' '[:lower:]') - case "$b" in *dec*.json) return 0 ;; *enc*.json) return 1 ;; esac + + 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 + + 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_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" + 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" + ' "$cfg" \ + | sed -n 's/.*"\([^"]*\)".*/\1/p' \ + | grep -vx "$k" } video_strings_from_array_key() { - k="$1"; cfg="$2" + k="$1" + cfg="$2" { video_extract_array_ml "$k" "$cfg" video_extract_array "$k" "$cfg" - } 2>/dev/null | sed '/^$/d' | awk '!seen[$0]++' + } 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 + 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 + 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 + 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 + 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 } @@ -751,25 +1075,58 @@ video_guess_codec_from_cfg() { 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" ;; + 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") + cfg="$1" + base=$(basename "$cfg" .json) + nm=$(video_extract_scalar "name" "$cfg") + if [ -z "$nm" ]; then + nm=$(video_extract_scalar "Name" "$cfg") + fi + if video_is_decode_cfg "$cfg"; then + cd_op="Decode" + else + cd_op="Encode" + fi + codec=$(video_extract_scalar "codec" "$cfg") + if [ -z "$codec" ]; then + codec=$(video_extract_scalar "Codec" "$cfg") + fi nice="$cd_op:$base" - if [ -n "$nm" ]; then nice="$nm"; elif [ -n "$codec" ]; then nice="$cd_op:$codec ($base)"; fi + 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" } @@ -779,7 +1136,6 @@ 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 \ @@ -790,13 +1146,11 @@ video_extract_input_clips() { } 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 @@ -809,15 +1163,27 @@ video_extract_input_clips() { # Network-aware clip ensure/fetch # ----------------------------------------------------------------------------- video_ensure_clips_present_or_fetch() { - cfg="$1"; tu="$2" + cfg="$1" + tu="$2" + clips=$(video_extract_input_clips "$cfg") - if [ -z "$clips" ]; then return 0; fi + 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 + case "$p" in + /*) + abs="$p" + ;; + *) + abs=$(cd "$(dirname "$cfg")" 2>/dev/null && pwd)/$p + ;; + esac [ -f "$abs" ] || printf '%s\n' "$abs" >> "$tmp_list" done @@ -827,7 +1193,10 @@ video_ensure_clips_present_or_fetch() { fi log_warn "Some input clips are missing (list: $tmp_list)" - [ -z "$tu" ] && tu="$TAR_URL" + + if [ -z "$tu" ]; then + tu="$TAR_URL" + fi if command -v ensure_network_online >/dev/null 2>&1; then if ! ensure_network_online; then @@ -857,18 +1226,36 @@ video_ensure_clips_present_or_fetch() { # JUnit helper # ----------------------------------------------------------------------------- video_junit_append_case() { - of="$1"; class="$2"; name="$3"; t="$4"; st="$5"; logf="$6" - [ -n "$of" ] || return 0 + of="$1" + class="$2" + name="$3" + t="$4" + st="$5" + logf="$6" + + if [ -z "$of" ]; then + return 0 + fi + 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' ;; + PASS) + : + ;; + SKIP) + printf ' \n' + ;; + FAIL) + printf ' \n' "failed" + printf '%s\n' "$tailtxt" + printf ' \n' + ;; esac printf ' \n' } >> "$of" @@ -877,9 +1264,10 @@ video_junit_append_case() { # ----------------------------------------------------------------------------- # Timeout wrapper availability + single run # ----------------------------------------------------------------------------- -video_have_run_with_timeout() { video_exist_cmd run_with_timeout; } +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}" @@ -888,18 +1276,15 @@ video_prepare_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" @@ -912,10 +1297,16 @@ video_prepare_app() { } video_run_once() { - cfg="$1"; logf="$2"; tmo="$3"; suc="$4"; lvl="$5" -# NEW: ensure the app is executable (or stage a temp copy) + cfg="$1" + logf="$2" + tmo="$3" + suc="$4" + lvl="$5" + 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" @@ -926,15 +1317,23 @@ video_run_once() { } >>"$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 + 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 + 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 + 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" @@ -950,38 +1349,46 @@ video_run_once() { # ----------------------------------------------------------------------------- # 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 + dst="$FW_PATH_KODIAK" 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; } + if ! cp -f "$src" "$tmp" 2>/dev/null; then + log_warn "FW copy to temp failed: $tmp" + return 1 + fi + 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; } + + if ! mv -f "$tmp" "$dst" 2>/dev/null; then + log_warn "FW mv into place failed: $dst" + rm -f "$tmp" 2>/dev/null || true + return 1 + fi + sync || true + if command -v restorecon >/dev/null 2>&1; then restorecon "$dst" 2>/dev/null || true fi @@ -990,18 +1397,15 @@ video_kodiak_install_fw() { 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; } + if [ ! -d "$src_dir" ]; then + log_warn "Backup dir not found: $src_dir" + return 0 + fi + mkdir -p "$(dirname "$dest")" 2>/dev/null || true candidates="" @@ -1010,10 +1414,13 @@ video_kodiak_install_firmware() { "$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 + "$src_dir"/vpu20_p1_gen2*.mbn.* \ + ; do for f in $g; do - [ -f "$f" ] && candidates="$candidates + if [ -f "$f" ]; then + candidates="$candidates $f" + fi done done @@ -1022,18 +1429,20 @@ $f" 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 + 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)" + + if [ -z "$newest" ]; then + newest="$(printf '%s\n' "$candidates" | head -n1)" + fi 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" @@ -1041,6 +1450,7 @@ $f" 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 @@ -1051,101 +1461,159 @@ $f" } # remoteproc reload must reference the *destination* basename +video_kodiak_fw_basename() { + printf '%s\n' "vpu20_p1_gen2.mbn" +} + video_kodiak_try_remoteproc_reload() { - bn="$(video_kodiak_fw_basename)" # always "vpu20_p1_gen2.mbn" + bn="$(video_kodiak_fw_basename)" 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* ) + *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 + 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 + video_usleep 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; } + + if [ $did -eq 1 ]; then + log_info "remoteproc reload attempted with $bn" + return 0 + fi + 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 + video_usleep 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 + if insmod "$ko" 2>/dev/null; then + rc=0 + else + rc=1 + fi fi fi - [ $rc -eq 0 ] && log_info "iris_vpu module reloaded" + + if [ $rc -eq 0 ]; then + log_info "iris_vpu module reloaded" + fi + 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 + 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 + video_usleep 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; } + + if [ $did -eq 1 ]; then + log_info "platform unbind/bind attempted" + return 0 + fi + 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; } + + if video_kodiak_try_remoteproc_reload; then + video_clean_and_refresh_v4l + return 0 + fi + + if video_kodiak_try_module_reload; then + video_clean_and_refresh_v4l + return 0 + fi + + if video_kodiak_try_unbind_bind; then + video_clean_and_refresh_v4l + return 0 + fi + 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 + if insmod "$p" 2>/dev/null; then + return 0 + fi fi + return 1 } fi @@ -1154,14 +1622,14 @@ fi : "${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 + if [ ! -f "$BLACKLIST_FILE" ]; then + return 1 + fi 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 @@ -1171,12 +1639,12 @@ video_ensure_blacklist() { fi } -# Remove any matching "blacklist " lines (idempotent) video_remove_blacklist() { tok="$1" - [ -f "$BLACKLIST_FILE" ] || return 0 + if [ ! -f "$BLACKLIST_FILE" ]; then + return 0 + fi 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" @@ -1186,17 +1654,17 @@ video_remove_blacklist() { # Blacklist desired stack (persistent, cross-boot) # ----------------------------------------------------------------------------- video_apply_blacklist_for_stack() { - plat="$1"; stack="$2" + 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) + else video_remove_blacklist "qcom-iris" video_remove_blacklist "qcom_iris" video_remove_blacklist "iris-vpu" @@ -1205,7 +1673,6 @@ video_apply_blacklist_for_stack() { ;; 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" @@ -1214,8 +1681,7 @@ video_apply_blacklist_for_stack() { video_ensure_blacklist "venus_enc" video_remove_blacklist "iris-vpu" video_remove_blacklist "iris_vpu" - else # upstream - # Unblock venus and iris_vpu + else video_remove_blacklist "venus-core" video_remove_blacklist "venus_core" video_remove_blacklist "venus-dec" @@ -1230,6 +1696,14 @@ video_apply_blacklist_for_stack() { return 1 ;; esac + + depmod -a 2>/dev/null || true + if command -v udevadm >/dev/null 2>&1; then + udevadm control --reload-rules 2>/dev/null || true + udevadm settle 2>/dev/null || true + fi + video_usleep "${MOD_SETTLE_SLEEP}" + return 0 } @@ -1238,20 +1712,24 @@ video_apply_blacklist_for_stack() { # ----------------------------------------------------------------------------- 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 + 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 + printf '%s\n' "downstream" + return 0 fi ;; esac + printf '%s\n' "unknown" return 0 }