USB提供了一套自顶向下的识别方法,从hub_event出发,检查是否真的有端口发生了改变,如果坐实是port被触发了,则进入port_event。
port_event()
先去获取hub的状态
if (hub_port_status(hub, port1, &portstatus, &portchange) < 0)
return;
最终也是调用get_port_stats()函数去获得想要的状态,hub也是一类usb设备,
static int get_port_status(struct usb_device *hdev, int port1,
void *data, u16 value, u16 length)
{
int i, status = -ETIMEDOUT;
for (i = 0; i < USB_STS_RETRIES &&
(status == -ETIMEDOUT || status == -EPIPE); i++) {
status = usb_control_msg(hdev, usb_rcvctrlpipe(hdev, 0),
USB_REQ_GET_STATUS, USB_DIR_IN | USB_RT_PORT, value,
port1, data, length, USB_STS_TIMEOUT);
}
return status;
}
根据spec对于port状态的请求
状态字段的定于
驱动也有相同的宏定义:
/*
* wPortChange bit field
* See USB 2.0 spec Table 11-22 and USB 2.0 LPM ECN Table-4.10
* Bits 0 to 5 shown, bits 6 to 15 are reserved
*/
#define USB_PORT_STAT_C_CONNECTION 0x0001
#define USB_PORT_STAT_C_ENABLE 0x0002
#define USB_PORT_STAT_C_SUSPEND 0x0004
#define USB_PORT_STAT_C_OVERCURRENT 0x0008
#define USB_PORT_STAT_C_RESET 0x0010
#define USB_PORT_STAT_C_L1 0x0020
/*
122 * wPortStatus bit field
123 * See USB 2.0 spec Table 11-21
124 */
125 #define USB_PORT_STAT_CONNECTION 0x0001
126 #define USB_PORT_STAT_ENABLE 0x0002
127 #define USB_PORT_STAT_SUSPEND 0x0004
128 #define USB_PORT_STAT_OVERCURRENT 0x0008
129 #define USB_PORT_STAT_RESET 0x0010
130 #define USB_PORT_STAT_L1 0x0020
131 /* bits 6 to 7 are reserved */
132 #define USB_PORT_STAT_POWER 0x0100
133 #define USB_PORT_STAT_LOW_SPEED 0x0200
134 #define USB_PORT_STAT_HIGH_SPEED 0x0400
135 #define USB_PORT_STAT_TEST 0x0800
136 #define USB_PORT_STAT_INDICATOR 0x1000
然后引发了一串 if 判断...
if (portchange & USB_PORT_STAT_C_CONNECTION) { //有新设备接入?
usb_clear_port_feature(hdev, port1, USB_PORT_FEAT_C_CONNECTION);
connect_change = 1;
}
if (portchange & USB_PORT_STAT_C_ENABLE) { //hub上port是否使能?
if (!connect_change)
dev_dbg(&port_dev->dev, "enable change, status %08x\n",
portstatus);
usb_clear_port_feature(hdev, port1, USB_PORT_FEAT_C_ENABLE);
/*
* EM interference sometimes causes badly shielded USB devices
* to be shutdown by the hub, this hack enables them again.
* Works at least with mouse driver.
*/
if (!(portstatus & USB_PORT_STAT_ENABLE)
&& !connect_change && udev) {
dev_err(&port_dev->dev, "disabled by hub (EMI?), re-enabling...\n");
connect_change = 1;
}
}
if (portchange & USB_PORT_STAT_C_OVERCURRENT) { //过电流?
u16 status = 0, unused;
dev_dbg(&port_dev->dev, "over-current change\n");
usb_clear_port_feature(hdev, port1,
USB_PORT_FEAT_C_OVER_CURRENT);
msleep(100); /* Cool down */
hub_power_on(hub, true);
hub_port_status(hub, port1, &status, &unused);
if (status & USB_PORT_STAT_OVERCURRENT)
dev_err(&port_dev->dev, "over-current condition\n");
}
if (portchange & USB_PORT_STAT_C_RESET) { //重启?
dev_dbg(&port_dev->dev, "reset change\n");
usb_clear_port_feature(hdev, port1, USB_PORT_FEAT_C_RESET);
}
if ((portchange & USB_PORT_STAT_C_BH_RESET) //以下是usb3.0相关的状态
&& hub_is_superspeed(hdev)) {
dev_dbg(&port_dev->dev, "warm reset change\n");
usb_clear_port_feature(hdev, port1,
USB_PORT_FEAT_C_BH_PORT_RESET);
}
if (portchange & USB_PORT_STAT_C_LINK_STATE) {
dev_dbg(&port_dev->dev, "link state change\n");
usb_clear_port_feature(hdev, port1,
USB_PORT_FEAT_C_PORT_LINK_STATE);
}
if (portchange & USB_PORT_STAT_C_CONFIG_ERROR) {
dev_warn(&port_dev->dev, "config error\n");
usb_clear_port_feature(hdev, port1,
USB_PORT_FEAT_C_PORT_CONFIG_ERROR);
}
connect_change 标志,用来标志着是不是真的有端口改变了。如果真的改变了,需要处理调用hub_port_connect_change()函数。
if (connect_change)
hub_port_connect_change(hub, port1, portstatus, portchange);
总结一下,在那些情况下会使得 connect_change 标志位 为1?
答:1. 端口对应的hub->change_bits 被置1
2.有设备接入的时候
3. 远程唤醒hub端口
port_event() 函数都干了些啥?
答:判定hub的port发生了什么(设备接入、唤醒...),做出相应的响应。
hub_port_connect_change()
函数原型:
/* Handle physical or logical connection change events.
* This routine is called when:
* a port connection-change occurs;
* a port enable-change occurs (often caused by EMI);
* usb_reset_and_verify_device() encounters changed descriptors (as from
* a firmware download)
* caller already locked the hub
*/
static void hub_port_connect_change(struct usb_hub *hub, int port1,
u16 portstatus, u16 portchange)
__must_hold(&port_dev->status_lock)
功能:响应物理层或者逻辑层的port改变
适用于: 端口连接情况发生改变;端口使能情况发生改变等
若hub有指示灯,则开始工作
if (hub->has_indicators) {
set_port_led(hub, port1, HUB_LED_AUTO);
hub->indicator[port1-1] = INDICATOR_AUTO;
}
如支持otg功能,设置标志位
#ifdef CONFIG_USB_OTG
/* during HNP, don't repeat the debounce */
if (hub->hdev->bus->is_b_host)
portchange &= ~(USB_PORT_STAT_C_CONNECTION |
USB_PORT_STAT_C_ENABLE);
#endif
检查端口设备是不是已连接需要被唤醒
if ((portstatus & USB_PORT_STAT_CONNECTION) && udev &&
udev->state != USB_STATE_NOTATTACHED) {
if (portstatus & USB_PORT_STAT_ENABLE) {
status = 0; /* Nothing to do */
#ifdef CONFIG_PM
} else if (udev->state == USB_STATE_SUSPENDED &&
udev->persist_enabled) {
/* For a suspended device, treat this as a
* remote wakeup event.
*/
usb_unlock_port(port_dev);
status = usb_remote_wakeup(udev);
usb_lock_port(port_dev);
#endif
} else {
/* Don't resuscitate */;
}
}
clear_bit(port1, hub->change_bits);
/* successfully revalidated the connection */
if (status == 0)
return;
如果之前没有不是只需要唤醒,就进入连接处理
usb_unlock_port(port_dev);
hub_port_connect(hub, port1, portstatus, portchange);
usb_lock_port(port_dev);
hub_port_connect 函数接着处理连接相关事务。