#!/usr/bin/env bash # Kaisa (sof-rt5682) audio one-shot diagnostics (UCM/HiFi mainline). # Collects high-signal state across ALSA/UCM2, PipeWire, WirePlumber, and policy/state files. set -euo pipefail ts="$(date +%Y%m%d_%H%M%S)" out="" fix=0 fix_only=0 verify=0 only_pcm="" retries=1 only_connected=0 log_dir="${KAISA_LOG_DIR:-./_logs}" usage() { cat <<'EOF' Usage: ./scripts/kaisa-audio-doctor.sh [--fix] [--fix-only] [--verify] [--only-pcm N] [--only-connected] [--retries N] [-o|--out /path/to/log] Modes: default Diagnostics only (no changes) --fix Best-effort recovery, then full diagnostics report --fix-only Same recovery as --fix, then exit (for login/boot automation; use with -o for a short log) --verify More detailed verification (route/ports/playback + kernel error window), best used right after boot Options: --only-pcm N Restrict fix/verify to a single PCM (0/2/3/4). Useful for single-monitor A/B tests. --only-connected Restrict fix/verify to HDMI PCMs that are currently connected (Jack=on + ELD non-empty). --retries N Playback retries per PCM (default 1). On failure, restart user audio services then retry. Recovery steps: - restart user PipeWire/WirePlumber - set card profile to HiFi - choose an "available" HDMI port (prefer pcm=3/4, then pcm=2), else fallback to Analog (pcm=0) - enable matching IEC958 switch (pcm=2->IEC958,0; pcm=3->IEC958,1; pcm=4->IEC958,2) - quick playback test (timeout) Notes: - Run as your desktop user (NOT via sudo). - This script assumes card name contains "cml_rt5682_def". EOF } while [[ $# -gt 0 ]]; do case "$1" in --fix-only) fix=1; fix_only=1; shift ;; --fix) fix=1; shift ;; --verify) verify=1; shift ;; --only-pcm) only_pcm="${2:-}"; shift 2 ;; --only-connected) only_connected=1; shift ;; --retries) retries="${2:-}"; shift 2 ;; -o|--out) out="${2:-}"; shift 2 ;; -h|--help) usage; exit 0 ;; --) shift; break ;; -*) echo "Unknown option: $1" >&2 usage >&2 exit 2 ;; *) # Backward compatible: treat the first positional argument as output path. if [[ -z "${out}" ]]; then out="$1" shift else echo "Unexpected argument: $1" >&2 usage >&2 exit 2 fi ;; esac done out="${out:-${log_dir}/kaisa-audio-doctor_${ts}.log}" mkdir -p "$(dirname "$out")" exec > >(tee "$out") 2>&1 hr() { printf '\n%s\n' "================================================================================"; } sec() { hr; printf '%s\n' "$1"; hr; } cmd() { printf '\n$ %s\n' "$*"; "$@"; } maybe() { printf '\n$ %s\n' "$*"; "$@" || true; } note() { printf '\n[NOTE] %s\n' "$*"; } warn() { printf '\n[WARN] %s\n' "$*"; } is_int() { [[ "${1:-}" =~ ^[0-9]+$ ]]; } get_kaisa_card_name() { pactl list cards short 2>/dev/null | awk '/cml_rt5682_def/ {print $2; exit}' } amixer_find_numid() { # Find numid by matching a single control line from `amixer -c0 controls`. # Usage: # amixer_find_numid "iface=PCM" "name='ELD'" "device=2" # amixer_find_numid "iface=CARD" "name='HDMI/DP,pcm=2 Jack'" local want_iface="${1:-}" local want_name="${2:-}" local want_device="${3:-}" amixer -c0 controls 2>/dev/null | awk -v wi="$want_iface" -v wn="$want_name" -v wd="$want_device" ' { ok=1 if (wi!="" && $0 !~ wi) ok=0 if (wn!="" && $0 !~ wn) ok=0 if (wd!="" && $0 !~ wd) ok=0 if (ok) { # line begins with: numid=XX,iface=... if (match($0, /numid=[0-9]+/)) { s=substr($0, RSTART, RLENGTH) sub(/^numid=/, "", s) print s exit 0 } } } ' } amixer_cget_by_control() { # Best-effort cget using stable identifiers; falls back to numid lookup. # For Jack: iface=CARD + exact name # For ELD: iface=PCM + name='ELD' + device=N local iface="${1:?iface}" local name="${2:?name}" local device="${3:-}" local numid if [[ -n "$device" ]]; then numid="$(amixer_find_numid "iface=${iface}" "name='${name}'" "device=${device}" || true)" else numid="$(amixer_find_numid "iface=${iface}" "name='${name}'" "" || true)" fi if [[ -n "$numid" ]]; then maybe amixer -c0 cget "numid=${numid}" else warn "Could not find numid for iface=${iface} name='${name}' device=${device:-}" fi } read_jack_on_for_pcm() { # Returns 0 if Jack is on, 1 otherwise/unknown. # Uses stable name->numid resolution. local pcm="${1:?pcm}" local name="HDMI/DP,pcm=${pcm} Jack" local numid numid="$(amixer_find_numid "iface=CARD" "name='${name}'" "" || true)" [[ -n "$numid" ]] || return 1 # Example output contains ": values=on/off" amixer -c0 cget "numid=${numid}" 2>/dev/null | awk ' BEGIN { ok=0; seen=0 } $1==":" && $2 ~ /^values=/ { seen=1 sub(/^values=/,"",$2) if ($2=="on" || $2=="1") ok=1 else ok=0 exit } END { if (seen && ok) exit 0 exit 1 } ' } eld_bytes_len_for_pcm() { # Return byte count for ELD device=N (0 if empty/unknown). local pcm="${1:?pcm}" local numid numid="$(amixer_find_numid "iface=PCM" "name='ELD'" "device=${pcm}" || true)" [[ -n "$numid" ]] || { echo 0; return 0; } amixer -c0 cget "numid=${numid}" 2>/dev/null | awk ' BEGIN { printed=0 } /values=/ { # Example: "; type=BYTES,...,values=36" if (match($0, /values=[0-9]+/)) { s=substr($0, RSTART, RLENGTH) sub(/^values=/,"",s) print s printed=1 exit } } END { if (!printed) print 0 } ' || true } wait_for_hdmi_ready_pcm() { # Best-effort wait until HDMI pcm looks "ready enough": # - Jack ON # - ELD has bytes # - ALSA subdevices avail > 0 (if we can parse) local pcm="${1:?pcm}" local timeout_s="${2:-6}" local i for i in $(seq 1 "$timeout_s"); do if read_jack_on_for_pcm "$pcm"; then local eld eld="$(eld_bytes_len_for_pcm "$pcm" || echo 0)" local avail total avail="$(alsa_pcm_subdevices_available "$pcm" || true)" total="$(alsa_pcm_subdevices_total "$pcm" || true)" if [[ "$eld" -gt 0 ]]; then if [[ -n "$avail" && -n "$total" && "$total" -gt 0 ]]; then if [[ "$avail" -gt 0 ]]; then return 0 fi else # If we can't parse subdevices, treat Jack+ELD as enough. return 0 fi fi fi sleep 1 done return 1 } connected_hdmi_pcms() { # Return space-separated HDMI PCMs that appear connected: Jack=on AND ELD has bytes. # Ordered by preference (3/4/2). local out=() local pcm for pcm in 3 4 2; do if read_jack_on_for_pcm "$pcm"; then local eld eld="$(eld_bytes_len_for_pcm "$pcm" || echo 0)" if [[ "$eld" -gt 0 ]]; then out+=("$pcm") fi fi done echo "${out[*]-}" } detect_available_pcm() { # Prefer HDMI2/HDMI3 over HDMI1 because pcm=2 is known flaky on some setups. # Returns one of: 3 4 2 0 (fallback to analog). local card_section card_section="$(pactl list cards 2>/dev/null | sed -n '/cml_rt5682_def/,+220p' || true)" if echo "$card_section" | awk 'BEGIN{ok=1} /pcm=3, available/ {ok=0} END{exit ok}'; then echo 3; return 0; fi if echo "$card_section" | awk 'BEGIN{ok=1} /pcm=4, available/ {ok=0} END{exit ok}'; then echo 4; return 0; fi if echo "$card_section" | awk 'BEGIN{ok=1} /pcm=2, available/ {ok=0} END{exit ok}'; then echo 2; return 0; fi echo 0 } alsa_pcm_subdevices_total() { # Return total subdevices for a given DEV from `aplay -l` (expects "子设备: X/Y" or "Subdevices: X/Y"). local dev="${1:?dev}" aplay -l 2>/dev/null | awk -v dev="$dev" ' $0 ~ ("device " dev ":") {inblk=1} inblk && ($0 ~ /子设备:/ || $0 ~ /Subdevices:/) { # formats: "子设备: 0/1" or "Subdevices: 0/1" gsub(/[^0-9\/]/,"",$0) split($0,a,"/") print a[2] exit 0 } inblk && $0 ~ /^card [0-9]+:/ {inblk=0} ' } alsa_pcm_subdevices_available() { # Return available subdevices count X from X/Y for a given DEV. local dev="${1:?dev}" aplay -l 2>/dev/null | awk -v dev="$dev" ' $0 ~ ("device " dev ":") {inblk=1} inblk && ($0 ~ /子设备:/ || $0 ~ /Subdevices:/) { gsub(/[^0-9\/]/,"",$0) split($0,a,"/") print a[1] exit 0 } inblk && $0 ~ /^card [0-9]+:/ {inblk=0} ' } iec958_index_for_pcm() { case "${1:-}" in 2) echo 0 ;; 3) echo 1 ;; 4) echo 2 ;; *) echo "" ;; esac } sink_name_for_pcm() { local pcm="${1:?pcm}" echo "alsa_output.pci-0000_00_1f.3-platform-cml_rt5682_def.HiFi__hw_sofrt5682_${pcm}__sink" } sink_exists() { local sink="${1:?sink}" pactl list short sinks 2>/dev/null | awk -v s="$sink" '$2==s {found=1} END{exit !found}' } kernel_tail_since_ts() { # Print kernel SOF/ASoC HDMI related lines since given timestamp. # ts format: "YYYY-MM-DD HH:MM:SS" local since_ts="${1:?since_ts}" journalctl -k -b --since "$since_ts" --no-pager 2>/dev/null | \ grep -nE 'sof-audio|sof_ipc3_pcm_hw_params|ipc tx error|STREAM_PCM_PARAMS|ASoC error|set_hw_params|HDMI[0-9]|pcm[0-9]+' 2>/dev/null | \ tail -n 200 || true } restart_user_audio_services() { maybe systemctl --user restart pipewire pipewire-pulse wireplumber maybe sleep 2 } verify_one_pcm() { local pcm="${1:?pcm}" local since_ts="${2:?since_ts}" local sink sink="$(sink_name_for_pcm "$pcm")" echo echo "=== VERIFY: pcm=$pcm ===" if [[ "$pcm" -ne 0 ]]; then local jack="off" if read_jack_on_for_pcm "$pcm"; then jack="on"; fi local eld eld="$(eld_bytes_len_for_pcm "$pcm" || echo 0)" local avail total avail="$(alsa_pcm_subdevices_available "$pcm" || true)" total="$(alsa_pcm_subdevices_total "$pcm" || true)" echo "Jack=$jack ELD_bytes=$eld Subdevices=${avail:-?}/${total:-?}" fi if ! sink_exists "$sink"; then warn "Sink missing in PipeWire: $sink" note "pactl list short sinks (for reference):" maybe pactl list short sinks return 2 fi note "Routing to sink: $sink" maybe pactl set-default-sink "$sink" maybe pactl info | sed -n '/默认音频入口/p' maybe wpctl set-mute @DEFAULT_AUDIO_SINK@ 0 maybe wpctl set-volume @DEFAULT_AUDIO_SINK@ 1.0 local iec iec="$(iec958_index_for_pcm "$pcm")" if [[ -n "$iec" ]]; then maybe amixer -c0 sset "IEC958,${iec}" on fi note "pw-play (attempt, 5s timeout)" local attempt=1 local max_attempts="${retries}" if ! is_int "$max_attempts" || [[ "$max_attempts" -lt 1 ]]; then max_attempts=1 fi local ok=0 while [[ "$attempt" -le "$max_attempts" ]]; do note "pw-play attempt ${attempt}/${max_attempts}" if timeout -k 1s 5s pw-play /usr/share/sounds/alsa/Front_Center.wav; then note "pw-play: OK (exit 0)" ok=1 break fi warn "pw-play: FAILED (non-zero exit)" if [[ "$attempt" -lt "$max_attempts" ]]; then note "Restarting user audio services before retry" restart_user_audio_services fi attempt=$((attempt+1)) done note "Kernel error window since $since_ts (tail)" local k k="$(kernel_tail_since_ts "$since_ts")" if [[ -n "$k" ]]; then warn "Kernel shows possible SOF/ASoC/HDMI issues in this window:" printf '%s\n' "$k" return 1 fi if [[ "$ok" -eq 1 ]]; then return 0 fi return 3 } verify_audio() { sec "VERIFY mode (more detailed validation)" note "This runs active route + playback attempts and captures kernel error windows." note "Best run right after boot and BEFORE any manual --fix, to catch first-open races." local since_ts since_ts="$(date '+%F %T')" echo "verify_start_ts=$since_ts" sec "VERIFY snapshot (user services / routing / objects)" maybe systemctl --user status pipewire pipewire-pulse wireplumber --no-pager maybe pactl info maybe pactl list short cards maybe pactl list cards | sed -n '/cml_rt5682_def/,+220p' maybe pactl list short sinks maybe wpctl status sec "VERIFY WirePlumber state files (default-*)" maybe ls -la ~/.local/state/wireplumber maybe sed -n '1,240p' ~/.local/state/wireplumber/default-profile maybe sed -n '1,240p' ~/.local/state/wireplumber/default-nodes maybe sed -n '1,240p' ~/.local/state/wireplumber/default-routes sec "VERIFY ALSA readiness (Jack/ELD/subdevices)" maybe aplay -l echo echo "HDMI/DP Jack + ELD + Subdevices:" local pcm for pcm in 2 3 4; do local jack="off" if read_jack_on_for_pcm "$pcm"; then jack="on"; fi local eld eld="$(eld_bytes_len_for_pcm "$pcm" || echo 0)" local avail total avail="$(alsa_pcm_subdevices_available "$pcm" || true)" total="$(alsa_pcm_subdevices_total "$pcm" || true)" echo "pcm=$pcm Jack=$jack ELD_bytes=$eld Subdevices=${avail:-?}/${total:-?}" done sec "VERIFY per-sink playback attempts (with kernel windows)" local preferred preferred="$(detect_available_pcm)" note "Order preference: detected=$preferred then 3,4,2 then 0" local pcms=() if [[ -n "$only_pcm" ]]; then pcms+=("$only_pcm") elif [[ "$only_connected" -eq 1 ]]; then local connected connected="$(connected_hdmi_pcms)" if [[ -n "$connected" ]]; then note "Restricting VERIFY targets due to --only-connected: ${connected}" # shellcheck disable=SC2206 pcms+=($connected) else warn "--only-connected set but no connected HDMI PCMs detected; falling back to pcm=0" pcms+=(0) fi else pcms+=("$preferred") for pcm in 3 4 2 0; do [[ "$pcm" == "$preferred" ]] || pcms+=("$pcm") done fi local ok_any=0 local ok_pcm="" local ret for pcm in "${pcms[@]}"; do if [[ "$pcm" -ne 0 ]]; then if ! wait_for_hdmi_ready_pcm "$pcm" 6; then note "pcm=$pcm not ready (Jack/ELD/subdevices); skipping verify attempt" continue fi fi verify_one_pcm "$pcm" "$since_ts" || ret=$? ret="${ret:-0}" if [[ "$ret" -eq 0 ]]; then ok_any=1 ok_pcm="$pcm" note "VERIFY succeeded on pcm=$pcm; stopping further attempts to avoid triggering flaky paths." break fi ret="" # Move the window forward so each attempt isolates new kernel lines. since_ts="$(date '+%F %T')" sleep 1 done sec "VERIFY summary" if [[ "$ok_any" -eq 1 ]]; then note "At least one sink attempt completed with no kernel error lines in its window (pcm=${ok_pcm})." note "Default sink is kept at this successful pcm to preserve working audio." note "If you still have silence but no kernel errors: focus on routing/monitor input/volume persistence." else warn "No sink attempt was clean. If windows show ipc tx error -5 / hw_params failures, this points to kernel/SOF." fi } apply_fix() { sec "FIX mode (best-effort recovery)" if [[ "${EUID}" -eq 0 ]]; then warn "You are running as root. Fix mode must run as your desktop user (no sudo)." warn "Abort fix to avoid writing state into the wrong user session." return 1 fi if [[ -z "${XDG_RUNTIME_DIR-}" || ! -d "${XDG_RUNTIME_DIR-}" ]]; then warn "XDG_RUNTIME_DIR is missing; likely not in a desktop session. Abort fix." return 1 fi note "Restarting user audio services" restart_user_audio_services note "Post-restart quick state (forensics)" maybe pactl info | sed -n '/默认音频入口/p' maybe pactl list cards | sed -n '/cml_rt5682_def/,+120p' maybe aplay -l local card card="$(get_kaisa_card_name)" if [[ -z "$card" ]]; then warn "Could not find card name (expected match: cml_rt5682_def). Abort fix." return 1 fi note "Forcing profile to HiFi on card: $card" maybe pactl set-card-profile "$card" HiFi maybe sleep 1 local preferred_pcm preferred_pcm="$(detect_available_pcm)" note "Detected available pcm (from port availability): $preferred_pcm (preference: 3/4/2, fallback 0)" # Try a small ordered set. Start with "available" one, but if ALSA reports 0/1 subdevices or playback fails, # fall back to other HDMI PCMs (3/4/2) then analog (0). local candidates=() if [[ -n "$only_pcm" ]]; then candidates+=("$only_pcm") note "Restricting FIX candidates due to --only-pcm: $only_pcm" elif [[ "$only_connected" -eq 1 ]]; then local connected connected="$(connected_hdmi_pcms)" if [[ -n "$connected" ]]; then note "Restricting FIX candidates due to --only-connected: ${connected}" # shellcheck disable=SC2206 candidates+=($connected) # Always keep analog fallback last, in case HDMI opens but remains silent. candidates+=(0) else warn "--only-connected set but no connected HDMI PCMs detected; falling back to pcm=0" candidates+=(0) fi else candidates+=("$preferred_pcm") for p in 3 4 2 0; do [[ "$p" == "$preferred_pcm" ]] || candidates+=("$p") done fi local pcm sink iec avail total for pcm in "${candidates[@]}"; do # For HDMI outputs, only try PCMs whose Jack is currently ON. if [[ "$pcm" -ne 0 ]]; then if ! wait_for_hdmi_ready_pcm "$pcm" 6; then note "pcm=$pcm not ready (Jack/ELD/subdevices); skipping" continue fi fi # Skip clearly broken HDMI devices (subdevices available == 0). if [[ "$pcm" -ne 0 ]]; then avail="$(alsa_pcm_subdevices_available "$pcm" || true)" total="$(alsa_pcm_subdevices_total "$pcm" || true)" if [[ -n "$avail" && -n "$total" && "$total" -gt 0 && "$avail" -eq 0 ]]; then warn "pcm=$pcm appears unavailable at ALSA level (subdevices ${avail}/${total}); skipping" continue fi fi sink="alsa_output.pci-0000_00_1f.3-platform-cml_rt5682_def.HiFi__hw_sofrt5682_${pcm}__sink" if [[ "$pcm" -eq 0 ]]; then note "Trying fallback Analog (Port1) sink: $sink" else note "Trying HDMI pcm=$pcm sink: $sink" fi maybe pactl set-default-sink "$sink" maybe pactl info | sed -n '/默认音频入口/p' maybe wpctl set-mute @DEFAULT_AUDIO_SINK@ 0 maybe wpctl set-volume @DEFAULT_AUDIO_SINK@ 1.0 iec="$(iec958_index_for_pcm "$pcm")" if [[ -n "$iec" ]]; then note "Enabling IEC958 for pcm=$pcm -> IEC958,$iec" maybe amixer -c0 sset "IEC958,${iec}" on fi note "Quick playback test on pcm=$pcm (timeout -k 1s 5s), retries=${retries}" local attempt=1 local max_attempts="${retries}" if ! is_int "$max_attempts" || [[ "$max_attempts" -lt 1 ]]; then max_attempts=1 fi local since_ts since_ts="$(date '+%F %T')" while [[ "$attempt" -le "$max_attempts" ]]; do note "pw-play attempt ${attempt}/${max_attempts} (pcm=$pcm)" if timeout -k 1s 5s pw-play /usr/share/sounds/alsa/Front_Center.wav; then note "Playback command returned success on pcm=$pcm" note "Post-play quick checks (kernel/user logs)" maybe journalctl --user -u pipewire -b --no-pager | tail -n 40 local k k="$(kernel_tail_since_ts "$since_ts")" if [[ -n "$k" ]]; then warn "Kernel window since $since_ts shows possible SOF/ASoC issues:" printf '%s\n' "$k" fi return 0 fi warn "Playback test failed on pcm=$pcm (attempt ${attempt}/${max_attempts})" local k k="$(kernel_tail_since_ts "$since_ts")" if [[ -n "$k" ]]; then warn "Kernel window since $since_ts shows possible SOF/ASoC issues:" printf '%s\n' "$k" fi if [[ "$attempt" -lt "$max_attempts" ]]; then note "Restarting user audio services before retry" restart_user_audio_services since_ts="$(date '+%F %T')" fi attempt=$((attempt+1)) done warn "Playback test failed on pcm=$pcm; trying next candidate" done warn "Fix mode could not find a working sink automatically. Check cables/monitor input and re-run with only one HDMI plugged." return 1 } sec "Kaisa audio doctor (sof-rt5682) — report: $out" maybe uname -a maybe date maybe id sec "Session sanity (THIS OFTEN EXPLAINS 'no sound')" echo echo "If you run this as root / without a logged-in desktop session:" echo "- systemctl --user will be offline" echo "- /run/user/\$UID may not exist" echo "- PipeWire/WirePlumber won't be running" echo "- ALSA may show 'no soundcards found'" echo echo "Current:" maybe bash -lc 'echo "USER=$USER UID=$UID HOME=$HOME XDG_RUNTIME_DIR=${XDG_RUNTIME_DIR-}"' maybe bash -lc 'if [ -n "${XDG_RUNTIME_DIR-}" ]; then ls -ld "${XDG_RUNTIME_DIR}" 2>/dev/null || true; else echo "XDG_RUNTIME_DIR is empty"; fi' maybe bash -lc 'test -S "${XDG_RUNTIME_DIR-}/pipewire-0" && echo "pipewire socket: OK" || echo "pipewire socket: MISSING"' maybe bash -lc 'test -S "${XDG_RUNTIME_DIR-}/pulse/native" && echo "pulse native socket: OK" || echo "pulse native socket: MISSING"' echo if [[ "${EUID}" -eq 0 ]]; then echo "WARNING: running as root (EUID=0). For best results, run as your desktop user WITHOUT sudo:" echo " ./scripts/kaisa-audio-doctor.sh" fi if [[ "$fix" -eq 1 ]]; then apply_fix || true fi if [[ "$fix_only" -eq 1 ]]; then exit 0 fi if [[ "$verify" -eq 1 ]]; then verify_audio || true fi sec "Versions (PipeWire / WirePlumber / ALSA utils)" maybe pipewire --version maybe wireplumber --version maybe wpctl --version maybe pactl --version maybe pw-play --version maybe speaker-test --version maybe alsaucm --version maybe amixer --version maybe aplay --version sec "User services status" maybe systemctl --user is-system-running maybe systemctl --user status pipewire pipewire-pulse wireplumber --no-pager maybe systemctl --user status pipewire.socket wireplumber.socket --no-pager sec "ALSA enumeration" maybe aplay -l sec "ALSA PCM list (check pipewire/default/pulse)" maybe aplay -L sec "UCM sanity" maybe alsaucm -c sof-rt5682 list _verbs maybe alsaucm -c sof-rt5682 list _devices sec "IEC958 switches (all 0/1/2)" maybe amixer -c0 sget 'IEC958',0 maybe amixer -c0 sget 'IEC958',1 maybe amixer -c0 sget 'IEC958',2 sec "HDMI Jack states (on/off) + ELD controls" amixer_cget_by_control CARD "HDMI/DP,pcm=2 Jack" amixer_cget_by_control CARD "HDMI/DP,pcm=3 Jack" amixer_cget_by_control CARD "HDMI/DP,pcm=4 Jack" amixer_cget_by_control PCM "ELD" 2 amixer_cget_by_control PCM "ELD" 3 amixer_cget_by_control PCM "ELD" 4 sec "Installed files (system paths)" maybe ls -l /usr/share/alsa/ucm2/conf.d/sof-rt5682/sof-rt5682.conf maybe ls -l /usr/share/alsa/ucm2/GoogleKaisa/sof-rt5682/HiFi.conf maybe ls -l /usr/share/wireplumber/main.lua.d/60-kaisa-ucm.lua maybe ls -l /usr/share/wireplumber/main.lua.d/60-kaisa-ucm.lua.disabled sec "Potential conflicting WirePlumber snippets (user/system)" maybe ls -la ~/.config/wireplumber/wireplumber.conf.d maybe ls -la ~/.config/wireplumber/wireplumber.conf.d/*kaisa* 2>/dev/null maybe ls -la /etc/wireplumber/wireplumber.conf.d 2>/dev/null maybe ls -la /etc/wireplumber/wireplumber.conf.d/*kaisa* 2>/dev/null sec "WirePlumber state (profile / nodes / routes)" maybe ls -la ~/.local/state/wireplumber maybe sed -n '1,200p' ~/.local/state/wireplumber/default-profile maybe sed -n '1,200p' ~/.local/state/wireplumber/default-nodes maybe sed -n '1,200p' ~/.local/state/wireplumber/default-routes sec "WirePlumber default-routes: persisted volume sanity" routes="${HOME}/.local/state/wireplumber/default-routes" if [[ -f "$routes" ]]; then low_lines="$(awk -F'channelVolumes=' ' NF >= 2 { rest = $2 sub(/;.*/, "", rest) v = rest + 0.0 if (v > 0 && v < 0.25) print $0 } ' "$routes" 2>/dev/null || true)" if [[ -n "$low_lines" ]]; then warn "Persisted channelVolumes below ~0.25 in default-routes (can sound like silence after reboot):" printf '%s\n' "$low_lines" note "See docs/linux-hdmi/ROOTCAUSE_Reboot_Silent_Analysis.md" else note "No suspiciously low channelVolumes lines in default-routes (threshold 0.25)." fi else note "No default-routes file (yet)." fi sec "PipeWire card / profile / ports (focus: cml_rt5682_def)" maybe pactl list cards short maybe pactl list cards | sed -n '/cml_rt5682_def/,+220p' sec "Sinks (PipeWire) + default sink" maybe pactl info maybe pactl list short sinks maybe wpctl status sec "Kernel hints (SOF/HDMI hw_params IPC errors)" echo echo "Note: if you see PipeWire 'set_hw_params: Input/output error', the real cause is often in kernel logs." echo " This section tries to summarize SOF/ASoC HDMI failures (e.g. ipc tx error -5 for pcm2 HDMI1)." kernel_snip="$( journalctl -k -b --no-pager 2>/dev/null | \ grep -nE 'sof-audio|sof_ipc3_pcm_hw_params|ipc tx error|STREAM_PCM_PARAMS|ASoC error|HDMI1|pcm2' 2>/dev/null | \ tail -n 120 || true )" if [[ -n "${kernel_snip}" ]]; then warn "Kernel log shows SOF/ASoC HDMI failures (tail):" printf '%s\n' "${kernel_snip}" note "If it mentions: sof_ipc3_pcm_hw_params: pcm2 (HDMI1) ... ipc failed ... -5" note "then this is a kernel/SOF issue (not UCM/WirePlumber). Capture full: journalctl -k -b | grep -nE 'sof-audio|ipc tx error|pcm2|HDMI1'" else note "No matching kernel SOF/HDMI error lines found (or insufficient permission to read kernel journal)." fi sec "Quick playback tests (non-destructive)" echo echo "Note: if ALSA 'pulse' PCM is missing, do NOT use: speaker-test -D pulse" echo "Try these instead (they use PipeWire):" echo echo "Tip: these are wrapped with a short timeout to avoid hanging." echo " (uses: timeout -k 1s 5s ... -> TERM then KILL)" maybe timeout -k 1s 5s speaker-test -D pipewire -c2 -t sine -f 440 -l 1 maybe timeout -k 1s 5s speaker-test -D default -c2 -t sine -f 440 -l 1 maybe timeout -k 1s 5s pw-play /usr/share/sounds/alsa/Front_Center.wav maybe timeout -k 1s 5s paplay /usr/share/sounds/alsa/Front_Center.wav sec "Recent logs (journalctl --user, current boot)" maybe journalctl --user -u wireplumber -b --no-pager -n 200 maybe journalctl --user -u pipewire -b --no-pager -n 200 maybe journalctl --user -u pipewire-pulse -b --no-pager -n 200 sec "Hints" cat <<'EOF' - If HDMI ports show "not available", verify cable/monitor input/EDID and re-plug. - If profile keeps reverting after reboot, compare: - ~/.local/state/wireplumber/default-profile - /usr/share/wireplumber/main.lua.d/60-kaisa-ucm.lua (device.profile) - any *kaisa* snippets under ~/.config/wireplumber/ or /etc/wireplumber/ - If set_hw_params errors appear in logs for a given pcm (2/3/4), test only ONE HDMI at a time and switch sink accordingly. EOF hr echo "Done. Report saved to: $out"