Re: [PATCH] usb-audio: Fix boot-time crackling for Generic USB Audio device

From: Zhang Heng

Date: Tue Jul 14 2026 - 09:17:48 EST


From 11c25cb49a0a233fda643b2eb6c0bca7bc726d37 Mon Sep 17 00:00:00 2001
From: Zhang Heng <zhangheng@xxxxxxxxxx>
Date: Tue, 14 Jul 2026 17:55:54 +0800
Subject: [PATCH] ALSA: usb-audio: Fix boot-time crackling for Generic USB
Audio device

The Generic USB Audio device (0x1e0b:d01e) produces crackling noise during
system boot when the boot music plays. The issue disappears once the system
has fully started.

Kernel logs show xhci-ring warnings when the device initializes:
[ 9.654586] xhci_hcd 0000:03:00.3: Frame ID 644 (reg 5154, index 13) beyond range (645, 1539)
[ 9.654589] xhci_hcd 0000:03:00.3: Ignore frame ID field, use SIA bit instead
[ 9.655053] xhci_hcd 0000:03:00.3: Frame ID 644 (reg 5158, index 14) beyond range (645, 1539)
[ 9.655055] xhci_hcd 0000:03:00.3: Ignore frame ID field, use SIA bit instead

These warnings indicate that xhci_get_isoc_frame_id() returns -EINVAL for
TDs at index > 0 because their calculated Frame IDs become stale before
validation completes. This causes a fallback to SIA mode for those TDs,
while index=0 TD uses direct Frame ID. The resulting mixed scheduling within
the same data endpoint causes audio data misalignment and crackling.

Fix by adding QUIRK_FLAG_ISO_ASAP and setting URB_ISO_ASAP flag for the
data endpoint. This ensures all TDs use consistent SIA scheduling. Also add
a quirk to skip the first 4 sync packets, as some devices send incorrect
feedback data during stream startup.

Using the quirk mechanism keeps endpoint.c device-agnostic and allows easy
addition of new devices with the same symptom.

Signed-off-by: Zhang Heng <zhangheng@xxxxxxxxxx>
---
v1->v2:

- Replace hardcoded USB ID check in endpoint.c with QUIRK_FLAG_ISO_ASAP
- Add QUIRK_TYPE_ISO_ASAP = 31 enum and QUIRK_FLAG_ISO_ASAP macro in usbaudio.h
- Add device entry to quirk_flags_table in quirks.c for Generic USB Audio (0x1e0b:d01e)
- Add ISO_ASAP string entry to snd_usb_audio_quirk_flag_names
- Add documentation comment for QUIRK_FLAG_ISO_ASAP in usbaudio.h
- Keep endpoint.c device-agnostic, allowing easy addition of new devices

sound/usb/endpoint.c | 2 ++
sound/usb/quirks.c | 8 ++++++++
sound/usb/usbaudio.h | 8 ++++++++
3 files changed, 18 insertions(+)

diff --git a/sound/usb/endpoint.c b/sound/usb/endpoint.c
index 24cd7692bd01..155b3858dac9 100644
--- a/sound/usb/endpoint.c
+++ b/sound/usb/endpoint.c
@@ -1256,6 +1256,8 @@ static int data_ep_set_params(struct snd_usb_endpoint *ep)
goto out_of_memory;
u->urb->pipe = ep->pipe;
u->urb->transfer_flags = URB_NO_TRANSFER_DMA_MAP;
+ if (ep->chip->quirk_flags & QUIRK_FLAG_ISO_ASAP)
+ u->urb->transfer_flags |= URB_ISO_ASAP;
u->urb->interval = 1 << ep->datainterval;
u->urb->context = u;
u->urb->complete = snd_complete_urb;
diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c
index 2949a0d2d961..f5e5b662bfd3 100644
--- a/sound/usb/quirks.c
+++ b/sound/usb/quirks.c
@@ -1938,6 +1938,10 @@ void snd_usb_endpoint_start_quirk(struct snd_usb_endpoint *ep)
ep->type == SND_USB_ENDPOINT_TYPE_SYNC)
ep->skip_packets = 4;

+ if (ep->chip->usb_id == USB_ID(0x1e0b, 0xd01e) &&
+ ep->type == SND_USB_ENDPOINT_TYPE_SYNC)
+ ep->skip_packets = 4;
+
/*
* M-Audio Fast Track C400/C600 - when packets are not skipped, real
* world latency varies by approx. +/- 50 frames (at 96kHz) each time
@@ -2582,6 +2586,9 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = {
VENDOR_FLG(0xc502, /* HiBy devices */
QUIRK_FLAG_DSD_RAW),

+ DEVICE_FLG(0x1e0b, 0xd01e, /* Generic USB Audio */
+ QUIRK_FLAG_ISO_ASAP),
+
{} /* terminator */
};

@@ -2620,6 +2627,7 @@ static const char *const snd_usb_audio_quirk_flag_names[] = {
QUIRK_STRING_ENTRY(MIXER_CAPTURE_LINEAR_VOL),
QUIRK_STRING_ENTRY(IFB_SILENCE_ON_EMPTY),
QUIRK_STRING_ENTRY(MIXER_GET_CUR_BROKEN),
+ QUIRK_STRING_ENTRY(ISO_ASAP),
NULL
};

diff --git a/sound/usb/usbaudio.h b/sound/usb/usbaudio.h
index e26f9092417e..907211fd5c3e 100644
--- a/sound/usb/usbaudio.h
+++ b/sound/usb/usbaudio.h
@@ -254,6 +254,12 @@ extern bool snd_usb_skip_validation;
* check being non-fatal and only disabling GET_CUR instead of the whole mixer.
* The current volume will then be provided by the internal cache that stores
* the last set volume
+ * QUIRK_FLAG_ISO_ASAP
+ * Enable URB_ISO_ASAP flag for isochronous URBs. This ensures consistent
+ * SIA (Schedule In Advance) scheduling for all TDs, avoiding mixed
+ * scheduling modes that can cause audible crackling at boot time. Needed by
+ * devices where xHCI Frame ID staleness for TDs at index > 0 causes
+ * mixed explicit Frame IDs with SIA.
*/

enum {
@@ -288,6 +294,7 @@ enum {
QUIRK_TYPE_MIXER_CAPTURE_LINEAR_VOL = 28,
QUIRK_TYPE_IFB_SILENCE_ON_EMPTY = 29,
QUIRK_TYPE_MIXER_GET_CUR_BROKEN = 30,
+ QUIRK_TYPE_ISO_ASAP = 31,
/* Please also edit snd_usb_audio_quirk_flag_names */
};

@@ -324,5 +331,6 @@ enum {
#define QUIRK_FLAG_MIXER_CAPTURE_LINEAR_VOL QUIRK_FLAG(MIXER_CAPTURE_LINEAR_VOL)
#define QUIRK_FLAG_IFB_SILENCE_ON_EMPTY QUIRK_FLAG(IFB_SILENCE_ON_EMPTY)
#define QUIRK_FLAG_MIXER_GET_CUR_BROKEN QUIRK_FLAG(MIXER_GET_CUR_BROKEN)
+#define QUIRK_FLAG_ISO_ASAP QUIRK_FLAG(ISO_ASAP)

#endif /* __USBAUDIO_H */
--
2.25.1