usb: dwc2: host: No ISOC OUT start-split allowed in uframe 7 - #7554
usb: dwc2: host: No ISOC OUT start-split allowed in uframe 7#7554eggfly wants to merge 1 commit into
Conversation
The microframe scheduler can place a full-speed isochronous OUT split so that its first start-split transaction is transmitted in microframe 7 of the previous HS frame: when the TT's low-speed schedule is empty, dwc2_uframe_schedule_split() picks rel_uframe 0, and by the driver's own convention the data-carrying SSPLIT goes on the wire one microframe earlier, i.e. in uframe 7. For transfers larger than 188 bytes the remaining start-splits then land in the following frame, so the begin/end start-splits of a single FS transaction straddle the HS frame boundary. This placement is not allowed for isochronous OUT. ehci-hcd enforces the same rule in commit 8c05dc5 ("USB: EHCI: No SSPLIT allowed in uframe 7"), and in fact never starts an FS ISO transaction later than microframe 5 (iso_stream_schedule() rejects (start % 8) >= 6 and its smask_out[] tables only cover microframes 0-5), so the placement dwc2 produces here is never generated by the reference EHCI implementation at all. At least some hub TTs silently discard such transactions. Observed with a Terminus FE2.1 multi-TT hub and a full-speed UAC1 audio device (48 kHz stereo S16, 192-byte packets) behind a BCM2712 (Raspberry Pi CM5) dwc2 host: every ISO OUT URB completes successfully (XferCompl, no error bits set in hcint), but nothing ever appears on the downstream full-speed bus and playback stays completely silent. The failure disappears as soon as any other periodic endpoint on the same TT claims the head of the schedule - for example, opening the cdc_acm notify endpoint of a composite device pushes the ISO OUT split to rel_uframe 1 and audio starts working - which is what made the broken placement visible in the first place. Skip rel_uframe 0 for isochronous OUT and retry from the next microframe, mirroring the existing "avoiding broken 1st xfer" check above. All start-splits of a transaction then stay inside one frame and none of them is transmitted in microframe 7. One microframe's worth of low-speed schedule slices is no longer usable by ISO OUT endpoints, so an endpoint with a wMaxPacketSize that previously only just fit may now be rejected with -ENOSPC instead of silently losing data on the bus. Fixes: 9f9f09b ("usb: dwc2: host: Totally redo the microframe scheduler") Cc: stable@vger.kernel.org Signed-off-by: Haohua Li <lihaohua90@gmail.com>
|
I won't comment on the AI generated wall of text, but note that the dwc OTG core is not in any way related to EHCI. It's perfectly permissible to schedule a periodic start-split transaction in microframe 7 because the hardware will issue the start-split token in the next microframe (well, generally - there's an oddframe register bit that's used to mediate this behaviour but mis-programming as a result of hardIRQ latency will occasionally result in instant transmission). Counterpoint: why has nobody else reported that 16-bit 48khz audio playback is "completely silent" with dwc2, which is now the default on 64-bit OSes for Pi 3 / Zero2W? |
|
Fair enough on EHCI - different core entirely, I brought it up for the TT-facing pattern, not the core mechanics. Let me stick to what I can measure. On the IRQ-latency theory: the u7 placement here is deliberate and happens on every packet, not occasionally. In the failing case the scheduler hands the begin-SSPLIT On the counterpoint: Zero2W has no onboard hub, so a FS audio device sits directly on the root port and never uses split transactions at all. Pi 3's onboard LAN9514/LAN7515 TT forwards this exact placement fine - the reporter in #7475 (Pi 3B, FS DAC behind the onboard hub, same scheduler) has clean 48 kHz playback and only 44.1 kHz broken, which is the alignment regression, not this. You need a hub whose TT drops the cross-frame pair to see this bug; the FE2.1 here does, 100% of the time. If the right model is "a u7 begin-SSPLIT is fine because the core defers it", then the bug would be in the ODDFRM / frame-boundary handling rather than in the schedule, and I'd genuinely rather fix that than burn a microframe of ISO OUT budget. I'm happy to test that variant, or any instrumentation you'd like to see - the device-side per-packet counters on this rig verify either way within minutes. |
I'm building a carrier board for the Raspberry Pi CM5 (BCM2712) — a handheld cyberdeck called TypixNode. On the board, a Terminus FE2.1 multi-TT USB 2.0 hub hangs off the CM5's dwc2 controller (CM_USB), and behind the hub sit a full-speed composite device we built ourselves on an ESP32-S3 (TinyUSB, UAC1 48 kHz 16-bit stereo audio plus a CDC-ACM debug port), a full-speed STM32 keyboard, and a low-speed mouse. The kernel is a self-built rpi-6.18.y (6.18.44). Topology, roughly:
48 kHz playback through the UAC device was completely silent. Not distorted, not choppy — silent, while everything on the host side reported success: URBs completed,
hcintreadXFERCOMPL|CHHLTD|ACK(0x23), and the ISO IN feedback endpoint kept delivering plausible rate data. What made this debuggable is that the audio device runs our own firmware: I added lock-free packet counters on the device side, and they showed exactly zero ISO OUT packets arriving. The host was discarding every audio packet without noticing.The clue that eventually cracked it was strange: opening the CDC-ACM port made audio work, 100% deterministically. Close the tty, silence; open it, sound. Even with DTR cleared so the firmware never writes a byte, merely having the host poll the notify interrupt endpoint was enough. That ruled out the firmware and pointed at host-side scheduling.
So I rebuilt the dwc2 module with the scheduler trace points turned into
trace_printk(plus a couple of extra prints at channel arm/completion time) and compared the two placements. Condensed from the traces:When the TT's low-speed schedule is empty,
dwc2_uframe_schedule_split()picks rel_uframe 0 for the ISO OUT split, and by the driver's own convention the data-carrying start-split goes on the wire one microframe earlier — in uframe 7 of the previous HS frame. For transfers larger than 188 bytes the remaining start-splits land in the following frame, so the begin/end start-splits of a single FS transaction straddle the HS frame boundary and carry different frame numbers. The FE2.1's TT silently discards the whole transaction when its frame advances.This is not an FE2.1 quirk being papered over: ehci-hcd fixed exactly this in 2013 (commit 8c05dc5, "USB: EHCI: No SSPLIT allowed in uframe 7", Alan Stern), and the EHCI reference implementation never produces such a placement at all —
iso_stream_schedule()rejects(start % 8) >= 6and itssmask_out[]tables only cover microframes 0-5. dwc2 generates a placement that the reference host implementation forbids. The discard behaviour does vary by TT (see the #7475 note below), which is why the commit message says "at least some hub TTs".The fix mirrors the existing "avoiding broken 1st xfer" retry in the same function: for ISO OUT, skip rel_uframe 0 and retry from the next microframe. All start-splits of a transaction then stay inside one frame and none is transmitted in uframe 7.
Verification on the board (CM5, 6.18.44 + this patch, FE2.1, CDC closed the entire time): three consecutive playback runs, device counters show 12028 packets received with zero inter-packet gaps above 1.5 ms, stable audio. To rule out our own device I also retested with an off-the-shelf C-Media CM108 (0d8c:0014, plain UAC1) — 48 kHz and 44.1 kHz both play correctly. Keyboard (FS interrupt splits), mouse (LS via PRE), and CDC are unaffected — the interrupt split path never reaches the new branch. We were previously forced to run the whole bus at full speed via a
maximum-speed = "full-speed"overlay to get audio on CM5 (which broke the low-speed mouse behind the hub, among other things); that workaround is no longer needed.One side effect, disclosed in the commit message: ISO OUT endpoints lose one microframe's worth of schedulable low-speed slices, so an endpoint with a wMaxPacketSize that previously only just fit may now be rejected with -ENOSPC. I think failing enumeration loudly beats completing transfers that never reach the bus.
Related issues, and why I believe this doesn't trade one bug for another:
Per the contribution guidelines,
hcd_queue.ccarries no downstream changes relative to mainline, so this fix belongs on linux-usb, and I'm preparing to send it to Minas Harutyunyan / Greg KH (it applies cleanly to mainline; Fixes: 9f9f09b, Cc: stable). I'm opening this PR so the Pi USB folks can see it early, poke holes in it, and verify on their own hardware — and carry it downstream first if that's useful. Happy to run more experiments on this setup; the device-side packet counters make bad placements directly observable.