Commit 86aa1ea1 authored by Rong Zhang's avatar Rong Zhang Committed by Takashi Iwai
Browse files

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: default avatarRong Zhang <i@rong.moe>
Link: https://patch.msgid.link/20260411-uac-sticky-mixer-v1-3-29d62717befd@rong.moe


Signed-off-by: default avatarTakashi Iwai <tiwai@suse.de>
parent e3ad86a8
Loading
Loading
Loading
Loading
+45 −3
Original line number Diff line number Diff line
@@ -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,12 +1354,19 @@ 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;

			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);