Dwc2驱动Suspend/Disconnect事件分离
一、客户问题
现象:客户端 App 在用户态读取 device 的连接状态时,出现以下异常:
- 拔出线缆:用户态没有获取到 disconnect,获取到了suspend
- 插入线缆:用户态首先获取到 disconnect,再获取到 connect
- 用户需求:断开线缆时能立马在用户态获取到disconnect
客户对齐:明确客户读取的是用户态的哪个状态?
udc state:cat /sys/class/udc/f8180000.usb/state (udc驱动状态,usb_gadget_set_state接口更新)
gadgetfs:usb/gadget/legacy/inode.c(gadget设备状态,call_gadget接口更新),比如当dwc2状态改变比如suspend,调用call_gadget(hsotg, suspend),执行流程如下:
1
call_gadget(hsotg, suspend) -> composite_suspend -> gadget设备的suspend -> gadgetfs_suspend
inode.c上报状态实现:
1 | /* 最终inode.c中的gadgetfs_suspend, |
- 客户对齐:使用的是gadgetfs上报的gadget设备状态,不是udc驱动层的状态
二、原因分析
在 dwc2 USB IP (device 模式) 下,硬件 没有寄存器可以直接判断线缆是否断开:
线缆移除时,device 只会产生 suspend 中断,中断函数回调 gadget 注册的
suspend。1
2
3
4
5
6
7
8
9
10
11
12
13static void dwc2_handle_usb_suspend_intr(struct dwc2_hsotg *hsotg)
{
......
if (!hsotg->params.no_clock_gating)
dwc2_gadget_enter_clock_gating(hsotg);
/* Change to L2 (suspend) state before releasing spinlock */
hsotg->lx_state = DWC2_L2;
/* Call gadget suspend callback */
call_gadget(hsotg, suspend);
......
}插入线缆后,触发 dwc2_hsotg_irq: USBRst,调用 dwc2_hsotg_disconnect,触发gadget回调,通知gadgetfs disconnect,最后枚举完又会调用到connect相关函数,触发底层gadgetfs通知用户层connect,这就是为什么插入线缆,在用户层检测到先来一个disconnect,再检测到connect的原因:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19static irqreturn_t dwc2_hsotg_irq(int irq, void *pw)
{
......
if (gintsts & (GINTSTS_USBRST | GINTSTS_RESETDET)) {
/* Report disconnection if it is not already done. */
dwc2_hsotg_disconnect(hsotg);
}
......
}
void dwc2_hsotg_disconnect(struct dwc2_hsotg *hsotg)
{
......
// 回调gadget的disconnect,触发gadgetfs更新用户态的gadget状态
call_gadget(hsotg, disconnect);
hsotg->lx_state = DWC2_L3;
// 更新用户态udc驱动的状态
usb_gadget_set_state(&hsotg->gadget, USB_STATE_NOTATTACHED);
}
三、方案讨论
3.1 phy vbus 方案
通过读取phy offset为0x13的地址,bit 1表示当前vbus的状态:
通过dwc2控制器来读取phy offset的数据:
3.2 临时方案:用户空间补丁
内核默认参数
no_clock_gating = falsepower_down = DWC2_POWER_DOWN_PARAM_NONE
以上参数导致:suspend中断函数会将PHY clock关闭省电,导致VBUS 不可用,不可以通过读取phy的vbus来区分是suspend还是断开线缆,dwc2默认这个参数,本意就是不区分suspend和断开,毕竟断开线缆后触发的suspend中断中,也是调用call_gadget(hsotg, suspend)解决方案
用户不区分是suspend还是disconnect的情况下,在应用层中判断:当 App 读取到 suspend 状态时,读取 USB PHY VBUS 寄存器。 该寄存器能反映 VBUS 电压,不管是断开线缆还是host主动发起suspend,都会进入suspend中断,内核默认参数配置下会将phy clock关闭,所以这两种情况读出来VBUS都为0,都当成disconnect处理。情况举例
情况一:Suspend + Resume
1
2
3
41. Host suspend → Device suspend intr → 关闭phy clock(VBUS=0)
gadget suspend -> gadgetfs上报用户态 -> 应用层读取vbus为0,通知为disconnect。
2. Host Resume → Device dwc2_handle_wakeup_detected_intr
恢复phy clock(VBUS=1) → gadget resume -> gadgetfs上报用户态 -> 通知为resume。情况二:Disconnect + connect
1
2
31. Host disconnect → Device suspend intr → 关闭phy clock(VBUS=0)
gadget suspend -> gadgetfs上报用户态 -> 应用层读取vbus为0,通知为disconnect。
2. Host connect → dwc2_hsotg_irq -> 后续参考:`原因分析章节第三点`
- 风险分析:
- 如果 Host 主动发送 suspend,此时读取 PHY VBUS 依然会得到 0
- 因为:默认情况下: no_clock_gating = false, 不管是suspend还是disconnect,都会导致 PHY clock关闭,VBUS 不可用。导致无法准确区分 suspend 与 disconnect 状态。
3.3 最终方案:内核空间补丁
3.3.1 params.c
修改内核默认参数no_clock_gating = truepower_down = DWC2_POWER_DOWN_PARAM_NONE
以上参数配置:suspend中断函数不会将PHY clock关闭省电,通过读取phy的vbus来区分是suspend还是断开线缆
3.3.2 core_intr.c
1. 修改suspend中断服务函数,在里面读取vbus区分suspend和disconnect
2. 修改resume中断服务函数,正确处理resume恢复动作
3.3.3 u_serial.c
cdc gadget测试,Windows休眠后挂起时 tty 关闭了,resume时需要修改,防止访问空指针
四、测试步骤
disconnect测试热拔插线缆即可
4.1 Linux测试suspend
1 | anlogic@anlogic-Vostro-3660:/sys/bus/usb/devices/usb1/1-1$ cat configuration |
4.2 Windows测试suspend
不同电脑的usb现象不同,同一台电脑的不同usb端口现象也不同:
有点电脑点击休眠,usb直接断电,唤醒后重新供电,相当于热拔插。
有的电脑点击休眠,usb持续供电,不发送suspend,唤醒后直接工作。
有的电脑点击休眠,usb持续供电,不发送suspend,唤醒时,掉电再上电,相当于热拔插。
有的电脑点击休眠,usb持续供电,发送suspend,唤醒发送resume,触发device resume流程。
以上所有场景,在最终方案中,使用内核自带的cdc gadget测试都可pass。
















