USB2.0之Suspend/Resume
最近深入学习了USB2.0的Suspend/Resume。
Suspend/Resume协议实现
USB2.0规范约定所有设备都需要支持Suspend 。
当一个设备从他的上行口(upstream facing port)看到持续超过3ms的Idle状态,那么它就应该进入Suspend状态。
对于high-speed,Idle开始时无法区分是Idle还是Reset。 USB规范约定,当检测到Idle 3ms,就要配置为full-speed,断开D+D-的端接电阻,打开D+的上拉。这时,如果bus状态为data J,则判定为IDLE,如果为SE0则判定为Reset。
Suspend又分为Global Suspend和Selective Suspend。
Global Suspend由host发起,全局3.0ms Idle,然后进入Suspend。
Selective Suspend通过给hub port发SetPortFeature使对应hub port下的设备进入Suspend。
当设备进入Suspend状态,它从vbus上抽取的电流必须在10ms之内切换到suspend电流,也就是2.5mA以下。
bus在suspend状态时,host和device都可以发送Resume信号唤醒。
Resume信号类似于Data K。
USB规范约定这个Resume信号需持续20ms以上。
USB规范约定device默认禁止remote wakeup。 host可以用SetDeviceFeature(DEVICE_REMOTE_WAKEUP)命令打开remote wakeup。
linux中对USB Suspend/Resume的支持
usb suspend/resume是通过SetPortFeature命令控制的。Linux USB驱动也是通过这种Port command控制suspend/resume。如果要让一个设备suspend,则向其所在的hub Port发SetPortFeature命令中的set port suspend。
对于roothub,它没有所在的Port,则无法set port suspeng而是执行hcd的bus_suspend。
有的时候在set port suspend前还需要enable remote wakeup。观察发现runtime suspend过程中会enable remote wakeup,而suspeng过程中不会enable remote wakeup。
linux kernel USB autosuspend
linux kernel中有两种场景会操作USB Suspend/Resume。一种是runtime pm,另一种是全系统的Suspend/Resume(即S3、Suspend to Ram)。
可以通过sysfs控制usb autosuspend
下面是关于Linux USB利用runtime pm实现Suspend/Resume的一些探索。
echo auto > /sys/bus/usb/devices/1-1/power/level
echo on > /sys/bus/usb/devices/1-1/power/level
这条命令使能了一个设备的rpm suspend。(虚拟文件level目前被标记为“不推荐”,推荐使用control。)
在sysfs.c中的level_store()用来支持这个功能。
注意,这个文件节点控制suspend是面向device的,而不是面向interface的。
当向这个文件写入on/auto时,kernel中通过usb_enable_autosuspend()/usb_disable_autosuspend()产生作用。最终调用的是pm_runtime_allow()和pm_runtime_forbid()。
usb autosuspend使用了runtime pm
runtime pm的回调函数通过dev_pm_ops结构体注册给tuntime pm框架。
USB驱动软件的这个结构体在core/usb.c中,叫做usb_device_pm_ops。
注册了usb_runtime_suspend()、usb_runtime_resume()、usb_runtime_idle()三个函数。
可以通过一些接口函数操作runtime pm的行为。USB驱动软件封装了一些操作runtime pm的函数。除了前面提到的usb_enable_autosuspend()、usb_disable_autosuspend(),还有一些,如 usb_autopm_get/put_interface_*()、以及usb_mask_last_busy()、usb_autosuspend_device()、usb_autoresume_device()。 注意这些函数的操作对象,有的是usb_device,有的是usb_interface。典型情况是get/put类函数操作的是usb_interface,其实很容易理解,因为业务总是基于interface运行的,所以“投票”动作也是从interface发起。
usb_interface是usb_device的子设备,usb_interface的投票会传递到usb_device。(usb_set_configuration()中可以看到intf->dev.parent = &dev->dev
。)
runtime pm支持设定一个自动suspend的延时时间。对于hub设备,在hub.c中的hub_probe()函数,将autosuspend delay设定为0。对于一般设备,usb.c中usb_alloc_dev()函数,设定为2秒。有一个sysfs接口可以修改autosuspend,与前文所述的level在同一个路径。
USB3.0的U3与USB2.0的Suspend同一个流程
关于USB3.0 的U1、U2、U3。 U1、U2对标USB2.0的LPM,软件只需要协商和初始化一些属性,不需要参与其进入/推出的运行。U3相当于USB2.0的Suspend。
U3的进入/推出也是利用runtime pm机制实现的。实际上他们使用的是相同的代码,区别在于hcd中的hub_control()函数。在xhci驱动中,USB2、USB3不仅是两个port,也注册了两个hcd,所以roothub也是两个,所以它们的runtime pm流程是相互独立的。
源代码分析
usb_runtime_suspend()
|- usb_suspend_both()
|-usb_suspend_both()
|- driver->susend() // 没有撤销urb的动作
|- usb_suspend_device()
|- udriver->suspend()
|- generic_suspend()
|- hcd_bus_suspend() // root hub的时候执行此分支
|- hcd->driver->bus_suspend()
|- xhci_bus_suspend()
|- usb_port_suspend() // 一般设备的时候执行此分支
|- USB3 : hub_set_port_link_state()
|- USB2 : set_port_feature()
参考资料
USB2.0协议:
7.1.7.6
7.1.7.7
9.1.6
10.2.7
10.5.4.5
11.9
11.24.1