ALSA: usb-audio: Do not expose sticky mixers

Some devices' mixers are sticky, which accept SET_CUR but do absolutely
nothing. Registering these mixers confuses userspace and results in
ineffective volume control.

Check if a mixer is sticky by setting the volume to the maximum or
minimum value and checking for effectiveness afterward. Prevent the
mixer from being registered if it turns out to be sticky.

Quirky device sample:

  usb 7-1: New USB device found, idVendor=0e0b, idProduct=fa01, bcdDevice= 1.00
  usb 7-1: New USB device strings: Mfr=1, Product=2, SerialNumber=3
  usb 7-1: Product: Feaulle Rainbow
  usb 7-1: Manufacturer: Generic
  usb 7-1: SerialNumber: 20210726905926
  (Mic Capture Volume)

Signed-off-by: Rong Zhang <i@rong.moe>
Link: https://patch.msgid.link/20260411-uac-sticky-mixer-v1-3-29d62717befd@rong.moe
Signed-off-by: Takashi Iwai <tiwai@suse.de>
master
Rong Zhang 2026-04-11 01:49:04 +08:00 committed by Takashi Iwai
parent e3ad86a828
commit 86aa1ea1f1
1 changed files with 45 additions and 3 deletions

View File

@ -1232,6 +1232,41 @@ static void init_cur_mix_raw(struct usb_mixer_elem_info *cval, int ch, int idx)
snd_usb_set_cur_mix_value(cval, ch, idx, cval->min);
}
/*
* Additional checks for sticky mixers
*
* Some devices' volume control mixers are sticky, which accept SET_CUR but
* do absolutely nothing.
*
* Prevent sticky mixers from being registered, otherwise they confuses
* userspace and results in ineffective volume control.
*/
static int check_sticky_volume_control(struct usb_mixer_elem_info *cval,
int channel, int saved)
{
int sticky_test_values[] = { cval->min, cval->max };
int test, check, i;
for (i = 0; i < ARRAY_SIZE(sticky_test_values); i++) {
test = sticky_test_values[i];
if (test == saved)
continue;
/* Assume non-sticky on failure. */
if (snd_usb_set_cur_mix_value(cval, channel, 0, test) ||
get_cur_mix_raw(cval, channel, &check) ||
check != saved) /* SET_CUR effective, non-sticky. */
return 0;
}
usb_audio_err(cval->head.mixer->chip,
"%d:%d: sticky mixer values (%d/%d/%d => %d), disabling\n",
cval->head.id, mixer_ctrl_intf(cval->head.mixer),
cval->min, cval->max, cval->res, saved);
return -ENODEV;
}
/*
* Additional checks for the proper resolution
*
@ -1270,7 +1305,7 @@ static void check_volume_control_res(struct usb_mixer_elem_info *cval,
static int get_min_max_with_quirks(struct usb_mixer_elem_info *cval,
int default_min, struct snd_kcontrol *kctl)
{
int i, idx;
int i, idx, ret;
/* for failsafe */
cval->min = default_min;
@ -1319,13 +1354,20 @@ static int get_min_max_with_quirks(struct usb_mixer_elem_info *cval,
if (cval->res == 0)
cval->res = 1;
if (cval->min + cval->res < cval->max) {
if (cval->min < cval->max) {
int saved;
if (get_cur_mix_raw(cval, minchn, &saved) < 0)
goto no_checks;
check_volume_control_res(cval, minchn, saved);
ret = check_sticky_volume_control(cval, minchn, saved);
if (ret < 0) {
snd_usb_set_cur_mix_value(cval, minchn, 0, saved);
return ret;
}
if (cval->min + cval->res < cval->max)
check_volume_control_res(cval, minchn, saved);
snd_usb_set_cur_mix_value(cval, minchn, 0, saved);
}