* [PATCH 1/3] ALSA: usb-audio: Add error checks against get_min_max*()
2026-04-10 17:49 [PATCH 0/3] ALSA: usb-audio: Refactor mixer checks and add check for sticky mixers Rong Zhang
@ 2026-04-10 17:49 ` Rong Zhang
2026-04-10 17:49 ` [PATCH 2/3] ALSA: usb-audio: Move volume control resolution check into a function Rong Zhang
` (2 subsequent siblings)
3 siblings, 0 replies; 5+ messages in thread
From: Rong Zhang @ 2026-04-10 17:49 UTC (permalink / raw)
To: Jaroslav Kysela, Takashi Iwai
Cc: Icenowy Zheng, linux-sound, linux-kernel, Rong Zhang,
Takashi Iwai
All callers of get_min_max*() ignore the latter's return code
completely. This means to ignore temporary errors at the probe time.
However, it is not optimal and leads to some maintenance burdens.
Return -EAGAIN for temporary errors, and check against it in the callers
of get_min_max*(). If any other error occurs, bail out of the caller
early.
Suggested-by: Takashi Iwai <tiwai@suse.de>
Link: https://lore.kernel.org/r/87ldewi4j8.wl-tiwai@suse.de
Signed-off-by: Rong Zhang <i@rong.moe>
---
sound/usb/mixer.c | 29 +++++++++++++++++++++--------
1 file changed, 21 insertions(+), 8 deletions(-)
diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c
index a25e8145af67..e5993364c825 100644
--- a/sound/usb/mixer.c
+++ b/sound/usb/mixer.c
@@ -1264,7 +1264,7 @@ static int get_min_max_with_quirks(struct usb_mixer_elem_info *cval,
"%d:%d: cannot get min/max values for control %d (id %d)\n",
cval->head.id, mixer_ctrl_intf(cval->head.mixer),
cval->control, cval->head.id);
- return -EINVAL;
+ return -EAGAIN;
}
if (get_ctl_value(cval, UAC_GET_RES,
(cval->control << 8) | minchn,
@@ -1388,6 +1388,7 @@ static int mixer_ctl_feature_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
struct usb_mixer_elem_info *cval = snd_kcontrol_chip(kcontrol);
+ int ret;
if (cval->val_type == USB_MIXER_BOOLEAN ||
cval->val_type == USB_MIXER_INV_BOOLEAN)
@@ -1398,8 +1399,9 @@ static int mixer_ctl_feature_info(struct snd_kcontrol *kcontrol,
if (cval->val_type != USB_MIXER_BOOLEAN &&
cval->val_type != USB_MIXER_INV_BOOLEAN) {
if (!cval->initialized) {
- get_min_max_with_quirks(cval, 0, kcontrol);
- if (cval->initialized && cval->dBmin >= cval->dBmax) {
+ ret = get_min_max_with_quirks(cval, 0, kcontrol);
+ if ((ret >= 0 || ret == -EAGAIN) &&
+ cval->initialized && cval->dBmin >= cval->dBmax) {
kcontrol->vd[0].access &=
~(SNDRV_CTL_ELEM_ACCESS_TLV_READ |
SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK);
@@ -1743,6 +1745,7 @@ static void __build_feature_ctl(struct usb_mixer_interface *mixer,
struct snd_kcontrol *kctl;
struct usb_mixer_elem_info *cval;
const struct usbmix_name_map *map;
+ int ret;
if (control == UAC_FU_GRAPHIC_EQUALIZER) {
/* FIXME: not supported yet */
@@ -1856,10 +1859,10 @@ static void __build_feature_ctl(struct usb_mixer_interface *mixer,
}
/* get min/max values */
- get_min_max_with_quirks(cval, 0, kctl);
+ ret = get_min_max_with_quirks(cval, 0, kctl);
/* skip a bogus volume range */
- if (cval->max <= cval->min) {
+ if ((ret < 0 && ret != -EAGAIN) || cval->max <= cval->min) {
usb_audio_dbg(mixer->chip,
"[%d] FU [%s] skipped due to invalid volume\n",
cval->head.id, kctl->id.name);
@@ -2233,6 +2236,7 @@ static void build_mixer_unit_ctl(struct mixer_build *state,
unsigned int i, len;
struct snd_kcontrol *kctl;
const struct usbmix_name_map *map;
+ int ret;
map = find_map(state->map, unitid, 0);
if (check_ignored_ctl(map))
@@ -2255,7 +2259,11 @@ static void build_mixer_unit_ctl(struct mixer_build *state,
}
/* get min/max values */
- get_min_max(cval, 0);
+ ret = get_min_max(cval, 0);
+ if (ret < 0 && ret != -EAGAIN) {
+ usb_mixer_elem_info_free(cval);
+ return;
+ }
kctl = snd_ctl_new1(&usb_feature_unit_ctl, cval);
if (!kctl) {
@@ -2627,7 +2635,7 @@ static int build_audio_procunit(struct mixer_build *state, int unitid,
break;
}
- get_min_max(cval, valinfo->min_value);
+ err = get_min_max(cval, valinfo->min_value);
break;
}
case USB_XU_CLOCK_RATE:
@@ -2639,11 +2647,16 @@ static int build_audio_procunit(struct mixer_build *state, int unitid,
cval->max = 5;
cval->res = 1;
cval->initialized = 1;
+ err = 0;
break;
default:
- get_min_max(cval, valinfo->min_value);
+ err = get_min_max(cval, valinfo->min_value);
break;
}
+ if (err < 0 && err != -EAGAIN) {
+ usb_mixer_elem_info_free(cval);
+ return err;
+ }
err = get_cur_ctl_value(cval, cval->control << 8, &val);
if (err < 0) {
--
2.53.0
^ permalink raw reply related [flat|nested] 5+ messages in thread* [PATCH 2/3] ALSA: usb-audio: Move volume control resolution check into a function
2026-04-10 17:49 [PATCH 0/3] ALSA: usb-audio: Refactor mixer checks and add check for sticky mixers Rong Zhang
2026-04-10 17:49 ` [PATCH 1/3] ALSA: usb-audio: Add error checks against get_min_max*() Rong Zhang
@ 2026-04-10 17:49 ` Rong Zhang
2026-04-10 17:49 ` [PATCH 3/3] ALSA: usb-audio: Do not expose sticky mixers Rong Zhang
2026-04-11 8:03 ` [PATCH 0/3] ALSA: usb-audio: Refactor mixer checks and add check for " Takashi Iwai
3 siblings, 0 replies; 5+ messages in thread
From: Rong Zhang @ 2026-04-10 17:49 UTC (permalink / raw)
To: Jaroslav Kysela, Takashi Iwai
Cc: Icenowy Zheng, linux-sound, linux-kernel, Rong Zhang,
Takashi Iwai
get_min_max_with_quirks() is too lengthy and hard to read.
Move the volume control resolution check code into a function as it's
relatively self-contained.
Suggested-by: Takashi Iwai <tiwai@suse.de>
Link: https://lore.kernel.org/r/87o6jsk3vs.wl-tiwai@suse.de
Signed-off-by: Rong Zhang <i@rong.moe>
---
sound/usb/mixer.c | 65 +++++++++++++++++++++++++++++++++----------------------
1 file changed, 39 insertions(+), 26 deletions(-)
diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c
index e5993364c825..e77c2d78a782 100644
--- a/sound/usb/mixer.c
+++ b/sound/usb/mixer.c
@@ -1232,6 +1232,38 @@ 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 the proper resolution
+ *
+ * Some devices report smaller resolutions than actually reacting.
+ * They don't return errors but simply clip to the lower aligned value.
+ */
+static void check_volume_control_res(struct usb_mixer_elem_info *cval,
+ int channel, int saved)
+{
+ int last_valid_res = cval->res;
+ int test, check;
+
+ for (;;) {
+ test = saved;
+ if (test < cval->max)
+ test += cval->res;
+ else
+ test -= cval->res;
+
+ if (test < cval->min || test > cval->max ||
+ snd_usb_set_cur_mix_value(cval, channel, 0, test) ||
+ get_cur_mix_raw(cval, channel, &check)) {
+ cval->res = last_valid_res;
+ break;
+ }
+ if (test == check)
+ break;
+
+ cval->res *= 2;
+ }
+}
+
/*
* retrieve the minimum and maximum values for the specified control
*/
@@ -1287,37 +1319,18 @@ static int get_min_max_with_quirks(struct usb_mixer_elem_info *cval,
if (cval->res == 0)
cval->res = 1;
- /* Additional checks for the proper resolution
- *
- * Some devices report smaller resolutions than actually
- * reacting. They don't return errors but simply clip
- * to the lower aligned value.
- */
if (cval->min + cval->res < cval->max) {
- int last_valid_res = cval->res;
- int saved, test, check;
+ int saved;
+
if (get_cur_mix_raw(cval, minchn, &saved) < 0)
- goto no_res_check;
- for (;;) {
- test = saved;
- if (test < cval->max)
- test += cval->res;
- else
- test -= cval->res;
- if (test < cval->min || test > cval->max ||
- snd_usb_set_cur_mix_value(cval, minchn, 0, test) ||
- get_cur_mix_raw(cval, minchn, &check)) {
- cval->res = last_valid_res;
- break;
- }
- if (test == check)
- break;
- cval->res *= 2;
- }
+ goto no_checks;
+
+ check_volume_control_res(cval, minchn, saved);
+
snd_usb_set_cur_mix_value(cval, minchn, 0, saved);
}
-no_res_check:
+no_checks:
cval->initialized = 1;
}
--
2.53.0
^ permalink raw reply related [flat|nested] 5+ messages in thread* [PATCH 3/3] ALSA: usb-audio: Do not expose sticky mixers
2026-04-10 17:49 [PATCH 0/3] ALSA: usb-audio: Refactor mixer checks and add check for sticky mixers Rong Zhang
2026-04-10 17:49 ` [PATCH 1/3] ALSA: usb-audio: Add error checks against get_min_max*() Rong Zhang
2026-04-10 17:49 ` [PATCH 2/3] ALSA: usb-audio: Move volume control resolution check into a function Rong Zhang
@ 2026-04-10 17:49 ` Rong Zhang
2026-04-11 8:03 ` [PATCH 0/3] ALSA: usb-audio: Refactor mixer checks and add check for " Takashi Iwai
3 siblings, 0 replies; 5+ messages in thread
From: Rong Zhang @ 2026-04-10 17:49 UTC (permalink / raw)
To: Jaroslav Kysela, Takashi Iwai
Cc: Icenowy Zheng, linux-sound, linux-kernel, Rong Zhang
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>
---
sound/usb/mixer.c | 48 +++++++++++++++++++++++++++++++++++++++++++++---
1 file changed, 45 insertions(+), 3 deletions(-)
diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c
index e77c2d78a782..d4ef45bf53d7 100644
--- a/sound/usb/mixer.c
+++ b/sound/usb/mixer.c
@@ -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);
}
--
2.53.0
^ permalink raw reply related [flat|nested] 5+ messages in thread* Re: [PATCH 0/3] ALSA: usb-audio: Refactor mixer checks and add check for sticky mixers
2026-04-10 17:49 [PATCH 0/3] ALSA: usb-audio: Refactor mixer checks and add check for sticky mixers Rong Zhang
` (2 preceding siblings ...)
2026-04-10 17:49 ` [PATCH 3/3] ALSA: usb-audio: Do not expose sticky mixers Rong Zhang
@ 2026-04-11 8:03 ` Takashi Iwai
3 siblings, 0 replies; 5+ messages in thread
From: Takashi Iwai @ 2026-04-11 8:03 UTC (permalink / raw)
To: Rong Zhang
Cc: Jaroslav Kysela, Takashi Iwai, Icenowy Zheng, linux-sound,
linux-kernel, Takashi Iwai
On Fri, 10 Apr 2026 19:49:01 +0200,
Rong Zhang wrote:
>
> All callers of get_min_max*() ignore the latter's return code
> completely. This means to ignore temporary errors at the probe time.
> However, it is not optimal and leads to some maintenance burdens.
> Besides, get_min_max_with_quirks() is too lengthy and hard to read.
>
> Some devices' mixers are sticky, which accept SET_CUR but do absolutely
> nothing. Registering these mixers confuses userspace and results in
> ineffective volume control.
>
> Patch 1 makes get_min_max*() return -EAGAIN for temporary errors, and
> check against it in the callers of get_min_max*(). If any other error
> occurs, bail out of the caller early.
>
> Patch 2 moves the volume control resolution check code into a function
> as it's relatively self-contained.
>
> Patch 3 checks if a mixer is sticky by setting the volume to the maximum
> or minimum value and checking for effectiveness afterward, and prevents
> 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)
>
> This series is separated from https://lore.kernel.org/r/20260409-feaulle-rainbow-v1-2-09179e09000d@rong.moe
>
> Signed-off-by: Rong Zhang <i@rong.moe>
> ---
> Rong Zhang (3):
> ALSA: usb-audio: Add error checks against get_min_max*()
> ALSA: usb-audio: Move volume control resolution check into a function
> ALSA: usb-audio: Do not expose sticky mixers
Applied all three patches now. Thanks.
Takashi
^ permalink raw reply [flat|nested] 5+ messages in thread