* [PATCH] ALSA: usb-audio: Check sticky mixers precisely
@ 2026-08-15 21:47 Rong Zhang
2026-08-16 5:14 ` Alexander Niemeyer
0 siblings, 1 reply; 11+ messages in thread
From: Rong Zhang @ 2026-08-15 21:47 UTC (permalink / raw)
To: Jaroslav Kysela, Takashi Iwai
Cc: Takashi Iwai, linux-sound, linux-kernel, Alexander Niemeyer, Rong Zhang
Some mixers are asynchronous, and some have broken min/max. They are
mistakenly considered sticky due to how the check is implemented.
Check sticky mixers more precisely by checking approximately 16 values
and adding a msleep(10) between each check, so that asynchronous mixers
have enough time to change the value and mixers with broken min/max are
checked properly. Additionally, mark GET_CUR as broken when
get_cur_mix_raw() fails, instead of returning successfully.
Reported-by: Alexander Niemeyer <adventureFAN@gmx.de>
Closes: https://lore.kernel.org/r/6262cbbd-d1f2-4c9d-a1c7-9c5d12636f4b@gmx.de
Signed-off-by: Rong Zhang <i@rong.moe>
---
sound/usb/mixer.c | 51 ++++++++++++++++++++++++++++++++++++++++++++-------
1 file changed, 44 insertions(+), 7 deletions(-)
diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c
index 703c118f9d4e..3d0f97730a06 100644
--- a/sound/usb/mixer.c
+++ b/sound/usb/mixer.c
@@ -1256,22 +1256,59 @@ static void init_cur_mix_raw(struct usb_mixer_elem_info *cval, int ch, int idx)
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;
+ int test, check, res;
+
+ /*
+ * Check approximately 16 values (15 intervals).
+ * If the resolution is not fine enough, check fewer values.
+ */
+ res = DIV_ROUND_UP(cval->max - cval->min, 15);
+ res = res ? roundup(res, cval->res) : cval->res;
+
+ /*
+ * If (cval->max - cval->min) is not a multiple of cval->res, we still
+ * want to test cval->max anyway.
+ */
+ for (test = cval->min; test < cval->max + res; test += res) {
+ if (test > cval->max)
+ test = cval->max;
- 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. */
+ if (snd_usb_set_cur_mix_value(cval, channel, 0, test))
+ return 0;
+
+ if (get_cur_mix_raw(cval, channel, &check))
+ goto get_cur_broken;
+ if (check != saved) /* SET_CUR effective, non-sticky. */
return 0;
+
+ /*
+ * Leave some time for asynchronous mixers to change the value.
+ *
+ * Note that there is no need to wait between SET_CUR and
+ * GET_CUR, as we don't care whether the GET_CUR value matches
+ * the SET_CUR one. IOW, what we expect is just a GET_CUR value
+ * differing from the saved one.
+ *
+ * Mixers of most devices are synchronous. The should have
+ * returned early without extra sleep. Asynchronous mixers will
+ * return once the accumulated time is enough for them to change
+ * the value.
+ */
+ msleep(10);
}
+ /* Check again after the last msleep(). */
+ if (get_cur_mix_raw(cval, channel, &check))
+ goto get_cur_broken;
+ if (check != saved)
+ return 0;
+
if (cval->head.mixer->chip->quirk_flags & QUIRK_FLAG_MIXER_GET_CUR_BROKEN) {
+get_cur_broken:
usb_audio_info(cval->head.mixer->chip,
"%d:%d: broken mixer GET_CUR (%d/%d/%d => %d)\n",
cval->head.id, mixer_ctrl_intf(cval->head.mixer),
---
base-commit: 3eb40771c00a8488fa6ed2cc1fe203477908bf38
change-id: 74676fce-uac-precise-sticky-check-94474a22b57d
Thanks,
Rong
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH] ALSA: usb-audio: Check sticky mixers precisely
2026-08-15 21:47 [PATCH] ALSA: usb-audio: Check sticky mixers precisely Rong Zhang
@ 2026-08-16 5:14 ` Alexander Niemeyer
2026-08-16 13:50 ` Rong Zhang
0 siblings, 1 reply; 11+ messages in thread
From: Alexander Niemeyer @ 2026-08-16 5:14 UTC (permalink / raw)
To: Rong Zhang, Jaroslav Kysela, Takashi Iwai
Cc: Takashi Iwai, linux-sound, linux-kernel
Hi Rong,
I tested the sticky-check part of your patch on the Logitech PRO X
Wireless (046d:0aba) on Fedora 44, kernel 7.1.8-200.fc44.x86_64.
Since your patch is based on a newer tree, I used a minimal backport of
the new ~16-value / 10 ms sticky-check logic to the 7.1.8 code. The
GET_CUR-broken handling from the newer tree was not included; GET_CUR
itself succeeds on this device.
Unfortunately, the playback control is still classified as sticky:
2:0: sticky mixer values (-16384/0/256 => -3840), disabling
I then instrumented the check and tried an additional diagnostic: after
every successful SET_CUR, wait 100 ms and perform another GET_CUR before
issuing the next SET_CUR.
For the playback volume, the saved value was -3840 and GET_CUR remained
at -3840 for every tested value, even after 100 ms, for example:
test=-15104 immediate=-3840 after100ms=-3840
test=-13824 immediate=-3840 after100ms=-3840
test=-3584 immediate=-3840 after100ms=-3840
test=-2304 immediate=-3840 after100ms=-3840
test=-1024 immediate=-3840 after100ms=-3840
test=0 immediate=-3840 after100ms=-3840
So in this case the issue does not appear to be simply that the
accumulated 10 ms sleeps are too short. During the probe-time sticky
check, SET_CUR succeeds but GET_CUR for the playback control remains
unchanged even when each SET_CUR is given 100 ms before the next one.
This differs from my previous direct libusb tests with the AudioControl
interface unbound, where valid SET_CUR values became visible through
GET_CUR after roughly 47–81 ms.
The first debug line I saw with |saved=0| was from the Mic Capture
Volume control; that control changed immediately and returned as
non-sticky. The sequence above with |saved=-3840| is the problematic PCM
Playback Volume control.
I'd be happy to test another version or run additional diagnostics if
useful.
Best regards,
Alexander
Am 15.08.2026 um 23:47 schrieb Rong Zhang:
> Some mixers are asynchronous, and some have broken min/max. They are
> mistakenly considered sticky due to how the check is implemented.
>
> Check sticky mixers more precisely by checking approximately 16 values
> and adding a msleep(10) between each check, so that asynchronous mixers
> have enough time to change the value and mixers with broken min/max are
> checked properly. Additionally, mark GET_CUR as broken when
> get_cur_mix_raw() fails, instead of returning successfully.
>
> Reported-by: Alexander Niemeyer <adventureFAN@gmx.de>
> Closes: https://lore.kernel.org/r/6262cbbd-d1f2-4c9d-a1c7-9c5d12636f4b@gmx.de
> Signed-off-by: Rong Zhang <i@rong.moe>
> ---
> sound/usb/mixer.c | 51 ++++++++++++++++++++++++++++++++++++++++++++-------
> 1 file changed, 44 insertions(+), 7 deletions(-)
>
> diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c
> index 703c118f9d4e..3d0f97730a06 100644
> --- a/sound/usb/mixer.c
> +++ b/sound/usb/mixer.c
> @@ -1256,22 +1256,59 @@ static void init_cur_mix_raw(struct usb_mixer_elem_info *cval, int ch, int idx)
> 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;
> + int test, check, res;
> +
> + /*
> + * Check approximately 16 values (15 intervals).
> + * If the resolution is not fine enough, check fewer values.
> + */
> + res = DIV_ROUND_UP(cval->max - cval->min, 15);
> + res = res ? roundup(res, cval->res) : cval->res;
> +
> + /*
> + * If (cval->max - cval->min) is not a multiple of cval->res, we still
> + * want to test cval->max anyway.
> + */
> + for (test = cval->min; test < cval->max + res; test += res) {
> + if (test > cval->max)
> + test = cval->max;
>
> - 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. */
> + if (snd_usb_set_cur_mix_value(cval, channel, 0, test))
> + return 0;
> +
> + if (get_cur_mix_raw(cval, channel, &check))
> + goto get_cur_broken;
> + if (check != saved) /* SET_CUR effective, non-sticky. */
> return 0;
> +
> + /*
> + * Leave some time for asynchronous mixers to change the value.
> + *
> + * Note that there is no need to wait between SET_CUR and
> + * GET_CUR, as we don't care whether the GET_CUR value matches
> + * the SET_CUR one. IOW, what we expect is just a GET_CUR value
> + * differing from the saved one.
> + *
> + * Mixers of most devices are synchronous. The should have
> + * returned early without extra sleep. Asynchronous mixers will
> + * return once the accumulated time is enough for them to change
> + * the value.
> + */
> + msleep(10);
> }
>
> + /* Check again after the last msleep(). */
> + if (get_cur_mix_raw(cval, channel, &check))
> + goto get_cur_broken;
> + if (check != saved)
> + return 0;
> +
> if (cval->head.mixer->chip->quirk_flags & QUIRK_FLAG_MIXER_GET_CUR_BROKEN) {
> +get_cur_broken:
> usb_audio_info(cval->head.mixer->chip,
> "%d:%d: broken mixer GET_CUR (%d/%d/%d => %d)\n",
> cval->head.id, mixer_ctrl_intf(cval->head.mixer),
>
> ---
> base-commit: 3eb40771c00a8488fa6ed2cc1fe203477908bf38
> change-id: 74676fce-uac-precise-sticky-check-94474a22b57d
>
> Thanks,
> Rong
>
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH] ALSA: usb-audio: Check sticky mixers precisely
2026-08-16 5:14 ` Alexander Niemeyer
@ 2026-08-16 13:50 ` Rong Zhang
[not found] ` <74ca2e17-8fb8-4ede-8e7e-441be815b5b6@gmx.de>
0 siblings, 1 reply; 11+ messages in thread
From: Rong Zhang @ 2026-08-16 13:50 UTC (permalink / raw)
To: Alexander Niemeyer, Jaroslav Kysela, Takashi Iwai
Cc: Takashi Iwai, linux-sound, linux-kernel
Hi Alexander,
On Sun, 2026-08-16 at 07:14 +0200, Alexander Niemeyer wrote:
> Hi Rong,
>
> I tested the sticky-check part of your patch on the Logitech PRO X
> Wireless (046d:0aba) on Fedora 44, kernel 7.1.8-200.fc44.x86_64.
>
> Since your patch is based on a newer tree, I used a minimal backport of
> the new ~16-value / 10 ms sticky-check logic to the 7.1.8 code. The
> GET_CUR-broken handling from the newer tree was not included; GET_CUR
> itself succeeds on this device.
>
> Unfortunately, the playback control is still classified as sticky:
>
> 2:0: sticky mixer values (-16384/0/256 => -3840), disabling
>
> I then instrumented the check and tried an additional diagnostic: after
> every successful SET_CUR, wait 100 ms and perform another GET_CUR before
> issuing the next SET_CUR.
>
> For the playback volume, the saved value was -3840 and GET_CUR remained
> at -3840 for every tested value, even after 100 ms, for example:
>
> test=-15104 immediate=-3840 after100ms=-3840
> test=-13824 immediate=-3840 after100ms=-3840
> test=-3584 immediate=-3840 after100ms=-3840
> test=-2304 immediate=-3840 after100ms=-3840
> test=-1024 immediate=-3840 after100ms=-3840
> test=0 immediate=-3840 after100ms=-3840
>
> So in this case the issue does not appear to be simply that the
> accumulated 10 ms sleeps are too short. During the probe-time sticky
> check, SET_CUR succeeds but GET_CUR for the playback control remains
> unchanged even when each SET_CUR is given 100 ms before the next one.
>
> This differs from my previous direct libusb tests with the AudioControl
> interface unbound, where valid SET_CUR values became visible through
> GET_CUR after roughly 47–81 ms.
Really interesting. Maybe the mixer changes its value only when there is
an opened playback stream.
Could you clarify your "libusb tests"?
Thanks,
Rong
>
> The first debug line I saw with |saved=0| was from the Mic Capture
> Volume control; that control changed immediately and returned as
> non-sticky. The sequence above with |saved=-3840| is the problematic PCM
> Playback Volume control.
>
> I'd be happy to test another version or run additional diagnostics if
> useful.
>
> Best regards,
> Alexander
>
>
> Am 15.08.2026 um 23:47 schrieb Rong Zhang:
> > Some mixers are asynchronous, and some have broken min/max. They are
> > mistakenly considered sticky due to how the check is implemented.
> >
> > Check sticky mixers more precisely by checking approximately 16 values
> > and adding a msleep(10) between each check, so that asynchronous mixers
> > have enough time to change the value and mixers with broken min/max are
> > checked properly. Additionally, mark GET_CUR as broken when
> > get_cur_mix_raw() fails, instead of returning successfully.
> >
> > Reported-by: Alexander Niemeyer <adventureFAN@gmx.de>
> > Closes: https://lore.kernel.org/r/6262cbbd-d1f2-4c9d-a1c7-9c5d12636f4b@gmx.de
> > Signed-off-by: Rong Zhang <i@rong.moe>
> > ---
> > sound/usb/mixer.c | 51 ++++++++++++++++++++++++++++++++++++++++++++-------
> > 1 file changed, 44 insertions(+), 7 deletions(-)
> >
> > diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c
> > index 703c118f9d4e..3d0f97730a06 100644
> > --- a/sound/usb/mixer.c
> > +++ b/sound/usb/mixer.c
> > @@ -1256,22 +1256,59 @@ static void init_cur_mix_raw(struct usb_mixer_elem_info *cval, int ch, int idx)
> > 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;
> > + int test, check, res;
> > +
> > + /*
> > + * Check approximately 16 values (15 intervals).
> > + * If the resolution is not fine enough, check fewer values.
> > + */
> > + res = DIV_ROUND_UP(cval->max - cval->min, 15);
> > + res = res ? roundup(res, cval->res) : cval->res;
> > +
> > + /*
> > + * If (cval->max - cval->min) is not a multiple of cval->res, we still
> > + * want to test cval->max anyway.
> > + */
> > + for (test = cval->min; test < cval->max + res; test += res) {
> > + if (test > cval->max)
> > + test = cval->max;
> >
> > - 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. */
> > + if (snd_usb_set_cur_mix_value(cval, channel, 0, test))
> > + return 0;
> > +
> > + if (get_cur_mix_raw(cval, channel, &check))
> > + goto get_cur_broken;
> > + if (check != saved) /* SET_CUR effective, non-sticky. */
> > return 0;
> > +
> > + /*
> > + * Leave some time for asynchronous mixers to change the value.
> > + *
> > + * Note that there is no need to wait between SET_CUR and
> > + * GET_CUR, as we don't care whether the GET_CUR value matches
> > + * the SET_CUR one. IOW, what we expect is just a GET_CUR value
> > + * differing from the saved one.
> > + *
> > + * Mixers of most devices are synchronous. The should have
> > + * returned early without extra sleep. Asynchronous mixers will
> > + * return once the accumulated time is enough for them to change
> > + * the value.
> > + */
> > + msleep(10);
> > }
> >
> > + /* Check again after the last msleep(). */
> > + if (get_cur_mix_raw(cval, channel, &check))
> > + goto get_cur_broken;
> > + if (check != saved)
> > + return 0;
> > +
> > if (cval->head.mixer->chip->quirk_flags & QUIRK_FLAG_MIXER_GET_CUR_BROKEN) {
> > +get_cur_broken:
> > usb_audio_info(cval->head.mixer->chip,
> > "%d:%d: broken mixer GET_CUR (%d/%d/%d => %d)\n",
> > cval->head.id, mixer_ctrl_intf(cval->head.mixer),
> >
> > ---
> > base-commit: 3eb40771c00a8488fa6ed2cc1fe203477908bf38
> > change-id: 74676fce-uac-precise-sticky-check-94474a22b57d
> >
> > Thanks,
> > Rong
> >
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH] ALSA: usb-audio: Check sticky mixers precisely
[not found] ` <74ca2e17-8fb8-4ede-8e7e-441be815b5b6@gmx.de>
@ 2026-08-16 15:08 ` Rong Zhang
2026-08-18 14:41 ` Alexander Niemeyer
0 siblings, 1 reply; 11+ messages in thread
From: Rong Zhang @ 2026-08-16 15:08 UTC (permalink / raw)
To: Alexander Niemeyer, Jaroslav Kysela, Takashi Iwai
Cc: Takashi Iwai, linux-sound, linux-kernel
Hi Alexander,
On Sun, 2026-08-16 at 16:09 +0200, Alexander Niemeyer wrote:
> Hi Rong,
>
> Sure. The libusb tests were direct USB Audio Class 1 control transfers
> to the headset using libusb/PyUSB, not ALSA mixer operations.
>
> I accessed Feature Unit 2 on AudioControl interface 0, master channel 0,
> with the UAC1 Volume control selector:
>
> wValue = 0x0200 /* Volume control, master channel */
> wIndex = 0x0200 /* Feature Unit 2, interface 0 */
>
> I used the standard class-specific requests directly, including GET_CUR,
> GET_MIN, GET_MAX, GET_RES and SET_CUR, with signed 16-bit little-endian
> volume values in 1/256 dB units.
>
> The device reported:
>
> GET_CUR: 0 ( 0 dB in that test)
> GET_MIN: -16384 (-64 dB)
> GET_MAX: 0 ( 0 dB)
> GET_RES: 256 ( 1 dB)
>
> For the timing tests I issued SET_CUR for a target value and then
> repeatedly queried GET_CUR until the value changed or the timeout expired.
>
> Valid values became visible after roughly:
>
> -1 dB ~81 ms
> -2 dB ~52 ms
> -4 dB ~47 ms
> -8 dB ~47 ms
> -16 dB ~52 ms
> -32 dB ~47 ms
>
> The advertised -64 dB minimum behaved differently: SET_CUR returned
> successfully, but GET_CUR did not change even after 1000 ms.
>
> To access the AudioControl interface with libusb, I unbound the
> AudioControl interface from snd-usb-audio for the duration of the test.
>
> I did not intentionally open a playback stream during those libusb
> tests. Because the AudioControl interface had been unbound from
> snd-usb-audio, I also do not believe there was an active ALSA playback
> stream at that point.
Thanks for the information.
Unfortunately, I still don't exactly see why the device behaved
differently when GET_CUR/SET_CUR requests were sent from snd-usb-audio
compared to your libusb tests.
snd-usb-audio also tries SET_RES to test the sanity of GET_RES. Could you
test if it breaks your device's GET_CUR?
Maybe comparing them with usbmon can show some clues. You can use
Wireshark to sniff /dev/usbmon*.
Hint: a Thunderbolt port usually corresponds to a dedicated USB root hub.
If you have one, plug the device to it to get pure usbmon trace results
with no noisy URBs from other devices.
Thanks,
Rong
>
> If the open-stream state is important, I can repeat the experiment
> specifically controlling for playback-stream-open versus
> playback-stream-closed.
>
> Thanks,
> Alexander
>
> Am 16.08.2026 um 15:50 schrieb Rong Zhang:
> > Hi Alexander,
> >
> > On Sun, 2026-08-16 at 07:14 +0200, Alexander Niemeyer wrote:
> > > Hi Rong,
> > >
> > > I tested the sticky-check part of your patch on the Logitech PRO X
> > > Wireless (046d:0aba) on Fedora 44, kernel 7.1.8-200.fc44.x86_64.
> > >
> > > Since your patch is based on a newer tree, I used a minimal backport of
> > > the new ~16-value / 10 ms sticky-check logic to the 7.1.8 code. The
> > > GET_CUR-broken handling from the newer tree was not included; GET_CUR
> > > itself succeeds on this device.
> > >
> > > Unfortunately, the playback control is still classified as sticky:
> > >
> > > 2:0: sticky mixer values (-16384/0/256 => -3840), disabling
> > >
> > > I then instrumented the check and tried an additional diagnostic: after
> > > every successful SET_CUR, wait 100 ms and perform another GET_CUR before
> > > issuing the next SET_CUR.
> > >
> > > For the playback volume, the saved value was -3840 and GET_CUR remained
> > > at -3840 for every tested value, even after 100 ms, for example:
> > >
> > > test=-15104 immediate=-3840 after100ms=-3840
> > > test=-13824 immediate=-3840 after100ms=-3840
> > > test=-3584 immediate=-3840 after100ms=-3840
> > > test=-2304 immediate=-3840 after100ms=-3840
> > > test=-1024 immediate=-3840 after100ms=-3840
> > > test=0 immediate=-3840 after100ms=-3840
> > >
> > > So in this case the issue does not appear to be simply that the
> > > accumulated 10 ms sleeps are too short. During the probe-time sticky
> > > check, SET_CUR succeeds but GET_CUR for the playback control remains
> > > unchanged even when each SET_CUR is given 100 ms before the next one.
> > >
> > > This differs from my previous direct libusb tests with the AudioControl
> > > interface unbound, where valid SET_CUR values became visible through
> > > GET_CUR after roughly 47–81 ms.
> > Really interesting. Maybe the mixer changes its value only when there is
> > an opened playback stream.
> >
> > Could you clarify your "libusb tests"?
> >
> > Thanks,
> > Rong
> >
> > > The first debug line I saw with |saved=0| was from the Mic Capture
> > > Volume control; that control changed immediately and returned as
> > > non-sticky. The sequence above with |saved=-3840| is the problematic PCM
> > > Playback Volume control.
> > >
> > > I'd be happy to test another version or run additional diagnostics if
> > > useful.
> > >
> > > Best regards,
> > > Alexander
> > >
> > >
> > > Am 15.08.2026 um 23:47 schrieb Rong Zhang:
> > > > Some mixers are asynchronous, and some have broken min/max. They are
> > > > mistakenly considered sticky due to how the check is implemented.
> > > >
> > > > Check sticky mixers more precisely by checking approximately 16 values
> > > > and adding a msleep(10) between each check, so that asynchronous mixers
> > > > have enough time to change the value and mixers with broken min/max are
> > > > checked properly. Additionally, mark GET_CUR as broken when
> > > > get_cur_mix_raw() fails, instead of returning successfully.
> > > >
> > > > Reported-by: Alexander Niemeyer<adventureFAN@gmx.de>
> > > > Closes:https://lore.kernel.org/r/6262cbbd-d1f2-4c9d-a1c7-9c5d12636f4b@gmx.de
> > > > Signed-off-by: Rong Zhang<i@rong.moe>
> > > > ---
> > > > sound/usb/mixer.c | 51 ++++++++++++++++++++++++++++++++++++++++++++-------
> > > > 1 file changed, 44 insertions(+), 7 deletions(-)
> > > >
> > > > diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c
> > > > index 703c118f9d4e..3d0f97730a06 100644
> > > > --- a/sound/usb/mixer.c
> > > > +++ b/sound/usb/mixer.c
> > > > @@ -1256,22 +1256,59 @@ static void init_cur_mix_raw(struct usb_mixer_elem_info *cval, int ch, int idx)
> > > > 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;
> > > > + int test, check, res;
> > > > +
> > > > + /*
> > > > + * Check approximately 16 values (15 intervals).
> > > > + * If the resolution is not fine enough, check fewer values.
> > > > + */
> > > > + res = DIV_ROUND_UP(cval->max - cval->min, 15);
> > > > + res = res ? roundup(res, cval->res) : cval->res;
> > > > +
> > > > + /*
> > > > + * If (cval->max - cval->min) is not a multiple of cval->res, we still
> > > > + * want to test cval->max anyway.
> > > > + */
> > > > + for (test = cval->min; test < cval->max + res; test += res) {
> > > > + if (test > cval->max)
> > > > + test = cval->max;
> > > >
> > > > - 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. */
> > > > + if (snd_usb_set_cur_mix_value(cval, channel, 0, test))
> > > > + return 0;
> > > > +
> > > > + if (get_cur_mix_raw(cval, channel, &check))
> > > > + goto get_cur_broken;
> > > > + if (check != saved) /* SET_CUR effective, non-sticky. */
> > > > return 0;
> > > > +
> > > > + /*
> > > > + * Leave some time for asynchronous mixers to change the value.
> > > > + *
> > > > + * Note that there is no need to wait between SET_CUR and
> > > > + * GET_CUR, as we don't care whether the GET_CUR value matches
> > > > + * the SET_CUR one. IOW, what we expect is just a GET_CUR value
> > > > + * differing from the saved one.
> > > > + *
> > > > + * Mixers of most devices are synchronous. The should have
> > > > + * returned early without extra sleep. Asynchronous mixers will
> > > > + * return once the accumulated time is enough for them to change
> > > > + * the value.
> > > > + */
> > > > + msleep(10);
> > > > }
> > > >
> > > > + /* Check again after the last msleep(). */
> > > > + if (get_cur_mix_raw(cval, channel, &check))
> > > > + goto get_cur_broken;
> > > > + if (check != saved)
> > > > + return 0;
> > > > +
> > > > if (cval->head.mixer->chip->quirk_flags & QUIRK_FLAG_MIXER_GET_CUR_BROKEN) {
> > > > +get_cur_broken:
> > > > usb_audio_info(cval->head.mixer->chip,
> > > > "%d:%d: broken mixer GET_CUR (%d/%d/%d => %d)\n",
> > > > cval->head.id, mixer_ctrl_intf(cval->head.mixer),
> > > >
> > > > ---
> > > > base-commit: 3eb40771c00a8488fa6ed2cc1fe203477908bf38
> > > > change-id: 74676fce-uac-precise-sticky-check-94474a22b57d
> > > >
> > > > Thanks,
> > > > Rong
> > > >
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH] ALSA: usb-audio: Check sticky mixers precisely
2026-08-16 15:08 ` Rong Zhang
@ 2026-08-18 14:41 ` Alexander Niemeyer
2026-08-18 15:24 ` Alexander Niemeyer
0 siblings, 1 reply; 11+ messages in thread
From: Alexander Niemeyer @ 2026-08-18 14:41 UTC (permalink / raw)
To: Rong Zhang, Jaroslav Kysela, Takashi Iwai
Cc: Takashi Iwai, linux-sound, linux-kernel
Hi Rong,
I think we found the reason for the different behavior.
I reproduced the snd-usb-audio initialization sequence step by step with
direct libusb UAC1 control transfers and isolated the problem to SET_RES
on the *Mic Capture Volume control (Feature Unit 3)*.
A fresh-device control test looks like this:
Mic GET_RES = 256
no SET_RES
Playback:
GET_CUR = -3840 (-15 dB)
SET_CUR = -2048 (-8 dB)
GET_CUR changes to -2048 after 68.0 ms
Result: PASS
After another power cycle, I repeated the same test but issued just
*one* SET_RES request to the Mic Feature Unit first:
Mic GET_RES before = 256
Mic SET_RES(128) = success
Mic GET_RES after = 256
Playback:
GET_CUR = -3840 (-15 dB)
SET_CUR = -2048 (-8 dB)
GET_CUR remains -3840 for more than 1200 ms
Result: FAIL
So a single successful |SET_RES(128)| on Feature Unit 3 is sufficient to
make subsequent |SET_CUR| requests to the Playback Volume control on
Feature Unit 2 ineffective.
I also tested the complete Mic SET_RES sequence used by snd-usb-audio:
SET_RES 128
SET_RES 64
SET_RES 32
SET_RES 16
SET_RES 8
SET_RES 4
SET_RES 2
SET_RES 1
All requests return success, while GET_RES remains 256. After that
sequence, Playback SET_CUR also remains ineffective for more than 1200 ms.
Interestingly, the Mic control itself still works after this. In an
ALSA-like Mic probe I could successfully change Mic Volume from 0 dB to
-64 dB and then +1 dB, with GET_CUR reflecting those changes essentially
immediately (~0.3 ms). Playback remained broken afterwards.
I also checked whether SET_RES on the Playback Feature Unit itself
causes the problem. It does not:
Playback GET_RES = 256
SET_RES 128 -> 64 -> 32 -> 16 -> 8 -> 4 -> 2 -> 1
GET_RES still = 256
Playback SET_CUR(-8 dB)
GET_CUR changes successfully after 87.7 ms
So the problematic operation appears specifically to be *SET_RES on the
Mic Feature Unit affecting the Playback Feature Unit*.
I also clarified the separate advertised-minimum issue:
Playback SET_CUR(-64 dB):
no change after >1200 ms
followed by SET_CUR(-8 dB):
works normally after 54.9 ms
Playback SET_CUR(-63 dB):
works after 75.8 ms
followed by SET_CUR(-8 dB):
works after 43.6 ms
Therefore the broken -64 dB endpoint does not leave the device in the
broken state; it is a separate issue. -63 dB works normally.
I also captured usbmon/pcapng traces for both a working direct-libusb
SET_CUR sequence and the failing snd-usb-audio initialization, so I can
send those as well if they are useful.
This also seems to explain why the sticky-check changes did not help: by
the time snd-usb-audio reaches the Playback Volume sticky check, the
earlier Mic SET_RES sanity test has already put the device into the
state where Playback SET_CUR no longer takes effect.
Let me know if you would like me to test a patch or capture any
additional traces.
Thanks,
Alexander
Am 16.08.2026 um 17:08 schrieb Rong Zhang:
> Hi Alexander,
>
> On Sun, 2026-08-16 at 16:09 +0200, Alexander Niemeyer wrote:
>> Hi Rong,
>>
>> Sure. The libusb tests were direct USB Audio Class 1 control transfers
>> to the headset using libusb/PyUSB, not ALSA mixer operations.
>>
>> I accessed Feature Unit 2 on AudioControl interface 0, master channel 0,
>> with the UAC1 Volume control selector:
>>
>> wValue = 0x0200 /* Volume control, master channel */
>> wIndex = 0x0200 /* Feature Unit 2, interface 0 */
>>
>> I used the standard class-specific requests directly, including GET_CUR,
>> GET_MIN, GET_MAX, GET_RES and SET_CUR, with signed 16-bit little-endian
>> volume values in 1/256 dB units.
>>
>> The device reported:
>>
>> GET_CUR: 0 ( 0 dB in that test)
>> GET_MIN: -16384 (-64 dB)
>> GET_MAX: 0 ( 0 dB)
>> GET_RES: 256 ( 1 dB)
>>
>> For the timing tests I issued SET_CUR for a target value and then
>> repeatedly queried GET_CUR until the value changed or the timeout expired.
>>
>> Valid values became visible after roughly:
>>
>> -1 dB ~81 ms
>> -2 dB ~52 ms
>> -4 dB ~47 ms
>> -8 dB ~47 ms
>> -16 dB ~52 ms
>> -32 dB ~47 ms
>>
>> The advertised -64 dB minimum behaved differently: SET_CUR returned
>> successfully, but GET_CUR did not change even after 1000 ms.
>>
>> To access the AudioControl interface with libusb, I unbound the
>> AudioControl interface from snd-usb-audio for the duration of the test.
>>
>> I did not intentionally open a playback stream during those libusb
>> tests. Because the AudioControl interface had been unbound from
>> snd-usb-audio, I also do not believe there was an active ALSA playback
>> stream at that point.
> Thanks for the information.
>
> Unfortunately, I still don't exactly see why the device behaved
> differently when GET_CUR/SET_CUR requests were sent from snd-usb-audio
> compared to your libusb tests.
>
> snd-usb-audio also tries SET_RES to test the sanity of GET_RES. Could you
> test if it breaks your device's GET_CUR?
>
> Maybe comparing them with usbmon can show some clues. You can use
> Wireshark to sniff /dev/usbmon*.
>
> Hint: a Thunderbolt port usually corresponds to a dedicated USB root hub.
> If you have one, plug the device to it to get pure usbmon trace results
> with no noisy URBs from other devices.
>
> Thanks,
> Rong
>
>> If the open-stream state is important, I can repeat the experiment
>> specifically controlling for playback-stream-open versus
>> playback-stream-closed.
>>
>> Thanks,
>> Alexander
>>
>> Am 16.08.2026 um 15:50 schrieb Rong Zhang:
>>> Hi Alexander,
>>>
>>> On Sun, 2026-08-16 at 07:14 +0200, Alexander Niemeyer wrote:
>>>> Hi Rong,
>>>>
>>>> I tested the sticky-check part of your patch on the Logitech PRO X
>>>> Wireless (046d:0aba) on Fedora 44, kernel 7.1.8-200.fc44.x86_64.
>>>>
>>>> Since your patch is based on a newer tree, I used a minimal backport of
>>>> the new ~16-value / 10 ms sticky-check logic to the 7.1.8 code. The
>>>> GET_CUR-broken handling from the newer tree was not included; GET_CUR
>>>> itself succeeds on this device.
>>>>
>>>> Unfortunately, the playback control is still classified as sticky:
>>>>
>>>> 2:0: sticky mixer values (-16384/0/256 => -3840), disabling
>>>>
>>>> I then instrumented the check and tried an additional diagnostic: after
>>>> every successful SET_CUR, wait 100 ms and perform another GET_CUR before
>>>> issuing the next SET_CUR.
>>>>
>>>> For the playback volume, the saved value was -3840 and GET_CUR remained
>>>> at -3840 for every tested value, even after 100 ms, for example:
>>>>
>>>> test=-15104 immediate=-3840 after100ms=-3840
>>>> test=-13824 immediate=-3840 after100ms=-3840
>>>> test=-3584 immediate=-3840 after100ms=-3840
>>>> test=-2304 immediate=-3840 after100ms=-3840
>>>> test=-1024 immediate=-3840 after100ms=-3840
>>>> test=0 immediate=-3840 after100ms=-3840
>>>>
>>>> So in this case the issue does not appear to be simply that the
>>>> accumulated 10 ms sleeps are too short. During the probe-time sticky
>>>> check, SET_CUR succeeds but GET_CUR for the playback control remains
>>>> unchanged even when each SET_CUR is given 100 ms before the next one.
>>>>
>>>> This differs from my previous direct libusb tests with the AudioControl
>>>> interface unbound, where valid SET_CUR values became visible through
>>>> GET_CUR after roughly 47–81 ms.
>>> Really interesting. Maybe the mixer changes its value only when there is
>>> an opened playback stream.
>>>
>>> Could you clarify your "libusb tests"?
>>>
>>> Thanks,
>>> Rong
>>>
>>>> The first debug line I saw with |saved=0| was from the Mic Capture
>>>> Volume control; that control changed immediately and returned as
>>>> non-sticky. The sequence above with |saved=-3840| is the problematic PCM
>>>> Playback Volume control.
>>>>
>>>> I'd be happy to test another version or run additional diagnostics if
>>>> useful.
>>>>
>>>> Best regards,
>>>> Alexander
>>>>
>>>>
>>>> Am 15.08.2026 um 23:47 schrieb Rong Zhang:
>>>>> Some mixers are asynchronous, and some have broken min/max. They are
>>>>> mistakenly considered sticky due to how the check is implemented.
>>>>>
>>>>> Check sticky mixers more precisely by checking approximately 16 values
>>>>> and adding a msleep(10) between each check, so that asynchronous mixers
>>>>> have enough time to change the value and mixers with broken min/max are
>>>>> checked properly. Additionally, mark GET_CUR as broken when
>>>>> get_cur_mix_raw() fails, instead of returning successfully.
>>>>>
>>>>> Reported-by: Alexander Niemeyer<adventureFAN@gmx.de>
>>>>> Closes:https://lore.kernel.org/r/6262cbbd-d1f2-4c9d-a1c7-9c5d12636f4b@gmx.de
>>>>> Signed-off-by: Rong Zhang<i@rong.moe>
>>>>> ---
>>>>> sound/usb/mixer.c | 51 ++++++++++++++++++++++++++++++++++++++++++++-------
>>>>> 1 file changed, 44 insertions(+), 7 deletions(-)
>>>>>
>>>>> diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c
>>>>> index 703c118f9d4e..3d0f97730a06 100644
>>>>> --- a/sound/usb/mixer.c
>>>>> +++ b/sound/usb/mixer.c
>>>>> @@ -1256,22 +1256,59 @@ static void init_cur_mix_raw(struct usb_mixer_elem_info *cval, int ch, int idx)
>>>>> 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;
>>>>> + int test, check, res;
>>>>> +
>>>>> + /*
>>>>> + * Check approximately 16 values (15 intervals).
>>>>> + * If the resolution is not fine enough, check fewer values.
>>>>> + */
>>>>> + res = DIV_ROUND_UP(cval->max - cval->min, 15);
>>>>> + res = res ? roundup(res, cval->res) : cval->res;
>>>>> +
>>>>> + /*
>>>>> + * If (cval->max - cval->min) is not a multiple of cval->res, we still
>>>>> + * want to test cval->max anyway.
>>>>> + */
>>>>> + for (test = cval->min; test < cval->max + res; test += res) {
>>>>> + if (test > cval->max)
>>>>> + test = cval->max;
>>>>>
>>>>> - 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. */
>>>>> + if (snd_usb_set_cur_mix_value(cval, channel, 0, test))
>>>>> + return 0;
>>>>> +
>>>>> + if (get_cur_mix_raw(cval, channel, &check))
>>>>> + goto get_cur_broken;
>>>>> + if (check != saved) /* SET_CUR effective, non-sticky. */
>>>>> return 0;
>>>>> +
>>>>> + /*
>>>>> + * Leave some time for asynchronous mixers to change the value.
>>>>> + *
>>>>> + * Note that there is no need to wait between SET_CUR and
>>>>> + * GET_CUR, as we don't care whether the GET_CUR value matches
>>>>> + * the SET_CUR one. IOW, what we expect is just a GET_CUR value
>>>>> + * differing from the saved one.
>>>>> + *
>>>>> + * Mixers of most devices are synchronous. The should have
>>>>> + * returned early without extra sleep. Asynchronous mixers will
>>>>> + * return once the accumulated time is enough for them to change
>>>>> + * the value.
>>>>> + */
>>>>> + msleep(10);
>>>>> }
>>>>>
>>>>> + /* Check again after the last msleep(). */
>>>>> + if (get_cur_mix_raw(cval, channel, &check))
>>>>> + goto get_cur_broken;
>>>>> + if (check != saved)
>>>>> + return 0;
>>>>> +
>>>>> if (cval->head.mixer->chip->quirk_flags & QUIRK_FLAG_MIXER_GET_CUR_BROKEN) {
>>>>> +get_cur_broken:
>>>>> usb_audio_info(cval->head.mixer->chip,
>>>>> "%d:%d: broken mixer GET_CUR (%d/%d/%d => %d)\n",
>>>>> cval->head.id, mixer_ctrl_intf(cval->head.mixer),
>>>>>
>>>>> ---
>>>>> base-commit: 3eb40771c00a8488fa6ed2cc1fe203477908bf38
>>>>> change-id: 74676fce-uac-precise-sticky-check-94474a22b57d
>>>>>
>>>>> Thanks,
>>>>> Rong
>>>>>
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH] ALSA: usb-audio: Check sticky mixers precisely
2026-08-18 14:41 ` Alexander Niemeyer
@ 2026-08-18 15:24 ` Alexander Niemeyer
2026-08-19 16:39 ` Rong Zhang
0 siblings, 1 reply; 11+ messages in thread
From: Alexander Niemeyer @ 2026-08-18 15:24 UTC (permalink / raw)
To: Rong Zhang, Jaroslav Kysela, Takashi Iwai
Cc: Takashi Iwai, linux-sound, linux-kernel
Hi Rong,
one important follow-up to my previous message:
I realized that the detachable microphone boom was not physically connected
to the headset during the direct libusb tests I reported earlier.
I therefore repeated the relevant tests with the boom microphone attached,
and the result changes in an important way.
With the boom microphone attached, on a fresh device:
Mic GET_RES = 256
no SET_RES
Playback:
GET_CUR = -3840 (-15 dB)
SET_CUR = -2048 (-8 dB)
GET_CUR changes to -2048 after 65.3 ms
Result: PASS
After another power cycle, with the boom still attached:
Mic GET_RES before = 256
Mic SET_RES(128) = success
Mic GET_RES after = 256
Playback:
GET_CUR = -3840 (-15 dB)
SET_CUR = -2048 (-8 dB)
GET_CUR changes to -2048 after 66.3 ms
Result: PASS
I also repeated the complete Mic SET_RES sequence:
SET_RES 128
SET_RES 64
SET_RES 32
SET_RES 16
SET_RES 8
SET_RES 4
SET_RES 2
SET_RES 1
All requests return success, GET_RES remains 256, and Playback
SET_CUR(-8 dB)
still works afterwards, with GET_CUR changing after 76.0 ms.
However, with the boom microphone attached, the stock Fedora 7.1.8
snd-usb-audio driver still reproduces the original failure:
sticky mixer values (-16384/0/256 => -3840), disabling
and no PCM Playback Volume control is exposed.
So I need to correct the conclusion from my previous message:
The Mic SET_RES behavior is real, but it depends on whether the detachable
microphone boom is physically connected.
With the boom disconnected:
a single Mic SET_RES(128) is sufficient to make subsequent Playback
SET_CUR ineffective for more than 1200 ms.
With the boom connected:
both a single Mic SET_RES(128) and the full SET_RES sequence leave
Playback SET_CUR working normally.
Therefore Mic SET_RES alone does not explain the complete snd-usb-audio
failure in all device configurations. There must be another difference in
the full snd-usb-audio initialization path when the boom is attached.
The separate Playback -64 dB minimum issue I reported remains unchanged:
-64 dB is ineffective, -63 dB works, and attempting -64 dB does not prevent
subsequent valid Playback SET_CUR requests from working.
Sorry for not noticing the detachable-microphone state earlier. I wanted to
send this correction as soon as I confirmed it.
I still have the usbmon/pcapng captures and can run further targeted tests
if useful.
Thanks,
Alexander
Am 18.08.2026 um 16:41 schrieb Alexander Niemeyer:
> Hi Rong,
>
> I think we found the reason for the different behavior.
>
> I reproduced the snd-usb-audio initialization sequence step by step
> with direct libusb UAC1 control transfers and isolated the problem to
> SET_RES on the *Mic Capture Volume control (Feature Unit 3)*.
>
> A fresh-device control test looks like this:
>
> Mic GET_RES = 256
> no SET_RES
> Playback:
> GET_CUR = -3840 (-15 dB)
> SET_CUR = -2048 (-8 dB)
> GET_CUR changes to -2048 after 68.0 ms
> Result: PASS
>
> After another power cycle, I repeated the same test but issued just
> *one* SET_RES request to the Mic Feature Unit first:
>
> Mic GET_RES before = 256
> Mic SET_RES(128) = success
> Mic GET_RES after = 256
> Playback:
> GET_CUR = -3840 (-15 dB)
> SET_CUR = -2048 (-8 dB)
> GET_CUR remains -3840 for more than 1200 ms
> Result: FAIL
>
> So a single successful |SET_RES(128)| on Feature Unit 3 is sufficient
> to make subsequent |SET_CUR| requests to the Playback Volume control
> on Feature Unit 2 ineffective.
>
> I also tested the complete Mic SET_RES sequence used by snd-usb-audio:
>
> SET_RES 128
> SET_RES 64
> SET_RES 32
> SET_RES 16
> SET_RES 8
> SET_RES 4
> SET_RES 2
> SET_RES 1
>
> All requests return success, while GET_RES remains 256. After that
> sequence, Playback SET_CUR also remains ineffective for more than 1200
> ms.
>
> Interestingly, the Mic control itself still works after this. In an
> ALSA-like Mic probe I could successfully change Mic Volume from 0 dB
> to -64 dB and then +1 dB, with GET_CUR reflecting those changes
> essentially immediately (~0.3 ms). Playback remained broken afterwards.
>
> I also checked whether SET_RES on the Playback Feature Unit itself
> causes the problem. It does not:
>
> Playback GET_RES = 256
> SET_RES 128 -> 64 -> 32 -> 16 -> 8 -> 4 -> 2 -> 1
> GET_RES still = 256
> Playback SET_CUR(-8 dB)
> GET_CUR changes successfully after 87.7 ms
>
> So the problematic operation appears specifically to be *SET_RES on
> the Mic Feature Unit affecting the Playback Feature Unit*.
>
> I also clarified the separate advertised-minimum issue:
>
> Playback SET_CUR(-64 dB):
> no change after >1200 ms
> followed by SET_CUR(-8 dB):
> works normally after 54.9 ms
> Playback SET_CUR(-63 dB):
> works after 75.8 ms
> followed by SET_CUR(-8 dB):
> works after 43.6 ms
>
> Therefore the broken -64 dB endpoint does not leave the device in the
> broken state; it is a separate issue. -63 dB works normally.
>
> I also captured usbmon/pcapng traces for both a working direct-libusb
> SET_CUR sequence and the failing snd-usb-audio initialization, so I
> can send those as well if they are useful.
>
> This also seems to explain why the sticky-check changes did not help:
> by the time snd-usb-audio reaches the Playback Volume sticky check,
> the earlier Mic SET_RES sanity test has already put the device into
> the state where Playback SET_CUR no longer takes effect.
>
> Let me know if you would like me to test a patch or capture any
> additional traces.
>
> Thanks,
> Alexander
>
>
> Am 16.08.2026 um 17:08 schrieb Rong Zhang:
>> Hi Alexander,
>>
>> On Sun, 2026-08-16 at 16:09 +0200, Alexander Niemeyer wrote:
>>> Hi Rong,
>>>
>>> Sure. The libusb tests were direct USB Audio Class 1 control transfers
>>> to the headset using libusb/PyUSB, not ALSA mixer operations.
>>>
>>> I accessed Feature Unit 2 on AudioControl interface 0, master
>>> channel 0,
>>> with the UAC1 Volume control selector:
>>>
>>> wValue = 0x0200 /* Volume control, master channel */
>>> wIndex = 0x0200 /* Feature Unit 2, interface 0 */
>>>
>>> I used the standard class-specific requests directly, including
>>> GET_CUR,
>>> GET_MIN, GET_MAX, GET_RES and SET_CUR, with signed 16-bit little-endian
>>> volume values in 1/256 dB units.
>>>
>>> The device reported:
>>>
>>> GET_CUR: 0 ( 0 dB in that test)
>>> GET_MIN: -16384 (-64 dB)
>>> GET_MAX: 0 ( 0 dB)
>>> GET_RES: 256 ( 1 dB)
>>>
>>> For the timing tests I issued SET_CUR for a target value and then
>>> repeatedly queried GET_CUR until the value changed or the timeout
>>> expired.
>>>
>>> Valid values became visible after roughly:
>>>
>>> -1 dB ~81 ms
>>> -2 dB ~52 ms
>>> -4 dB ~47 ms
>>> -8 dB ~47 ms
>>> -16 dB ~52 ms
>>> -32 dB ~47 ms
>>>
>>> The advertised -64 dB minimum behaved differently: SET_CUR returned
>>> successfully, but GET_CUR did not change even after 1000 ms.
>>>
>>> To access the AudioControl interface with libusb, I unbound the
>>> AudioControl interface from snd-usb-audio for the duration of the test.
>>>
>>> I did not intentionally open a playback stream during those libusb
>>> tests. Because the AudioControl interface had been unbound from
>>> snd-usb-audio, I also do not believe there was an active ALSA playback
>>> stream at that point.
>> Thanks for the information.
>>
>> Unfortunately, I still don't exactly see why the device behaved
>> differently when GET_CUR/SET_CUR requests were sent from snd-usb-audio
>> compared to your libusb tests.
>>
>> snd-usb-audio also tries SET_RES to test the sanity of GET_RES. Could
>> you
>> test if it breaks your device's GET_CUR?
>>
>> Maybe comparing them with usbmon can show some clues. You can use
>> Wireshark to sniff /dev/usbmon*.
>>
>> Hint: a Thunderbolt port usually corresponds to a dedicated USB root
>> hub.
>> If you have one, plug the device to it to get pure usbmon trace results
>> with no noisy URBs from other devices.
>>
>> Thanks,
>> Rong
>>
>>> If the open-stream state is important, I can repeat the experiment
>>> specifically controlling for playback-stream-open versus
>>> playback-stream-closed.
>>>
>>> Thanks,
>>> Alexander
>>>
>>> Am 16.08.2026 um 15:50 schrieb Rong Zhang:
>>>> Hi Alexander,
>>>>
>>>> On Sun, 2026-08-16 at 07:14 +0200, Alexander Niemeyer wrote:
>>>>> Hi Rong,
>>>>>
>>>>> I tested the sticky-check part of your patch on the Logitech PRO X
>>>>> Wireless (046d:0aba) on Fedora 44, kernel 7.1.8-200.fc44.x86_64.
>>>>>
>>>>> Since your patch is based on a newer tree, I used a minimal
>>>>> backport of
>>>>> the new ~16-value / 10 ms sticky-check logic to the 7.1.8 code. The
>>>>> GET_CUR-broken handling from the newer tree was not included; GET_CUR
>>>>> itself succeeds on this device.
>>>>>
>>>>> Unfortunately, the playback control is still classified as sticky:
>>>>>
>>>>> 2:0: sticky mixer values (-16384/0/256 => -3840), disabling
>>>>>
>>>>> I then instrumented the check and tried an additional diagnostic:
>>>>> after
>>>>> every successful SET_CUR, wait 100 ms and perform another GET_CUR
>>>>> before
>>>>> issuing the next SET_CUR.
>>>>>
>>>>> For the playback volume, the saved value was -3840 and GET_CUR
>>>>> remained
>>>>> at -3840 for every tested value, even after 100 ms, for example:
>>>>>
>>>>> test=-15104 immediate=-3840 after100ms=-3840
>>>>> test=-13824 immediate=-3840 after100ms=-3840
>>>>> test=-3584 immediate=-3840 after100ms=-3840
>>>>> test=-2304 immediate=-3840 after100ms=-3840
>>>>> test=-1024 immediate=-3840 after100ms=-3840
>>>>> test=0 immediate=-3840 after100ms=-3840
>>>>>
>>>>> So in this case the issue does not appear to be simply that the
>>>>> accumulated 10 ms sleeps are too short. During the probe-time sticky
>>>>> check, SET_CUR succeeds but GET_CUR for the playback control remains
>>>>> unchanged even when each SET_CUR is given 100 ms before the next one.
>>>>>
>>>>> This differs from my previous direct libusb tests with the
>>>>> AudioControl
>>>>> interface unbound, where valid SET_CUR values became visible through
>>>>> GET_CUR after roughly 47–81 ms.
>>>> Really interesting. Maybe the mixer changes its value only when
>>>> there is
>>>> an opened playback stream.
>>>>
>>>> Could you clarify your "libusb tests"?
>>>>
>>>> Thanks,
>>>> Rong
>>>>
>>>>> The first debug line I saw with |saved=0| was from the Mic Capture
>>>>> Volume control; that control changed immediately and returned as
>>>>> non-sticky. The sequence above with |saved=-3840| is the
>>>>> problematic PCM
>>>>> Playback Volume control.
>>>>>
>>>>> I'd be happy to test another version or run additional diagnostics if
>>>>> useful.
>>>>>
>>>>> Best regards,
>>>>> Alexander
>>>>>
>>>>>
>>>>> Am 15.08.2026 um 23:47 schrieb Rong Zhang:
>>>>>> Some mixers are asynchronous, and some have broken min/max. They are
>>>>>> mistakenly considered sticky due to how the check is implemented.
>>>>>>
>>>>>> Check sticky mixers more precisely by checking approximately 16
>>>>>> values
>>>>>> and adding a msleep(10) between each check, so that asynchronous
>>>>>> mixers
>>>>>> have enough time to change the value and mixers with broken
>>>>>> min/max are
>>>>>> checked properly. Additionally, mark GET_CUR as broken when
>>>>>> get_cur_mix_raw() fails, instead of returning successfully.
>>>>>>
>>>>>> Reported-by: Alexander Niemeyer<adventureFAN@gmx.de>
>>>>>> Closes:https://lore.kernel.org/r/6262cbbd-d1f2-4c9d-a1c7-9c5d12636f4b@gmx.de
>>>>>>
>>>>>> Signed-off-by: Rong Zhang<i@rong.moe>
>>>>>> ---
>>>>>> sound/usb/mixer.c | 51
>>>>>> ++++++++++++++++++++++++++++++++++++++++++++-------
>>>>>> 1 file changed, 44 insertions(+), 7 deletions(-)
>>>>>>
>>>>>> diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c
>>>>>> index 703c118f9d4e..3d0f97730a06 100644
>>>>>> --- a/sound/usb/mixer.c
>>>>>> +++ b/sound/usb/mixer.c
>>>>>> @@ -1256,22 +1256,59 @@ static void init_cur_mix_raw(struct
>>>>>> usb_mixer_elem_info *cval, int ch, int idx)
>>>>>> 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;
>>>>>> + int test, check, res;
>>>>>> +
>>>>>> + /*
>>>>>> + * Check approximately 16 values (15 intervals).
>>>>>> + * If the resolution is not fine enough, check fewer values.
>>>>>> + */
>>>>>> + res = DIV_ROUND_UP(cval->max - cval->min, 15);
>>>>>> + res = res ? roundup(res, cval->res) : cval->res;
>>>>>> +
>>>>>> + /*
>>>>>> + * If (cval->max - cval->min) is not a multiple of
>>>>>> cval->res, we still
>>>>>> + * want to test cval->max anyway.
>>>>>> + */
>>>>>> + for (test = cval->min; test < cval->max + res; test += res) {
>>>>>> + if (test > cval->max)
>>>>>> + test = cval->max;
>>>>>> - 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. */
>>>>>> + if (snd_usb_set_cur_mix_value(cval, channel, 0, test))
>>>>>> + return 0;
>>>>>> +
>>>>>> + if (get_cur_mix_raw(cval, channel, &check))
>>>>>> + goto get_cur_broken;
>>>>>> + if (check != saved) /* SET_CUR effective, non-sticky. */
>>>>>> return 0;
>>>>>> +
>>>>>> + /*
>>>>>> + * Leave some time for asynchronous mixers to change the
>>>>>> value.
>>>>>> + *
>>>>>> + * Note that there is no need to wait between SET_CUR and
>>>>>> + * GET_CUR, as we don't care whether the GET_CUR value
>>>>>> matches
>>>>>> + * the SET_CUR one. IOW, what we expect is just a
>>>>>> GET_CUR value
>>>>>> + * differing from the saved one.
>>>>>> + *
>>>>>> + * Mixers of most devices are synchronous. The should have
>>>>>> + * returned early without extra sleep. Asynchronous
>>>>>> mixers will
>>>>>> + * return once the accumulated time is enough for them
>>>>>> to change
>>>>>> + * the value.
>>>>>> + */
>>>>>> + msleep(10);
>>>>>> }
>>>>>> + /* Check again after the last msleep(). */
>>>>>> + if (get_cur_mix_raw(cval, channel, &check))
>>>>>> + goto get_cur_broken;
>>>>>> + if (check != saved)
>>>>>> + return 0;
>>>>>> +
>>>>>> if (cval->head.mixer->chip->quirk_flags &
>>>>>> QUIRK_FLAG_MIXER_GET_CUR_BROKEN) {
>>>>>> +get_cur_broken:
>>>>>> usb_audio_info(cval->head.mixer->chip,
>>>>>> "%d:%d: broken mixer GET_CUR (%d/%d/%d =>
>>>>>> %d)\n",
>>>>>> cval->head.id,
>>>>>> mixer_ctrl_intf(cval->head.mixer),
>>>>>>
>>>>>> ---
>>>>>> base-commit: 3eb40771c00a8488fa6ed2cc1fe203477908bf38
>>>>>> change-id: 74676fce-uac-precise-sticky-check-94474a22b57d
>>>>>>
>>>>>> Thanks,
>>>>>> Rong
>>>>>>
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH] ALSA: usb-audio: Check sticky mixers precisely
2026-08-18 15:24 ` Alexander Niemeyer
@ 2026-08-19 16:39 ` Rong Zhang
2026-08-19 18:37 ` Alexander Niemeyer
0 siblings, 1 reply; 11+ messages in thread
From: Rong Zhang @ 2026-08-19 16:39 UTC (permalink / raw)
To: Alexander Niemeyer, Jaroslav Kysela, Takashi Iwai
Cc: Takashi Iwai, linux-sound, linux-kernel
Hi Alexander,
On Tue, 2026-08-18 at 17:24 +0200, Alexander Niemeyer wrote:
> Hi Rong,
>
> one important follow-up to my previous message:
>
> I realized that the detachable microphone boom was not physically connected
> to the headset during the direct libusb tests I reported earlier.
>
> I therefore repeated the relevant tests with the boom microphone attached,
> and the result changes in an important way.
>
> With the boom microphone attached, on a fresh device:
>
> Mic GET_RES = 256
> no SET_RES
>
> Playback:
> GET_CUR = -3840 (-15 dB)
> SET_CUR = -2048 (-8 dB)
> GET_CUR changes to -2048 after 65.3 ms
>
> Result: PASS
>
> After another power cycle, with the boom still attached:
>
> Mic GET_RES before = 256
> Mic SET_RES(128) = success
> Mic GET_RES after = 256
>
> Playback:
> GET_CUR = -3840 (-15 dB)
> SET_CUR = -2048 (-8 dB)
> GET_CUR changes to -2048 after 66.3 ms
>
> Result: PASS
>
> I also repeated the complete Mic SET_RES sequence:
>
> SET_RES 128
> SET_RES 64
> SET_RES 32
> SET_RES 16
> SET_RES 8
> SET_RES 4
> SET_RES 2
> SET_RES 1
>
> All requests return success, GET_RES remains 256, and Playback
> SET_CUR(-8 dB)
> still works afterwards, with GET_CUR changing after 76.0 ms.
>
> However, with the boom microphone attached, the stock Fedora 7.1.8
> snd-usb-audio driver still reproduces the original failure:
>
> sticky mixer values (-16384/0/256 => -3840), disabling
>
> and no PCM Playback Volume control is exposed.
Could you test my patch with Mic attached?
>
> So I need to correct the conclusion from my previous message:
>
> The Mic SET_RES behavior is real, but it depends on whether the detachable
> microphone boom is physically connected.
>
> With the boom disconnected:
> a single Mic SET_RES(128) is sufficient to make subsequent Playback
> SET_CUR ineffective for more than 1200 ms.
>
> With the boom connected:
> both a single Mic SET_RES(128) and the full SET_RES sequence leave
> Playback SET_CUR working normally.
>
Thanks for your tests.
Let me conclude:
- Mic detached: SET_RES on Mic breaks Playback
- Mic attached: SET_RES on Mic breaks nothing
Still, I doubt if Playback SET_CUR is really broken in the first case.
There is a chance that Playback SET_CUR is effective while GET_CUR
becomes broken.
Could you test:
- Leave the Mic detached
- SET_RES on Mic
- GET_CUR on Playback
- Play some audio, listen to it
- SET_CUR on Playback
- GET_CUR on Playback
- Play some audio again, listen to it, and tell if the physical volume
has changed
The procedure can be achieved by hacking into the snd-usb-audio driver
and nullifying its cache mechanism, so that you can fetch GET_CUR values
from standard ALSA mixer interface.
If you can notice physical volume changes, it implies SET_RES on Mic
break Playback GET_CUR without breaking SET_CUR. Otherwise, it break
Playback SET_CUR.
If it only breaks GET_CUR, it implies the methodology of sticky check is
problematic. In this case I would probably consider demoting the sticky
check's severity.
I am also thinking about how we should demote the severity. Could you
also test if applying QUIRK_FLAG_MIXER_GET_CUR_BROKEN (without this
patch) breaks mixer change notifications? I.e., tune the volume with the
buttons/knobs on the device and see if the audio stack reflects the
change. I guess it will break the notifications, but I would still like
to wait for your confirmation.
Thanks,
Rong
> Therefore Mic SET_RES alone does not explain the complete snd-usb-audio
> failure in all device configurations. There must be another difference in
> the full snd-usb-audio initialization path when the boom is attached.
>
> The separate Playback -64 dB minimum issue I reported remains unchanged:
> -64 dB is ineffective, -63 dB works, and attempting -64 dB does not prevent
> subsequent valid Playback SET_CUR requests from working.
>
> Sorry for not noticing the detachable-microphone state earlier. I wanted to
> send this correction as soon as I confirmed it.
>
> I still have the usbmon/pcapng captures and can run further targeted tests
> if useful.
>
> Thanks,
> Alexander
>
> Am 18.08.2026 um 16:41 schrieb Alexander Niemeyer:
> > Hi Rong,
> >
> > I think we found the reason for the different behavior.
> >
> > I reproduced the snd-usb-audio initialization sequence step by step
> > with direct libusb UAC1 control transfers and isolated the problem to
> > SET_RES on the *Mic Capture Volume control (Feature Unit 3)*.
> >
> > A fresh-device control test looks like this:
> >
> > Mic GET_RES = 256
> > no SET_RES
> > Playback:
> > GET_CUR = -3840 (-15 dB)
> > SET_CUR = -2048 (-8 dB)
> > GET_CUR changes to -2048 after 68.0 ms
> > Result: PASS
> >
> > After another power cycle, I repeated the same test but issued just
> > *one* SET_RES request to the Mic Feature Unit first:
> >
> > Mic GET_RES before = 256
> > Mic SET_RES(128) = success
> > Mic GET_RES after = 256
> > Playback:
> > GET_CUR = -3840 (-15 dB)
> > SET_CUR = -2048 (-8 dB)
> > GET_CUR remains -3840 for more than 1200 ms
> > Result: FAIL
> >
> > So a single successful |SET_RES(128)| on Feature Unit 3 is sufficient
> > to make subsequent |SET_CUR| requests to the Playback Volume control
> > on Feature Unit 2 ineffective.
> >
> > I also tested the complete Mic SET_RES sequence used by snd-usb-audio:
> >
> > SET_RES 128
> > SET_RES 64
> > SET_RES 32
> > SET_RES 16
> > SET_RES 8
> > SET_RES 4
> > SET_RES 2
> > SET_RES 1
> >
> > All requests return success, while GET_RES remains 256. After that
> > sequence, Playback SET_CUR also remains ineffective for more than 1200
> > ms.
> >
> > Interestingly, the Mic control itself still works after this. In an
> > ALSA-like Mic probe I could successfully change Mic Volume from 0 dB
> > to -64 dB and then +1 dB, with GET_CUR reflecting those changes
> > essentially immediately (~0.3 ms). Playback remained broken afterwards.
> >
> > I also checked whether SET_RES on the Playback Feature Unit itself
> > causes the problem. It does not:
> >
> > Playback GET_RES = 256
> > SET_RES 128 -> 64 -> 32 -> 16 -> 8 -> 4 -> 2 -> 1
> > GET_RES still = 256
> > Playback SET_CUR(-8 dB)
> > GET_CUR changes successfully after 87.7 ms
> >
> > So the problematic operation appears specifically to be *SET_RES on
> > the Mic Feature Unit affecting the Playback Feature Unit*.
> >
> > I also clarified the separate advertised-minimum issue:
> >
> > Playback SET_CUR(-64 dB):
> > no change after >1200 ms
> > followed by SET_CUR(-8 dB):
> > works normally after 54.9 ms
> > Playback SET_CUR(-63 dB):
> > works after 75.8 ms
> > followed by SET_CUR(-8 dB):
> > works after 43.6 ms
> >
> > Therefore the broken -64 dB endpoint does not leave the device in the
> > broken state; it is a separate issue. -63 dB works normally.
> >
> > I also captured usbmon/pcapng traces for both a working direct-libusb
> > SET_CUR sequence and the failing snd-usb-audio initialization, so I
> > can send those as well if they are useful.
> >
> > This also seems to explain why the sticky-check changes did not help:
> > by the time snd-usb-audio reaches the Playback Volume sticky check,
> > the earlier Mic SET_RES sanity test has already put the device into
> > the state where Playback SET_CUR no longer takes effect.
> >
> > Let me know if you would like me to test a patch or capture any
> > additional traces.
> >
> > Thanks,
> > Alexander
> >
> >
> > Am 16.08.2026 um 17:08 schrieb Rong Zhang:
> > > Hi Alexander,
> > >
> > > On Sun, 2026-08-16 at 16:09 +0200, Alexander Niemeyer wrote:
> > > > Hi Rong,
> > > >
> > > > Sure. The libusb tests were direct USB Audio Class 1 control transfers
> > > > to the headset using libusb/PyUSB, not ALSA mixer operations.
> > > >
> > > > I accessed Feature Unit 2 on AudioControl interface 0, master
> > > > channel 0,
> > > > with the UAC1 Volume control selector:
> > > >
> > > > wValue = 0x0200 /* Volume control, master channel */
> > > > wIndex = 0x0200 /* Feature Unit 2, interface 0 */
> > > >
> > > > I used the standard class-specific requests directly, including
> > > > GET_CUR,
> > > > GET_MIN, GET_MAX, GET_RES and SET_CUR, with signed 16-bit little-endian
> > > > volume values in 1/256 dB units.
> > > >
> > > > The device reported:
> > > >
> > > > GET_CUR: 0 ( 0 dB in that test)
> > > > GET_MIN: -16384 (-64 dB)
> > > > GET_MAX: 0 ( 0 dB)
> > > > GET_RES: 256 ( 1 dB)
> > > >
> > > > For the timing tests I issued SET_CUR for a target value and then
> > > > repeatedly queried GET_CUR until the value changed or the timeout
> > > > expired.
> > > >
> > > > Valid values became visible after roughly:
> > > >
> > > > -1 dB ~81 ms
> > > > -2 dB ~52 ms
> > > > -4 dB ~47 ms
> > > > -8 dB ~47 ms
> > > > -16 dB ~52 ms
> > > > -32 dB ~47 ms
> > > >
> > > > The advertised -64 dB minimum behaved differently: SET_CUR returned
> > > > successfully, but GET_CUR did not change even after 1000 ms.
> > > >
> > > > To access the AudioControl interface with libusb, I unbound the
> > > > AudioControl interface from snd-usb-audio for the duration of the test.
> > > >
> > > > I did not intentionally open a playback stream during those libusb
> > > > tests. Because the AudioControl interface had been unbound from
> > > > snd-usb-audio, I also do not believe there was an active ALSA playback
> > > > stream at that point.
> > > Thanks for the information.
> > >
> > > Unfortunately, I still don't exactly see why the device behaved
> > > differently when GET_CUR/SET_CUR requests were sent from snd-usb-audio
> > > compared to your libusb tests.
> > >
> > > snd-usb-audio also tries SET_RES to test the sanity of GET_RES. Could
> > > you
> > > test if it breaks your device's GET_CUR?
> > >
> > > Maybe comparing them with usbmon can show some clues. You can use
> > > Wireshark to sniff /dev/usbmon*.
> > >
> > > Hint: a Thunderbolt port usually corresponds to a dedicated USB root
> > > hub.
> > > If you have one, plug the device to it to get pure usbmon trace results
> > > with no noisy URBs from other devices.
> > >
> > > Thanks,
> > > Rong
> > >
> > > > If the open-stream state is important, I can repeat the experiment
> > > > specifically controlling for playback-stream-open versus
> > > > playback-stream-closed.
> > > >
> > > > Thanks,
> > > > Alexander
> > > >
> > > > Am 16.08.2026 um 15:50 schrieb Rong Zhang:
> > > > > Hi Alexander,
> > > > >
> > > > > On Sun, 2026-08-16 at 07:14 +0200, Alexander Niemeyer wrote:
> > > > > > Hi Rong,
> > > > > >
> > > > > > I tested the sticky-check part of your patch on the Logitech PRO X
> > > > > > Wireless (046d:0aba) on Fedora 44, kernel 7.1.8-200.fc44.x86_64.
> > > > > >
> > > > > > Since your patch is based on a newer tree, I used a minimal
> > > > > > backport of
> > > > > > the new ~16-value / 10 ms sticky-check logic to the 7.1.8 code. The
> > > > > > GET_CUR-broken handling from the newer tree was not included; GET_CUR
> > > > > > itself succeeds on this device.
> > > > > >
> > > > > > Unfortunately, the playback control is still classified as sticky:
> > > > > >
> > > > > > 2:0: sticky mixer values (-16384/0/256 => -3840), disabling
> > > > > >
> > > > > > I then instrumented the check and tried an additional diagnostic:
> > > > > > after
> > > > > > every successful SET_CUR, wait 100 ms and perform another GET_CUR
> > > > > > before
> > > > > > issuing the next SET_CUR.
> > > > > >
> > > > > > For the playback volume, the saved value was -3840 and GET_CUR
> > > > > > remained
> > > > > > at -3840 for every tested value, even after 100 ms, for example:
> > > > > >
> > > > > > test=-15104 immediate=-3840 after100ms=-3840
> > > > > > test=-13824 immediate=-3840 after100ms=-3840
> > > > > > test=-3584 immediate=-3840 after100ms=-3840
> > > > > > test=-2304 immediate=-3840 after100ms=-3840
> > > > > > test=-1024 immediate=-3840 after100ms=-3840
> > > > > > test=0 immediate=-3840 after100ms=-3840
> > > > > >
> > > > > > So in this case the issue does not appear to be simply that the
> > > > > > accumulated 10 ms sleeps are too short. During the probe-time sticky
> > > > > > check, SET_CUR succeeds but GET_CUR for the playback control remains
> > > > > > unchanged even when each SET_CUR is given 100 ms before the next one.
> > > > > >
> > > > > > This differs from my previous direct libusb tests with the
> > > > > > AudioControl
> > > > > > interface unbound, where valid SET_CUR values became visible through
> > > > > > GET_CUR after roughly 47–81 ms.
> > > > > Really interesting. Maybe the mixer changes its value only when
> > > > > there is
> > > > > an opened playback stream.
> > > > >
> > > > > Could you clarify your "libusb tests"?
> > > > >
> > > > > Thanks,
> > > > > Rong
> > > > >
> > > > > > The first debug line I saw with |saved=0| was from the Mic Capture
> > > > > > Volume control; that control changed immediately and returned as
> > > > > > non-sticky. The sequence above with |saved=-3840| is the
> > > > > > problematic PCM
> > > > > > Playback Volume control.
> > > > > >
> > > > > > I'd be happy to test another version or run additional diagnostics if
> > > > > > useful.
> > > > > >
> > > > > > Best regards,
> > > > > > Alexander
> > > > > >
> > > > > >
> > > > > > Am 15.08.2026 um 23:47 schrieb Rong Zhang:
> > > > > > > Some mixers are asynchronous, and some have broken min/max. They are
> > > > > > > mistakenly considered sticky due to how the check is implemented.
> > > > > > >
> > > > > > > Check sticky mixers more precisely by checking approximately 16
> > > > > > > values
> > > > > > > and adding a msleep(10) between each check, so that asynchronous
> > > > > > > mixers
> > > > > > > have enough time to change the value and mixers with broken
> > > > > > > min/max are
> > > > > > > checked properly. Additionally, mark GET_CUR as broken when
> > > > > > > get_cur_mix_raw() fails, instead of returning successfully.
> > > > > > >
> > > > > > > Reported-by: Alexander Niemeyer<adventureFAN@gmx.de>
> > > > > > > Closes:https://lore.kernel.org/r/6262cbbd-d1f2-4c9d-a1c7-9c5d12636f4b@gmx.de
> > > > > > >
> > > > > > > Signed-off-by: Rong Zhang<i@rong.moe>
> > > > > > > ---
> > > > > > > sound/usb/mixer.c | 51
> > > > > > > ++++++++++++++++++++++++++++++++++++++++++++-------
> > > > > > > 1 file changed, 44 insertions(+), 7 deletions(-)
> > > > > > >
> > > > > > > diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c
> > > > > > > index 703c118f9d4e..3d0f97730a06 100644
> > > > > > > --- a/sound/usb/mixer.c
> > > > > > > +++ b/sound/usb/mixer.c
> > > > > > > @@ -1256,22 +1256,59 @@ static void init_cur_mix_raw(struct
> > > > > > > usb_mixer_elem_info *cval, int ch, int idx)
> > > > > > > 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;
> > > > > > > + int test, check, res;
> > > > > > > +
> > > > > > > + /*
> > > > > > > + * Check approximately 16 values (15 intervals).
> > > > > > > + * If the resolution is not fine enough, check fewer values.
> > > > > > > + */
> > > > > > > + res = DIV_ROUND_UP(cval->max - cval->min, 15);
> > > > > > > + res = res ? roundup(res, cval->res) : cval->res;
> > > > > > > +
> > > > > > > + /*
> > > > > > > + * If (cval->max - cval->min) is not a multiple of
> > > > > > > cval->res, we still
> > > > > > > + * want to test cval->max anyway.
> > > > > > > + */
> > > > > > > + for (test = cval->min; test < cval->max + res; test += res) {
> > > > > > > + if (test > cval->max)
> > > > > > > + test = cval->max;
> > > > > > > - 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. */
> > > > > > > + if (snd_usb_set_cur_mix_value(cval, channel, 0, test))
> > > > > > > + return 0;
> > > > > > > +
> > > > > > > + if (get_cur_mix_raw(cval, channel, &check))
> > > > > > > + goto get_cur_broken;
> > > > > > > + if (check != saved) /* SET_CUR effective, non-sticky. */
> > > > > > > return 0;
> > > > > > > +
> > > > > > > + /*
> > > > > > > + * Leave some time for asynchronous mixers to change the
> > > > > > > value.
> > > > > > > + *
> > > > > > > + * Note that there is no need to wait between SET_CUR and
> > > > > > > + * GET_CUR, as we don't care whether the GET_CUR value
> > > > > > > matches
> > > > > > > + * the SET_CUR one. IOW, what we expect is just a
> > > > > > > GET_CUR value
> > > > > > > + * differing from the saved one.
> > > > > > > + *
> > > > > > > + * Mixers of most devices are synchronous. The should have
> > > > > > > + * returned early without extra sleep. Asynchronous
> > > > > > > mixers will
> > > > > > > + * return once the accumulated time is enough for them
> > > > > > > to change
> > > > > > > + * the value.
> > > > > > > + */
> > > > > > > + msleep(10);
> > > > > > > }
> > > > > > > + /* Check again after the last msleep(). */
> > > > > > > + if (get_cur_mix_raw(cval, channel, &check))
> > > > > > > + goto get_cur_broken;
> > > > > > > + if (check != saved)
> > > > > > > + return 0;
> > > > > > > +
> > > > > > > if (cval->head.mixer->chip->quirk_flags &
> > > > > > > QUIRK_FLAG_MIXER_GET_CUR_BROKEN) {
> > > > > > > +get_cur_broken:
> > > > > > > usb_audio_info(cval->head.mixer->chip,
> > > > > > > "%d:%d: broken mixer GET_CUR (%d/%d/%d =>
> > > > > > > %d)\n",
> > > > > > > cval->head.id,
> > > > > > > mixer_ctrl_intf(cval->head.mixer),
> > > > > > >
> > > > > > > ---
> > > > > > > base-commit: 3eb40771c00a8488fa6ed2cc1fe203477908bf38
> > > > > > > change-id: 74676fce-uac-precise-sticky-check-94474a22b57d
> > > > > > >
> > > > > > > Thanks,
> > > > > > > Rong
> > > > > > >
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH] ALSA: usb-audio: Check sticky mixers precisely
2026-08-19 16:39 ` Rong Zhang
@ 2026-08-19 18:37 ` Alexander Niemeyer
2026-08-20 12:51 ` Rong Zhang
0 siblings, 1 reply; 11+ messages in thread
From: Alexander Niemeyer @ 2026-08-19 18:37 UTC (permalink / raw)
To: Rong Zhang, Jaroslav Kysela, Takashi Iwai
Cc: Takashi Iwai, linux-sound, linux-kernel
Hi Rong,
thanks. I have now completed the three tests you asked for.
1. Your sticky-check patch with the detachable Mic boom attached
I tested the same minimal 7.1.8 backport of your ~16-value / 10 ms
sticky-check logic again, this time with the detachable microphone boom
physically attached.
The external snd-usb-audio module was rebuilt and loaded successfully
(the module taint was "O"), and the old 100 ms diagnostic changes were
removed before this test.
The result is still a failure:
sticky mixer values (-16384/0/256 => -3840), disabling
The PCM control exposes only:
Capabilities: pswitch pswitch-joined
and no Playback Volume control.
I repeated this once more with another dongle replug, without changing
the module or touching the volume wheel, and got the same result again.
So:
your sticky-check patch + Mic boom attached
-> still classified as sticky
-> reproduced twice
2. Is Playback SET_CUR really broken after Mic SET_RES with the boom
detached?
For this test I detached the microphone boom.
I used a test version of snd-usb-audio which:
- performed the Mic UAC1 resolution probe,
- kept the Playback Volume control exposed,
- clamped the known-broken advertised -64 dB minimum to -63 dB,
- and bypassed the normal ALSA mixer cache so that mixer reads fetch
GET_CUR from the device.
The initial Playback value was:
43 = -20 dB
I played a 3-second 1 kHz test tone and listened to its level.
Then I requested:
23 = -40 dB
The immediate output from the cset operation still showed:
values=43
I waited two seconds and issued a fresh cget. It then returned:
values=23
I played the exact same test tone again, and the second tone was clearly
quieter.
So Playback SET_CUR is physically effective.
At least in this test, the effect after Mic SET_RES with the boom
detached is not that Playback SET_CUR stops working. Instead, GET_CUR
can remain stale for some time after SET_CUR and later reflect the
actual value.
In short:
before: 43 (-20 dB)
SET_CUR requested: 23 (-40 dB)
immediate result: 43
GET_CUR after 2 s: 23
physical volume: clearly quieter
3. QUIRK_FLAG_MIXER_GET_CUR_BROKEN and mixer change notifications
Linux 7.1.8 does not contain QUIRK_FLAG_MIXER_GET_CUR_BROKEN yet, so for
this test I made a minimal backport of the flag behavior to the otherwise
stock 7.1.8 mixer code, without your new sticky-check patch.
I loaded snd-usb-audio with:
quirk_flags=046d:0aba:mixer_get_cur_broken
The kernel detected the constant GET_CUR condition and kept the Playback
Volume control exposed:
sticky mixer values (-16384/0/256 => -3840), disabling
PROXTESTC: treating constant GET_CUR as broken
For this test the microphone boom was attached.
Before turning the physical headset wheel:
ALSA cget: 56
wpctl: Volume 0.72
I then deliberately turned the physical volume wheel further down.
Afterwards:
ALSA cget: 47
wpctl: Volume 0.52
KDE's volume OSD also reacted normally while I turned the wheel.
So, on this device, using MIXER_GET_CUR_BROKEN did not break propagation
of physical mixer changes through the audio stack:
physical wheel
-> ALSA value changed
-> PipeWire value changed
-> KDE OSD reacted normally
Therefore the current picture seems to be:
- Your revised sticky check still rejects the Playback Volume control,
both with the detachable Mic boom attached and detached.
- With the Mic boom detached, Mic SET_RES can make Playback GET_CUR stay
stale after a Playback SET_CUR, but Playback SET_CUR itself is still
physically effective.
- At least on this Logitech device, treating GET_CUR as broken did not
prevent physical wheel changes from reaching ALSA/PipeWire/KDE.
Let me know what you would like me to test next. I have kept the Fedora
test environment and module-signing setup intact.
Thanks,
Alexander
Am 19.08.2026 um 18:39 schrieb Rong Zhang:
> Hi Alexander,
>
> On Tue, 2026-08-18 at 17:24 +0200, Alexander Niemeyer wrote:
>> Hi Rong,
>>
>> one important follow-up to my previous message:
>>
>> I realized that the detachable microphone boom was not physically connected
>> to the headset during the direct libusb tests I reported earlier.
>>
>> I therefore repeated the relevant tests with the boom microphone attached,
>> and the result changes in an important way.
>>
>> With the boom microphone attached, on a fresh device:
>>
>> Mic GET_RES = 256
>> no SET_RES
>>
>> Playback:
>> GET_CUR = -3840 (-15 dB)
>> SET_CUR = -2048 (-8 dB)
>> GET_CUR changes to -2048 after 65.3 ms
>>
>> Result: PASS
>>
>> After another power cycle, with the boom still attached:
>>
>> Mic GET_RES before = 256
>> Mic SET_RES(128) = success
>> Mic GET_RES after = 256
>>
>> Playback:
>> GET_CUR = -3840 (-15 dB)
>> SET_CUR = -2048 (-8 dB)
>> GET_CUR changes to -2048 after 66.3 ms
>>
>> Result: PASS
>>
>> I also repeated the complete Mic SET_RES sequence:
>>
>> SET_RES 128
>> SET_RES 64
>> SET_RES 32
>> SET_RES 16
>> SET_RES 8
>> SET_RES 4
>> SET_RES 2
>> SET_RES 1
>>
>> All requests return success, GET_RES remains 256, and Playback
>> SET_CUR(-8 dB)
>> still works afterwards, with GET_CUR changing after 76.0 ms.
>>
>> However, with the boom microphone attached, the stock Fedora 7.1.8
>> snd-usb-audio driver still reproduces the original failure:
>>
>> sticky mixer values (-16384/0/256 => -3840), disabling
>>
>> and no PCM Playback Volume control is exposed.
> Could you test my patch with Mic attached?
>
>> So I need to correct the conclusion from my previous message:
>>
>> The Mic SET_RES behavior is real, but it depends on whether the detachable
>> microphone boom is physically connected.
>>
>> With the boom disconnected:
>> a single Mic SET_RES(128) is sufficient to make subsequent Playback
>> SET_CUR ineffective for more than 1200 ms.
>>
>> With the boom connected:
>> both a single Mic SET_RES(128) and the full SET_RES sequence leave
>> Playback SET_CUR working normally.
>>
> Thanks for your tests.
>
> Let me conclude:
>
> - Mic detached: SET_RES on Mic breaks Playback
> - Mic attached: SET_RES on Mic breaks nothing
>
> Still, I doubt if Playback SET_CUR is really broken in the first case.
> There is a chance that Playback SET_CUR is effective while GET_CUR
> becomes broken.
>
> Could you test:
>
> - Leave the Mic detached
> - SET_RES on Mic
> - GET_CUR on Playback
> - Play some audio, listen to it
> - SET_CUR on Playback
> - GET_CUR on Playback
> - Play some audio again, listen to it, and tell if the physical volume
> has changed
>
> The procedure can be achieved by hacking into the snd-usb-audio driver
> and nullifying its cache mechanism, so that you can fetch GET_CUR values
> from standard ALSA mixer interface.
>
> If you can notice physical volume changes, it implies SET_RES on Mic
> break Playback GET_CUR without breaking SET_CUR. Otherwise, it break
> Playback SET_CUR.
>
> If it only breaks GET_CUR, it implies the methodology of sticky check is
> problematic. In this case I would probably consider demoting the sticky
> check's severity.
>
> I am also thinking about how we should demote the severity. Could you
> also test if applying QUIRK_FLAG_MIXER_GET_CUR_BROKEN (without this
> patch) breaks mixer change notifications? I.e., tune the volume with the
> buttons/knobs on the device and see if the audio stack reflects the
> change. I guess it will break the notifications, but I would still like
> to wait for your confirmation.
>
> Thanks,
> Rong
>
>> Therefore Mic SET_RES alone does not explain the complete snd-usb-audio
>> failure in all device configurations. There must be another difference in
>> the full snd-usb-audio initialization path when the boom is attached.
>>
>> The separate Playback -64 dB minimum issue I reported remains unchanged:
>> -64 dB is ineffective, -63 dB works, and attempting -64 dB does not prevent
>> subsequent valid Playback SET_CUR requests from working.
>>
>> Sorry for not noticing the detachable-microphone state earlier. I wanted to
>> send this correction as soon as I confirmed it.
>>
>> I still have the usbmon/pcapng captures and can run further targeted tests
>> if useful.
>>
>> Thanks,
>> Alexander
>>
>> Am 18.08.2026 um 16:41 schrieb Alexander Niemeyer:
>>> Hi Rong,
>>>
>>> I think we found the reason for the different behavior.
>>>
>>> I reproduced the snd-usb-audio initialization sequence step by step
>>> with direct libusb UAC1 control transfers and isolated the problem to
>>> SET_RES on the *Mic Capture Volume control (Feature Unit 3)*.
>>>
>>> A fresh-device control test looks like this:
>>>
>>> Mic GET_RES = 256
>>> no SET_RES
>>> Playback:
>>> GET_CUR = -3840 (-15 dB)
>>> SET_CUR = -2048 (-8 dB)
>>> GET_CUR changes to -2048 after 68.0 ms
>>> Result: PASS
>>>
>>> After another power cycle, I repeated the same test but issued just
>>> *one* SET_RES request to the Mic Feature Unit first:
>>>
>>> Mic GET_RES before = 256
>>> Mic SET_RES(128) = success
>>> Mic GET_RES after = 256
>>> Playback:
>>> GET_CUR = -3840 (-15 dB)
>>> SET_CUR = -2048 (-8 dB)
>>> GET_CUR remains -3840 for more than 1200 ms
>>> Result: FAIL
>>>
>>> So a single successful |SET_RES(128)| on Feature Unit 3 is sufficient
>>> to make subsequent |SET_CUR| requests to the Playback Volume control
>>> on Feature Unit 2 ineffective.
>>>
>>> I also tested the complete Mic SET_RES sequence used by snd-usb-audio:
>>>
>>> SET_RES 128
>>> SET_RES 64
>>> SET_RES 32
>>> SET_RES 16
>>> SET_RES 8
>>> SET_RES 4
>>> SET_RES 2
>>> SET_RES 1
>>>
>>> All requests return success, while GET_RES remains 256. After that
>>> sequence, Playback SET_CUR also remains ineffective for more than 1200
>>> ms.
>>>
>>> Interestingly, the Mic control itself still works after this. In an
>>> ALSA-like Mic probe I could successfully change Mic Volume from 0 dB
>>> to -64 dB and then +1 dB, with GET_CUR reflecting those changes
>>> essentially immediately (~0.3 ms). Playback remained broken afterwards.
>>>
>>> I also checked whether SET_RES on the Playback Feature Unit itself
>>> causes the problem. It does not:
>>>
>>> Playback GET_RES = 256
>>> SET_RES 128 -> 64 -> 32 -> 16 -> 8 -> 4 -> 2 -> 1
>>> GET_RES still = 256
>>> Playback SET_CUR(-8 dB)
>>> GET_CUR changes successfully after 87.7 ms
>>>
>>> So the problematic operation appears specifically to be *SET_RES on
>>> the Mic Feature Unit affecting the Playback Feature Unit*.
>>>
>>> I also clarified the separate advertised-minimum issue:
>>>
>>> Playback SET_CUR(-64 dB):
>>> no change after >1200 ms
>>> followed by SET_CUR(-8 dB):
>>> works normally after 54.9 ms
>>> Playback SET_CUR(-63 dB):
>>> works after 75.8 ms
>>> followed by SET_CUR(-8 dB):
>>> works after 43.6 ms
>>>
>>> Therefore the broken -64 dB endpoint does not leave the device in the
>>> broken state; it is a separate issue. -63 dB works normally.
>>>
>>> I also captured usbmon/pcapng traces for both a working direct-libusb
>>> SET_CUR sequence and the failing snd-usb-audio initialization, so I
>>> can send those as well if they are useful.
>>>
>>> This also seems to explain why the sticky-check changes did not help:
>>> by the time snd-usb-audio reaches the Playback Volume sticky check,
>>> the earlier Mic SET_RES sanity test has already put the device into
>>> the state where Playback SET_CUR no longer takes effect.
>>>
>>> Let me know if you would like me to test a patch or capture any
>>> additional traces.
>>>
>>> Thanks,
>>> Alexander
>>>
>>>
>>> Am 16.08.2026 um 17:08 schrieb Rong Zhang:
>>>> Hi Alexander,
>>>>
>>>> On Sun, 2026-08-16 at 16:09 +0200, Alexander Niemeyer wrote:
>>>>> Hi Rong,
>>>>>
>>>>> Sure. The libusb tests were direct USB Audio Class 1 control transfers
>>>>> to the headset using libusb/PyUSB, not ALSA mixer operations.
>>>>>
>>>>> I accessed Feature Unit 2 on AudioControl interface 0, master
>>>>> channel 0,
>>>>> with the UAC1 Volume control selector:
>>>>>
>>>>> wValue = 0x0200 /* Volume control, master channel */
>>>>> wIndex = 0x0200 /* Feature Unit 2, interface 0 */
>>>>>
>>>>> I used the standard class-specific requests directly, including
>>>>> GET_CUR,
>>>>> GET_MIN, GET_MAX, GET_RES and SET_CUR, with signed 16-bit little-endian
>>>>> volume values in 1/256 dB units.
>>>>>
>>>>> The device reported:
>>>>>
>>>>> GET_CUR: 0 ( 0 dB in that test)
>>>>> GET_MIN: -16384 (-64 dB)
>>>>> GET_MAX: 0 ( 0 dB)
>>>>> GET_RES: 256 ( 1 dB)
>>>>>
>>>>> For the timing tests I issued SET_CUR for a target value and then
>>>>> repeatedly queried GET_CUR until the value changed or the timeout
>>>>> expired.
>>>>>
>>>>> Valid values became visible after roughly:
>>>>>
>>>>> -1 dB ~81 ms
>>>>> -2 dB ~52 ms
>>>>> -4 dB ~47 ms
>>>>> -8 dB ~47 ms
>>>>> -16 dB ~52 ms
>>>>> -32 dB ~47 ms
>>>>>
>>>>> The advertised -64 dB minimum behaved differently: SET_CUR returned
>>>>> successfully, but GET_CUR did not change even after 1000 ms.
>>>>>
>>>>> To access the AudioControl interface with libusb, I unbound the
>>>>> AudioControl interface from snd-usb-audio for the duration of the test.
>>>>>
>>>>> I did not intentionally open a playback stream during those libusb
>>>>> tests. Because the AudioControl interface had been unbound from
>>>>> snd-usb-audio, I also do not believe there was an active ALSA playback
>>>>> stream at that point.
>>>> Thanks for the information.
>>>>
>>>> Unfortunately, I still don't exactly see why the device behaved
>>>> differently when GET_CUR/SET_CUR requests were sent from snd-usb-audio
>>>> compared to your libusb tests.
>>>>
>>>> snd-usb-audio also tries SET_RES to test the sanity of GET_RES. Could
>>>> you
>>>> test if it breaks your device's GET_CUR?
>>>>
>>>> Maybe comparing them with usbmon can show some clues. You can use
>>>> Wireshark to sniff /dev/usbmon*.
>>>>
>>>> Hint: a Thunderbolt port usually corresponds to a dedicated USB root
>>>> hub.
>>>> If you have one, plug the device to it to get pure usbmon trace results
>>>> with no noisy URBs from other devices.
>>>>
>>>> Thanks,
>>>> Rong
>>>>
>>>>> If the open-stream state is important, I can repeat the experiment
>>>>> specifically controlling for playback-stream-open versus
>>>>> playback-stream-closed.
>>>>>
>>>>> Thanks,
>>>>> Alexander
>>>>>
>>>>> Am 16.08.2026 um 15:50 schrieb Rong Zhang:
>>>>>> Hi Alexander,
>>>>>>
>>>>>> On Sun, 2026-08-16 at 07:14 +0200, Alexander Niemeyer wrote:
>>>>>>> Hi Rong,
>>>>>>>
>>>>>>> I tested the sticky-check part of your patch on the Logitech PRO X
>>>>>>> Wireless (046d:0aba) on Fedora 44, kernel 7.1.8-200.fc44.x86_64.
>>>>>>>
>>>>>>> Since your patch is based on a newer tree, I used a minimal
>>>>>>> backport of
>>>>>>> the new ~16-value / 10 ms sticky-check logic to the 7.1.8 code. The
>>>>>>> GET_CUR-broken handling from the newer tree was not included; GET_CUR
>>>>>>> itself succeeds on this device.
>>>>>>>
>>>>>>> Unfortunately, the playback control is still classified as sticky:
>>>>>>>
>>>>>>> 2:0: sticky mixer values (-16384/0/256 => -3840), disabling
>>>>>>>
>>>>>>> I then instrumented the check and tried an additional diagnostic:
>>>>>>> after
>>>>>>> every successful SET_CUR, wait 100 ms and perform another GET_CUR
>>>>>>> before
>>>>>>> issuing the next SET_CUR.
>>>>>>>
>>>>>>> For the playback volume, the saved value was -3840 and GET_CUR
>>>>>>> remained
>>>>>>> at -3840 for every tested value, even after 100 ms, for example:
>>>>>>>
>>>>>>> test=-15104 immediate=-3840 after100ms=-3840
>>>>>>> test=-13824 immediate=-3840 after100ms=-3840
>>>>>>> test=-3584 immediate=-3840 after100ms=-3840
>>>>>>> test=-2304 immediate=-3840 after100ms=-3840
>>>>>>> test=-1024 immediate=-3840 after100ms=-3840
>>>>>>> test=0 immediate=-3840 after100ms=-3840
>>>>>>>
>>>>>>> So in this case the issue does not appear to be simply that the
>>>>>>> accumulated 10 ms sleeps are too short. During the probe-time sticky
>>>>>>> check, SET_CUR succeeds but GET_CUR for the playback control remains
>>>>>>> unchanged even when each SET_CUR is given 100 ms before the next one.
>>>>>>>
>>>>>>> This differs from my previous direct libusb tests with the
>>>>>>> AudioControl
>>>>>>> interface unbound, where valid SET_CUR values became visible through
>>>>>>> GET_CUR after roughly 47–81 ms.
>>>>>> Really interesting. Maybe the mixer changes its value only when
>>>>>> there is
>>>>>> an opened playback stream.
>>>>>>
>>>>>> Could you clarify your "libusb tests"?
>>>>>>
>>>>>> Thanks,
>>>>>> Rong
>>>>>>
>>>>>>> The first debug line I saw with |saved=0| was from the Mic Capture
>>>>>>> Volume control; that control changed immediately and returned as
>>>>>>> non-sticky. The sequence above with |saved=-3840| is the
>>>>>>> problematic PCM
>>>>>>> Playback Volume control.
>>>>>>>
>>>>>>> I'd be happy to test another version or run additional diagnostics if
>>>>>>> useful.
>>>>>>>
>>>>>>> Best regards,
>>>>>>> Alexander
>>>>>>>
>>>>>>>
>>>>>>> Am 15.08.2026 um 23:47 schrieb Rong Zhang:
>>>>>>>> Some mixers are asynchronous, and some have broken min/max. They are
>>>>>>>> mistakenly considered sticky due to how the check is implemented.
>>>>>>>>
>>>>>>>> Check sticky mixers more precisely by checking approximately 16
>>>>>>>> values
>>>>>>>> and adding a msleep(10) between each check, so that asynchronous
>>>>>>>> mixers
>>>>>>>> have enough time to change the value and mixers with broken
>>>>>>>> min/max are
>>>>>>>> checked properly. Additionally, mark GET_CUR as broken when
>>>>>>>> get_cur_mix_raw() fails, instead of returning successfully.
>>>>>>>>
>>>>>>>> Reported-by: Alexander Niemeyer<adventureFAN@gmx.de>
>>>>>>>> Closes:https://lore.kernel.org/r/6262cbbd-d1f2-4c9d-a1c7-9c5d12636f4b@gmx.de
>>>>>>>>
>>>>>>>> Signed-off-by: Rong Zhang<i@rong.moe>
>>>>>>>> ---
>>>>>>>> sound/usb/mixer.c | 51
>>>>>>>> ++++++++++++++++++++++++++++++++++++++++++++-------
>>>>>>>> 1 file changed, 44 insertions(+), 7 deletions(-)
>>>>>>>>
>>>>>>>> diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c
>>>>>>>> index 703c118f9d4e..3d0f97730a06 100644
>>>>>>>> --- a/sound/usb/mixer.c
>>>>>>>> +++ b/sound/usb/mixer.c
>>>>>>>> @@ -1256,22 +1256,59 @@ static void init_cur_mix_raw(struct
>>>>>>>> usb_mixer_elem_info *cval, int ch, int idx)
>>>>>>>> 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;
>>>>>>>> + int test, check, res;
>>>>>>>> +
>>>>>>>> + /*
>>>>>>>> + * Check approximately 16 values (15 intervals).
>>>>>>>> + * If the resolution is not fine enough, check fewer values.
>>>>>>>> + */
>>>>>>>> + res = DIV_ROUND_UP(cval->max - cval->min, 15);
>>>>>>>> + res = res ? roundup(res, cval->res) : cval->res;
>>>>>>>> +
>>>>>>>> + /*
>>>>>>>> + * If (cval->max - cval->min) is not a multiple of
>>>>>>>> cval->res, we still
>>>>>>>> + * want to test cval->max anyway.
>>>>>>>> + */
>>>>>>>> + for (test = cval->min; test < cval->max + res; test += res) {
>>>>>>>> + if (test > cval->max)
>>>>>>>> + test = cval->max;
>>>>>>>> - 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. */
>>>>>>>> + if (snd_usb_set_cur_mix_value(cval, channel, 0, test))
>>>>>>>> + return 0;
>>>>>>>> +
>>>>>>>> + if (get_cur_mix_raw(cval, channel, &check))
>>>>>>>> + goto get_cur_broken;
>>>>>>>> + if (check != saved) /* SET_CUR effective, non-sticky. */
>>>>>>>> return 0;
>>>>>>>> +
>>>>>>>> + /*
>>>>>>>> + * Leave some time for asynchronous mixers to change the
>>>>>>>> value.
>>>>>>>> + *
>>>>>>>> + * Note that there is no need to wait between SET_CUR and
>>>>>>>> + * GET_CUR, as we don't care whether the GET_CUR value
>>>>>>>> matches
>>>>>>>> + * the SET_CUR one. IOW, what we expect is just a
>>>>>>>> GET_CUR value
>>>>>>>> + * differing from the saved one.
>>>>>>>> + *
>>>>>>>> + * Mixers of most devices are synchronous. The should have
>>>>>>>> + * returned early without extra sleep. Asynchronous
>>>>>>>> mixers will
>>>>>>>> + * return once the accumulated time is enough for them
>>>>>>>> to change
>>>>>>>> + * the value.
>>>>>>>> + */
>>>>>>>> + msleep(10);
>>>>>>>> }
>>>>>>>> + /* Check again after the last msleep(). */
>>>>>>>> + if (get_cur_mix_raw(cval, channel, &check))
>>>>>>>> + goto get_cur_broken;
>>>>>>>> + if (check != saved)
>>>>>>>> + return 0;
>>>>>>>> +
>>>>>>>> if (cval->head.mixer->chip->quirk_flags &
>>>>>>>> QUIRK_FLAG_MIXER_GET_CUR_BROKEN) {
>>>>>>>> +get_cur_broken:
>>>>>>>> usb_audio_info(cval->head.mixer->chip,
>>>>>>>> "%d:%d: broken mixer GET_CUR (%d/%d/%d =>
>>>>>>>> %d)\n",
>>>>>>>> cval->head.id,
>>>>>>>> mixer_ctrl_intf(cval->head.mixer),
>>>>>>>>
>>>>>>>> ---
>>>>>>>> base-commit: 3eb40771c00a8488fa6ed2cc1fe203477908bf38
>>>>>>>> change-id: 74676fce-uac-precise-sticky-check-94474a22b57d
>>>>>>>>
>>>>>>>> Thanks,
>>>>>>>> Rong
>>>>>>>>
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH] ALSA: usb-audio: Check sticky mixers precisely
2026-08-19 18:37 ` Alexander Niemeyer
@ 2026-08-20 12:51 ` Rong Zhang
2026-08-20 15:01 ` Alexander Niemeyer
0 siblings, 1 reply; 11+ messages in thread
From: Rong Zhang @ 2026-08-20 12:51 UTC (permalink / raw)
To: Alexander Niemeyer, Jaroslav Kysela, Takashi Iwai
Cc: Takashi Iwai, linux-sound, linux-kernel
Hi Alexander,
Thanks for testing.
On Wed, 2026-08-19 at 20:37 +0200, Alexander Niemeyer wrote:
> Hi Rong,
>
> thanks. I have now completed the three tests you asked for.
>
> 1. Your sticky-check patch with the detachable Mic boom attached
>
> I tested the same minimal 7.1.8 backport of your ~16-value / 10 ms
> sticky-check logic again, this time with the detachable microphone boom
> physically attached.
>
> The external snd-usb-audio module was rebuilt and loaded successfully
> (the module taint was "O"), and the old 100 ms diagnostic changes were
> removed before this test.
>
> The result is still a failure:
>
> sticky mixer values (-16384/0/256 => -3840), disabling
>
> The PCM control exposes only:
>
> Capabilities: pswitch pswitch-joined
>
> and no Playback Volume control.
>
> I repeated this once more with another dongle replug, without changing
> the module or touching the volume wheel, and got the same result again.
>
> So:
>
> your sticky-check patch + Mic boom attached
> -> still classified as sticky
> -> reproduced twice
>
I didn't expect that, but I will take your result anyway. Your device
seems to have more mysterious behavior, sigh.
>
> 2. Is Playback SET_CUR really broken after Mic SET_RES with the boom
> detached?
>
> For this test I detached the microphone boom.
>
> I used a test version of snd-usb-audio which:
>
> - performed the Mic UAC1 resolution probe,
> - kept the Playback Volume control exposed,
> - clamped the known-broken advertised -64 dB minimum to -63 dB,
> - and bypassed the normal ALSA mixer cache so that mixer reads fetch
> GET_CUR from the device.
>
> The initial Playback value was:
>
> 43 = -20 dB
>
> I played a 3-second 1 kHz test tone and listened to its level.
>
> Then I requested:
>
> 23 = -40 dB
>
> The immediate output from the cset operation still showed:
>
> values=43
>
> I waited two seconds and issued a fresh cget. It then returned:
>
> values=23
>
> I played the exact same test tone again, and the second tone was clearly
> quieter.
Just curious: could you play the test tone throughout the entire process
and see how long does the device need to change its physical volume?
>
> So Playback SET_CUR is physically effective.
OK then. I will propose a patch to demote the sticky check to
informational warning. Before that, I need to reconfirm your test results
about MIXER_GET_CUR_BROKEN.
>
> At least in this test, the effect after Mic SET_RES with the boom
> detached is not that Playback SET_CUR stops working. Instead, GET_CUR
> can remain stale for some time after SET_CUR and later reflect the
> actual value.
>
> In short:
>
> before: 43 (-20 dB)
> SET_CUR requested: 23 (-40 dB)
> immediate result: 43
> GET_CUR after 2 s: 23
> physical volume: clearly quieter
>
>
> 3. QUIRK_FLAG_MIXER_GET_CUR_BROKEN and mixer change notifications
>
> Linux 7.1.8 does not contain QUIRK_FLAG_MIXER_GET_CUR_BROKEN yet, so for
> this test I made a minimal backport of the flag behavior to the otherwise
> stock 7.1.8 mixer code,
>
What was "a minimal backport of the flag behavior"? Commit ad2c7d2c42af
("ALSA: usb-audio: Add QUIRK_FLAG_MIXER_GET_CUR_BROKEN") is already
minimal and must be backported as a whole.
> without your new sticky-check patch.
>
> I loaded snd-usb-audio with:
>
> quirk_flags=046d:0aba:mixer_get_cur_broken
>
> The kernel detected the constant GET_CUR condition and kept the Playback
> Volume control exposed:
>
> sticky mixer values (-16384/0/256 => -3840), disabling
> PROXTESTC: treating constant GET_CUR as broken
>
> For this test the microphone boom was attached.
>
> Before turning the physical headset wheel:
>
> ALSA cget: 56
> wpctl: Volume 0.72
>
> I then deliberately turned the physical volume wheel further down.
>
> Afterwards:
>
> ALSA cget: 47
> wpctl: Volume 0.52
>
> KDE's volume OSD also reacted normally while I turned the wheel.
>
> So, on this device, using MIXER_GET_CUR_BROKEN did not break propagation
> of physical mixer changes through the audio stack:
>
> physical wheel
> -> ALSA value changed
> -> PipeWire value changed
> -> KDE OSD reacted normally
Did you properly backport QUIRK_FLAG_MIXER_GET_CUR_BROKEN? In particular,
did you backport the change in snd_usb_mixer_notify_id()?
If you backported the change properly, your test results imply that your
device doesn't depend on mixer interrupts at all -- does the volume wheel
emit HID events to ask software to tune the mixer value on its behalf?
Please try `evtest' to see if it emits multimedia key events.
Thanks,
Rong
>
>
> Therefore the current picture seems to be:
>
> - Your revised sticky check still rejects the Playback Volume control,
> both with the detachable Mic boom attached and detached.
>
> - With the Mic boom detached, Mic SET_RES can make Playback GET_CUR stay
> stale after a Playback SET_CUR, but Playback SET_CUR itself is still
> physically effective.
>
> - At least on this Logitech device, treating GET_CUR as broken did not
> prevent physical wheel changes from reaching ALSA/PipeWire/KDE.
>
> Let me know what you would like me to test next. I have kept the Fedora
> test environment and module-signing setup intact.
>
> Thanks,
> Alexander
>
> Am 19.08.2026 um 18:39 schrieb Rong Zhang:
> > Hi Alexander,
> >
> > On Tue, 2026-08-18 at 17:24 +0200, Alexander Niemeyer wrote:
> > > Hi Rong,
> > >
> > > one important follow-up to my previous message:
> > >
> > > I realized that the detachable microphone boom was not physically connected
> > > to the headset during the direct libusb tests I reported earlier.
> > >
> > > I therefore repeated the relevant tests with the boom microphone attached,
> > > and the result changes in an important way.
> > >
> > > With the boom microphone attached, on a fresh device:
> > >
> > > Mic GET_RES = 256
> > > no SET_RES
> > >
> > > Playback:
> > > GET_CUR = -3840 (-15 dB)
> > > SET_CUR = -2048 (-8 dB)
> > > GET_CUR changes to -2048 after 65.3 ms
> > >
> > > Result: PASS
> > >
> > > After another power cycle, with the boom still attached:
> > >
> > > Mic GET_RES before = 256
> > > Mic SET_RES(128) = success
> > > Mic GET_RES after = 256
> > >
> > > Playback:
> > > GET_CUR = -3840 (-15 dB)
> > > SET_CUR = -2048 (-8 dB)
> > > GET_CUR changes to -2048 after 66.3 ms
> > >
> > > Result: PASS
> > >
> > > I also repeated the complete Mic SET_RES sequence:
> > >
> > > SET_RES 128
> > > SET_RES 64
> > > SET_RES 32
> > > SET_RES 16
> > > SET_RES 8
> > > SET_RES 4
> > > SET_RES 2
> > > SET_RES 1
> > >
> > > All requests return success, GET_RES remains 256, and Playback
> > > SET_CUR(-8 dB)
> > > still works afterwards, with GET_CUR changing after 76.0 ms.
> > >
> > > However, with the boom microphone attached, the stock Fedora 7.1.8
> > > snd-usb-audio driver still reproduces the original failure:
> > >
> > > sticky mixer values (-16384/0/256 => -3840), disabling
> > >
> > > and no PCM Playback Volume control is exposed.
> > Could you test my patch with Mic attached?
> >
> > > So I need to correct the conclusion from my previous message:
> > >
> > > The Mic SET_RES behavior is real, but it depends on whether the detachable
> > > microphone boom is physically connected.
> > >
> > > With the boom disconnected:
> > > a single Mic SET_RES(128) is sufficient to make subsequent Playback
> > > SET_CUR ineffective for more than 1200 ms.
> > >
> > > With the boom connected:
> > > both a single Mic SET_RES(128) and the full SET_RES sequence leave
> > > Playback SET_CUR working normally.
> > >
> > Thanks for your tests.
> >
> > Let me conclude:
> >
> > - Mic detached: SET_RES on Mic breaks Playback
> > - Mic attached: SET_RES on Mic breaks nothing
> >
> > Still, I doubt if Playback SET_CUR is really broken in the first case.
> > There is a chance that Playback SET_CUR is effective while GET_CUR
> > becomes broken.
> >
> > Could you test:
> >
> > - Leave the Mic detached
> > - SET_RES on Mic
> > - GET_CUR on Playback
> > - Play some audio, listen to it
> > - SET_CUR on Playback
> > - GET_CUR on Playback
> > - Play some audio again, listen to it, and tell if the physical volume
> > has changed
> >
> > The procedure can be achieved by hacking into the snd-usb-audio driver
> > and nullifying its cache mechanism, so that you can fetch GET_CUR values
> > from standard ALSA mixer interface.
> >
> > If you can notice physical volume changes, it implies SET_RES on Mic
> > break Playback GET_CUR without breaking SET_CUR. Otherwise, it break
> > Playback SET_CUR.
> >
> > If it only breaks GET_CUR, it implies the methodology of sticky check is
> > problematic. In this case I would probably consider demoting the sticky
> > check's severity.
> >
> > I am also thinking about how we should demote the severity. Could you
> > also test if applying QUIRK_FLAG_MIXER_GET_CUR_BROKEN (without this
> > patch) breaks mixer change notifications? I.e., tune the volume with the
> > buttons/knobs on the device and see if the audio stack reflects the
> > change. I guess it will break the notifications, but I would still like
> > to wait for your confirmation.
> >
> > Thanks,
> > Rong
> >
> > > Therefore Mic SET_RES alone does not explain the complete snd-usb-audio
> > > failure in all device configurations. There must be another difference in
> > > the full snd-usb-audio initialization path when the boom is attached.
> > >
> > > The separate Playback -64 dB minimum issue I reported remains unchanged:
> > > -64 dB is ineffective, -63 dB works, and attempting -64 dB does not prevent
> > > subsequent valid Playback SET_CUR requests from working.
> > >
> > > Sorry for not noticing the detachable-microphone state earlier. I wanted to
> > > send this correction as soon as I confirmed it.
> > >
> > > I still have the usbmon/pcapng captures and can run further targeted tests
> > > if useful.
> > >
> > > Thanks,
> > > Alexander
> > >
> > > Am 18.08.2026 um 16:41 schrieb Alexander Niemeyer:
> > > > Hi Rong,
> > > >
> > > > I think we found the reason for the different behavior.
> > > >
> > > > I reproduced the snd-usb-audio initialization sequence step by step
> > > > with direct libusb UAC1 control transfers and isolated the problem to
> > > > SET_RES on the *Mic Capture Volume control (Feature Unit 3)*.
> > > >
> > > > A fresh-device control test looks like this:
> > > >
> > > > Mic GET_RES = 256
> > > > no SET_RES
> > > > Playback:
> > > > GET_CUR = -3840 (-15 dB)
> > > > SET_CUR = -2048 (-8 dB)
> > > > GET_CUR changes to -2048 after 68.0 ms
> > > > Result: PASS
> > > >
> > > > After another power cycle, I repeated the same test but issued just
> > > > *one* SET_RES request to the Mic Feature Unit first:
> > > >
> > > > Mic GET_RES before = 256
> > > > Mic SET_RES(128) = success
> > > > Mic GET_RES after = 256
> > > > Playback:
> > > > GET_CUR = -3840 (-15 dB)
> > > > SET_CUR = -2048 (-8 dB)
> > > > GET_CUR remains -3840 for more than 1200 ms
> > > > Result: FAIL
> > > >
> > > > So a single successful |SET_RES(128)| on Feature Unit 3 is sufficient
> > > > to make subsequent |SET_CUR| requests to the Playback Volume control
> > > > on Feature Unit 2 ineffective.
> > > >
> > > > I also tested the complete Mic SET_RES sequence used by snd-usb-audio:
> > > >
> > > > SET_RES 128
> > > > SET_RES 64
> > > > SET_RES 32
> > > > SET_RES 16
> > > > SET_RES 8
> > > > SET_RES 4
> > > > SET_RES 2
> > > > SET_RES 1
> > > >
> > > > All requests return success, while GET_RES remains 256. After that
> > > > sequence, Playback SET_CUR also remains ineffective for more than 1200
> > > > ms.
> > > >
> > > > Interestingly, the Mic control itself still works after this. In an
> > > > ALSA-like Mic probe I could successfully change Mic Volume from 0 dB
> > > > to -64 dB and then +1 dB, with GET_CUR reflecting those changes
> > > > essentially immediately (~0.3 ms). Playback remained broken afterwards.
> > > >
> > > > I also checked whether SET_RES on the Playback Feature Unit itself
> > > > causes the problem. It does not:
> > > >
> > > > Playback GET_RES = 256
> > > > SET_RES 128 -> 64 -> 32 -> 16 -> 8 -> 4 -> 2 -> 1
> > > > GET_RES still = 256
> > > > Playback SET_CUR(-8 dB)
> > > > GET_CUR changes successfully after 87.7 ms
> > > >
> > > > So the problematic operation appears specifically to be *SET_RES on
> > > > the Mic Feature Unit affecting the Playback Feature Unit*.
> > > >
> > > > I also clarified the separate advertised-minimum issue:
> > > >
> > > > Playback SET_CUR(-64 dB):
> > > > no change after >1200 ms
> > > > followed by SET_CUR(-8 dB):
> > > > works normally after 54.9 ms
> > > > Playback SET_CUR(-63 dB):
> > > > works after 75.8 ms
> > > > followed by SET_CUR(-8 dB):
> > > > works after 43.6 ms
> > > >
> > > > Therefore the broken -64 dB endpoint does not leave the device in the
> > > > broken state; it is a separate issue. -63 dB works normally.
> > > >
> > > > I also captured usbmon/pcapng traces for both a working direct-libusb
> > > > SET_CUR sequence and the failing snd-usb-audio initialization, so I
> > > > can send those as well if they are useful.
> > > >
> > > > This also seems to explain why the sticky-check changes did not help:
> > > > by the time snd-usb-audio reaches the Playback Volume sticky check,
> > > > the earlier Mic SET_RES sanity test has already put the device into
> > > > the state where Playback SET_CUR no longer takes effect.
> > > >
> > > > Let me know if you would like me to test a patch or capture any
> > > > additional traces.
> > > >
> > > > Thanks,
> > > > Alexander
> > > >
> > > >
> > > > Am 16.08.2026 um 17:08 schrieb Rong Zhang:
> > > > > Hi Alexander,
> > > > >
> > > > > On Sun, 2026-08-16 at 16:09 +0200, Alexander Niemeyer wrote:
> > > > > > Hi Rong,
> > > > > >
> > > > > > Sure. The libusb tests were direct USB Audio Class 1 control transfers
> > > > > > to the headset using libusb/PyUSB, not ALSA mixer operations.
> > > > > >
> > > > > > I accessed Feature Unit 2 on AudioControl interface 0, master
> > > > > > channel 0,
> > > > > > with the UAC1 Volume control selector:
> > > > > >
> > > > > > wValue = 0x0200 /* Volume control, master channel */
> > > > > > wIndex = 0x0200 /* Feature Unit 2, interface 0 */
> > > > > >
> > > > > > I used the standard class-specific requests directly, including
> > > > > > GET_CUR,
> > > > > > GET_MIN, GET_MAX, GET_RES and SET_CUR, with signed 16-bit little-endian
> > > > > > volume values in 1/256 dB units.
> > > > > >
> > > > > > The device reported:
> > > > > >
> > > > > > GET_CUR: 0 ( 0 dB in that test)
> > > > > > GET_MIN: -16384 (-64 dB)
> > > > > > GET_MAX: 0 ( 0 dB)
> > > > > > GET_RES: 256 ( 1 dB)
> > > > > >
> > > > > > For the timing tests I issued SET_CUR for a target value and then
> > > > > > repeatedly queried GET_CUR until the value changed or the timeout
> > > > > > expired.
> > > > > >
> > > > > > Valid values became visible after roughly:
> > > > > >
> > > > > > -1 dB ~81 ms
> > > > > > -2 dB ~52 ms
> > > > > > -4 dB ~47 ms
> > > > > > -8 dB ~47 ms
> > > > > > -16 dB ~52 ms
> > > > > > -32 dB ~47 ms
> > > > > >
> > > > > > The advertised -64 dB minimum behaved differently: SET_CUR returned
> > > > > > successfully, but GET_CUR did not change even after 1000 ms.
> > > > > >
> > > > > > To access the AudioControl interface with libusb, I unbound the
> > > > > > AudioControl interface from snd-usb-audio for the duration of the test.
> > > > > >
> > > > > > I did not intentionally open a playback stream during those libusb
> > > > > > tests. Because the AudioControl interface had been unbound from
> > > > > > snd-usb-audio, I also do not believe there was an active ALSA playback
> > > > > > stream at that point.
> > > > > Thanks for the information.
> > > > >
> > > > > Unfortunately, I still don't exactly see why the device behaved
> > > > > differently when GET_CUR/SET_CUR requests were sent from snd-usb-audio
> > > > > compared to your libusb tests.
> > > > >
> > > > > snd-usb-audio also tries SET_RES to test the sanity of GET_RES. Could
> > > > > you
> > > > > test if it breaks your device's GET_CUR?
> > > > >
> > > > > Maybe comparing them with usbmon can show some clues. You can use
> > > > > Wireshark to sniff /dev/usbmon*.
> > > > >
> > > > > Hint: a Thunderbolt port usually corresponds to a dedicated USB root
> > > > > hub.
> > > > > If you have one, plug the device to it to get pure usbmon trace results
> > > > > with no noisy URBs from other devices.
> > > > >
> > > > > Thanks,
> > > > > Rong
> > > > >
> > > > > > If the open-stream state is important, I can repeat the experiment
> > > > > > specifically controlling for playback-stream-open versus
> > > > > > playback-stream-closed.
> > > > > >
> > > > > > Thanks,
> > > > > > Alexander
> > > > > >
> > > > > > Am 16.08.2026 um 15:50 schrieb Rong Zhang:
> > > > > > > Hi Alexander,
> > > > > > >
> > > > > > > On Sun, 2026-08-16 at 07:14 +0200, Alexander Niemeyer wrote:
> > > > > > > > Hi Rong,
> > > > > > > >
> > > > > > > > I tested the sticky-check part of your patch on the Logitech PRO X
> > > > > > > > Wireless (046d:0aba) on Fedora 44, kernel 7.1.8-200.fc44.x86_64.
> > > > > > > >
> > > > > > > > Since your patch is based on a newer tree, I used a minimal
> > > > > > > > backport of
> > > > > > > > the new ~16-value / 10 ms sticky-check logic to the 7.1.8 code. The
> > > > > > > > GET_CUR-broken handling from the newer tree was not included; GET_CUR
> > > > > > > > itself succeeds on this device.
> > > > > > > >
> > > > > > > > Unfortunately, the playback control is still classified as sticky:
> > > > > > > >
> > > > > > > > 2:0: sticky mixer values (-16384/0/256 => -3840), disabling
> > > > > > > >
> > > > > > > > I then instrumented the check and tried an additional diagnostic:
> > > > > > > > after
> > > > > > > > every successful SET_CUR, wait 100 ms and perform another GET_CUR
> > > > > > > > before
> > > > > > > > issuing the next SET_CUR.
> > > > > > > >
> > > > > > > > For the playback volume, the saved value was -3840 and GET_CUR
> > > > > > > > remained
> > > > > > > > at -3840 for every tested value, even after 100 ms, for example:
> > > > > > > >
> > > > > > > > test=-15104 immediate=-3840 after100ms=-3840
> > > > > > > > test=-13824 immediate=-3840 after100ms=-3840
> > > > > > > > test=-3584 immediate=-3840 after100ms=-3840
> > > > > > > > test=-2304 immediate=-3840 after100ms=-3840
> > > > > > > > test=-1024 immediate=-3840 after100ms=-3840
> > > > > > > > test=0 immediate=-3840 after100ms=-3840
> > > > > > > >
> > > > > > > > So in this case the issue does not appear to be simply that the
> > > > > > > > accumulated 10 ms sleeps are too short. During the probe-time sticky
> > > > > > > > check, SET_CUR succeeds but GET_CUR for the playback control remains
> > > > > > > > unchanged even when each SET_CUR is given 100 ms before the next one.
> > > > > > > >
> > > > > > > > This differs from my previous direct libusb tests with the
> > > > > > > > AudioControl
> > > > > > > > interface unbound, where valid SET_CUR values became visible through
> > > > > > > > GET_CUR after roughly 47–81 ms.
> > > > > > > Really interesting. Maybe the mixer changes its value only when
> > > > > > > there is
> > > > > > > an opened playback stream.
> > > > > > >
> > > > > > > Could you clarify your "libusb tests"?
> > > > > > >
> > > > > > > Thanks,
> > > > > > > Rong
> > > > > > >
> > > > > > > > The first debug line I saw with |saved=0| was from the Mic Capture
> > > > > > > > Volume control; that control changed immediately and returned as
> > > > > > > > non-sticky. The sequence above with |saved=-3840| is the
> > > > > > > > problematic PCM
> > > > > > > > Playback Volume control.
> > > > > > > >
> > > > > > > > I'd be happy to test another version or run additional diagnostics if
> > > > > > > > useful.
> > > > > > > >
> > > > > > > > Best regards,
> > > > > > > > Alexander
> > > > > > > >
> > > > > > > >
> > > > > > > > Am 15.08.2026 um 23:47 schrieb Rong Zhang:
> > > > > > > > > Some mixers are asynchronous, and some have broken min/max. They are
> > > > > > > > > mistakenly considered sticky due to how the check is implemented.
> > > > > > > > >
> > > > > > > > > Check sticky mixers more precisely by checking approximately 16
> > > > > > > > > values
> > > > > > > > > and adding a msleep(10) between each check, so that asynchronous
> > > > > > > > > mixers
> > > > > > > > > have enough time to change the value and mixers with broken
> > > > > > > > > min/max are
> > > > > > > > > checked properly. Additionally, mark GET_CUR as broken when
> > > > > > > > > get_cur_mix_raw() fails, instead of returning successfully.
> > > > > > > > >
> > > > > > > > > Reported-by: Alexander Niemeyer<adventureFAN@gmx.de>
> > > > > > > > > Closes:https://lore.kernel.org/r/6262cbbd-d1f2-4c9d-a1c7-9c5d12636f4b@gmx.de
> > > > > > > > >
> > > > > > > > > Signed-off-by: Rong Zhang<i@rong.moe>
> > > > > > > > > ---
> > > > > > > > > sound/usb/mixer.c | 51
> > > > > > > > > ++++++++++++++++++++++++++++++++++++++++++++-------
> > > > > > > > > 1 file changed, 44 insertions(+), 7 deletions(-)
> > > > > > > > >
> > > > > > > > > diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c
> > > > > > > > > index 703c118f9d4e..3d0f97730a06 100644
> > > > > > > > > --- a/sound/usb/mixer.c
> > > > > > > > > +++ b/sound/usb/mixer.c
> > > > > > > > > @@ -1256,22 +1256,59 @@ static void init_cur_mix_raw(struct
> > > > > > > > > usb_mixer_elem_info *cval, int ch, int idx)
> > > > > > > > > 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;
> > > > > > > > > + int test, check, res;
> > > > > > > > > +
> > > > > > > > > + /*
> > > > > > > > > + * Check approximately 16 values (15 intervals).
> > > > > > > > > + * If the resolution is not fine enough, check fewer values.
> > > > > > > > > + */
> > > > > > > > > + res = DIV_ROUND_UP(cval->max - cval->min, 15);
> > > > > > > > > + res = res ? roundup(res, cval->res) : cval->res;
> > > > > > > > > +
> > > > > > > > > + /*
> > > > > > > > > + * If (cval->max - cval->min) is not a multiple of
> > > > > > > > > cval->res, we still
> > > > > > > > > + * want to test cval->max anyway.
> > > > > > > > > + */
> > > > > > > > > + for (test = cval->min; test < cval->max + res; test += res) {
> > > > > > > > > + if (test > cval->max)
> > > > > > > > > + test = cval->max;
> > > > > > > > > - 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. */
> > > > > > > > > + if (snd_usb_set_cur_mix_value(cval, channel, 0, test))
> > > > > > > > > + return 0;
> > > > > > > > > +
> > > > > > > > > + if (get_cur_mix_raw(cval, channel, &check))
> > > > > > > > > + goto get_cur_broken;
> > > > > > > > > + if (check != saved) /* SET_CUR effective, non-sticky. */
> > > > > > > > > return 0;
> > > > > > > > > +
> > > > > > > > > + /*
> > > > > > > > > + * Leave some time for asynchronous mixers to change the
> > > > > > > > > value.
> > > > > > > > > + *
> > > > > > > > > + * Note that there is no need to wait between SET_CUR and
> > > > > > > > > + * GET_CUR, as we don't care whether the GET_CUR value
> > > > > > > > > matches
> > > > > > > > > + * the SET_CUR one. IOW, what we expect is just a
> > > > > > > > > GET_CUR value
> > > > > > > > > + * differing from the saved one.
> > > > > > > > > + *
> > > > > > > > > + * Mixers of most devices are synchronous. The should have
> > > > > > > > > + * returned early without extra sleep. Asynchronous
> > > > > > > > > mixers will
> > > > > > > > > + * return once the accumulated time is enough for them
> > > > > > > > > to change
> > > > > > > > > + * the value.
> > > > > > > > > + */
> > > > > > > > > + msleep(10);
> > > > > > > > > }
> > > > > > > > > + /* Check again after the last msleep(). */
> > > > > > > > > + if (get_cur_mix_raw(cval, channel, &check))
> > > > > > > > > + goto get_cur_broken;
> > > > > > > > > + if (check != saved)
> > > > > > > > > + return 0;
> > > > > > > > > +
> > > > > > > > > if (cval->head.mixer->chip->quirk_flags &
> > > > > > > > > QUIRK_FLAG_MIXER_GET_CUR_BROKEN) {
> > > > > > > > > +get_cur_broken:
> > > > > > > > > usb_audio_info(cval->head.mixer->chip,
> > > > > > > > > "%d:%d: broken mixer GET_CUR (%d/%d/%d =>
> > > > > > > > > %d)\n",
> > > > > > > > > cval->head.id,
> > > > > > > > > mixer_ctrl_intf(cval->head.mixer),
> > > > > > > > >
> > > > > > > > > ---
> > > > > > > > > base-commit: 3eb40771c00a8488fa6ed2cc1fe203477908bf38
> > > > > > > > > change-id: 74676fce-uac-precise-sticky-check-94474a22b57d
> > > > > > > > >
> > > > > > > > > Thanks,
> > > > > > > > > Rong
> > > > > > > > >
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH] ALSA: usb-audio: Check sticky mixers precisely
2026-08-20 12:51 ` Rong Zhang
@ 2026-08-20 15:01 ` Alexander Niemeyer
2026-08-20 16:29 ` Rong Zhang
0 siblings, 1 reply; 11+ messages in thread
From: Alexander Niemeyer @ 2026-08-20 15:01 UTC (permalink / raw)
To: Rong Zhang, Jaroslav Kysela, Takashi Iwai
Cc: Takashi Iwai, linux-sound, linux-kernel
Hi Rong,
thanks. I repeated the MIXER_GET_CUR_BROKEN test with the complete
upstream commit and also performed the two additional tests you asked
for.
First, a correction regarding my previous test:
My earlier "minimal backport" of QUIRK_FLAG_MIXER_GET_CUR_BROKEN was
incomplete. In particular, it did not include the get_cur_broken checks
around cache invalidation in snd_usb_mixer_notify_id() and
snd_usb_mixer_interrupt_v2().
I have therefore discarded that result and repeated the test properly.
1. MIXER_GET_CUR_BROKEN with the complete upstream commit
I started from a clean Linux 7.1.8 source tree and applied the complete
upstream commit:
ad2c7d2c42af943a3e711bdc1e4fd55b76fcbe55
("ALSA: usb-audio: Add QUIRK_FLAG_MIXER_GET_CUR_BROKEN")
Before applying it, I verified that the relevant 7.1.8 source files were
byte-identical to freshly extracted copies from the Linux 7.1.8 tarball.
The complete patch then applied with fuzz=0. There were only line-number
offsets due to the older source tree.
I also explicitly verified that the resulting source contained the
get_cur_broken handling in both:
snd_usb_mixer_notify_id()
and:
snd_usb_mixer_interrupt_v2()
The external snd-usb-audio module built successfully for:
7.1.8-200.fc44.x86_64
and was signed with my enrolled module-signing key.
I then loaded snd-usb-audio with:
quirk_flags=046d:0aba:mixer_get_cur_broken
The detachable microphone boom was physically attached for this test.
On initialization the kernel reported:
2:0: broken mixer GET_CUR (-16384/0/256 => -3840)
and the PCM Playback Volume control remained exposed:
Capabilities: pvolume pvolume-joined pswitch pswitch-joined
The initial values were:
ALSA: 47
PipeWire: 0.52
I then turned the physical headset volume wheel significantly down.
Afterwards:
ALSA: 31
PipeWire: 0.28
KDE's volume OSD also reacted correctly while turning the wheel.
So the previous observation is reproducible even with the complete
QUIRK_FLAG_MIXER_GET_CUR_BROKEN implementation.
2. evtest on the headset Consumer Control interface
I tested:
Logitech PRO X Wireless Gaming Headset Consumer Control
USB 046d:0aba
The physical volume wheel does emit multimedia key events.
Turning the wheel down produced repeated:
KEY_VOLUMEDOWN
events, and turning it up produced repeated:
KEY_VOLUMEUP
events.
For example:
EV_MSC MSC_SCAN c00ea
EV_KEY KEY_VOLUMEDOWN 1
and:
EV_MSC MSC_SCAN c00e9
EV_KEY KEY_VOLUMEUP 1
This corrects my earlier observation that evtest did not show volume-key
events.
So your suggestion was correct: the device does emit HID multimedia
volume events.
This also explains why software volume can still follow the physical
wheel even when mixer GET_CUR is treated as broken.
3. Continuous-tone SET_CUR test with the microphone boom detached
For this test I physically detached the microphone boom and replugged
the dongle so that the device initialized in that state.
I stopped the desktop audio services and used ALSA directly.
I played a continuous 1 kHz tone.
The initial playback volume was:
43 = -20 dB
While the tone continued playing, I issued:
23 = -40 dB
The tone became quieter immediately when SET_CUR was issued. I could not
perceive any delay between issuing SET_CUR and the physical volume
change.
The amixer command itself completed in approximately 10 ms, but I only
mention that as command timing. I did not measure the actual hardware
latency with instrumentation.
So the useful observation is:
Playback SET_CUR -> physical volume change:
no perceptible delay
Therefore the current results are:
- Your revised sticky check still classifies the Playback Volume control
as sticky, including with the detachable Mic boom attached.
- With the Mic boom detached, Playback SET_CUR is physically effective.
- Playback GET_CUR can remain stale/delayed after SET_CUR in the
Mic-detached state.
- The physical response to Playback SET_CUR appears immediate to the
listener.
- With the complete MIXER_GET_CUR_BROKEN implementation, physical wheel
changes still propagate to PipeWire/KDE.
- The volume wheel emits KEY_VOLUMEUP / KEY_VOLUMEDOWN HID events, which
explains how the software volume can follow the wheel without relying
on mixer GET_CUR notifications.
I should also mention that I am not a kernel developer and my technical
knowledge is not sufficient to prepare or review this kind of driver
change on my own. I am using ChatGPT (Plus) to help me understand your test
requests, prepare the test setup and backports, and make sure I collect
the requested results as carefully as possible.
I still want to help with testing this hardware, so I am happy to
continue running any tests you need and report the observations and raw
results back to you.
Let me know if you would like me to test anything else.
Thanks,
Alexander
Am 20.08.2026 um 14:51 schrieb Rong Zhang:
> Hi Alexander,
>
> Thanks for testing.
>
> On Wed, 2026-08-19 at 20:37 +0200, Alexander Niemeyer wrote:
>> Hi Rong,
>>
>> thanks. I have now completed the three tests you asked for.
>>
>> 1. Your sticky-check patch with the detachable Mic boom attached
>>
>> I tested the same minimal 7.1.8 backport of your ~16-value / 10 ms
>> sticky-check logic again, this time with the detachable microphone boom
>> physically attached.
>>
>> The external snd-usb-audio module was rebuilt and loaded successfully
>> (the module taint was "O"), and the old 100 ms diagnostic changes were
>> removed before this test.
>>
>> The result is still a failure:
>>
>> sticky mixer values (-16384/0/256 => -3840), disabling
>>
>> The PCM control exposes only:
>>
>> Capabilities: pswitch pswitch-joined
>>
>> and no Playback Volume control.
>>
>> I repeated this once more with another dongle replug, without changing
>> the module or touching the volume wheel, and got the same result again.
>>
>> So:
>>
>> your sticky-check patch + Mic boom attached
>> -> still classified as sticky
>> -> reproduced twice
>>
> I didn't expect that, but I will take your result anyway. Your device
> seems to have more mysterious behavior, sigh.
>
>> 2. Is Playback SET_CUR really broken after Mic SET_RES with the boom
>> detached?
>>
>> For this test I detached the microphone boom.
>>
>> I used a test version of snd-usb-audio which:
>>
>> - performed the Mic UAC1 resolution probe,
>> - kept the Playback Volume control exposed,
>> - clamped the known-broken advertised -64 dB minimum to -63 dB,
>> - and bypassed the normal ALSA mixer cache so that mixer reads fetch
>> GET_CUR from the device.
>>
>> The initial Playback value was:
>>
>> 43 = -20 dB
>>
>> I played a 3-second 1 kHz test tone and listened to its level.
>>
>> Then I requested:
>>
>> 23 = -40 dB
>>
>> The immediate output from the cset operation still showed:
>>
>> values=43
>>
>> I waited two seconds and issued a fresh cget. It then returned:
>>
>> values=23
>>
>> I played the exact same test tone again, and the second tone was clearly
>> quieter.
> Just curious: could you play the test tone throughout the entire process
> and see how long does the device need to change its physical volume?
>
>> So Playback SET_CUR is physically effective.
> OK then. I will propose a patch to demote the sticky check to
> informational warning. Before that, I need to reconfirm your test results
> about MIXER_GET_CUR_BROKEN.
>
>> At least in this test, the effect after Mic SET_RES with the boom
>> detached is not that Playback SET_CUR stops working. Instead, GET_CUR
>> can remain stale for some time after SET_CUR and later reflect the
>> actual value.
>>
>> In short:
>>
>> before: 43 (-20 dB)
>> SET_CUR requested: 23 (-40 dB)
>> immediate result: 43
>> GET_CUR after 2 s: 23
>> physical volume: clearly quieter
>>
>>
>> 3. QUIRK_FLAG_MIXER_GET_CUR_BROKEN and mixer change notifications
>>
>> Linux 7.1.8 does not contain QUIRK_FLAG_MIXER_GET_CUR_BROKEN yet, so for
>> this test I made a minimal backport of the flag behavior to the otherwise
>> stock 7.1.8 mixer code,
>>
> What was "a minimal backport of the flag behavior"? Commit ad2c7d2c42af
> ("ALSA: usb-audio: Add QUIRK_FLAG_MIXER_GET_CUR_BROKEN") is already
> minimal and must be backported as a whole.
>
>> without your new sticky-check patch.
>>
>> I loaded snd-usb-audio with:
>>
>> quirk_flags=046d:0aba:mixer_get_cur_broken
>>
>> The kernel detected the constant GET_CUR condition and kept the Playback
>> Volume control exposed:
>>
>> sticky mixer values (-16384/0/256 => -3840), disabling
>> PROXTESTC: treating constant GET_CUR as broken
>>
>> For this test the microphone boom was attached.
>>
>> Before turning the physical headset wheel:
>>
>> ALSA cget: 56
>> wpctl: Volume 0.72
>>
>> I then deliberately turned the physical volume wheel further down.
>>
>> Afterwards:
>>
>> ALSA cget: 47
>> wpctl: Volume 0.52
>>
>> KDE's volume OSD also reacted normally while I turned the wheel.
>>
>> So, on this device, using MIXER_GET_CUR_BROKEN did not break propagation
>> of physical mixer changes through the audio stack:
>>
>> physical wheel
>> -> ALSA value changed
>> -> PipeWire value changed
>> -> KDE OSD reacted normally
> Did you properly backport QUIRK_FLAG_MIXER_GET_CUR_BROKEN? In particular,
> did you backport the change in snd_usb_mixer_notify_id()?
>
> If you backported the change properly, your test results imply that your
> device doesn't depend on mixer interrupts at all -- does the volume wheel
> emit HID events to ask software to tune the mixer value on its behalf?
> Please try `evtest' to see if it emits multimedia key events.
>
> Thanks,
> Rong
>
>>
>> Therefore the current picture seems to be:
>>
>> - Your revised sticky check still rejects the Playback Volume control,
>> both with the detachable Mic boom attached and detached.
>>
>> - With the Mic boom detached, Mic SET_RES can make Playback GET_CUR stay
>> stale after a Playback SET_CUR, but Playback SET_CUR itself is still
>> physically effective.
>>
>> - At least on this Logitech device, treating GET_CUR as broken did not
>> prevent physical wheel changes from reaching ALSA/PipeWire/KDE.
>>
>> Let me know what you would like me to test next. I have kept the Fedora
>> test environment and module-signing setup intact.
>>
>> Thanks,
>> Alexander
>>
>> Am 19.08.2026 um 18:39 schrieb Rong Zhang:
>>> Hi Alexander,
>>>
>>> On Tue, 2026-08-18 at 17:24 +0200, Alexander Niemeyer wrote:
>>>> Hi Rong,
>>>>
>>>> one important follow-up to my previous message:
>>>>
>>>> I realized that the detachable microphone boom was not physically connected
>>>> to the headset during the direct libusb tests I reported earlier.
>>>>
>>>> I therefore repeated the relevant tests with the boom microphone attached,
>>>> and the result changes in an important way.
>>>>
>>>> With the boom microphone attached, on a fresh device:
>>>>
>>>> Mic GET_RES = 256
>>>> no SET_RES
>>>>
>>>> Playback:
>>>> GET_CUR = -3840 (-15 dB)
>>>> SET_CUR = -2048 (-8 dB)
>>>> GET_CUR changes to -2048 after 65.3 ms
>>>>
>>>> Result: PASS
>>>>
>>>> After another power cycle, with the boom still attached:
>>>>
>>>> Mic GET_RES before = 256
>>>> Mic SET_RES(128) = success
>>>> Mic GET_RES after = 256
>>>>
>>>> Playback:
>>>> GET_CUR = -3840 (-15 dB)
>>>> SET_CUR = -2048 (-8 dB)
>>>> GET_CUR changes to -2048 after 66.3 ms
>>>>
>>>> Result: PASS
>>>>
>>>> I also repeated the complete Mic SET_RES sequence:
>>>>
>>>> SET_RES 128
>>>> SET_RES 64
>>>> SET_RES 32
>>>> SET_RES 16
>>>> SET_RES 8
>>>> SET_RES 4
>>>> SET_RES 2
>>>> SET_RES 1
>>>>
>>>> All requests return success, GET_RES remains 256, and Playback
>>>> SET_CUR(-8 dB)
>>>> still works afterwards, with GET_CUR changing after 76.0 ms.
>>>>
>>>> However, with the boom microphone attached, the stock Fedora 7.1.8
>>>> snd-usb-audio driver still reproduces the original failure:
>>>>
>>>> sticky mixer values (-16384/0/256 => -3840), disabling
>>>>
>>>> and no PCM Playback Volume control is exposed.
>>> Could you test my patch with Mic attached?
>>>
>>>> So I need to correct the conclusion from my previous message:
>>>>
>>>> The Mic SET_RES behavior is real, but it depends on whether the detachable
>>>> microphone boom is physically connected.
>>>>
>>>> With the boom disconnected:
>>>> a single Mic SET_RES(128) is sufficient to make subsequent Playback
>>>> SET_CUR ineffective for more than 1200 ms.
>>>>
>>>> With the boom connected:
>>>> both a single Mic SET_RES(128) and the full SET_RES sequence leave
>>>> Playback SET_CUR working normally.
>>>>
>>> Thanks for your tests.
>>>
>>> Let me conclude:
>>>
>>> - Mic detached: SET_RES on Mic breaks Playback
>>> - Mic attached: SET_RES on Mic breaks nothing
>>>
>>> Still, I doubt if Playback SET_CUR is really broken in the first case.
>>> There is a chance that Playback SET_CUR is effective while GET_CUR
>>> becomes broken.
>>>
>>> Could you test:
>>>
>>> - Leave the Mic detached
>>> - SET_RES on Mic
>>> - GET_CUR on Playback
>>> - Play some audio, listen to it
>>> - SET_CUR on Playback
>>> - GET_CUR on Playback
>>> - Play some audio again, listen to it, and tell if the physical volume
>>> has changed
>>>
>>> The procedure can be achieved by hacking into the snd-usb-audio driver
>>> and nullifying its cache mechanism, so that you can fetch GET_CUR values
>>> from standard ALSA mixer interface.
>>>
>>> If you can notice physical volume changes, it implies SET_RES on Mic
>>> break Playback GET_CUR without breaking SET_CUR. Otherwise, it break
>>> Playback SET_CUR.
>>>
>>> If it only breaks GET_CUR, it implies the methodology of sticky check is
>>> problematic. In this case I would probably consider demoting the sticky
>>> check's severity.
>>>
>>> I am also thinking about how we should demote the severity. Could you
>>> also test if applying QUIRK_FLAG_MIXER_GET_CUR_BROKEN (without this
>>> patch) breaks mixer change notifications? I.e., tune the volume with the
>>> buttons/knobs on the device and see if the audio stack reflects the
>>> change. I guess it will break the notifications, but I would still like
>>> to wait for your confirmation.
>>>
>>> Thanks,
>>> Rong
>>>
>>>> Therefore Mic SET_RES alone does not explain the complete snd-usb-audio
>>>> failure in all device configurations. There must be another difference in
>>>> the full snd-usb-audio initialization path when the boom is attached.
>>>>
>>>> The separate Playback -64 dB minimum issue I reported remains unchanged:
>>>> -64 dB is ineffective, -63 dB works, and attempting -64 dB does not prevent
>>>> subsequent valid Playback SET_CUR requests from working.
>>>>
>>>> Sorry for not noticing the detachable-microphone state earlier. I wanted to
>>>> send this correction as soon as I confirmed it.
>>>>
>>>> I still have the usbmon/pcapng captures and can run further targeted tests
>>>> if useful.
>>>>
>>>> Thanks,
>>>> Alexander
>>>>
>>>> Am 18.08.2026 um 16:41 schrieb Alexander Niemeyer:
>>>>> Hi Rong,
>>>>>
>>>>> I think we found the reason for the different behavior.
>>>>>
>>>>> I reproduced the snd-usb-audio initialization sequence step by step
>>>>> with direct libusb UAC1 control transfers and isolated the problem to
>>>>> SET_RES on the *Mic Capture Volume control (Feature Unit 3)*.
>>>>>
>>>>> A fresh-device control test looks like this:
>>>>>
>>>>> Mic GET_RES = 256
>>>>> no SET_RES
>>>>> Playback:
>>>>> GET_CUR = -3840 (-15 dB)
>>>>> SET_CUR = -2048 (-8 dB)
>>>>> GET_CUR changes to -2048 after 68.0 ms
>>>>> Result: PASS
>>>>>
>>>>> After another power cycle, I repeated the same test but issued just
>>>>> *one* SET_RES request to the Mic Feature Unit first:
>>>>>
>>>>> Mic GET_RES before = 256
>>>>> Mic SET_RES(128) = success
>>>>> Mic GET_RES after = 256
>>>>> Playback:
>>>>> GET_CUR = -3840 (-15 dB)
>>>>> SET_CUR = -2048 (-8 dB)
>>>>> GET_CUR remains -3840 for more than 1200 ms
>>>>> Result: FAIL
>>>>>
>>>>> So a single successful |SET_RES(128)| on Feature Unit 3 is sufficient
>>>>> to make subsequent |SET_CUR| requests to the Playback Volume control
>>>>> on Feature Unit 2 ineffective.
>>>>>
>>>>> I also tested the complete Mic SET_RES sequence used by snd-usb-audio:
>>>>>
>>>>> SET_RES 128
>>>>> SET_RES 64
>>>>> SET_RES 32
>>>>> SET_RES 16
>>>>> SET_RES 8
>>>>> SET_RES 4
>>>>> SET_RES 2
>>>>> SET_RES 1
>>>>>
>>>>> All requests return success, while GET_RES remains 256. After that
>>>>> sequence, Playback SET_CUR also remains ineffective for more than 1200
>>>>> ms.
>>>>>
>>>>> Interestingly, the Mic control itself still works after this. In an
>>>>> ALSA-like Mic probe I could successfully change Mic Volume from 0 dB
>>>>> to -64 dB and then +1 dB, with GET_CUR reflecting those changes
>>>>> essentially immediately (~0.3 ms). Playback remained broken afterwards.
>>>>>
>>>>> I also checked whether SET_RES on the Playback Feature Unit itself
>>>>> causes the problem. It does not:
>>>>>
>>>>> Playback GET_RES = 256
>>>>> SET_RES 128 -> 64 -> 32 -> 16 -> 8 -> 4 -> 2 -> 1
>>>>> GET_RES still = 256
>>>>> Playback SET_CUR(-8 dB)
>>>>> GET_CUR changes successfully after 87.7 ms
>>>>>
>>>>> So the problematic operation appears specifically to be *SET_RES on
>>>>> the Mic Feature Unit affecting the Playback Feature Unit*.
>>>>>
>>>>> I also clarified the separate advertised-minimum issue:
>>>>>
>>>>> Playback SET_CUR(-64 dB):
>>>>> no change after >1200 ms
>>>>> followed by SET_CUR(-8 dB):
>>>>> works normally after 54.9 ms
>>>>> Playback SET_CUR(-63 dB):
>>>>> works after 75.8 ms
>>>>> followed by SET_CUR(-8 dB):
>>>>> works after 43.6 ms
>>>>>
>>>>> Therefore the broken -64 dB endpoint does not leave the device in the
>>>>> broken state; it is a separate issue. -63 dB works normally.
>>>>>
>>>>> I also captured usbmon/pcapng traces for both a working direct-libusb
>>>>> SET_CUR sequence and the failing snd-usb-audio initialization, so I
>>>>> can send those as well if they are useful.
>>>>>
>>>>> This also seems to explain why the sticky-check changes did not help:
>>>>> by the time snd-usb-audio reaches the Playback Volume sticky check,
>>>>> the earlier Mic SET_RES sanity test has already put the device into
>>>>> the state where Playback SET_CUR no longer takes effect.
>>>>>
>>>>> Let me know if you would like me to test a patch or capture any
>>>>> additional traces.
>>>>>
>>>>> Thanks,
>>>>> Alexander
>>>>>
>>>>>
>>>>> Am 16.08.2026 um 17:08 schrieb Rong Zhang:
>>>>>> Hi Alexander,
>>>>>>
>>>>>> On Sun, 2026-08-16 at 16:09 +0200, Alexander Niemeyer wrote:
>>>>>>> Hi Rong,
>>>>>>>
>>>>>>> Sure. The libusb tests were direct USB Audio Class 1 control transfers
>>>>>>> to the headset using libusb/PyUSB, not ALSA mixer operations.
>>>>>>>
>>>>>>> I accessed Feature Unit 2 on AudioControl interface 0, master
>>>>>>> channel 0,
>>>>>>> with the UAC1 Volume control selector:
>>>>>>>
>>>>>>> wValue = 0x0200 /* Volume control, master channel */
>>>>>>> wIndex = 0x0200 /* Feature Unit 2, interface 0 */
>>>>>>>
>>>>>>> I used the standard class-specific requests directly, including
>>>>>>> GET_CUR,
>>>>>>> GET_MIN, GET_MAX, GET_RES and SET_CUR, with signed 16-bit little-endian
>>>>>>> volume values in 1/256 dB units.
>>>>>>>
>>>>>>> The device reported:
>>>>>>>
>>>>>>> GET_CUR: 0 ( 0 dB in that test)
>>>>>>> GET_MIN: -16384 (-64 dB)
>>>>>>> GET_MAX: 0 ( 0 dB)
>>>>>>> GET_RES: 256 ( 1 dB)
>>>>>>>
>>>>>>> For the timing tests I issued SET_CUR for a target value and then
>>>>>>> repeatedly queried GET_CUR until the value changed or the timeout
>>>>>>> expired.
>>>>>>>
>>>>>>> Valid values became visible after roughly:
>>>>>>>
>>>>>>> -1 dB ~81 ms
>>>>>>> -2 dB ~52 ms
>>>>>>> -4 dB ~47 ms
>>>>>>> -8 dB ~47 ms
>>>>>>> -16 dB ~52 ms
>>>>>>> -32 dB ~47 ms
>>>>>>>
>>>>>>> The advertised -64 dB minimum behaved differently: SET_CUR returned
>>>>>>> successfully, but GET_CUR did not change even after 1000 ms.
>>>>>>>
>>>>>>> To access the AudioControl interface with libusb, I unbound the
>>>>>>> AudioControl interface from snd-usb-audio for the duration of the test.
>>>>>>>
>>>>>>> I did not intentionally open a playback stream during those libusb
>>>>>>> tests. Because the AudioControl interface had been unbound from
>>>>>>> snd-usb-audio, I also do not believe there was an active ALSA playback
>>>>>>> stream at that point.
>>>>>> Thanks for the information.
>>>>>>
>>>>>> Unfortunately, I still don't exactly see why the device behaved
>>>>>> differently when GET_CUR/SET_CUR requests were sent from snd-usb-audio
>>>>>> compared to your libusb tests.
>>>>>>
>>>>>> snd-usb-audio also tries SET_RES to test the sanity of GET_RES. Could
>>>>>> you
>>>>>> test if it breaks your device's GET_CUR?
>>>>>>
>>>>>> Maybe comparing them with usbmon can show some clues. You can use
>>>>>> Wireshark to sniff /dev/usbmon*.
>>>>>>
>>>>>> Hint: a Thunderbolt port usually corresponds to a dedicated USB root
>>>>>> hub.
>>>>>> If you have one, plug the device to it to get pure usbmon trace results
>>>>>> with no noisy URBs from other devices.
>>>>>>
>>>>>> Thanks,
>>>>>> Rong
>>>>>>
>>>>>>> If the open-stream state is important, I can repeat the experiment
>>>>>>> specifically controlling for playback-stream-open versus
>>>>>>> playback-stream-closed.
>>>>>>>
>>>>>>> Thanks,
>>>>>>> Alexander
>>>>>>>
>>>>>>> Am 16.08.2026 um 15:50 schrieb Rong Zhang:
>>>>>>>> Hi Alexander,
>>>>>>>>
>>>>>>>> On Sun, 2026-08-16 at 07:14 +0200, Alexander Niemeyer wrote:
>>>>>>>>> Hi Rong,
>>>>>>>>>
>>>>>>>>> I tested the sticky-check part of your patch on the Logitech PRO X
>>>>>>>>> Wireless (046d:0aba) on Fedora 44, kernel 7.1.8-200.fc44.x86_64.
>>>>>>>>>
>>>>>>>>> Since your patch is based on a newer tree, I used a minimal
>>>>>>>>> backport of
>>>>>>>>> the new ~16-value / 10 ms sticky-check logic to the 7.1.8 code. The
>>>>>>>>> GET_CUR-broken handling from the newer tree was not included; GET_CUR
>>>>>>>>> itself succeeds on this device.
>>>>>>>>>
>>>>>>>>> Unfortunately, the playback control is still classified as sticky:
>>>>>>>>>
>>>>>>>>> 2:0: sticky mixer values (-16384/0/256 => -3840), disabling
>>>>>>>>>
>>>>>>>>> I then instrumented the check and tried an additional diagnostic:
>>>>>>>>> after
>>>>>>>>> every successful SET_CUR, wait 100 ms and perform another GET_CUR
>>>>>>>>> before
>>>>>>>>> issuing the next SET_CUR.
>>>>>>>>>
>>>>>>>>> For the playback volume, the saved value was -3840 and GET_CUR
>>>>>>>>> remained
>>>>>>>>> at -3840 for every tested value, even after 100 ms, for example:
>>>>>>>>>
>>>>>>>>> test=-15104 immediate=-3840 after100ms=-3840
>>>>>>>>> test=-13824 immediate=-3840 after100ms=-3840
>>>>>>>>> test=-3584 immediate=-3840 after100ms=-3840
>>>>>>>>> test=-2304 immediate=-3840 after100ms=-3840
>>>>>>>>> test=-1024 immediate=-3840 after100ms=-3840
>>>>>>>>> test=0 immediate=-3840 after100ms=-3840
>>>>>>>>>
>>>>>>>>> So in this case the issue does not appear to be simply that the
>>>>>>>>> accumulated 10 ms sleeps are too short. During the probe-time sticky
>>>>>>>>> check, SET_CUR succeeds but GET_CUR for the playback control remains
>>>>>>>>> unchanged even when each SET_CUR is given 100 ms before the next one.
>>>>>>>>>
>>>>>>>>> This differs from my previous direct libusb tests with the
>>>>>>>>> AudioControl
>>>>>>>>> interface unbound, where valid SET_CUR values became visible through
>>>>>>>>> GET_CUR after roughly 47–81 ms.
>>>>>>>> Really interesting. Maybe the mixer changes its value only when
>>>>>>>> there is
>>>>>>>> an opened playback stream.
>>>>>>>>
>>>>>>>> Could you clarify your "libusb tests"?
>>>>>>>>
>>>>>>>> Thanks,
>>>>>>>> Rong
>>>>>>>>
>>>>>>>>> The first debug line I saw with |saved=0| was from the Mic Capture
>>>>>>>>> Volume control; that control changed immediately and returned as
>>>>>>>>> non-sticky. The sequence above with |saved=-3840| is the
>>>>>>>>> problematic PCM
>>>>>>>>> Playback Volume control.
>>>>>>>>>
>>>>>>>>> I'd be happy to test another version or run additional diagnostics if
>>>>>>>>> useful.
>>>>>>>>>
>>>>>>>>> Best regards,
>>>>>>>>> Alexander
>>>>>>>>>
>>>>>>>>>
>>>>>>>>> Am 15.08.2026 um 23:47 schrieb Rong Zhang:
>>>>>>>>>> Some mixers are asynchronous, and some have broken min/max. They are
>>>>>>>>>> mistakenly considered sticky due to how the check is implemented.
>>>>>>>>>>
>>>>>>>>>> Check sticky mixers more precisely by checking approximately 16
>>>>>>>>>> values
>>>>>>>>>> and adding a msleep(10) between each check, so that asynchronous
>>>>>>>>>> mixers
>>>>>>>>>> have enough time to change the value and mixers with broken
>>>>>>>>>> min/max are
>>>>>>>>>> checked properly. Additionally, mark GET_CUR as broken when
>>>>>>>>>> get_cur_mix_raw() fails, instead of returning successfully.
>>>>>>>>>>
>>>>>>>>>> Reported-by: Alexander Niemeyer<adventureFAN@gmx.de>
>>>>>>>>>> Closes:https://lore.kernel.org/r/6262cbbd-d1f2-4c9d-a1c7-9c5d12636f4b@gmx.de
>>>>>>>>>>
>>>>>>>>>> Signed-off-by: Rong Zhang<i@rong.moe>
>>>>>>>>>> ---
>>>>>>>>>> sound/usb/mixer.c | 51
>>>>>>>>>> ++++++++++++++++++++++++++++++++++++++++++++-------
>>>>>>>>>> 1 file changed, 44 insertions(+), 7 deletions(-)
>>>>>>>>>>
>>>>>>>>>> diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c
>>>>>>>>>> index 703c118f9d4e..3d0f97730a06 100644
>>>>>>>>>> --- a/sound/usb/mixer.c
>>>>>>>>>> +++ b/sound/usb/mixer.c
>>>>>>>>>> @@ -1256,22 +1256,59 @@ static void init_cur_mix_raw(struct
>>>>>>>>>> usb_mixer_elem_info *cval, int ch, int idx)
>>>>>>>>>> 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;
>>>>>>>>>> + int test, check, res;
>>>>>>>>>> +
>>>>>>>>>> + /*
>>>>>>>>>> + * Check approximately 16 values (15 intervals).
>>>>>>>>>> + * If the resolution is not fine enough, check fewer values.
>>>>>>>>>> + */
>>>>>>>>>> + res = DIV_ROUND_UP(cval->max - cval->min, 15);
>>>>>>>>>> + res = res ? roundup(res, cval->res) : cval->res;
>>>>>>>>>> +
>>>>>>>>>> + /*
>>>>>>>>>> + * If (cval->max - cval->min) is not a multiple of
>>>>>>>>>> cval->res, we still
>>>>>>>>>> + * want to test cval->max anyway.
>>>>>>>>>> + */
>>>>>>>>>> + for (test = cval->min; test < cval->max + res; test += res) {
>>>>>>>>>> + if (test > cval->max)
>>>>>>>>>> + test = cval->max;
>>>>>>>>>> - 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. */
>>>>>>>>>> + if (snd_usb_set_cur_mix_value(cval, channel, 0, test))
>>>>>>>>>> + return 0;
>>>>>>>>>> +
>>>>>>>>>> + if (get_cur_mix_raw(cval, channel, &check))
>>>>>>>>>> + goto get_cur_broken;
>>>>>>>>>> + if (check != saved) /* SET_CUR effective, non-sticky. */
>>>>>>>>>> return 0;
>>>>>>>>>> +
>>>>>>>>>> + /*
>>>>>>>>>> + * Leave some time for asynchronous mixers to change the
>>>>>>>>>> value.
>>>>>>>>>> + *
>>>>>>>>>> + * Note that there is no need to wait between SET_CUR and
>>>>>>>>>> + * GET_CUR, as we don't care whether the GET_CUR value
>>>>>>>>>> matches
>>>>>>>>>> + * the SET_CUR one. IOW, what we expect is just a
>>>>>>>>>> GET_CUR value
>>>>>>>>>> + * differing from the saved one.
>>>>>>>>>> + *
>>>>>>>>>> + * Mixers of most devices are synchronous. The should have
>>>>>>>>>> + * returned early without extra sleep. Asynchronous
>>>>>>>>>> mixers will
>>>>>>>>>> + * return once the accumulated time is enough for them
>>>>>>>>>> to change
>>>>>>>>>> + * the value.
>>>>>>>>>> + */
>>>>>>>>>> + msleep(10);
>>>>>>>>>> }
>>>>>>>>>> + /* Check again after the last msleep(). */
>>>>>>>>>> + if (get_cur_mix_raw(cval, channel, &check))
>>>>>>>>>> + goto get_cur_broken;
>>>>>>>>>> + if (check != saved)
>>>>>>>>>> + return 0;
>>>>>>>>>> +
>>>>>>>>>> if (cval->head.mixer->chip->quirk_flags &
>>>>>>>>>> QUIRK_FLAG_MIXER_GET_CUR_BROKEN) {
>>>>>>>>>> +get_cur_broken:
>>>>>>>>>> usb_audio_info(cval->head.mixer->chip,
>>>>>>>>>> "%d:%d: broken mixer GET_CUR (%d/%d/%d =>
>>>>>>>>>> %d)\n",
>>>>>>>>>> cval->head.id,
>>>>>>>>>> mixer_ctrl_intf(cval->head.mixer),
>>>>>>>>>>
>>>>>>>>>> ---
>>>>>>>>>> base-commit: 3eb40771c00a8488fa6ed2cc1fe203477908bf38
>>>>>>>>>> change-id: 74676fce-uac-precise-sticky-check-94474a22b57d
>>>>>>>>>>
>>>>>>>>>> Thanks,
>>>>>>>>>> Rong
>>>>>>>>>>
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH] ALSA: usb-audio: Check sticky mixers precisely
2026-08-20 15:01 ` Alexander Niemeyer
@ 2026-08-20 16:29 ` Rong Zhang
0 siblings, 0 replies; 11+ messages in thread
From: Rong Zhang @ 2026-08-20 16:29 UTC (permalink / raw)
To: Alexander Niemeyer, Jaroslav Kysela, Takashi Iwai
Cc: Takashi Iwai, linux-sound, linux-kernel
Hi Alexander,
Thanks for your testing so far.
On Thu, 2026-08-20 at 17:01 +0200, Alexander Niemeyer wrote:
> Hi Rong,
>
> thanks. I repeated the MIXER_GET_CUR_BROKEN test with the complete
> upstream commit and also performed the two additional tests you asked
> for.
>
> First, a correction regarding my previous test:
>
> My earlier "minimal backport" of QUIRK_FLAG_MIXER_GET_CUR_BROKEN was
> incomplete. In particular, it did not include the get_cur_broken checks
> around cache invalidation in snd_usb_mixer_notify_id() and
> snd_usb_mixer_interrupt_v2().
>
> I have therefore discarded that result and repeated the test properly.
>
>
> 1. MIXER_GET_CUR_BROKEN with the complete upstream commit
>
> I started from a clean Linux 7.1.8 source tree and applied the complete
> upstream commit:
>
> ad2c7d2c42af943a3e711bdc1e4fd55b76fcbe55
> ("ALSA: usb-audio: Add QUIRK_FLAG_MIXER_GET_CUR_BROKEN")
>
> Before applying it, I verified that the relevant 7.1.8 source files were
> byte-identical to freshly extracted copies from the Linux 7.1.8 tarball.
>
> The complete patch then applied with fuzz=0. There were only line-number
> offsets due to the older source tree.
>
> I also explicitly verified that the resulting source contained the
> get_cur_broken handling in both:
>
> snd_usb_mixer_notify_id()
>
> and:
>
> snd_usb_mixer_interrupt_v2()
>
> The external snd-usb-audio module built successfully for:
>
> 7.1.8-200.fc44.x86_64
>
> and was signed with my enrolled module-signing key.
>
> I then loaded snd-usb-audio with:
>
> quirk_flags=046d:0aba:mixer_get_cur_broken
>
> The detachable microphone boom was physically attached for this test.
>
> On initialization the kernel reported:
>
> 2:0: broken mixer GET_CUR (-16384/0/256 => -3840)
>
> and the PCM Playback Volume control remained exposed:
>
> Capabilities: pvolume pvolume-joined pswitch pswitch-joined
>
> The initial values were:
>
> ALSA: 47
> PipeWire: 0.52
>
> I then turned the physical headset volume wheel significantly down.
>
> Afterwards:
>
> ALSA: 31
> PipeWire: 0.28
>
> KDE's volume OSD also reacted correctly while turning the wheel.
>
> So the previous observation is reproducible even with the complete
> QUIRK_FLAG_MIXER_GET_CUR_BROKEN implementation.
>
>
> 2. evtest on the headset Consumer Control interface
>
> I tested:
>
> Logitech PRO X Wireless Gaming Headset Consumer Control
> USB 046d:0aba
>
> The physical volume wheel does emit multimedia key events.
>
> Turning the wheel down produced repeated:
>
> KEY_VOLUMEDOWN
>
> events, and turning it up produced repeated:
>
> KEY_VOLUMEUP
>
> events.
>
> For example:
>
> EV_MSC MSC_SCAN c00ea
> EV_KEY KEY_VOLUMEDOWN 1
>
> and:
>
> EV_MSC MSC_SCAN c00e9
> EV_KEY KEY_VOLUMEUP 1
>
> This corrects my earlier observation that evtest did not show volume-key
> events.
>
> So your suggestion was correct: the device does emit HID multimedia
> volume events.
>
> This also explains why software volume can still follow the physical
> wheel even when mixer GET_CUR is treated as broken.
Thanks. It matches my expectation.
>
>
> 3. Continuous-tone SET_CUR test with the microphone boom detached
>
> For this test I physically detached the microphone boom and replugged
> the dongle so that the device initialized in that state.
>
> I stopped the desktop audio services and used ALSA directly.
>
> I played a continuous 1 kHz tone.
>
> The initial playback volume was:
>
> 43 = -20 dB
>
> While the tone continued playing, I issued:
>
> 23 = -40 dB
>
> The tone became quieter immediately when SET_CUR was issued. I could not
> perceive any delay between issuing SET_CUR and the physical volume
> change.
>
> The amixer command itself completed in approximately 10 ms, but I only
> mention that as command timing. I did not measure the actual hardware
> latency with instrumentation.
>
> So the useful observation is:
>
> Playback SET_CUR -> physical volume change:
> no perceptible delay
Ditto.
>
>
> Therefore the current results are:
>
> - Your revised sticky check still classifies the Playback Volume control
> as sticky, including with the detachable Mic boom attached.
>
> - With the Mic boom detached, Playback SET_CUR is physically effective.
>
> - Playback GET_CUR can remain stale/delayed after SET_CUR in the
> Mic-detached state.
>
> - The physical response to Playback SET_CUR appears immediate to the
> listener.
>
> - With the complete MIXER_GET_CUR_BROKEN implementation, physical wheel
> changes still propagate to PipeWire/KDE.
>
> - The volume wheel emits KEY_VOLUMEUP / KEY_VOLUMEDOWN HID events, which
> explains how the software volume can follow the wheel without relying
> on mixer GET_CUR notifications.
>
>
> I should also mention that I am not a kernel developer and my technical
> knowledge is not sufficient to prepare or review this kind of driver
> change on my own. I am using ChatGPT (Plus) to help me understand your test
> requests, prepare the test setup and backports, and make sure I collect
> the requested results as carefully as possible.
>
> I still want to help with testing this hardware, so I am happy to
> continue running any tests you need and report the observations and raw
> results back to you.
>
> Let me know if you would like me to test anything else.
I think I've got enough bits about your device.
I will abandon this patch and find some time to prepare a new patch
demoting the sticky check into an informational warning, and make it only
set get_cur_broken. Afterward, QUIRK_FLAG_MIXER_GET_CUR_BROKEN can be
dropped. It will still gate GET_CUR on your device as a >1s delay is
anyway unacceptable, but it's completely fine as your device doesn't
depends on mixer interrupts at all.
Thanks,
Rong
>
> Thanks,
> Alexander
>
> Am 20.08.2026 um 14:51 schrieb Rong Zhang:
> > Hi Alexander,
> >
> > Thanks for testing.
> >
> > On Wed, 2026-08-19 at 20:37 +0200, Alexander Niemeyer wrote:
> > > Hi Rong,
> > >
> > > thanks. I have now completed the three tests you asked for.
> > >
> > > 1. Your sticky-check patch with the detachable Mic boom attached
> > >
> > > I tested the same minimal 7.1.8 backport of your ~16-value / 10 ms
> > > sticky-check logic again, this time with the detachable microphone boom
> > > physically attached.
> > >
> > > The external snd-usb-audio module was rebuilt and loaded successfully
> > > (the module taint was "O"), and the old 100 ms diagnostic changes were
> > > removed before this test.
> > >
> > > The result is still a failure:
> > >
> > > sticky mixer values (-16384/0/256 => -3840), disabling
> > >
> > > The PCM control exposes only:
> > >
> > > Capabilities: pswitch pswitch-joined
> > >
> > > and no Playback Volume control.
> > >
> > > I repeated this once more with another dongle replug, without changing
> > > the module or touching the volume wheel, and got the same result again.
> > >
> > > So:
> > >
> > > your sticky-check patch + Mic boom attached
> > > -> still classified as sticky
> > > -> reproduced twice
> > >
> > I didn't expect that, but I will take your result anyway. Your device
> > seems to have more mysterious behavior, sigh.
> >
> > > 2. Is Playback SET_CUR really broken after Mic SET_RES with the boom
> > > detached?
> > >
> > > For this test I detached the microphone boom.
> > >
> > > I used a test version of snd-usb-audio which:
> > >
> > > - performed the Mic UAC1 resolution probe,
> > > - kept the Playback Volume control exposed,
> > > - clamped the known-broken advertised -64 dB minimum to -63 dB,
> > > - and bypassed the normal ALSA mixer cache so that mixer reads fetch
> > > GET_CUR from the device.
> > >
> > > The initial Playback value was:
> > >
> > > 43 = -20 dB
> > >
> > > I played a 3-second 1 kHz test tone and listened to its level.
> > >
> > > Then I requested:
> > >
> > > 23 = -40 dB
> > >
> > > The immediate output from the cset operation still showed:
> > >
> > > values=43
> > >
> > > I waited two seconds and issued a fresh cget. It then returned:
> > >
> > > values=23
> > >
> > > I played the exact same test tone again, and the second tone was clearly
> > > quieter.
> > Just curious: could you play the test tone throughout the entire process
> > and see how long does the device need to change its physical volume?
> >
> > > So Playback SET_CUR is physically effective.
> > OK then. I will propose a patch to demote the sticky check to
> > informational warning. Before that, I need to reconfirm your test results
> > about MIXER_GET_CUR_BROKEN.
> >
> > > At least in this test, the effect after Mic SET_RES with the boom
> > > detached is not that Playback SET_CUR stops working. Instead, GET_CUR
> > > can remain stale for some time after SET_CUR and later reflect the
> > > actual value.
> > >
> > > In short:
> > >
> > > before: 43 (-20 dB)
> > > SET_CUR requested: 23 (-40 dB)
> > > immediate result: 43
> > > GET_CUR after 2 s: 23
> > > physical volume: clearly quieter
> > >
> > >
> > > 3. QUIRK_FLAG_MIXER_GET_CUR_BROKEN and mixer change notifications
> > >
> > > Linux 7.1.8 does not contain QUIRK_FLAG_MIXER_GET_CUR_BROKEN yet, so for
> > > this test I made a minimal backport of the flag behavior to the otherwise
> > > stock 7.1.8 mixer code,
> > >
> > What was "a minimal backport of the flag behavior"? Commit ad2c7d2c42af
> > ("ALSA: usb-audio: Add QUIRK_FLAG_MIXER_GET_CUR_BROKEN") is already
> > minimal and must be backported as a whole.
> >
> > > without your new sticky-check patch.
> > >
> > > I loaded snd-usb-audio with:
> > >
> > > quirk_flags=046d:0aba:mixer_get_cur_broken
> > >
> > > The kernel detected the constant GET_CUR condition and kept the Playback
> > > Volume control exposed:
> > >
> > > sticky mixer values (-16384/0/256 => -3840), disabling
> > > PROXTESTC: treating constant GET_CUR as broken
> > >
> > > For this test the microphone boom was attached.
> > >
> > > Before turning the physical headset wheel:
> > >
> > > ALSA cget: 56
> > > wpctl: Volume 0.72
> > >
> > > I then deliberately turned the physical volume wheel further down.
> > >
> > > Afterwards:
> > >
> > > ALSA cget: 47
> > > wpctl: Volume 0.52
> > >
> > > KDE's volume OSD also reacted normally while I turned the wheel.
> > >
> > > So, on this device, using MIXER_GET_CUR_BROKEN did not break propagation
> > > of physical mixer changes through the audio stack:
> > >
> > > physical wheel
> > > -> ALSA value changed
> > > -> PipeWire value changed
> > > -> KDE OSD reacted normally
> > Did you properly backport QUIRK_FLAG_MIXER_GET_CUR_BROKEN? In particular,
> > did you backport the change in snd_usb_mixer_notify_id()?
> >
> > If you backported the change properly, your test results imply that your
> > device doesn't depend on mixer interrupts at all -- does the volume wheel
> > emit HID events to ask software to tune the mixer value on its behalf?
> > Please try `evtest' to see if it emits multimedia key events.
> >
> > Thanks,
> > Rong
> >
> > >
> > > Therefore the current picture seems to be:
> > >
> > > - Your revised sticky check still rejects the Playback Volume control,
> > > both with the detachable Mic boom attached and detached.
> > >
> > > - With the Mic boom detached, Mic SET_RES can make Playback GET_CUR stay
> > > stale after a Playback SET_CUR, but Playback SET_CUR itself is still
> > > physically effective.
> > >
> > > - At least on this Logitech device, treating GET_CUR as broken did not
> > > prevent physical wheel changes from reaching ALSA/PipeWire/KDE.
> > >
> > > Let me know what you would like me to test next. I have kept the Fedora
> > > test environment and module-signing setup intact.
> > >
> > > Thanks,
> > > Alexander
> > >
> > > Am 19.08.2026 um 18:39 schrieb Rong Zhang:
> > > > Hi Alexander,
> > > >
> > > > On Tue, 2026-08-18 at 17:24 +0200, Alexander Niemeyer wrote:
> > > > > Hi Rong,
> > > > >
> > > > > one important follow-up to my previous message:
> > > > >
> > > > > I realized that the detachable microphone boom was not physically connected
> > > > > to the headset during the direct libusb tests I reported earlier.
> > > > >
> > > > > I therefore repeated the relevant tests with the boom microphone attached,
> > > > > and the result changes in an important way.
> > > > >
> > > > > With the boom microphone attached, on a fresh device:
> > > > >
> > > > > Mic GET_RES = 256
> > > > > no SET_RES
> > > > >
> > > > > Playback:
> > > > > GET_CUR = -3840 (-15 dB)
> > > > > SET_CUR = -2048 (-8 dB)
> > > > > GET_CUR changes to -2048 after 65.3 ms
> > > > >
> > > > > Result: PASS
> > > > >
> > > > > After another power cycle, with the boom still attached:
> > > > >
> > > > > Mic GET_RES before = 256
> > > > > Mic SET_RES(128) = success
> > > > > Mic GET_RES after = 256
> > > > >
> > > > > Playback:
> > > > > GET_CUR = -3840 (-15 dB)
> > > > > SET_CUR = -2048 (-8 dB)
> > > > > GET_CUR changes to -2048 after 66.3 ms
> > > > >
> > > > > Result: PASS
> > > > >
> > > > > I also repeated the complete Mic SET_RES sequence:
> > > > >
> > > > > SET_RES 128
> > > > > SET_RES 64
> > > > > SET_RES 32
> > > > > SET_RES 16
> > > > > SET_RES 8
> > > > > SET_RES 4
> > > > > SET_RES 2
> > > > > SET_RES 1
> > > > >
> > > > > All requests return success, GET_RES remains 256, and Playback
> > > > > SET_CUR(-8 dB)
> > > > > still works afterwards, with GET_CUR changing after 76.0 ms.
> > > > >
> > > > > However, with the boom microphone attached, the stock Fedora 7.1.8
> > > > > snd-usb-audio driver still reproduces the original failure:
> > > > >
> > > > > sticky mixer values (-16384/0/256 => -3840), disabling
> > > > >
> > > > > and no PCM Playback Volume control is exposed.
> > > > Could you test my patch with Mic attached?
> > > >
> > > > > So I need to correct the conclusion from my previous message:
> > > > >
> > > > > The Mic SET_RES behavior is real, but it depends on whether the detachable
> > > > > microphone boom is physically connected.
> > > > >
> > > > > With the boom disconnected:
> > > > > a single Mic SET_RES(128) is sufficient to make subsequent Playback
> > > > > SET_CUR ineffective for more than 1200 ms.
> > > > >
> > > > > With the boom connected:
> > > > > both a single Mic SET_RES(128) and the full SET_RES sequence leave
> > > > > Playback SET_CUR working normally.
> > > > >
> > > > Thanks for your tests.
> > > >
> > > > Let me conclude:
> > > >
> > > > - Mic detached: SET_RES on Mic breaks Playback
> > > > - Mic attached: SET_RES on Mic breaks nothing
> > > >
> > > > Still, I doubt if Playback SET_CUR is really broken in the first case.
> > > > There is a chance that Playback SET_CUR is effective while GET_CUR
> > > > becomes broken.
> > > >
> > > > Could you test:
> > > >
> > > > - Leave the Mic detached
> > > > - SET_RES on Mic
> > > > - GET_CUR on Playback
> > > > - Play some audio, listen to it
> > > > - SET_CUR on Playback
> > > > - GET_CUR on Playback
> > > > - Play some audio again, listen to it, and tell if the physical volume
> > > > has changed
> > > >
> > > > The procedure can be achieved by hacking into the snd-usb-audio driver
> > > > and nullifying its cache mechanism, so that you can fetch GET_CUR values
> > > > from standard ALSA mixer interface.
> > > >
> > > > If you can notice physical volume changes, it implies SET_RES on Mic
> > > > break Playback GET_CUR without breaking SET_CUR. Otherwise, it break
> > > > Playback SET_CUR.
> > > >
> > > > If it only breaks GET_CUR, it implies the methodology of sticky check is
> > > > problematic. In this case I would probably consider demoting the sticky
> > > > check's severity.
> > > >
> > > > I am also thinking about how we should demote the severity. Could you
> > > > also test if applying QUIRK_FLAG_MIXER_GET_CUR_BROKEN (without this
> > > > patch) breaks mixer change notifications? I.e., tune the volume with the
> > > > buttons/knobs on the device and see if the audio stack reflects the
> > > > change. I guess it will break the notifications, but I would still like
> > > > to wait for your confirmation.
> > > >
> > > > Thanks,
> > > > Rong
> > > >
> > > > > Therefore Mic SET_RES alone does not explain the complete snd-usb-audio
> > > > > failure in all device configurations. There must be another difference in
> > > > > the full snd-usb-audio initialization path when the boom is attached.
> > > > >
> > > > > The separate Playback -64 dB minimum issue I reported remains unchanged:
> > > > > -64 dB is ineffective, -63 dB works, and attempting -64 dB does not prevent
> > > > > subsequent valid Playback SET_CUR requests from working.
> > > > >
> > > > > Sorry for not noticing the detachable-microphone state earlier. I wanted to
> > > > > send this correction as soon as I confirmed it.
> > > > >
> > > > > I still have the usbmon/pcapng captures and can run further targeted tests
> > > > > if useful.
> > > > >
> > > > > Thanks,
> > > > > Alexander
> > > > >
> > > > > Am 18.08.2026 um 16:41 schrieb Alexander Niemeyer:
> > > > > > Hi Rong,
> > > > > >
> > > > > > I think we found the reason for the different behavior.
> > > > > >
> > > > > > I reproduced the snd-usb-audio initialization sequence step by step
> > > > > > with direct libusb UAC1 control transfers and isolated the problem to
> > > > > > SET_RES on the *Mic Capture Volume control (Feature Unit 3)*.
> > > > > >
> > > > > > A fresh-device control test looks like this:
> > > > > >
> > > > > > Mic GET_RES = 256
> > > > > > no SET_RES
> > > > > > Playback:
> > > > > > GET_CUR = -3840 (-15 dB)
> > > > > > SET_CUR = -2048 (-8 dB)
> > > > > > GET_CUR changes to -2048 after 68.0 ms
> > > > > > Result: PASS
> > > > > >
> > > > > > After another power cycle, I repeated the same test but issued just
> > > > > > *one* SET_RES request to the Mic Feature Unit first:
> > > > > >
> > > > > > Mic GET_RES before = 256
> > > > > > Mic SET_RES(128) = success
> > > > > > Mic GET_RES after = 256
> > > > > > Playback:
> > > > > > GET_CUR = -3840 (-15 dB)
> > > > > > SET_CUR = -2048 (-8 dB)
> > > > > > GET_CUR remains -3840 for more than 1200 ms
> > > > > > Result: FAIL
> > > > > >
> > > > > > So a single successful |SET_RES(128)| on Feature Unit 3 is sufficient
> > > > > > to make subsequent |SET_CUR| requests to the Playback Volume control
> > > > > > on Feature Unit 2 ineffective.
> > > > > >
> > > > > > I also tested the complete Mic SET_RES sequence used by snd-usb-audio:
> > > > > >
> > > > > > SET_RES 128
> > > > > > SET_RES 64
> > > > > > SET_RES 32
> > > > > > SET_RES 16
> > > > > > SET_RES 8
> > > > > > SET_RES 4
> > > > > > SET_RES 2
> > > > > > SET_RES 1
> > > > > >
> > > > > > All requests return success, while GET_RES remains 256. After that
> > > > > > sequence, Playback SET_CUR also remains ineffective for more than 1200
> > > > > > ms.
> > > > > >
> > > > > > Interestingly, the Mic control itself still works after this. In an
> > > > > > ALSA-like Mic probe I could successfully change Mic Volume from 0 dB
> > > > > > to -64 dB and then +1 dB, with GET_CUR reflecting those changes
> > > > > > essentially immediately (~0.3 ms). Playback remained broken afterwards.
> > > > > >
> > > > > > I also checked whether SET_RES on the Playback Feature Unit itself
> > > > > > causes the problem. It does not:
> > > > > >
> > > > > > Playback GET_RES = 256
> > > > > > SET_RES 128 -> 64 -> 32 -> 16 -> 8 -> 4 -> 2 -> 1
> > > > > > GET_RES still = 256
> > > > > > Playback SET_CUR(-8 dB)
> > > > > > GET_CUR changes successfully after 87.7 ms
> > > > > >
> > > > > > So the problematic operation appears specifically to be *SET_RES on
> > > > > > the Mic Feature Unit affecting the Playback Feature Unit*.
> > > > > >
> > > > > > I also clarified the separate advertised-minimum issue:
> > > > > >
> > > > > > Playback SET_CUR(-64 dB):
> > > > > > no change after >1200 ms
> > > > > > followed by SET_CUR(-8 dB):
> > > > > > works normally after 54.9 ms
> > > > > > Playback SET_CUR(-63 dB):
> > > > > > works after 75.8 ms
> > > > > > followed by SET_CUR(-8 dB):
> > > > > > works after 43.6 ms
> > > > > >
> > > > > > Therefore the broken -64 dB endpoint does not leave the device in the
> > > > > > broken state; it is a separate issue. -63 dB works normally.
> > > > > >
> > > > > > I also captured usbmon/pcapng traces for both a working direct-libusb
> > > > > > SET_CUR sequence and the failing snd-usb-audio initialization, so I
> > > > > > can send those as well if they are useful.
> > > > > >
> > > > > > This also seems to explain why the sticky-check changes did not help:
> > > > > > by the time snd-usb-audio reaches the Playback Volume sticky check,
> > > > > > the earlier Mic SET_RES sanity test has already put the device into
> > > > > > the state where Playback SET_CUR no longer takes effect.
> > > > > >
> > > > > > Let me know if you would like me to test a patch or capture any
> > > > > > additional traces.
> > > > > >
> > > > > > Thanks,
> > > > > > Alexander
> > > > > >
> > > > > >
> > > > > > Am 16.08.2026 um 17:08 schrieb Rong Zhang:
> > > > > > > Hi Alexander,
> > > > > > >
> > > > > > > On Sun, 2026-08-16 at 16:09 +0200, Alexander Niemeyer wrote:
> > > > > > > > Hi Rong,
> > > > > > > >
> > > > > > > > Sure. The libusb tests were direct USB Audio Class 1 control transfers
> > > > > > > > to the headset using libusb/PyUSB, not ALSA mixer operations.
> > > > > > > >
> > > > > > > > I accessed Feature Unit 2 on AudioControl interface 0, master
> > > > > > > > channel 0,
> > > > > > > > with the UAC1 Volume control selector:
> > > > > > > >
> > > > > > > > wValue = 0x0200 /* Volume control, master channel */
> > > > > > > > wIndex = 0x0200 /* Feature Unit 2, interface 0 */
> > > > > > > >
> > > > > > > > I used the standard class-specific requests directly, including
> > > > > > > > GET_CUR,
> > > > > > > > GET_MIN, GET_MAX, GET_RES and SET_CUR, with signed 16-bit little-endian
> > > > > > > > volume values in 1/256 dB units.
> > > > > > > >
> > > > > > > > The device reported:
> > > > > > > >
> > > > > > > > GET_CUR: 0 ( 0 dB in that test)
> > > > > > > > GET_MIN: -16384 (-64 dB)
> > > > > > > > GET_MAX: 0 ( 0 dB)
> > > > > > > > GET_RES: 256 ( 1 dB)
> > > > > > > >
> > > > > > > > For the timing tests I issued SET_CUR for a target value and then
> > > > > > > > repeatedly queried GET_CUR until the value changed or the timeout
> > > > > > > > expired.
> > > > > > > >
> > > > > > > > Valid values became visible after roughly:
> > > > > > > >
> > > > > > > > -1 dB ~81 ms
> > > > > > > > -2 dB ~52 ms
> > > > > > > > -4 dB ~47 ms
> > > > > > > > -8 dB ~47 ms
> > > > > > > > -16 dB ~52 ms
> > > > > > > > -32 dB ~47 ms
> > > > > > > >
> > > > > > > > The advertised -64 dB minimum behaved differently: SET_CUR returned
> > > > > > > > successfully, but GET_CUR did not change even after 1000 ms.
> > > > > > > >
> > > > > > > > To access the AudioControl interface with libusb, I unbound the
> > > > > > > > AudioControl interface from snd-usb-audio for the duration of the test.
> > > > > > > >
> > > > > > > > I did not intentionally open a playback stream during those libusb
> > > > > > > > tests. Because the AudioControl interface had been unbound from
> > > > > > > > snd-usb-audio, I also do not believe there was an active ALSA playback
> > > > > > > > stream at that point.
> > > > > > > Thanks for the information.
> > > > > > >
> > > > > > > Unfortunately, I still don't exactly see why the device behaved
> > > > > > > differently when GET_CUR/SET_CUR requests were sent from snd-usb-audio
> > > > > > > compared to your libusb tests.
> > > > > > >
> > > > > > > snd-usb-audio also tries SET_RES to test the sanity of GET_RES. Could
> > > > > > > you
> > > > > > > test if it breaks your device's GET_CUR?
> > > > > > >
> > > > > > > Maybe comparing them with usbmon can show some clues. You can use
> > > > > > > Wireshark to sniff /dev/usbmon*.
> > > > > > >
> > > > > > > Hint: a Thunderbolt port usually corresponds to a dedicated USB root
> > > > > > > hub.
> > > > > > > If you have one, plug the device to it to get pure usbmon trace results
> > > > > > > with no noisy URBs from other devices.
> > > > > > >
> > > > > > > Thanks,
> > > > > > > Rong
> > > > > > >
> > > > > > > > If the open-stream state is important, I can repeat the experiment
> > > > > > > > specifically controlling for playback-stream-open versus
> > > > > > > > playback-stream-closed.
> > > > > > > >
> > > > > > > > Thanks,
> > > > > > > > Alexander
> > > > > > > >
> > > > > > > > Am 16.08.2026 um 15:50 schrieb Rong Zhang:
> > > > > > > > > Hi Alexander,
> > > > > > > > >
> > > > > > > > > On Sun, 2026-08-16 at 07:14 +0200, Alexander Niemeyer wrote:
> > > > > > > > > > Hi Rong,
> > > > > > > > > >
> > > > > > > > > > I tested the sticky-check part of your patch on the Logitech PRO X
> > > > > > > > > > Wireless (046d:0aba) on Fedora 44, kernel 7.1.8-200.fc44.x86_64.
> > > > > > > > > >
> > > > > > > > > > Since your patch is based on a newer tree, I used a minimal
> > > > > > > > > > backport of
> > > > > > > > > > the new ~16-value / 10 ms sticky-check logic to the 7.1.8 code. The
> > > > > > > > > > GET_CUR-broken handling from the newer tree was not included; GET_CUR
> > > > > > > > > > itself succeeds on this device.
> > > > > > > > > >
> > > > > > > > > > Unfortunately, the playback control is still classified as sticky:
> > > > > > > > > >
> > > > > > > > > > 2:0: sticky mixer values (-16384/0/256 => -3840), disabling
> > > > > > > > > >
> > > > > > > > > > I then instrumented the check and tried an additional diagnostic:
> > > > > > > > > > after
> > > > > > > > > > every successful SET_CUR, wait 100 ms and perform another GET_CUR
> > > > > > > > > > before
> > > > > > > > > > issuing the next SET_CUR.
> > > > > > > > > >
> > > > > > > > > > For the playback volume, the saved value was -3840 and GET_CUR
> > > > > > > > > > remained
> > > > > > > > > > at -3840 for every tested value, even after 100 ms, for example:
> > > > > > > > > >
> > > > > > > > > > test=-15104 immediate=-3840 after100ms=-3840
> > > > > > > > > > test=-13824 immediate=-3840 after100ms=-3840
> > > > > > > > > > test=-3584 immediate=-3840 after100ms=-3840
> > > > > > > > > > test=-2304 immediate=-3840 after100ms=-3840
> > > > > > > > > > test=-1024 immediate=-3840 after100ms=-3840
> > > > > > > > > > test=0 immediate=-3840 after100ms=-3840
> > > > > > > > > >
> > > > > > > > > > So in this case the issue does not appear to be simply that the
> > > > > > > > > > accumulated 10 ms sleeps are too short. During the probe-time sticky
> > > > > > > > > > check, SET_CUR succeeds but GET_CUR for the playback control remains
> > > > > > > > > > unchanged even when each SET_CUR is given 100 ms before the next one.
> > > > > > > > > >
> > > > > > > > > > This differs from my previous direct libusb tests with the
> > > > > > > > > > AudioControl
> > > > > > > > > > interface unbound, where valid SET_CUR values became visible through
> > > > > > > > > > GET_CUR after roughly 47–81 ms.
> > > > > > > > > Really interesting. Maybe the mixer changes its value only when
> > > > > > > > > there is
> > > > > > > > > an opened playback stream.
> > > > > > > > >
> > > > > > > > > Could you clarify your "libusb tests"?
> > > > > > > > >
> > > > > > > > > Thanks,
> > > > > > > > > Rong
> > > > > > > > >
> > > > > > > > > > The first debug line I saw with |saved=0| was from the Mic Capture
> > > > > > > > > > Volume control; that control changed immediately and returned as
> > > > > > > > > > non-sticky. The sequence above with |saved=-3840| is the
> > > > > > > > > > problematic PCM
> > > > > > > > > > Playback Volume control.
> > > > > > > > > >
> > > > > > > > > > I'd be happy to test another version or run additional diagnostics if
> > > > > > > > > > useful.
> > > > > > > > > >
> > > > > > > > > > Best regards,
> > > > > > > > > > Alexander
> > > > > > > > > >
> > > > > > > > > >
> > > > > > > > > > Am 15.08.2026 um 23:47 schrieb Rong Zhang:
> > > > > > > > > > > Some mixers are asynchronous, and some have broken min/max. They are
> > > > > > > > > > > mistakenly considered sticky due to how the check is implemented.
> > > > > > > > > > >
> > > > > > > > > > > Check sticky mixers more precisely by checking approximately 16
> > > > > > > > > > > values
> > > > > > > > > > > and adding a msleep(10) between each check, so that asynchronous
> > > > > > > > > > > mixers
> > > > > > > > > > > have enough time to change the value and mixers with broken
> > > > > > > > > > > min/max are
> > > > > > > > > > > checked properly. Additionally, mark GET_CUR as broken when
> > > > > > > > > > > get_cur_mix_raw() fails, instead of returning successfully.
> > > > > > > > > > >
> > > > > > > > > > > Reported-by: Alexander Niemeyer<adventureFAN@gmx.de>
> > > > > > > > > > > Closes:https://lore.kernel.org/r/6262cbbd-d1f2-4c9d-a1c7-9c5d12636f4b@gmx.de
> > > > > > > > > > >
> > > > > > > > > > > Signed-off-by: Rong Zhang<i@rong.moe>
> > > > > > > > > > > ---
> > > > > > > > > > > sound/usb/mixer.c | 51
> > > > > > > > > > > ++++++++++++++++++++++++++++++++++++++++++++-------
> > > > > > > > > > > 1 file changed, 44 insertions(+), 7 deletions(-)
> > > > > > > > > > >
> > > > > > > > > > > diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c
> > > > > > > > > > > index 703c118f9d4e..3d0f97730a06 100644
> > > > > > > > > > > --- a/sound/usb/mixer.c
> > > > > > > > > > > +++ b/sound/usb/mixer.c
> > > > > > > > > > > @@ -1256,22 +1256,59 @@ static void init_cur_mix_raw(struct
> > > > > > > > > > > usb_mixer_elem_info *cval, int ch, int idx)
> > > > > > > > > > > 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;
> > > > > > > > > > > + int test, check, res;
> > > > > > > > > > > +
> > > > > > > > > > > + /*
> > > > > > > > > > > + * Check approximately 16 values (15 intervals).
> > > > > > > > > > > + * If the resolution is not fine enough, check fewer values.
> > > > > > > > > > > + */
> > > > > > > > > > > + res = DIV_ROUND_UP(cval->max - cval->min, 15);
> > > > > > > > > > > + res = res ? roundup(res, cval->res) : cval->res;
> > > > > > > > > > > +
> > > > > > > > > > > + /*
> > > > > > > > > > > + * If (cval->max - cval->min) is not a multiple of
> > > > > > > > > > > cval->res, we still
> > > > > > > > > > > + * want to test cval->max anyway.
> > > > > > > > > > > + */
> > > > > > > > > > > + for (test = cval->min; test < cval->max + res; test += res) {
> > > > > > > > > > > + if (test > cval->max)
> > > > > > > > > > > + test = cval->max;
> > > > > > > > > > > - 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. */
> > > > > > > > > > > + if (snd_usb_set_cur_mix_value(cval, channel, 0, test))
> > > > > > > > > > > + return 0;
> > > > > > > > > > > +
> > > > > > > > > > > + if (get_cur_mix_raw(cval, channel, &check))
> > > > > > > > > > > + goto get_cur_broken;
> > > > > > > > > > > + if (check != saved) /* SET_CUR effective, non-sticky. */
> > > > > > > > > > > return 0;
> > > > > > > > > > > +
> > > > > > > > > > > + /*
> > > > > > > > > > > + * Leave some time for asynchronous mixers to change the
> > > > > > > > > > > value.
> > > > > > > > > > > + *
> > > > > > > > > > > + * Note that there is no need to wait between SET_CUR and
> > > > > > > > > > > + * GET_CUR, as we don't care whether the GET_CUR value
> > > > > > > > > > > matches
> > > > > > > > > > > + * the SET_CUR one. IOW, what we expect is just a
> > > > > > > > > > > GET_CUR value
> > > > > > > > > > > + * differing from the saved one.
> > > > > > > > > > > + *
> > > > > > > > > > > + * Mixers of most devices are synchronous. The should have
> > > > > > > > > > > + * returned early without extra sleep. Asynchronous
> > > > > > > > > > > mixers will
> > > > > > > > > > > + * return once the accumulated time is enough for them
> > > > > > > > > > > to change
> > > > > > > > > > > + * the value.
> > > > > > > > > > > + */
> > > > > > > > > > > + msleep(10);
> > > > > > > > > > > }
> > > > > > > > > > > + /* Check again after the last msleep(). */
> > > > > > > > > > > + if (get_cur_mix_raw(cval, channel, &check))
> > > > > > > > > > > + goto get_cur_broken;
> > > > > > > > > > > + if (check != saved)
> > > > > > > > > > > + return 0;
> > > > > > > > > > > +
> > > > > > > > > > > if (cval->head.mixer->chip->quirk_flags &
> > > > > > > > > > > QUIRK_FLAG_MIXER_GET_CUR_BROKEN) {
> > > > > > > > > > > +get_cur_broken:
> > > > > > > > > > > usb_audio_info(cval->head.mixer->chip,
> > > > > > > > > > > "%d:%d: broken mixer GET_CUR (%d/%d/%d =>
> > > > > > > > > > > %d)\n",
> > > > > > > > > > > cval->head.id,
> > > > > > > > > > > mixer_ctrl_intf(cval->head.mixer),
> > > > > > > > > > >
> > > > > > > > > > > ---
> > > > > > > > > > > base-commit: 3eb40771c00a8488fa6ed2cc1fe203477908bf38
> > > > > > > > > > > change-id: 74676fce-uac-precise-sticky-check-94474a22b57d
> > > > > > > > > > >
> > > > > > > > > > > Thanks,
> > > > > > > > > > > Rong
> > > > > > > > > > >
^ permalink raw reply [flat|nested] 11+ messages in thread
end of thread, other threads:[~2026-08-20 16:35 UTC | newest]
Thread overview: 11+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-08-15 21:47 [PATCH] ALSA: usb-audio: Check sticky mixers precisely Rong Zhang
2026-08-16 5:14 ` Alexander Niemeyer
2026-08-16 13:50 ` Rong Zhang
[not found] ` <74ca2e17-8fb8-4ede-8e7e-441be815b5b6@gmx.de>
2026-08-16 15:08 ` Rong Zhang
2026-08-18 14:41 ` Alexander Niemeyer
2026-08-18 15:24 ` Alexander Niemeyer
2026-08-19 16:39 ` Rong Zhang
2026-08-19 18:37 ` Alexander Niemeyer
2026-08-20 12:51 ` Rong Zhang
2026-08-20 15:01 ` Alexander Niemeyer
2026-08-20 16:29 ` Rong Zhang
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®