Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions case-lib/relay.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#!/bin/bash

# SPDX-License-Identifier: BSD-3-Clause
# Copyright(c) 2021-2025 Intel Corporation. All rights reserved.

# needs usbrelay package: https://github.com/darrylb123/usbrelay
# param1: --debug | switch name
# param2: switch state
usbrelay_switch()
{
if [[ "$1" == "--debug" ]]; then
dlogi "Debug mode: Current status of all relays:"
usbrelay || {
die "Failed to get usbrelay status.
The usbrelay hw module is not responding or no relays detected.
Check hardware connection."
}
fi

# Declare a constant for the relay settle time
local USBRELAY_SETTLE_TIME=0.5

local switch_name=$1
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does it run with $1 == --debug ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

local state=$2

dlogi "Setting usbrelay switch $switch_name to $state."
usbrelay "$switch_name=$state" --quiet || {
die "Failed to set usbrelay switch $switch_name to $state.
The usbrelay hw module is not responding or no relays detected.
Check hardware connection."
}

# wait for the switch to settle
sleep "$USBRELAY_SETTLE_TIME"

# Display current state of the switch
current_state=$(usbrelay | awk -F= -v name="$switch_name" '$1 == name { print $2 }')

# Check if current_state is equal to the requested state
[[ "$current_state" == "$state" ]] || {
die "usbrelay switch $switch_name failed to set to $state (current: $current_state)"
}

case "$current_state" in
'1') dlogi "Current state of $switch_name is: on";;
'0') dlogi "Current state of $switch_name is: off";;
*) die "Invalid state for $switch_name: $current_state";;
esac
}
2 changes: 2 additions & 0 deletions env-check.sh
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ func_check_pkg aplay
func_check_pkg sox
func_check_pkg tinycap
func_check_pkg tinyplay
# MIC privacy / JACK Audio detection relay switch
func_check_pkg usbrelay
# JACK Audio Connection Kit
func_check_pkg jackd
func_check_pkg jack_iodelay
Expand Down
208 changes: 208 additions & 0 deletions test-case/test-jack-detection-playback-capture.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
#!/bin/bash

# SPDX-License-Identifier: BSD-3-Clause
# Copyright(c) 2025 Intel Corporation. All rights reserved.

##
## Preconditions
# USB relay switch is available and configured.
# Jack detection header should be connected to the USB relay switch
# to the port HURTM_2 (NC) connector.

## Test Description
# Verify jack detection functionality by simulating plugging and unplugging
# of audio jack using a USB relay switch.
# The unplugging and plugging of the jack will be during playback operations
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should the test also try jack detection while no playback is running and its handling is correct in this mode ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this scenario will test in a different test - test_jack_detection_during_suspend

# to ensure the system can handle jack detection events correctly.
# The test will check if the jack detection status is updated correctly
# when the jack is plugged in and unplugged.

## Case Steps
# 1. Ensure the USB relay switch is configured to control the jack detection header.
# 2. Ensure the aplay (playback) command works properly.
# 3. Set the USB relay switch to state on (1), simulate unplugging the headset from the jack.
# 4. Check the jack detection status via amixer. The status should indicate **off**.
# 5. Check if the aplay process is still running after unplugging the jack.
# If the process is not running, the test fails.
# If the process is still running, continue to the next step.
# 6. Set the USB relay switch to state off (0), simulate plugging the headset.
# 7. Check the jack detection status via amixer. The status should indicate **on**.
# 8. Check if the aplay process is still running after plugging the jack.
# If the process is not running, the test fails.
# If the process is still running, continue to the next step.
# 9. Terminate the aplay process.
# 10. Check if the aplay process is terminated successfully.
# If the process is still running, the test fails.
# If the process is terminated successfully, the test passes.
# 11. Check dmesg for any unexpected errors.
#
# Repeat steps 3-11 for all pcm jack playback devices.

TESTDIR=$(realpath -e "$(dirname "${BASH_SOURCE[0]}")/..")
TESTLIB="${TESTDIR}/case-lib"

# shellcheck disable=SC1091 source=case-lib/lib.sh
source "${TESTLIB}/lib.sh"
# shellcheck disable=SC1091 source=case-lib/relay.sh
source "${TESTLIB}/relay.sh"

# shellcheck disable=SC2153
OPT_NAME['t']='tplg' OPT_DESC['t']="tplg file, default value is env TPLG: $TPLG"
OPT_HAS_ARG['t']=1 OPT_VAL['t']="$TPLG"

OPT_NAME['l']='loop' OPT_DESC['l']='loop count'
OPT_HAS_ARG['l']=1 OPT_VAL['l']=1

OPT_NAME['s']='sof-logger' OPT_DESC['s']="Open sof-logger trace the data will store at $LOG_ROOT"
OPT_HAS_ARG['s']=0 OPT_VAL['s']=1

