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 都 “成功”完成、没有任何错误位,但全速总线上什么也没收到。
修复与上游状态
Section titled “修复与上游状态”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 合入并随树莓派内核包发布后,原版内核就能出声,本页也就不再需要了。
在原版 Raspberry Pi OS 上安装
Section titled “在原版 Raspberry Pi OS 上安装”make bindeb-pkg 打出来的 .deb 没有 Raspberry Pi OS 的启动钩子,所以启动文件要手动放置——
和镜像构建时执行的步骤一样:
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/然后只让 CM5 用它启动——追加到 /boot/firmware/config.txt:
[cm5]kernel=kernel-dwc2fix.imginitramfs initramfs-dwc2fix followkernel[all]重启并检查:
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。我们试过,打补丁的内核才是更好的取舍。