Skip to content

重启后 USB WiFi 概率性枚举不到 ​

环境:eCos USB Host 上移植的 Linux USB 协议栈 · RTL8188 系 USB WiFi dongle(vendor 驱动)· MT7601 作对照
关联:USB 2.0 枚举流程 · hub_port_init 调用链 · USB Device 公头悬空误报 Suspend
状态:已解决(重启前按序停流量再下电)


目录 ​


1. 现象 ​

系统待机重启或升级重启之后,USB WiFi dongle 概率性枚举不到,只能把板子断电再上电才能恢复。热插拔一下 dongle 同样能恢复,但产品形态上 dongle 是插死的。

主机侧唯一的错误是控制传输超时:

text
Err: usb_start_wait_urb, timeout.

这行信息量很低:它只说明某次控制传输没等到应答,既可能是主机没发出去,也可能是设备没回。要往下走,先得知道是主机侧枚举出了问题,还是设备根本没准备好应答。

值得注意的是:冷启动从不复现,只有软复位重启才复现。 冷启动与软复位对主机侧代码是同一条路径,差别只在设备有没有真正断过电——这一点后面是关键。


2. 结论先行 ​

问题答案
主机枚举代码有问题吗没有。同一份代码冷启动 100% 正常,失败发生在设备侧上电序列
设备侧卡在哪vendor 驱动上电序列里对偏移 0x05 的轮询超时:Fail to polling Offset[0x5]=01,rtw_hal_power_on() 返回 0
为什么只有软复位复现软复位不切设备电源。上一次运行残留的内部状态没清掉,再次上电时状态机走不完
停掉 USB 流量够不够不够。抓包确认总线上只剩 SOF 后仍然复现
单独调下电函数够不够不够。必须先让 IPS 处理结束、线程停掉、URB 取消,再下电
为什么 MT7601 不复现它的 vendor 驱动在移除时已经把设备带回了可重新上电的状态;这是驱动差异,不是芯片更"稳"

3. 先把复现变成可计数的实验 ​

概率性问题最先要做的不是猜根因,而是把"偶尔"变成"多少轮一次",否则任何改动都无法判断有没有效果。

做法是脚本化反复重启并统计轮数。基线:

设备结果
RTL818810 多轮即可复现
MT760150 轮未复现

两款 dongle 挂在同一个 USB 口、跑同一套主机代码,一个复现一个不复现——这基本排除了"主机枚举流程本身有 bug",把嫌疑指向设备侧和它的 vendor 驱动。

后面每一次改动都按同一方式跑轮数,用轮数变化判断有没有效果。


4. 停掉总线流量:不够 ​

第一个假设是:重启时总线上还有未完成的传输,设备被打断在半路。

先按常规路径关接口:

c
status = netdev_close(pnetdev);
GXRTL8188_rtusb_exit();

29 轮复现。

再往狠里做,把驱动的收发彻底停掉、把挂起的 URB 全部取消:

c
/* 1. 阻止驱动再发起新的 USB 读写 */
rtw_set_drv_stopped(padapter);
RTW_DISABLE_FUNC(padapter, DF_TX_BIT);
RTW_DISABLE_FUNC(padapter, DF_RX_BIT);

/* 2. 停命令线程 */
rtw_stop_drv_threads(padapter);

/* 3. 取消挂起的 URB */
rtw_read_port_cancel(padapter);
rtw_write_port_cancel(padapter);

这次用抓包确认了效果:重启前总线上只剩 SOF,没有任何数据事务。即便如此,34 轮复现。

这一步的价值是排除了一整类假设:问题与"重启瞬间总线上还有没有数据"无关。 设备的状态不是被传输打断的,而是本来就停在了一个不该停的地方。


5. 复位固件与反初始化:也不够 ​

既然不是传输,就往设备内部状态查。依次试了三层,每一层都比上一层更彻底:

尝试代码结果
跳过固件下载后的一次寄存器通知rtw_write8(adapter, REG_C2HEVT_MSG_NORMAL, C2H_DEFEATURE_RSVD)仍复现
复位片内 8051 固件rtw_write8(padapter, REG_MCUFWDL, 0x00) + _8051Reset8188(padapter)仍复现
走驱动完整反初始化rtw_hal_deinit(padapter)仍复现
单独调下电rtw_hal_power_off() + rtl8188fu_hw_power_down()仍复现

四层全部无效,包括看起来最彻底的那一层。到这里排除法已经用尽,必须换方向:不再猜"重启前少做了什么",而去看下一次上电时到底在哪一步失败。


6. 失败发生在上电序列,不在枚举 ​

给上电路径加打印,正常与失败两种情况的差别很干净:

text
正常:
dtwdebug _InitPowerOn_8188FU: status = 1
dtwdebug rtw_hal_power_on: ret = 1

失败:
dtwdebug rtw_hal_power_on: ret = 0

失败时紧挨着的一行给出了具体位置:

text
dtwdebug HalPwrSeqCmdParsing: Fail to polling Offset[0x5]=01

HalPwrSeqCmdParsing() 是 vendor 驱动执行上电/下电序列表的解释器,序列表里每一项是"往某偏移写某位"或"轮询某偏移某位直到期望值"。这里失败的是一条轮询项,落在偏移 0x05 的 bit0 上,对应上电序列中"让 MAC 上电"那一步。它的语义是:

  1. 先把 0x05 的 bit0 置 1,让硬件执行一段内部上电与状态切换;
  2. 然后轮询该位,等硬件把它清回 0,表示这一步做完;
  3. 超时时读到的仍是 01,即 bit0 一直卡在 1 —— 状态机没走完。

