#!/usr/bin/env bash # Kaisa (sof-rt5682) HDMI stability probe. # Goal: one script to reproduce/quantify "reboot -> silent -> later OK" and capture # kernel SOF IPC(-5)/ASoC errors with monotonic timestamps. # # What it does (non-destructive, no service stop by default): # - Repeatedly tries playback on HiFi HDMI sinks (pcm=2/3/4 as requested) # - After each attempt, scrapes *new* kernel lines for SOF/ASoC HDMI hw_params failures # - Records ALSA subdevices availability, Jack/ELD states, default sink, and sinks list # # Logs: # ./_logs/kaisa-hdmi-stability-probe_.log (or $KAISA_LOG_DIR) # # Notes: # - Run as desktop user (no sudo). Needs access to kernel journal for best results. # - If journalctl -k is restricted, the script will still log PipeWire errors and ALSA state. set -euo pipefail ts="$(date +%Y%m%d_%H%M%S)" log_dir="${KAISA_LOG_DIR:-./_logs}" out="${log_dir}/kaisa-hdmi-stability-probe_${ts}.log" duration_s="${KAISA_PROBE_DURATION_S:-120}" interval_s="${KAISA_PROBE_INTERVAL_S:-2}" pcms="${KAISA_PROBE_PCMS:-2,3}" # comma-separated list: 2,3,4 audio_file="${KAISA_PROBE_AUDIO_FILE:-/usr/share/sounds/alsa/Front_Center.wav}" usage() { cat <<'EOF' Usage: ./scripts/kaisa-hdmi-stability-probe.sh [--duration S] [--interval S] [--pcms 2,3,4] Env overrides: KAISA_LOG_DIR KAISA_PROBE_DURATION_S KAISA_PROBE_INTERVAL_S KAISA_PROBE_PCMS KAISA_PROBE_AUDIO_FILE Example: KAISA_PROBE_DURATION_S=180 KAISA_PROBE_PCMS=2,3 ./scripts/kaisa-hdmi-stability-probe.sh EOF } while [[ $# -gt 0 ]]; do case "$1" in -h|--help) usage; exit 0 ;; --duration) duration_s="${2:?seconds}"; shift 2 ;; --interval) interval_s="${2:?seconds}"; shift 2 ;; --pcms) pcms="${2:?list}"; shift 2 ;; *) echo "Unknown arg: $1" >&2; usage >&2; exit 2 ;; esac done mkdir -p "$log_dir" exec > >(tee "$out") 2>&1 hr() { printf '\n%s\n' "================================================================================"; } sec() { hr; printf '%s\n' "$1"; hr; } note() { printf '\n[NOTE] %s\n' "$*"; } warn() { printf '\n[WARN] %s\n' "$*"; } kernel_grep_re='sof-audio|sof_ipc3_pcm_hw_params|ipc tx error|STREAM_PCM_PARAMS|ASoC error|HDMI1|HDMI2|HDMI3|pcm2|pcm3|pcm4' pipewire_grep_re='set_hw_params|suspended -> error|Start error|hw_sofrt5682_[234]__sink' get_default_sink() { pactl info 2>/dev/null | awk -F': ' '/^默认音频入口:|^Default Sink:/{print $2; exit}' } list_sinks_short() { pactl list short sinks 2>/dev/null || true } alsa_dump_subdevices() { aplay -l 2>/dev/null | awk ' BEGIN { dev=""; } $0 ~ /^card [0-9]+: / { next } $0 ~ /, device [0-9]+: / { match($0, /device [0-9]+:/) dev=substr($0, RSTART+7, RLENGTH-8) gsub(/[^0-9]/,"",dev) print $0 next } $0 ~ /(子设备:|Subdevices:)/ { print " " $0 next } ' } amixer_dump_jack_eld() { # Stable numids for this platform are often: Jack 10/16/22; ELD 15/21/27. # If they differ, doctor script can resolve dynamically; here we best-effort. for n in 10 16 22 15 21 27; do echo echo "\$ amixer -c0 cget numid=$n" amixer -c0 cget "numid=$n" 2>/dev/null || true done } kernel_tail_since() { # Print kernel lines since a monotonic timestamp (string like "[ 78.312861]") # We do not rely on exact parse; we simply filter journalctl -k -b -o short-monotonic output # by lexicographic comparison on the bracketed numeric value. local since="${1:-}" if ! journalctl -k -b -o short-monotonic --no-pager &>/dev/null; then note "Cannot read kernel journal (journalctl -k)." return 0 fi if [[ -z "$since" ]]; then journalctl -k -b -o short-monotonic --no-pager | grep -E "$kernel_grep_re" | tail -n 80 || true return 0 fi journalctl -k -b -o short-monotonic --no-pager | awk -v since="$since" ' # since is like "[ 78.312861]" function num(s, x) { x=s gsub(/[\[\]]/,"",x) gsub(/^[[:space:]]+/,"",x) return x+0.0 } { tag="" if (match($0, /^\[[^]]+\]/)) { tag=substr($0, RSTART, RLENGTH) } if (tag != "" && num(tag) >= num(since)) print $0 } ' | grep -E "$kernel_grep_re" | tail -n 120 || true } now_monotonic() { # Current kernel journal last monotonic tag, best-effort. # journalctl -o short-monotonic lines start with: "[ 3.588851] ..." # which may tokenize as "[" "3.588851]" ... so we must extract up to the first "]". journalctl -k -b -o short-monotonic --no-pager 2>/dev/null | tail -n 1 | \ awk '{ if (match($0, /^\[[^]]+\]/)) print substr($0, RSTART, RLENGTH); }' || true } play_on_pcm() { local pcm="${1:?pcm}" local sink="alsa_output.pci-0000_00_1f.3-platform-cml_rt5682_def.HiFi__hw_sofrt5682_${pcm}__sink" echo echo "\$ pactl set-default-sink $sink" pactl set-default-sink "$sink" 2>/dev/null || true echo "\$ pw-play $audio_file" timeout -k 1s 5s pw-play "$audio_file" 2>/dev/null || true } sec "Kaisa HDMI stability probe — report: $out" echo "\$ date"; date echo "\$ uname -a"; uname -a echo "\$ id"; id echo note "duration_s=$duration_s interval_s=$interval_s pcms=$pcms audio_file=$audio_file" note "Tip: run right after reboot, before any manual audio tweaks." sec "Baseline: ALSA + Jack/ELD + sinks" echo "\$ aplay -l"; aplay -l || true echo echo "\$ aplay -l (subdevices summary)"; alsa_dump_subdevices || true sec "Jack/ELD (best-effort numid dump)" amixer_dump_jack_eld sec "PipeWire sinks" echo "\$ pactl info"; pactl info || true echo "\$ pactl list short sinks"; list_sinks_short start_epoch="$(date +%s)" end_epoch="$((start_epoch + duration_s))" iter=0 last_mon="$(now_monotonic)" note "Kernel monotonic cursor (initial): ${last_mon:-}" sec "Loop" while [[ "$(date +%s)" -lt "$end_epoch" ]]; do iter="$((iter+1))" echo hr echo "[ITER $iter] time=$(date) default_sink=$(get_default_sink || true)" # Try play on each requested pcm IFS=',' read -r -a pcm_arr <<< "$pcms" for pcm in "${pcm_arr[@]}"; do pcm="$(echo "$pcm" | tr -d '[:space:]')" [[ -n "$pcm" ]] || continue play_on_pcm "$pcm" done echo echo "[ITER $iter] sinks:" list_sinks_short echo echo "[ITER $iter] new kernel hints since ${last_mon:-}:" new_kernel="$(kernel_tail_since "${last_mon:-}")" if [[ -n "${new_kernel}" ]]; then warn "Kernel SOF/ASoC lines (tail):" printf '%s\n' "${new_kernel}" else echo "(none)" fi # Advance cursor last_mon="$(now_monotonic)" sleep "$interval_s" done sec "Done" note "Saved: $out" note "If you hit failures, also capture: journalctl -k -b -o short-monotonic | grep -nE '$kernel_grep_re' | tail -n 200"