Linux下USB suspend/resume源码分析
Author:aaron
本文主要从自己开发的一个USB驱动的例子来深入讲解linux内核是如何支持USB设备的休眠和唤醒的,
最近我在为我们公司的一个模块写linux下的驱动, 其中之一就是要支持USB的休眠唤醒问题, 实际上linux内核对USB的这个功能的支持还是比较新的, 也就是最近几年的事.
一 打开/关闭USB suspend/resuem功能
要让linux支持usb suspend/resuem, 当然先要把内核中这个功能的代码编译进去咯, 即要在make menuconfig时打开对这项功能的支持.
第一个打开的项是CONFIG_PM, 即整个系统的电源管理, USB suspend/resuem只是整个电源管理的一个自系统. 只有打开了这个功能才能让USB的这个特性能用.
第二个要打开的当让是USB自己的开关了CONFIG_USB_SUSPEND. 即打开了这个功能后我们只要在我们自己的驱动里简单调用下USB core提供的函数接口就能使我们的设备休眠了.
二 源码分析
在2.6.19之前的代码中不支持USB自动休眠的功能, 它只能是在host休眠情况下才会让USB设备也休眠. 所以如果我们要让自己的设备在不使用的情况下就休眠就得自己添加相应的代码, 幸运的是我们不需要添加复杂的代码就能达到这个目的, 因为USB core里提供了几个接口可以直接让我们的驱动调用以把我们的设备置入休眠状态.
下面我们以2.6.16的代码为例来分析下USB设备是如何进入休眠的.
Drivers/usr/core/hub.c:
int usb_suspend_device(struct usb_device *udev)
{
#ifdef CONFIG_USB_SUSPEND
if (udev->state == USB_STATE_NOTATTACHED)
return -ENODEV;
return __usb_suspend_device(udev, udev->portnum);
#else
/* NOTE: udev->state unchanged, it's not lying ... */
udev->dev.power.power_state = PMSG_SUSPEND;
return 0;
#endif
}
没错, 在我们的驱动里只要在适当的地方调用这个函数就可以使我们的设备休眠了. 但是需要注意的是, 内核没有EXPORT这个函数, 因此如果我们的驱动要编译成模块的话, 我们只有修改内核以EXPORT这个函数了.
实际上真正干正事的函数是__usb_suspend_device
Drivers/usr/core/hub.c:
static int __usb_suspend_device (struct usb_device *udev, int port1)
{
int status = 0;
/* caller owns the udev device lock */
if (port1 < 0)
return port1;
/*如果设备已休眠或还没attach上则直接返回*/
if (udev->state == USB_STATE_SUSPENDED
|| udev->state == USB_STATE_NOTATTACHED) {
return 0;
}
/* all interfaces must already be suspended */
/*要休眠设备, 首先必须要设备下的每个interface都可以休眠才行*/
if (udev->actconfig) {
int i;
for (i = 0; i < udev->actconfig->desc.bNumInterfaces; i++) {
struct usb_interface *intf;
intf = udev->actconfig->interface[i];
if (is_active(intf)) { /*如果某个interface处于活动状态则不能休眠*/
dev_dbg(&intf->dev, "nyet suspended/n");
return -EBUSY;
}
}
}
/* we only change a device's upstream USB link.
* root hubs have no upstream USB link.
*/
/*干正事的一个函数*/
if (udev->parent)
status = hub_port_suspend(hdev_to_hub(udev->parent), port1,
udev);
if (status == 0)
udev->dev.power.power_state = PMSG_SUSPEND; /*保存设备状态*/
return status;
}
第一个参数是要休眠的USB设备, 第二个参数是该USB设备所连接到的hub的某个端口.
从这个函数我们可以大概猜测到, 要一个设备休眠原理就是要把这个设备attach到的那个端口休眠掉.
没错USB spec规定了只要把设备所attach上的那个端口disable掉, 那么这条路径上就没有任何传输了, 在过了一段时间后设备端应该会产生一个suspend的中断, 以让设备进入休眠状态.
Drivers/usr/core/hub.c:
static int hub_port_suspend(struct usb_hub *hub, int port1,
struct usb_device *udev)
{
int status;
// dev_dbg(hub->intfdev, "suspend port %d/n", port1);
/* enable remote wakeup when appropriate; this lets the device
* wake up the upstream hub (including maybe the root hub).
*
* NOTE: OTG devices may issue remote wakeup (or SRP) even when
* we don't explicitly enable it here.
*/
/*如果设备支持远程唤醒功能, 则打开此功能*/
if (device_may_wakeup(&udev->dev)) {
status = usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
USB_REQ_SET_FEATURE, USB_RECIP_DEVICE,
USB_DEVICE_REMOTE_WAKEUP, 0,
NULL, 0,
USB_CTRL_SET_TIMEOUT);
if (status)
dev_dbg(&udev->dev,
"won't remote wakeup, status %d/n",
status);
}
/* see 7.1.7.6 */
/*^_^看usb spec 7.1.7.6吧, 这个命令就是把hub的这个port disable掉, 这样就达到休眠的目的了*/
status = set_port_feature(hub->hdev, port1, USB_PORT_FEAT_SUSPEND);
if (status) {
dev_dbg(hub->intfdev,
"can't suspend port %d, status %d/n",
port1, status);
/* paranoia: "should not happen" */
(void) usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
USB_REQ_CLEAR_FEATURE, USB_RECIP_DEVICE,
USB_DEVICE_REMOTE_WAKEUP, 0,
NULL, 0,
USB_CTRL_SET_TIMEOUT);
} else {
/* device has up to 10 msec to fully suspend */
dev_dbg(&udev->dev, "usb suspend/n");
usb_set_device_state(udev, USB_STATE_SUSPENDED); /*设置设备状态*/
msleep(10);
}
return status;
}
OK, 很简单把usb设备attach上的那个port disable掉, 就可以达到休眠的目的了(当然实际上它只是把这个port所在的那条链路上的传输停掉, 至于设备是否真会休眠要考设备本省的, 正常情况下, 设备硬件会检测总线上是否有传输, 如没有则把设备转入suspend状态).
OK, 理解了USB设备如何休眠, 那么对于USB设备如何唤醒就很清楚了, 就是重新enable那个port就行了. 这里就不在分析了.
因此假如说我们的设备要在打开之后禁止休眠, 在关闭之后才允许休眠, 该怎么做呢? 呵呵,只要在驱动的open函数里resume设备, 在close函数里suspend设备就行了.
至于2.6.19以后的版本, 加入了自动休眠的功能, 具体实现原理可以参考由fudan_abc写的<<linux那些事儿之HUB>>