至此现象与结论对上了:主机侧那句 usb_start_wait_urb, timeout 只是后果。设备的 MAC 压根没上电成功,自然不会应答控制传输。主机枚举代码没有任何问题。

同时也解释了"为什么只有软复位复现":冷启动时设备真正断过电,内部状态全清;软复位只重启了主控,dongle 一直带电,上一次运行残留的状态让这一步的握手无法完成。


7. 顺序比动作重要 ​

§5 里单独调 rtw_hal_power_off() 是无效的,但把它放在一串前置动作之后就有效了:

c
int usb_wifi_netdev_close(struct eth_drv_sc *pnetdev)
{
	_adapter *padapter = (_adapter *)rtw_netdev_priv(pnetdev);
	struct pwrctrl_priv *pwrctl = adapter_to_pwrctl(padapter);

	/* 1. 标准断连 */
	netdev_close(pnetdev);

	/* 2. 等 IPS 处理结束 —— 省掉这一步,下电会和省电流程打架 */
	while (pwrctl->bips_processing == _TRUE)
		rtw_msleep_os(1);

	/* 3. 停线程、取消 URB,避免下电时卡住 */
	rtw_set_drv_stopped(padapter);
	RTW_DISABLE_FUNC(padapter, DF_TX_BIT | DF_RX_BIT);
	rtw_stop_drv_threads(padapter);
	rtw_read_port_cancel(padapter);
	rtw_write_port_cancel(padapter);

	/* 4. 此时再下电 */
	rtw_hal_power_off(padapter);

	padapter->bup = _FALSE;
	return 0;
}

206 轮未复现。

关键在第 2 步。RTL8188 系驱动有 IPS(Inactive Power Save,空闲省电):没有流量时驱动会自行把射频和部分模块下电,恢复时再上电。bips_processing 表示这个流程正在进行中。如果在 IPS 半途插入一次 rtw_hal_power_off(),两条路径会对同一组电源位一起动手,设备就可能停在中间态——这正是前面单独调下电无效的原因。

所以修法不是"加一个下电动作",而是让设备处在一个可以安全下电的状态之后再下电:先断连、等 IPS 收敛、停线程、取消 URB,最后才下电。


8. 反向验证与横向对照 ​

一次改动跑 206 轮没复现,仍不足以断言修好了——概率性问题需要反向验证:把认为起作用的那一步单独去掉,看它是否重新出现。

去掉第 4 步的 rtw_hal_power_off(),其余保持不变:

text
14 轮复现

同一套代码,有这一步 206 轮不复现、没这一步 14 轮复现,因果关系才算成立。

横向对照 MT7601:同样的重启脚本 120 轮未复现。它的 vendor 驱动在移除时已经把设备带回了能重新上电的状态,所以不依赖上层补这一步。这是两份 vendor 驱动的下电路径差异,不能据此说某颗芯片更稳定。

补丁交付客户后,客户侧回归测试也不再复现。


9. 小结 ​

  • usb_start_wait_urb, timeout 只是后果;根因是设备 MAC 没上电成功,不在主机枚举代码里。
  • 失败点是 vendor 上电序列对偏移 0x05 bit0 的轮询超时(写 1 → 等硬件清 0 → 超时仍读到 01)。
  • 冷启动不复现、软复位复现,差别只在设备有没有真正断过电;软复位会把上一次的内部状态带过来。
  • 停掉总线流量(抓包只剩 SOF)无效,说明问题与"传输被打断"无关。
  • 单独调下电无效,先断连、等 IPS 收敛、停线程、取消 URB,再下电才有效:顺序比动作本身重要。
  • 结论靠三组数据支撑:加下电 206 轮未复现、去掉下电 14 轮复现、MT7601 对照 120 轮未复现。

附录 A 实验轮数对照 ​

重启前执行的动作复现情况
无(基线,RTL8188)10 多轮复现
netdev_close + rtusb_exit29 轮复现
停线程 + 取消 TX/RX URB(抓包只剩 SOF)34 轮复现
跳过 REG_C2HEVT_MSG_NORMAL 写入仍复现
REG_MCUFWDL = 0 + _8051Reset8188仍复现
rtw_hal_deinit仍复现
单独 rtw_hal_power_off + hw_power_down仍复现
断连 + 等 IPS + 停线程 + 取消 URB + rtw_hal_power_off206 轮未复现
上一行去掉 rtw_hal_power_off14 轮复现
MT7601(基线对照)50 轮 / 120 轮均未复现

附录 B 要点速记 ​

  1. 概率性问题第一步是把复现变成轮数,否则改动有没有效果无从判断。
  2. 主机侧超时报错常是后果;先确认设备有没有能力应答,再去查主机。
  3. 冷启动正常、软复位异常 → 优先怀疑设备侧未断电的状态残留。
  4. 排除一类假设也是进展:抓包确认只剩 SOF,就能把"传输被打断"整类原因划掉。
  5. 下电要在设备可安全下电时做;与 IPS 一类自动省电流程并发操作电源位容易停在中间态。
  6. 结论要有反向验证:去掉关键那一步,问题应当重新出现。

基于 VitePress 构建