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>>