T113 音源播放器上跑着两路业务串口:uart4(ttyS4)接 OTG 音源切换芯片,uart5(ttyS5)接蓝牙模块,DTS 里都配了 use_dma = <3>(TX/RX 全走 DMA)。这周先后出现两起"串口收不到数":先是 OTG 芯片"未连接",修好之后隔了一次烧录,蓝牙又"聋"了——同一个病根,两次发作在不同的串口上。这篇完整复盘根因、修复和一个值得记住的竞争格局故事。

一、第一次发作:OTG 永远"未连接"

现象:面板 OTG 页面永远显示"未连接",发查询无响应。

分层排查下来芯片电源、使能脚都正常,dmesg 里躺着一行指纹:

1
sunxi-uart uart4: get rx dma descriptor failed

每次开机必现。业务进程读 ttyS4 永远是 0 字节。

关键一步是外置串口旁路实测:USB 串口适配器并联到芯片的 TX 脚上(只听不打扰),主机发查询——芯片清清楚楚回了一帧 B6。硬件完好,数据确实在发,是内核这头的接收链聋了。

二、第二次发作:蓝牙只剩占位符

uart4 修好后(下面讲怎么修的)打的新固件上板,蓝牙页变成这样:

字段 显示
设备名 Bluetoooth
标题 蓝牙音乐
艺术家 蓝牙设备已连接
专辑 / 进度 / 格式 / 采样率 全空

全是面板的占位文案——说明主机一条蓝牙推送都没解析到。dmesg 这次轮到:

1
sunxi-uart uart5: get rx dma descriptor failed

而同一个固件,上一次开机蓝牙还是好的。

三、病根:开局竞态,谁后开谁失聪

机制拆开是这样的:

  1. 这颗 SoC 的 UART 驱动(sun6i-dma)用一组公共的 RX 描述符池,use_dma=3 时 RX 也走 DMA,open 设备时就占用 RX 描述符;
  2. 业务进程启动时几乎同时 open ttyS4 和 ttyS5,池子不大,先到的拿走,后到的分配失败;
  3. 分配失败的路径上没有重试也没有降级,这个 tty 的 RX 链路从此空转,close 再 open 也不恢复。

所以它是"开局竞态":每次开机看谁后 open,谁就聋。这就解释了两件事——为什么同一固件两次开机两种结局(不是硬件偶坏);以及为什么修好 uart4 之后 uart5 反而聋了:uart4 退出竞争后,竞争格局翻面,轮到 uart5 当"后开"的那个。修好 A、B 立刻坏,是资源竞争类问题的标准指纹。

四、修复:RX 让出 DMA,回到中断接收

两路都钉死 use_dma = <1>(TX 走 DMA,RX 中断/PIO):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
&uart4 {
	pinctrl-0 = <&uart4_pins_a>;
	dmas = <&dma 18>, <&dma 18>;
	dma-names = "tx", "rx";
	use_dma = <1>;	/* TX DMA + RX 中断 */
	status = "okay";
};

&uart5 {
	pinctrl-0 = <&uart5_pins_a>;
	dmas = <&dma 19>, <&dma 19>;
	dma-names = "tx", "rx";
	use_dma = <1>;
	status = "okay";
};

RX 放弃 DMA 划不划算,看流量:

串口 实际流量 中断接收开销
uart4(OTG 芯片) 一次查询 7 条读、约 7×5 字节 / 3 秒 忽略不计
uart5(蓝牙模块) 文本状态行,峰值约 20 行/秒 115200 波特率下绰绰有余

RX DMA 的价值场景是高波特率大流量连续灌数据;业务侧这种小包文本状态流,PIO 完全够用。为用不上的性能引入一个开局竞态,纯亏。

五、方法论沉淀

  1. dmesg 指纹先行。get rx dma descriptor failed 一行顶半小时瞎猜——串口类问题先把 dmesg 里 dma/uart 相关的行捞干净。
  2. 外置串口旁路是分清"物理死"和"内核聋"的唯一可靠手段。并联在对方 TX 上只听不打扰,芯片到底有没有发数一目了然,不会被主机侧的解析 bug 带偏。
  3. “修好 A,B 立刻坏"指向资源竞争。不是你修坏了,是你改变了竞争格局——把两路外设的同类配置一起检查一遍。
  4. 不确定性本身就是线索。同一固件两次开机不同结局 = 开局竞态,别往"偶现硬件故障"的方向想。

六、遗留提醒

如果哪天确实需要 RX DMA(比如串口流量上去了),正确解法不是改回 3,而是先解决描述符池的分配时序(串行 open / 延迟第二路 / 加大池子),并且 dmesg 里必须盯着那行 descriptor failed 做开关机压力验证。在这个修改落地之前,use_dma=1 就是这对业务串口的最终答案。