跳转到内容

USB 音频内核(CM5)

CM5 用原版内核时,ESP32-S3 USB 声卡能正常识别,aplay -l 里能看到,PipeWire 也把它选成默认输出, 播放看起来在“进行”——但喇叭和耳机口完全没有声音。键盘等其他 USB 设备都正常。

  • ESP32-S3 声卡是一个全速(12 Mbit/s)USB 音频类设备,挂在板上的高速 FE2.1 hub 后面。 hub 的事务转换器(TT)负责转换,所以主机必须把每个音频包拆成一串按 USB 微帧对齐的 split 事务 (start-split / complete-split)发出去。
  • CM5 模块的 USB 引脚只连到 dwc2 控制器,而 dwc2 把微帧时序交给驱动软件来安排。
  • 当 TT 调度表为空时,dwc2_uframe_schedule_split() 会把等时 OUT 的 split 排在相对微帧 0,于是 第一个携带数据的 start-split 实际落在上一帧的微帧 7。包大于 188 字节时,剩下的 start-split 落到下一帧,同一个全速事务就横跨了一个高速帧边界。
  • Linux 的 EHCI 驱动明确禁止这种排法(提交 8c05dc598e5b,“USB: EHCI: No SSPLIT allowed in uframe 7”), 而且从不在微帧 5 之后开始全速等时事务。FE2.1 的 TT 会悄无声息地丢弃这样的事务:每个 URB 都 “成功”完成、没有任何错误位,但全速总线上什么也没收到。

drivers/usb/dwc2/hcd_queue.c 里 24 行的改动:dwc2_uframe_schedule_split() 不再为等时 OUT split 选择相对微帧 0。

上游 PR raspberrypi/linux#7554——“usb: dwc2: host: No ISOC OUT start-split allowed in uframe 7”(撰写本文时仍为 open)
相关、已合入 #6936 和 #7290 修的是中断 split(hub 下的键盘鼠标)——不涵盖等时音频
我们的构建 树莓派 rpi-6.18.y(6.18.44,基线 ce0873cb5e0c)+ 该补丁,bcm2712_defconfig,CONFIG_LOCALVERSION="-dwc2fix",make bindeb-pkg
下载 release kernel-dwc2fix-6.18.44-4——linux-image + linux-headers 的 deb 和 SHA256SUMS

等这个 PR 合入并随树莓派内核包发布后,原版内核就能出声,本页也就不再需要了。

make bindeb-pkg 打出来的 .deb 没有 Raspberry Pi OS 的启动钩子,所以启动文件要手动放置—— 和镜像构建时执行的步骤一样:

Terminal window
REL=https://github.com/TypixNode/pi-gen/releases/download/kernel-dwc2fix-6.18.44-4
mkdir -p ~/dwc2fix && cd ~/dwc2fix
curl -fLO "$REL/SHA256SUMS"
for f in $(awk '{print $2}' SHA256SUMS); do curl -fLO "$REL/$f"; done
sha256sum -c SHA256SUMS
sudo dpkg -i linux-image-*.deb linux-headers-*.deb
KVER=$(ls /lib/modules | grep dwc2fix | head -n1)
sudo update-initramfs -c -k "$KVER"
# boot files: uncompressed kernel image + initramfs under our own names
if [ "$(od -An -tx1 -N2 /boot/vmlinuz-$KVER | tr -d ' ')" = 1f8b ]; then
zcat "/boot/vmlinuz-$KVER" | sudo tee /boot/firmware/kernel-dwc2fix.img >/dev/null
else
sudo cp "/boot/vmlinuz-$KVER" /boot/firmware/kernel-dwc2fix.img
fi
sudo cp "/boot/initrd.img-$KVER" /boot/firmware/initramfs-dwc2fix
# device trees and overlays built with this kernel
sudo cp /usr/lib/linux-image-$KVER/broadcom/bcm2712*.dtb /boot/firmware/
sudo cp /usr/lib/linux-image-$KVER/overlays/* /boot/firmware/overlays/

然后只让 CM5 用它启动——追加到 /boot/firmware/config.txt:

[cm5]
kernel=kernel-dwc2fix.img
initramfs initramfs-dwc2fix followkernel
[all]

重启并检查:

Terminal window
uname -r # 6.18.44-dwc2fix+
speaker-test -c 2 -t sine -l 1
  • 回退到原版内核:注释掉 kernel= 那行即可;原版的 kernel_2712.img 一直在,并继续由 apt 更新。
  • apt 升级原版内核时会把它自己的设备树写回去,覆盖上面复制的那些。这对原版内核没有影响;如果升级后 dwc2fix 内核工作异常,重新执行那两行 cp 即可。
  • 如果电量计驱动是通过 DKMS 安装的,它会自动为这个内核编译, 因为对应的 headers 包也一起装上了。
  • 为什么不干脆把整条 USB 总线强制成全速?那样确实没有 split 了,但低速设备(很多鼠标)会在 hub 后面 枚举失败,而且所有 USB 设备都降到 12 Mbit/s。我们试过,打补丁的内核才是更好的取舍。