OPT_NAME['u']='relay' OPT_DESC['u']='name of usbrelay switch, default value is HURTM_2'
OPT_HAS_ARG['u']=1 OPT_VAL['u']="HURTM_2"

func_opt_parse_option "$@"

tplg=${OPT_VAL['t']}
relay=${OPT_VAL['u']}
loop_cnt=${OPT_VAL['l']}

DSP_SETTLE_TIME=2

check_control_switch_state()
{
# Check the state of the switch using amixer.
# The switch name is passed as the first argument, and the expected state (on/off)
# is passed as the second argument.
# Returns 0 if the state matches, 1 otherwise.
local control_name="$1"
local expected_control_state="$2"
local control_state

control_state=$(amixer -c "$SOFCARD" contents | awk -v name="$control_name" '
BEGIN {
RS = "";
IGNORECASE = 1;
split(name, parts, " ");
};
$0 ~ parts[1] && $0 ~ parts[2] {
if (match($0, /values=(on|off)/, m)) print m[1];
}
')
dlogi "$control_name switch is: $control_state"

if [[ "$expected_control_state" == "$control_state" ]]; then
return 0
else
return 1
fi
}

testing_one_pcm()
{
dlogi "===== Testing: (PCM: $pcm [$dev]<$type>) (Loop: $i/$loop_cnt) ====="
dlogi "DEVICE: $dev, TYPE: $type, PCM: $pcm, RATE: $rate, CHANNEL: $channel"

dlogi "Command: aplay -Dplug$dev -q /dev/zero"
# Start alsabat in background with longer duration
dlogi "Starting aplay in background..."
aplay "-Dplug$dev" -d10 -q /dev/zero & pid_playback=$! || {
func_lib_lsof_error_dump "$snd"
die "Failed to start aplay on PCM: $pcm"
}
# Wait briefly to ensure the device is ready and avoid read errors
sleep 1
dlogi "aplay started with PID: $pid_playback"

dlogi "Unplug jack audio."
usbrelay_switch "$relay" 1

# Wait for a short period to allow the system to detect the unplug event
sleep $DSP_SETTLE_TIME

# check if the aplay process is still running after unplugging the jack
ps -p "$pid_playback" > /dev/null || {
func_lib_lsof_error_dump "$snd"
die "Playback process terminated unexpectedly after unplugging the jack."
}

check_control_switch_state "headset" "off" || {
die "unplug headset jack failed."
}

check_control_switch_state "headphone" 'off' || {
die "unplug headphone jack failed."
}

dlogi "Plug jack audio."
usbrelay_switch "$relay" 0

# Wait for a short period to allow the system to detect the plug event
sleep $DSP_SETTLE_TIME

# check if the aplay process is still running after unplugging the jack
ps -p "$pid_playback" > /dev/null || {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I hope we already have some code for this, maybe in case-lib/

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO ps -p is more explicit about checking for a process

func_lib_lsof_error_dump "$snd"
die "Playback process terminated unexpectedly after plugging the jack."
}

check_control_switch_state "headset" "on" || {
die "Plug headset jack failed."
}

check_control_switch_state "headphone" "on" || {
die "Plug headphone jack failed."
}

kill -9 $pid_playback > /dev/null 2>&1
wait $pid_playback 2>/dev/null || true
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I hope we already have some code for this, maybe in case-lib/

(every script tries to re-invent this)

ps -p "$pid_playback" > /dev/null && {
dloge "Failed to kill playback process."
func_lib_lsof_error_dump "$snd"
die "Playback process did not terminate as expected."
}
dlogi "Playback process terminated."
dlogi "===== Testing: PASSED ====="
}

main()
{
func_pipeline_export "$tplg" "type:playback"

setup_kernel_check_point

start_test

logger_disabled || func_lib_start_log_collect

# Check if usbrelay tool is installed
command -v usbrelay || {
# If usbrelay package is not installed
skip_test "usbrelay command not found. Please install usbrelay package."
}

# display current status of relays
usbrelay --debug

dlogi "Reset - plug jack audio"
usbrelay_switch "$relay" 0

for idx in $(seq 0 $((PIPELINE_COUNT - 1)))
do
initialize_audio_params "$idx"

[[ "$pcm" == *Jack* ]] || {
dlogi "PCM $pcm is not a Jack, skipping..."
continue
}

for i in $(seq 1 "$loop_cnt")
do
testing_one_pcm
sof-kernel-log-check.sh "$KERNEL_CHECKPOINT"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think in these two nested loops it should either die on the first errors here, or set new kernel check point on each iteration.

setup_kernel_check_point
done
done
}

{
main "$@"; exit "$?"
}