docs/scripts(kaisa): add doctor verify mode and HDMI readiness checks

Add a more detailed --verify flow to capture Jack/ELD/subdevices, route per sink, and collect kernel error windows. Improve --fix with readiness gating, retries, and connected-only selection; document single-monitor pcm mapping behavior and ignore local logs/artifacts.

Made-with: Cursor
This commit is contained in:
2026-04-09 22:26:35 +08:00
parent bda6b60c15
commit 60b7cd389f
14 changed files with 1406 additions and 76 deletions

View File

@@ -0,0 +1,213 @@
#!/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_<ts>.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:-<none>}"
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:-<none>}:"
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"