[PATCH 1/2] usb: xhci: fix isoc silent reschedule creating stream gap on CFC controllers
From: Nicola Lunghi
Date: Mon May 04 2026 - 19:34:50 EST
xhci_get_isoc_frame_id() silently rescheduled the first TRB to
start_frame_id+1 when the requested start frame was out of the valid
scheduling window or landed exactly on its boundary. This creates an
explicit one-frame hole in the isochronous stream.
Most USB audio devices tolerate a brief gap with a small glitch and
recover automatically. However, some devices assume that once
isochronous packets start streaming they flow continuously until the
stream is explicitly stopped. Any gap causes the device firmware to
permanently lose channel synchronization — subsequent packets are
routed to the wrong output channels until the device is reset.
This was observed with the MOTU 1248 (USB ID 0x07fd:0x0005), where
after a gap the 24-channel output stream shifts by a fixed number of
channels, mapping audio intended for ch1/ch2 onto ch7/ch8 or other
channel pairs depending on timing.
Return -EINVAL instead so the caller falls back to TRB_SIA (Schedule
Immediately After), which lets the hardware place the TRB right after
the previous one without introducing a frame-aligned gap.
Link: https://bugzilla.kernel.org/show_bug.cgi?id=220748
Assisted-by: Claude:claude-sonnet-4-6 sparse checkpatch
Signed-off-by: Nicola Lunghi <nick83ola@xxxxxxxxx>
---
drivers/usb/host/xhci-ring.c | 20 +++++++++++++-------
1 file changed, 13 insertions(+), 7 deletions(-)
diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c
index e47e644b296e..03e47db82092 100644
--- a/drivers/usb/host/xhci-ring.c
+++ b/drivers/usb/host/xhci-ring.c
@@ -4030,15 +4030,21 @@ static int xhci_get_isoc_frame_id(struct xhci_hcd *xhci,
ret = -EINVAL;
}
+ /*
+ * If the first TRB's start frame is out of the scheduling window or
+ * lands exactly on its boundary, fall back to SIA (Schedule Immediately
+ * After) rather than forcing start_frame_id+1. A forced +1 creates an
+ * explicit one-frame hole that audio devices with strict continuity
+ * requirements cannot recover from. The caller handles -EINVAL by
+ * leaving sia_frame_id as TRB_SIA.
+ */
if (index == 0) {
if (ret == -EINVAL || start_frame == start_frame_id) {
- start_frame = start_frame_id + 1;
- if (urb->dev->speed == USB_SPEED_LOW ||
- urb->dev->speed == USB_SPEED_FULL)
- urb->start_frame = start_frame;
- else
- urb->start_frame = start_frame << 3;
- ret = 0;
+ xhci_dbg(xhci, "isoc: start frame %d %s window [%d, %d], using SIA\n",
+ start_frame,
+ ret == -EINVAL ? "behind" : "at boundary of",
+ start_frame_id, end_frame_id);
+ return -EINVAL;
}
}
--
2.51.0