USB audio kernel (CM5)
Symptom
Section titled “Symptom”On a CM5 with the stock kernel the ESP32-S3 USB sound card enumerates, shows up in
aplay -l, PipeWire picks it as the default output and playback “runs” - but the speakers
and the headphone jack stay completely silent. The keyboard and other USB devices work.
- The ESP32-S3 sound card is a full-speed (12 Mbit/s) USB Audio Class device behind the board’s high-speed FE2.1 hub. The hub’s Transaction Translator (TT) converts, so the host has to send each audio packet as a series of split transactions (start-split / complete-split) timed to USB microframes.
- On the CM5 the only controller wired to the module’s USB pins is dwc2, and dwc2 leaves that microframe timing to the driver.
- When the TT schedule is empty,
dwc2_uframe_schedule_split()places the isochronous-OUT split at relative microframe 0, which puts the first, data-carrying start-split on the wire in microframe 7 of the previous frame. For packets larger than 188 bytes the remaining start-splits fall into the next frame, so one full-speed transaction straddles a high-speed frame boundary. - The Linux EHCI driver forbids exactly this (commit
8c05dc598e5b, “USB: EHCI: No SSPLIT allowed in uframe 7”) and never starts an isochronous full-speed transaction later than microframe 5. The FE2.1’s TT silently discards such transactions: every URB completes without an error bit, and nothing reaches the full-speed bus.
Fix and upstream status
Section titled “Fix and upstream status”A 24-line change in drivers/usb/dwc2/hcd_queue.c: dwc2_uframe_schedule_split() no longer
picks relative microframe 0 for isochronous OUT splits.
| Upstream pull request | raspberrypi/linux#7554 - “usb: dwc2: host: No ISOC OUT start-split allowed in uframe 7” (open at the time of writing) |
| Related, already merged | #6936 and #7290 fix interrupt splits (keyboards and mice behind a hub) - they do not cover isochronous audio |
| Our build | Raspberry Pi rpi-6.18.y (6.18.44, base ce0873cb5e0c) + the patch, bcm2712_defconfig, CONFIG_LOCALVERSION="-dwc2fix", make bindeb-pkg |
| Download | release kernel-dwc2fix-6.18.44-4 - linux-image + linux-headers debs and SHA256SUMS |
Once the pull request is merged and released in Raspberry Pi’s kernel packages, the stock kernel will play audio and this page will no longer be needed.
Install on stock Raspberry Pi OS
Section titled “Install on stock Raspberry Pi OS”The .deb from make bindeb-pkg has none of Raspberry Pi OS’s boot hooks, so the boot files
are placed by hand - the same steps the image build runs:
REL=https://github.com/TypixNode/pi-gen/releases/download/kernel-dwc2fix-6.18.44-4mkdir -p ~/dwc2fix && cd ~/dwc2fixcurl -fLO "$REL/SHA256SUMS"for f in $(awk '{print $2}' SHA256SUMS); do curl -fLO "$REL/$f"; donesha256sum -c SHA256SUMS
sudo dpkg -i linux-image-*.deb linux-headers-*.debKVER=$(ls /lib/modules | grep dwc2fix | head -n1)sudo update-initramfs -c -k "$KVER"
# boot files: uncompressed kernel image + initramfs under our own namesif [ "$(od -An -tx1 -N2 /boot/vmlinuz-$KVER | tr -d ' ')" = 1f8b ]; then zcat "/boot/vmlinuz-$KVER" | sudo tee /boot/firmware/kernel-dwc2fix.img >/dev/nullelse sudo cp "/boot/vmlinuz-$KVER" /boot/firmware/kernel-dwc2fix.imgfisudo cp "/boot/initrd.img-$KVER" /boot/firmware/initramfs-dwc2fix
# device trees and overlays built with this kernelsudo cp /usr/lib/linux-image-$KVER/broadcom/bcm2712*.dtb /boot/firmware/sudo cp /usr/lib/linux-image-$KVER/overlays/* /boot/firmware/overlays/Then boot it on the CM5 only - append to /boot/firmware/config.txt:
[cm5]kernel=kernel-dwc2fix.imginitramfs initramfs-dwc2fix followkernel[all]Reboot and check:
uname -r # 6.18.44-dwc2fix+speaker-test -c 2 -t sine -l 1- Fall back to the stock kernel by commenting out the
kernel=line; the stockkernel_2712.imgstays installed and keeps being updated byapt. - An
aptupgrade of the stock kernel writes its own device trees back over the ones copied above. That is harmless for the stock kernel; repeat the twocplines if the dwc2fix kernel misbehaves after an upgrade. - If the battery driver is installed through DKMS, it is built for this kernel automatically, because the headers package is installed alongside.
- Why not force the whole USB bus to full speed instead? That avoids splits, but low-speed devices (many mice) then fail to enumerate behind the hub, and every USB device drops to 12 Mbit/s. We tried it; the patched kernel is the better trade.