USB Bulk链路"失联永不恢复"实录:重扫devicePath与三条自愈修复
Contents
控制面板与播放主机之间走 USB Bulk 通信:面板是 host,按 VID/PID 枚举设备、claim 接口、开端点读写。这条链路平时很稳,但只要下位机重启或电气抖动,面板就会"失联且永不恢复"——只能重启面板。花了两天把这条链路修成自愈的,这篇记录三次修复和背后的通用教训。
一、症状 A:主机重启,面板必失联
现象:下位机(播放主机)重启后,面板再也连不上,UI 显示一切正常但没有数据推送。
根因:USB 重枚举后设备的 bus/dev 号变了,面板缓存的 devicePath(/dev/bus/usb/00X/00Y)已成 Past——拿着旧路径 open 必败,而重连逻辑用的正是旧路径。错不在"断",错在"重连时不知道要重新找路"。
修复:重连入口先重扫 USB——重新按 VID/PID 枚举、刷新 devicePath/bus/dev/端点描述,再走 claim/open。重新枚举是重连的一部分,不是可选步骤。
二、症状 B:下位机冷启动抖动期,面板线程直接死了
现象:下位机冷启动时电气浪涌会把 USB gadget 打抖几秒(枚举-掉-再枚举)。这个窗口里面板的 bulk 读写线程直接退出了——UI 冻结,只能重启面板。
根因:线程启动段有 4 个 return——枚举失败、claim 失败、open 失败、首次读写失败,任一发生就把整个通信线程杀掉。启动期失败被当成了"致命错误",实际上它只是"暂时不可用"。
修复:启动段改成外层重试环——这个线程的寿命与进程同寿,任何失败都回到环顶重试。读者线程永不自杀:它死了没有任何组件会拉起它,等于整机单点报废。
三、症状 C:掉线后重连"只试一次"
现象:链路断开后再也回不来,哪怕下位机早已就绪。
根因:翻代码发现重连尝试逻辑嵌在 isConnected() 里——这是个"查询"函数,却背着副作用:失败一次之后,调用路径再也不会走到重连分支。一次失败 = 永不重试。
修复:掉线态收敛到唯一重连入口(状态机的一个明确状态),入口内无限重试 + 重扫(症状 A 的修复)。查询函数回归纯查询,副作用全部赶出。
四、对抗性验证:不靠等故障
改完不等于修好。这套修复的验证是主动制造故障:
- gadget 解绑/重绑:在主机侧手动unbind/bind USB gadget,模拟"设备消失又回来"——面板必须自动恢复;
- 无设备冷启动:面板在主机未上电时启动,模拟最差开局——通信线程必须活着等,主机上线后自动连上;
- 抖动窗口:反复快速上下电主机,专打冷启动浪涌期。
三条都过了才算闭环。“等下次现场复现"不是验证。
五、方法论沉淀
- 症状指纹:「UI 活 / 无推送 / 主机还在放」——三件事对不上,先查链路层,别查业务层。
- 双层根因:电气层(浪涌打抖 gadget)+ 软件层(重连设计缺陷)同时存在。软件修复要让链路扛住电气现实,而不是指望电气完美。
- 连接管理三原则:重连前重新枚举;通信线程永不退出;重连入口唯一。这三条适用于一切"客户端-设备"型链路(USB、串口、TCP 皆然)。
尾注
这次还有一个意外收获:排查时实锤了 evdev 输入设备单读者特性(第二个读者读 event 设备恒 0 字节),按键测量从此只走应用日志一条通路——嵌入式排障里,“测量手段本身不可靠"是必须先排除的干扰。
Author 软件开发大郭
LastMod 2026-09-20