From 04d896d041eaf7455d8a2fcf13dfa8dd0336de5f Mon Sep 17 00:00:00 2001 From: Srikanth Muppandam Date: Wed, 13 May 2026 22:59:17 +0530 Subject: [PATCH 1/2] audio: add ALSA card registration helper functions Add common audio helper functions for validating ALSA sound card registration from tests. The new helpers provide support to: - dump ALSA/kernel sound inventory for CI debug - detect registered non-dummy ALSA cards - wait for card registration - validate /dev/snd/controlC nodes - optionally validate PCM/playback/capture entries - infer whether audio registration is expected from DT/sysfs - collect audio-focused dmesg triage These helpers are intended for lightweight audio preflight tests and do not start or restart PipeWire, PulseAudio, ADSP, or remoteproc. Signed-off-by: Srikanth Muppandam --- Runner/utils/audio_common.sh | 472 +++++++++++++++++++++++++++++++++++ 1 file changed, 472 insertions(+) diff --git a/Runner/utils/audio_common.sh b/Runner/utils/audio_common.sh index 46fb37ea..50417481 100755 --- a/Runner/utils/audio_common.sh +++ b/Runner/utils/audio_common.sh @@ -2544,3 +2544,475 @@ audio_probe_alsa_capture_profile() { probe_cleanup return 1 } + +############################################################################### +# ALSA sound card registration helpers +############################################################################### +audio_card_log_alsa_inventory() { + log_info "----- ALSA sound inventory -----" + + if [ -f /proc/asound/cards ]; then + log_info "/proc/asound/cards:" + while IFS= read -r line || [ -n "$line" ]; do + log_info "[asound-cards] $line" + done < /proc/asound/cards + else + log_warn "/proc/asound/cards is missing" + fi + + if [ -f /proc/asound/devices ]; then + log_info "/proc/asound/devices:" + while IFS= read -r line || [ -n "$line" ]; do + log_info "[asound-devices] $line" + done < /proc/asound/devices + else + log_warn "/proc/asound/devices is missing" + fi + + if [ -f /proc/asound/pcm ]; then + log_info "/proc/asound/pcm:" + while IFS= read -r line || [ -n "$line" ]; do + log_info "[asound-pcm] $line" + done < /proc/asound/pcm + else + log_warn "/proc/asound/pcm is missing" + fi + + if [ -d /dev/snd ]; then + log_info "/dev/snd:" + for audio_snd_path in /dev/snd/*; do + [ -e "$audio_snd_path" ] || continue + + if command -v stat >/dev/null 2>&1; then + audio_snd_mode="$(stat -c '%A' "$audio_snd_path" 2>/dev/null || printf '%s' '?')" + audio_snd_owner="$(stat -c '%U:%G' "$audio_snd_path" 2>/dev/null || printf '%s' '?')" + audio_snd_type="$(stat -c '%F' "$audio_snd_path" 2>/dev/null || printf '%s' '?')" + log_info "[dev-snd] ${audio_snd_mode} ${audio_snd_owner} ${audio_snd_type} ${audio_snd_path}" + else + log_info "[dev-snd] ${audio_snd_path}" + fi + done + else + log_warn "/dev/snd is missing" + fi + + if [ -d /sys/class/sound ]; then + log_info "/sys/class/sound:" + for audio_sound_path in /sys/class/sound/*; do + [ -e "$audio_sound_path" ] || continue + + audio_sound_target="" + if [ -L "$audio_sound_path" ] && command -v readlink >/dev/null 2>&1; then + audio_sound_target="$(readlink "$audio_sound_path" 2>/dev/null || true)" + fi + + if command -v stat >/dev/null 2>&1; then + audio_sound_mode="$(stat -c '%A' "$audio_sound_path" 2>/dev/null || printf '%s' '?')" + audio_sound_owner="$(stat -c '%U:%G' "$audio_sound_path" 2>/dev/null || printf '%s' '?')" + audio_sound_type="$(stat -c '%F' "$audio_sound_path" 2>/dev/null || printf '%s' '?')" + + if [ -n "$audio_sound_target" ]; then + log_info "[sys-sound] ${audio_sound_mode} ${audio_sound_owner} ${audio_sound_type} ${audio_sound_path} -> ${audio_sound_target}" + else + log_info "[sys-sound] ${audio_sound_mode} ${audio_sound_owner} ${audio_sound_type} ${audio_sound_path}" + fi + else + if [ -n "$audio_sound_target" ]; then + log_info "[sys-sound] ${audio_sound_path} -> ${audio_sound_target}" + else + log_info "[sys-sound] ${audio_sound_path}" + fi + fi + done + else + log_warn "/sys/class/sound is missing" + fi + + if command -v aplay >/dev/null 2>&1; then + log_info "aplay -l:" + aplay -l 2>&1 | while IFS= read -r line || [ -n "$line" ]; do + log_info "[aplay] $line" + done + else + log_info "aplay not available, skipping aplay -l dump" + fi + + if command -v arecord >/dev/null 2>&1; then + log_info "arecord -l:" + arecord -l 2>&1 | while IFS= read -r line || [ -n "$line" ]; do + log_info "[arecord] $line" + done + else + log_info "arecord not available, skipping arecord -l dump" + fi + + log_info "----- End ALSA sound inventory -----" +} + +# Print registered ALSA cards as: +# card_index|card_id|description +audio_card_get_registered_cards() { + if [ ! -f /proc/asound/cards ]; then + return 1 + fi + + awk ' + /^[[:space:]]*[0-9]+[[:space:]]+\[/ { + idx = $1 + id = $0 + desc = $0 + + sub(/^[^[]*\[/, "", id) + sub(/\].*$/, "", id) + + sub(/^[^:]*:[[:space:]]*/, "", desc) + + gsub(/^[[:space:]]+|[[:space:]]+$/, "", id) + gsub(/^[[:space:]]+|[[:space:]]+$/, "", desc) + + print idx "|" id "|" desc + } + ' /proc/asound/cards +} + +# Return 0 if the card line is dummy/loopback-only. +audio_card_is_dummy_or_generic() { + card_line="$1" + + printf '%s\n' "$card_line" | grep -Eiq 'dummy|loopback|aloop|snd-dummy' +} + +# Print only non-dummy registered cards. +audio_card_get_valid_cards() { + audio_card_get_registered_cards 2>/dev/null | while IFS= read -r card_line || [ -n "$card_line" ]; do + [ -n "$card_line" ] || continue + + if audio_card_is_dummy_or_generic "$card_line"; then + continue + fi + + printf '%s\n' "$card_line" + done +} + +# Count non-dummy registered cards. +audio_card_count_valid_cards() { + count="$(audio_card_get_valid_cards 2>/dev/null | wc -l | awk '{print $1}')" + + if [ -z "$count" ]; then + count=0 + fi + + printf '%s\n' "$count" +} + +# Print valid cards matching a case-insensitive substring. +# Empty match means all valid cards. +audio_card_find_matching_cards() { + match="$1" + + if [ -z "$match" ]; then + audio_card_get_valid_cards + return $? + fi + + audio_card_get_valid_cards 2>/dev/null | awk -v pat="$match" ' + BEGIN { + pat = tolower(pat) + } + { + line = tolower($0) + if (index(line, pat) > 0) { + print $0 + } + } + ' +} + +# Return 0 if current DT/sysfs suggests audio card registration is expected. +audio_card_dt_audio_expected() { + if [ -d /sys/class/sound ]; then + for card_path in /sys/class/sound/card*; do + if [ -e "$card_path" ]; then + return 0 + fi + done + fi + + if [ ! -d /proc/device-tree ]; then + return 1 + fi + + if find /proc/device-tree -type d \( \ + -name "sound" -o \ + -name "*sound*" -o \ + -name "*audio*" \ + \) 2>/dev/null | grep -q .; then + return 0 + fi + + compat_match_file="$(mktemp "${TMPDIR:-/tmp}/audio_compat_match.XXXXXX" 2>/dev/null || printf '%s\n' "${TMPDIR:-/tmp}/audio_compat_match.$$")" + : > "$compat_match_file" 2>/dev/null || return 1 + + find /proc/device-tree -name compatible -type f 2>/dev/null | + while IFS= read -r compat_file || [ -n "$compat_file" ]; do + [ -n "$compat_file" ] || continue + + if tr '\000' '\n' < "$compat_file" 2>/dev/null | grep -Eiq 'qcom,.*(sound|audio|snd|lpass|wsa|rx-macro|tx-macro|va-macro|codec|swr|soundwire)'; then + printf '%s\n' "$compat_file" > "$compat_match_file" + break + fi + done + + if [ -s "$compat_match_file" ]; then + rm -f "$compat_match_file" 2>/dev/null || true + return 0 + fi + + rm -f "$compat_match_file" 2>/dev/null || true + return 1 +} + +# Wait for at least one valid ALSA card, optionally matching AUDIO_CARD_MATCH. +audio_card_wait_for_cards() { + wait_secs="$1" + card_match="$2" + elapsed=0 + + case "$wait_secs" in + ''|*[!0-9]*) + wait_secs=30 + ;; + esac + + log_info "Waiting up to ${wait_secs}s for ALSA sound card registration" + + while [ "$elapsed" -le "$wait_secs" ]; do + if [ -n "$card_match" ]; then + if audio_card_find_matching_cards "$card_match" | grep -q .; then + return 0 + fi + else + card_count="$(audio_card_count_valid_cards)" + if [ "$card_count" -gt 0 ] 2>/dev/null; then + return 0 + fi + fi + + if [ "$elapsed" -eq "$wait_secs" ]; then + break + fi + + sleep 1 + elapsed=$((elapsed + 1)) + + case "$elapsed" in + 5|10|15|20|25|30|45|60) + log_info "Still waiting for ALSA card registration, elapsed=${elapsed}s" + ;; + esac + done + + return 1 +} + +# Validate /dev/snd/controlC for every matched valid card. +audio_card_validate_control_nodes() { + cards_file="$1" + failed=0 + + if [ ! -s "$cards_file" ]; then + log_fail "No matched ALSA cards provided for control node validation" + return 1 + fi + + while IFS='|' read -r card_idx card_id card_desc || [ -n "$card_idx" ]; do + [ -n "$card_idx" ] || continue + + control_node="/dev/snd/controlC${card_idx}" + + if [ -e "$control_node" ]; then + log_pass "ALSA control node present for card ${card_idx}, ${control_node}, id='${card_id}', desc='${card_desc}'" + else + log_fail "Missing ALSA control node for card ${card_idx}, expected ${control_node}, id='${card_id}', desc='${card_desc}'" + failed=1 + fi + done < "$cards_file" + + if [ "$failed" -eq 0 ]; then + return 0 + fi + + return 1 +} + +# Validate PCM entries for every matched valid card. +# mode: +# any - require any PCM for the card +# playback - require playback PCM +# capture - require capture PCM +audio_card_validate_pcm_nodes() { + cards_file="$1" + mode="$2" + failed=0 + total_checked=0 + pcm_tmp_file="" + + if [ -z "$mode" ]; then + mode="any" + fi + + case "$mode" in + any|playback|capture) + ;; + *) + log_warn "Invalid PCM validation mode '${mode}', using any" + mode="any" + ;; + esac + + if [ ! -s "$cards_file" ]; then + log_fail "No matched ALSA cards provided for PCM validation" + return 1 + fi + + if [ ! -f /proc/asound/pcm ]; then + log_fail "/proc/asound/pcm is missing" + return 1 + fi + + pcm_tmp_file="$(mktemp "${TMPDIR:-/tmp}/audio_pcm_lines.XXXXXX" 2>/dev/null || printf '%s\n' "${TMPDIR:-/tmp}/audio_pcm_lines.$$")" + : > "$pcm_tmp_file" 2>/dev/null || return 1 + + while IFS='|' read -r card_idx card_id card_desc || [ -n "$card_idx" ]; do + [ -n "$card_idx" ] || continue + + case "$card_idx" in + ''|*[!0-9]*) + log_warn "Invalid ALSA card index '${card_idx}', skipping PCM validation for this entry" + continue + ;; + esac + + card_prefix="$(printf '%02d' "$card_idx" 2>/dev/null || printf '%s' "$card_idx")" + card_has_pcm=0 + card_has_mode=0 + card_checked=0 + + grep "^${card_prefix}-" /proc/asound/pcm 2>/dev/null > "$pcm_tmp_file" || true + + if [ ! -s "$pcm_tmp_file" ]; then + log_fail "PCM entry missing for card ${card_idx}, id='${card_id}', desc='${card_desc}'" + failed=1 + continue + fi + + while IFS= read -r pcm_line || [ -n "$pcm_line" ]; do + [ -n "$pcm_line" ] || continue + + card_has_pcm=1 + + pcm_dev="$(printf '%s\n' "$pcm_line" | sed -n 's/^[0-9][0-9]-\([0-9][0-9]\):.*/\1/p')" + [ -n "$pcm_dev" ] || continue + + pcm_dev_num="$(printf '%s\n' "$pcm_dev" | sed 's/^0*//')" + if [ -z "$pcm_dev_num" ]; then + pcm_dev_num=0 + fi + + if printf '%s\n' "$pcm_line" | grep -qi 'playback'; then + if [ "$mode" = "any" ] || [ "$mode" = "playback" ]; then + card_has_mode=1 + card_checked=$((card_checked + 1)) + total_checked=$((total_checked + 1)) + pcm_node="/dev/snd/pcmC${card_idx}D${pcm_dev_num}p" + + if [ -e "$pcm_node" ]; then + if [ "$mode" = "any" ]; then + log_pass "Advertised playback PCM node present, ${pcm_node}, card=${card_idx}, id='${card_id}', desc='${card_desc}'" + fi + else + log_fail "Advertised playback PCM node missing, expected ${pcm_node}, line='${pcm_line}', card=${card_idx}, id='${card_id}', desc='${card_desc}'" + failed=1 + fi + fi + fi + + if printf '%s\n' "$pcm_line" | grep -qi 'capture'; then + if [ "$mode" = "any" ] || [ "$mode" = "capture" ]; then + card_has_mode=1 + card_checked=$((card_checked + 1)) + total_checked=$((total_checked + 1)) + pcm_node="/dev/snd/pcmC${card_idx}D${pcm_dev_num}c" + + if [ -e "$pcm_node" ]; then + if [ "$mode" = "any" ]; then + log_pass "Advertised capture PCM node present, ${pcm_node}, card=${card_idx}, id='${card_id}', desc='${card_desc}'" + fi + else + log_fail "Advertised capture PCM node missing, expected ${pcm_node}, line='${pcm_line}', card=${card_idx}, id='${card_id}', desc='${card_desc}'" + failed=1 + fi + fi + fi + done < "$pcm_tmp_file" + + if [ "$mode" = "any" ]; then + if [ "$card_has_pcm" -eq 1 ]; then + log_pass "PCM entry present for card ${card_idx}, id='${card_id}', desc='${card_desc}'" + else + log_fail "PCM entry missing for card ${card_idx}, id='${card_id}', desc='${card_desc}'" + failed=1 + fi + elif [ "$mode" = "playback" ]; then + if [ "$card_has_mode" -eq 1 ]; then + log_pass "Playback PCM validation passed for card ${card_idx}, count=${card_checked}, id='${card_id}', desc='${card_desc}'" + else + log_fail "Playback PCM entry missing for card ${card_idx}, id='${card_id}', desc='${card_desc}'" + failed=1 + fi + elif [ "$mode" = "capture" ]; then + if [ "$card_has_mode" -eq 1 ]; then + log_pass "Capture PCM validation passed for card ${card_idx}, count=${card_checked}, id='${card_id}', desc='${card_desc}'" + else + log_fail "Capture PCM entry missing for card ${card_idx}, id='${card_id}', desc='${card_desc}'" + failed=1 + fi + fi + done < "$cards_file" + + rm -f "$pcm_tmp_file" 2>/dev/null || true + + if [ "$total_checked" -eq 0 ]; then + log_warn "No advertised PCM device nodes were validated from /proc/asound/pcm, mode=${mode}" + else + log_info "Validated advertised PCM device nodes, mode=${mode}, count=${total_checked}" + fi + + if [ "$failed" -eq 0 ]; then + return 0 + fi + + return 1 +} + +# Audio-card focused dmesg scan. +audio_card_dmesg_scan() { + outdir="$1" + + if [ -z "$outdir" ]; then + outdir="." + fi + + audio_dmesg_modules="snd|asoc|audio|lpass|q6|q6afe|q6asm|q6adm|apr|glink|wsa|rx-macro|tx-macro|va-macro|soundwire|swr|codec|remoteproc" + audio_dmesg_benign="dummy regulator|probe deferred|deferred probe pending" + + if command -v scan_dmesg_errors >/dev/null 2>&1; then + scan_dmesg_errors \ + "$outdir" \ + "$audio_dmesg_modules" \ + "$audio_dmesg_benign" || true + else + log_warn "scan_dmesg_errors helper not available, skipping audio dmesg scan" + fi +} From a469ecd6624e4b2586d62c0bf764aa2de8ea9883 Mon Sep 17 00:00:00 2001 From: Srikanth Muppandam Date: Wed, 13 May 2026 23:00:01 +0530 Subject: [PATCH 2/2] audio: add Audio_Card_Registration test Add a new Audio_Card_Registration test to validate that the kernel/ALSA audio card is registered after boot. The test validates: - ALSA card presence under /proc/asound/cards - non-dummy sound card registration - /dev/snd/controlC node creation - optional PCM/playback/capture entries - audio-related dmesg diagnostics This provides a fast audio preflight check before running playback or record tests. It helps distinguish kernel/ASoC card registration issues from userspace audio backend, routing, clip, or mixer failures. Signed-off-by: Srikanth Muppandam --- .../Audio_Card_Registration.yaml | 25 ++ .../README_Audio_Card_Registration.md | 276 ++++++++++++ .../Audio/Audio_Card_Registration/run.sh | 414 ++++++++++++++++++ 3 files changed, 715 insertions(+) create mode 100755 Runner/suites/Multimedia/Audio/Audio_Card_Registration/Audio_Card_Registration.yaml create mode 100644 Runner/suites/Multimedia/Audio/Audio_Card_Registration/README_Audio_Card_Registration.md create mode 100755 Runner/suites/Multimedia/Audio/Audio_Card_Registration/run.sh diff --git a/Runner/suites/Multimedia/Audio/Audio_Card_Registration/Audio_Card_Registration.yaml b/Runner/suites/Multimedia/Audio/Audio_Card_Registration/Audio_Card_Registration.yaml new file mode 100755 index 00000000..2ad497ee --- /dev/null +++ b/Runner/suites/Multimedia/Audio/Audio_Card_Registration/Audio_Card_Registration.yaml @@ -0,0 +1,25 @@ +metadata: + name: audio-card-registration + format: "Lava-Test Test Definition 1.0" + description: "Validate Qualcomm ALSA audio card registration after boot" + os: + - linux + scope: + - functional + +params: + AUDIO_CARD_WAIT_SECS: "30" + AUDIO_CARD_REQUIRED: "auto" + AUDIO_CARD_MATCH: "" + REQUIRE_CONTROL_NODE: "1" + REQUIRE_PCM_NODE: "1" + REQUIRE_PLAYBACK_PCM: "1" + REQUIRE_CAPTURE_PCM: "1" + DMESG_SCAN: "1" + +run: + steps: + - REPO_PATH=$PWD + - cd Runner/suites/Multimedia/Audio/Audio_Card_Registration/ + - ./run.sh --wait-secs "${AUDIO_CARD_WAIT_SECS}" --required "${AUDIO_CARD_REQUIRED}" --card-match "${AUDIO_CARD_MATCH}" --require-control-node "${REQUIRE_CONTROL_NODE}" --require-pcm-node "${REQUIRE_PCM_NODE}" --require-playback-pcm "${REQUIRE_PLAYBACK_PCM}" --require-capture-pcm "${REQUIRE_CAPTURE_PCM}" --dmesg-scan "${DMESG_SCAN}" || true + - $REPO_PATH/Runner/utils/send-to-lava.sh Audio_Card_Registration.res diff --git a/Runner/suites/Multimedia/Audio/Audio_Card_Registration/README_Audio_Card_Registration.md b/Runner/suites/Multimedia/Audio/Audio_Card_Registration/README_Audio_Card_Registration.md new file mode 100644 index 00000000..cc4d287a --- /dev/null +++ b/Runner/suites/Multimedia/Audio/Audio_Card_Registration/README_Audio_Card_Registration.md @@ -0,0 +1,276 @@ +# Audio_Card_Registration + +## Overview + +`Audio_Card_Registration` validates that the kernel/ALSA audio card is registered correctly after boot on Qualcomm Linux platforms. + +This test is intended as an audio preflight check before running playback or record tests. It verifies the ALSA registration layer only and does not play audio, record audio, restart audio services, or modify mixer controls. + +## Test Location + +```text +Runner/suites/Multimedia/Audio/Audio_Card_Registration/ +``` + +Main files: + +```text +run.sh +Audio_Card_Registration.yaml +``` + +Common helper dependency: + +```text +Runner/utils/audio_common.sh +``` + +## What This Test Validates + +The test checks: + +- ALSA sound card registration through `/proc/asound/cards` +- Non-dummy/non-loopback card detection +- `/dev/snd/controlC` control node availability +- Optional PCM entry validation through `/proc/asound/pcm` +- Optional playback PCM validation +- Optional capture PCM validation +- Audio-related dmesg triage for debug visibility + +## What This Test Does Not Do + +This test intentionally does not: + +- Start or restart PipeWire +- Start or restart PulseAudio +- Start or stop ADSP/remoteproc +- Play audio +- Record audio +- Change mixer controls +- Download or use audio clips + +Playback and record functionality should remain covered by the existing `AudioPlayback` and `AudioRecord` tests. + +## Default Behavior + +By default, the test waits up to 30 seconds for at least one valid ALSA card and validates the matching `/dev/snd/controlC` node. + +Default configuration: + +```sh +AUDIO_CARD_WAIT_SECS=30 +AUDIO_CARD_REQUIRED=auto +AUDIO_CARD_MATCH= +REQUIRE_CONTROL_NODE=1 +REQUIRE_PCM_NODE=0 +REQUIRE_PLAYBACK_PCM=0 +REQUIRE_CAPTURE_PCM=0 +DMESG_SCAN=1 +``` + +## Result File + +The test always writes a single fixed result file: + +```text +Audio_Card_Registration.res +``` + +The result file contains one of: + +```text +Audio_Card_Registration PASS +Audio_Card_Registration FAIL +Audio_Card_Registration SKIP +``` + +## PASS Criteria + +The test reports `PASS` when: + +- At least one valid ALSA sound card is registered, or a card matching `AUDIO_CARD_MATCH` is registered. +- Required `/dev/snd/controlC` nodes are present. +- Optional PCM requirements pass when enabled. + +## SKIP Criteria + +The test reports `SKIP` when: + +- Audio is marked optional and no valid ALSA card is registered. +- `AUDIO_CARD_REQUIRED=auto` decides audio is not expected from DT/sysfs and no valid card appears. +- Required userspace dependencies for the test itself are missing. + +## FAIL Criteria + +The test reports `FAIL` when: + +- Audio is required but no valid ALSA sound card registers. +- `AUDIO_CARD_MATCH` is set and no valid card matches it. +- A matched card exists but `/dev/snd/controlC` is missing while `REQUIRE_CONTROL_NODE=1`. +- PCM validation is enabled and required PCM entries are missing. + +## Command Line Usage + +```sh +./run.sh [options] +``` + +Options: + +```text +--wait-secs N + Wait time for ALSA sound card registration. + Default: 30 + +--required auto|required|optional + auto : infer whether audio is expected from DT/sysfs. + required : fail if no valid ALSA card is registered. + optional : skip if no valid ALSA card is registered. + Default: auto + +--card-match TEXT + Optional case-insensitive substring to match card ID or description. + Example: --card-match qcom + +--require-control-node 0|1 + Require /dev/snd/controlC for matched cards. + Default: 1 + +--require-pcm-node 0|1 + Require at least one /proc/asound/pcm entry for matched cards. + Default: 0 + +--require-playback-pcm 0|1 + Require playback PCM entry for matched cards. + Default: 0 + +--require-capture-pcm 0|1 + Require capture PCM entry for matched cards. + Default: 0 + +--dmesg-scan 0|1 + Enable or disable audio-related dmesg scan. + Default: 1 + +--no-dmesg + Disable audio-related dmesg scan. + +--verbose + Enable verbose mode. + +--help|-h + Show usage. +``` + +## Examples + +Run with default settings: + +```sh +./run.sh +``` + +Require audio card registration explicitly: + +```sh +./run.sh --required required +``` + +Match a specific card string: + +```sh +./run.sh --card-match qcom +``` + +Require any PCM entry: + +```sh +./run.sh --require-pcm-node 1 +``` + +Require both playback and capture PCM entries: + +```sh +./run.sh --require-playback-pcm 1 --require-capture-pcm 1 +``` + +Disable dmesg scan: + +```sh +./run.sh --no-dmesg +``` + +## LAVA YAML Usage + +The matching YAML runs the test and emits only the fixed result file: + +```yaml +run: + steps: + - REPO_PATH=$PWD + - cd Runner/suites/Multimedia/Audio/Audio_Card_Registration/ + - ./run.sh --wait-secs "${AUDIO_CARD_WAIT_SECS}" --required "${AUDIO_CARD_REQUIRED}" --card-match "${AUDIO_CARD_MATCH}" --require-control-node "${REQUIRE_CONTROL_NODE}" --require-pcm-node "${REQUIRE_PCM_NODE}" --require-playback-pcm "${REQUIRE_PLAYBACK_PCM}" --require-capture-pcm "${REQUIRE_CAPTURE_PCM}" --dmesg-scan "${DMESG_SCAN}" || true + - $REPO_PATH/Runner/utils/send-to-lava.sh Audio_Card_Registration.res +``` + +## Recommended CI Defaults + +Use the conservative defaults first: + +```yaml +params: + AUDIO_CARD_WAIT_SECS: "30" + AUDIO_CARD_REQUIRED: "auto" + AUDIO_CARD_MATCH: "" + REQUIRE_CONTROL_NODE: "1" + REQUIRE_PCM_NODE: "0" + REQUIRE_PLAYBACK_PCM: "0" + REQUIRE_CAPTURE_PCM: "0" + DMESG_SCAN: "1" +``` + +After board coverage is confirmed, stricter validation can be enabled per board or per job: + +```yaml +REQUIRE_PCM_NODE: "1" +``` + +or: + +```yaml +REQUIRE_PLAYBACK_PCM: "1" +REQUIRE_CAPTURE_PCM: "1" +``` + +## Debug Output + +The test logs the following inventories for CI debug: + +```text +/proc/asound/cards +/proc/asound/devices +/proc/asound/pcm +/dev/snd +/sys/class/sound +aplay -l, if available +arecord -l, if available +``` + +It also stores matched card data under: + +```text +results/Audio_Card_Registration/matched_cards.txt +``` + +## Notes for Qualcomm Boards + +On Qualcomm boards, this test is useful for identifying early audio bring-up issues such as: + +- Machine driver not probing +- Sound card DT node missing or incorrect +- Codec or macro probe failures +- LPASS/Q6/ASoC registration issues +- SoundWire-related probe issues +- `/dev/snd` node creation problems + +This test should remain lightweight and non-destructive. It should not attempt to recover the audio subsystem by restarting services or remote processors. diff --git a/Runner/suites/Multimedia/Audio/Audio_Card_Registration/run.sh b/Runner/suites/Multimedia/Audio/Audio_Card_Registration/run.sh new file mode 100755 index 00000000..ca298e50 --- /dev/null +++ b/Runner/suites/Multimedia/Audio/Audio_Card_Registration/run.sh @@ -0,0 +1,414 @@ +#!/bin/sh +# Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. +# SPDX-License-Identifier: BSD-3-Clause +# +# Audio card registration validation: +# - validates ALSA sound card registration +# - validates /dev/snd/controlC nodes +# - optionally validates PCM/playback/capture entries +# - does not start/restart PipeWire, PulseAudio, ADSP, or remoteproc +# - does not play or record audio + +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" + break + fi + SEARCH=$(dirname "$SEARCH") +done + +if [ -z "$INIT_ENV" ]; then + echo "[ERROR] Could not find init_env (starting at $SCRIPT_DIR)" >&2 + exit 1 +fi + +if [ -z "${__INIT_ENV_LOADED:-}" ]; then + # shellcheck disable=SC1090 + . "$INIT_ENV" + __INIT_ENV_LOADED=1 +fi + +# shellcheck disable=SC1091 +. "$TOOLS/functestlib.sh" +# shellcheck disable=SC1091 +. "$TOOLS/audio_common.sh" + +TESTNAME="Audio_Card_Registration" + +AUDIO_CARD_WAIT_SECS="${AUDIO_CARD_WAIT_SECS:-30}" +AUDIO_CARD_REQUIRED="${AUDIO_CARD_REQUIRED:-auto}" +AUDIO_CARD_MATCH="${AUDIO_CARD_MATCH:-}" +REQUIRE_CONTROL_NODE="${REQUIRE_CONTROL_NODE:-1}" +REQUIRE_PCM_NODE="${REQUIRE_PCM_NODE:-1}" +REQUIRE_PLAYBACK_PCM="${REQUIRE_PLAYBACK_PCM:-1}" +REQUIRE_CAPTURE_PCM="${REQUIRE_CAPTURE_PCM:-1}" +DMESG_SCAN="${DMESG_SCAN:-1}" +VERBOSE="${VERBOSE:-0}" + +usage() { + cat < for matched cards. + Default: 1 + + --require-pcm-node {0|1} + Require at least one /proc/asound/pcm entry for matched cards. + Default: 0 + + --require-playback-pcm {0|1} + Require playback PCM entry for matched cards. + Default: 0 + + --require-capture-pcm {0|1} + Require capture PCM entry for matched cards. + Default: 0 + + --dmesg-scan {0|1} + Enable or disable audio-related dmesg scan. + Default: 1 + + --no-dmesg + Disable audio-related dmesg scan. + + --verbose + Enable verbose mode. + + --help|-h + Show this help. + +Examples: + $0 + $0 --required required + $0 --card-match qcom + $0 --require-pcm-node 1 + $0 --require-playback-pcm 1 --require-capture-pcm 1 +EOF +} + +while [ $# -gt 0 ]; do + case "$1" in + --wait-secs) + if [ $# -lt 2 ]; then + echo "[ERROR] --wait-secs requires an argument" >&2 + exit 1 + fi + AUDIO_CARD_WAIT_SECS="$2" + shift 2 + ;; + --required) + if [ $# -lt 2 ]; then + echo "[ERROR] --required requires an argument" >&2 + exit 1 + fi + AUDIO_CARD_REQUIRED="$2" + shift 2 + ;; + --card-match) + if [ $# -lt 2 ]; then + echo "[ERROR] --card-match requires an argument" >&2 + exit 1 + fi + AUDIO_CARD_MATCH="$2" + shift 2 + ;; + --require-control-node) + if [ $# -lt 2 ]; then + echo "[ERROR] --require-control-node requires an argument" >&2 + exit 1 + fi + REQUIRE_CONTROL_NODE="$2" + shift 2 + ;; + --require-pcm-node) + if [ $# -lt 2 ]; then + echo "[ERROR] --require-pcm-node requires an argument" >&2 + exit 1 + fi + REQUIRE_PCM_NODE="$2" + shift 2 + ;; + --require-playback-pcm) + if [ $# -lt 2 ]; then + echo "[ERROR] --require-playback-pcm requires an argument" >&2 + exit 1 + fi + REQUIRE_PLAYBACK_PCM="$2" + shift 2 + ;; + --require-capture-pcm) + if [ $# -lt 2 ]; then + echo "[ERROR] --require-capture-pcm requires an argument" >&2 + exit 1 + fi + REQUIRE_CAPTURE_PCM="$2" + shift 2 + ;; + --dmesg-scan) + if [ $# -lt 2 ]; then + echo "[ERROR] --dmesg-scan requires an argument" >&2 + exit 1 + fi + DMESG_SCAN="$2" + shift 2 + ;; + --no-dmesg) + DMESG_SCAN=0 + shift + ;; + --verbose) + VERBOSE=1 + export VERBOSE + shift + ;; + --help|-h) + usage + exit 0 + ;; + *) + echo "[WARN] Unknown option: $1" >&2 + shift + ;; + esac +done + +case "$AUDIO_CARD_WAIT_SECS" in + ''|*[!0-9]*) + log_warn "Invalid AUDIO_CARD_WAIT_SECS='$AUDIO_CARD_WAIT_SECS', using 30" + AUDIO_CARD_WAIT_SECS=30 + ;; +esac + +case "$AUDIO_CARD_REQUIRED" in + auto|required|optional) + ;; + *) + log_warn "Invalid AUDIO_CARD_REQUIRED='$AUDIO_CARD_REQUIRED', using auto" + AUDIO_CARD_REQUIRED="auto" + ;; +esac + +case "$REQUIRE_CONTROL_NODE" in + 0|1) + ;; + *) + log_warn "Invalid REQUIRE_CONTROL_NODE='$REQUIRE_CONTROL_NODE', using 1" + REQUIRE_CONTROL_NODE=1 + ;; +esac + +case "$REQUIRE_PCM_NODE" in + 0|1) + ;; + *) + log_warn "Invalid REQUIRE_PCM_NODE='$REQUIRE_PCM_NODE', using 0" + REQUIRE_PCM_NODE=0 + ;; +esac + +case "$REQUIRE_PLAYBACK_PCM" in + 0|1) + ;; + *) + log_warn "Invalid REQUIRE_PLAYBACK_PCM='$REQUIRE_PLAYBACK_PCM', using 0" + REQUIRE_PLAYBACK_PCM=0 + ;; +esac + +case "$REQUIRE_CAPTURE_PCM" in + 0|1) + ;; + *) + log_warn "Invalid REQUIRE_CAPTURE_PCM='$REQUIRE_CAPTURE_PCM', using 0" + REQUIRE_CAPTURE_PCM=0 + ;; +esac + +case "$DMESG_SCAN" in + 0|1) + ;; + *) + log_warn "Invalid DMESG_SCAN='$DMESG_SCAN', using 1" + DMESG_SCAN=1 + ;; +esac + +test_path="$(find_test_case_by_name "$TESTNAME" 2>/dev/null || echo "$SCRIPT_DIR")" +if ! cd "$test_path"; then + log_fail "cd failed: $test_path" + exit 1 +fi + +RES_FILE="./$TESTNAME.res" +LOGDIR="./results/$TESTNAME" + +mkdir -p "$LOGDIR" 2>/dev/null || true +: > "$RES_FILE" + +if ! CHECK_DEPS_NO_EXIT=1 check_dependencies awk grep sed cat ls find sleep wc; then + log_skip "$TESTNAME SKIP: missing required dependencies" + echo "$TESTNAME SKIP" > "$RES_FILE" + exit 0 +fi + +log_info "--------------------------------------------------------------------------" +log_info "------------------- Starting $TESTNAME Testcase --------------------------" +log_info "Config, AUDIO_CARD_WAIT_SECS=$AUDIO_CARD_WAIT_SECS AUDIO_CARD_REQUIRED=$AUDIO_CARD_REQUIRED AUDIO_CARD_MATCH=${AUDIO_CARD_MATCH:-}" +log_info "Config, REQUIRE_CONTROL_NODE=$REQUIRE_CONTROL_NODE REQUIRE_PCM_NODE=$REQUIRE_PCM_NODE REQUIRE_PLAYBACK_PCM=$REQUIRE_PLAYBACK_PCM REQUIRE_CAPTURE_PCM=$REQUIRE_CAPTURE_PCM DMESG_SCAN=$DMESG_SCAN" + +if command -v detect_platform >/dev/null 2>&1; then + detect_platform >/dev/null 2>&1 || true + log_info "Platform Details: machine='${PLATFORM_MACHINE:-unknown}' target='${PLATFORM_TARGET:-unknown}' kernel='${PLATFORM_KERNEL:-unknown}' arch='${PLATFORM_ARCH:-unknown}'" +else + log_info "Platform Details: kernel='$(uname -r 2>/dev/null || echo unknown)' arch='$(uname -m 2>/dev/null || echo unknown)'" +fi + +audio_expected=0 + +case "$AUDIO_CARD_REQUIRED" in + required) + audio_expected=1 + log_info "Audio card registration is marked required" + ;; + optional) + audio_expected=0 + log_info "Audio card registration is marked optional" + ;; + auto) + if audio_card_dt_audio_expected; then + audio_expected=1 + log_info "Audio card registration appears expected from DT/sysfs" + else + audio_expected=0 + log_info "Audio card registration does not appear expected from DT/sysfs" + fi + ;; +esac + +audio_card_log_alsa_inventory + +if ! audio_card_wait_for_cards "$AUDIO_CARD_WAIT_SECS" "$AUDIO_CARD_MATCH"; then + audio_card_log_alsa_inventory + + if [ "$audio_expected" -eq 1 ]; then + if [ -n "$AUDIO_CARD_MATCH" ]; then + log_fail "$TESTNAME FAIL: no valid ALSA card matched '$AUDIO_CARD_MATCH'" + else + log_fail "$TESTNAME FAIL: no valid ALSA sound card registered" + fi + + if [ "$DMESG_SCAN" -eq 1 ]; then + audio_card_dmesg_scan "$LOGDIR" + fi + + echo "$TESTNAME FAIL" > "$RES_FILE" + exit 0 + fi + + if [ -n "$AUDIO_CARD_MATCH" ]; then + log_skip "$TESTNAME SKIP: no valid ALSA card matched '$AUDIO_CARD_MATCH' and audio is optional" + else + log_skip "$TESTNAME SKIP: no valid ALSA sound card registered and audio is optional" + fi + + if [ "$DMESG_SCAN" -eq 1 ]; then + audio_card_dmesg_scan "$LOGDIR" + fi + + echo "$TESTNAME SKIP" > "$RES_FILE" + exit 0 +fi + +MATCHED_CARDS_FILE="$LOGDIR/matched_cards.txt" +: > "$MATCHED_CARDS_FILE" + +if [ -n "$AUDIO_CARD_MATCH" ]; then + audio_card_find_matching_cards "$AUDIO_CARD_MATCH" > "$MATCHED_CARDS_FILE" +else + audio_card_get_valid_cards > "$MATCHED_CARDS_FILE" +fi + +if [ ! -s "$MATCHED_CARDS_FILE" ]; then + log_fail "$TESTNAME FAIL: card wait succeeded but no matched card inventory was captured" + echo "$TESTNAME FAIL" > "$RES_FILE" + exit 0 +fi + +log_info "Matched ALSA cards:" +while IFS='|' read -r card_idx card_id card_desc || [ -n "$card_idx" ]; do + [ -n "$card_idx" ] || continue + log_info "[matched-card] index=${card_idx} id='${card_id}' desc='${card_desc}'" +done < "$MATCHED_CARDS_FILE" + +test_failed=0 + +if [ "$REQUIRE_CONTROL_NODE" -eq 1 ]; then + if ! audio_card_validate_control_nodes "$MATCHED_CARDS_FILE"; then + test_failed=1 + fi +else + log_info "Skipping ALSA control node validation, REQUIRE_CONTROL_NODE=0" +fi + +if [ "$REQUIRE_PCM_NODE" -eq 1 ]; then + if ! audio_card_validate_pcm_nodes "$MATCHED_CARDS_FILE" "any"; then + test_failed=1 + fi +else + log_info "Skipping generic PCM node validation, REQUIRE_PCM_NODE=0" +fi + +if [ "$REQUIRE_PLAYBACK_PCM" -eq 1 ]; then + if ! audio_card_validate_pcm_nodes "$MATCHED_CARDS_FILE" "playback"; then + test_failed=1 + fi +else + log_info "Skipping playback PCM validation, REQUIRE_PLAYBACK_PCM=0" +fi + +if [ "$REQUIRE_CAPTURE_PCM" -eq 1 ]; then + if ! audio_card_validate_pcm_nodes "$MATCHED_CARDS_FILE" "capture"; then + test_failed=1 + fi +else + log_info "Skipping capture PCM validation, REQUIRE_CAPTURE_PCM=0" +fi + +if [ "$DMESG_SCAN" -eq 1 ]; then + audio_card_dmesg_scan "$LOGDIR" +fi + +if [ "$test_failed" -eq 0 ]; then + log_pass "$TESTNAME : PASS" + echo "$TESTNAME PASS" > "$RES_FILE" +else + log_fail "$TESTNAME : FAIL" + echo "$TESTNAME FAIL" > "$RES_FILE" +fi + +log_info "------------------- Completed $TESTNAME Testcase --------------------------" +exit 0