插入一个 USB设备的处理机制总体计:
1. 中断定时查询:
2. 总体架构设计:
3. 解析各个部分:
中断定时查询:
Hub层处理
usb枚举
当守护程序第一次运行或usb port上状态发生变化,守护进程被唤醒都会运行hub_events
函数,这个函数在usb系统中处理核心位置,usb的枚举过程就是由它完成。
usb具体的枚举流程:
hub_events
函数
static void hub_events(void)
{
struct list_head *tmp;
struct usb_device *hdev;
struct usb_interface *intf;
struct usb_hub *hub;
struct device *hub_dev;
u16 hubstatus;
u16 hubchange;
u16 portstatus;
u16 portchange;
int i, ret;
int connect_change, wakeup_change;
/*
* We restart the list every time to avoid a deadlock with
* deleting hubs downstream from this one. This should be
* safe since we delete the hub from the event list.
* Not the most efficient, but avoids deadlocks.
*/
while (1) {
/* Grab the first entry at the beginning of the list */
spin_lock_irq(&hub_event_lock);
if (list_empty(&hub_event_list)) {
spin_unlock_irq(&hub_event_lock);
break;
}
tmp = hub_event_list.next;
list_del_init(tmp);
hub = list_entry(tmp, struct usb_hub, event_list);
kref_get(&hub->kref);
/* make sure hdev is not freed before accessing it */
if (hub->disconnected) { //判断hub是否连接
spin_unlock_irq(&hub_event_lock);
goto hub_disconnected;
} else {
usb_get_dev(hub->hdev);
}
spin_unlock_irq(&hub_event_lock);
hdev = hub->hdev;
hub_dev = hub->intfdev;
intf = to_usb_interface(hub_dev);
dev_dbg(hub_dev, "state %d ports %d chg %04x evt %04x\n",
hdev->state, hub->descriptor
? hub->descriptor->bNbrPorts
: 0,
/* NOTE: expects max 15 ports... */
(u16) hub->change_bits[0],
(u16) hub->event_bits[0]);
/* Lock the device, then check to see if we were
* disconnected while waiting for the lock to succeed. */
usb_lock_device(hdev);
if (unlikely(hub->disconnected))
goto loop_disconnected;
/* If the hub has died, clean up after it */
if (hdev->state == USB_STATE_NOTATTACHED) {
hub->error = -ENODEV;
hub_quiesce(hub, HUB_DISCONNECT);
goto loop;
}
/* Autoresume */
ret = usb_autopm_get_interface(intf);
if (ret) {
dev_dbg(hub_dev, "Can't autoresume: %d\n", ret);
goto loop;
}
/* If this is an inactive hub, do nothing */
if (hub->quiescing)
goto loop_autopm;
if (hub->error) {
dev_dbg (hub_dev, "resetting for error %d\n",
hub->error);
ret = usb_reset_device(hdev);
if (ret) {
dev_dbg (hub_dev,
"error resetting hub: %d\n", ret);
goto loop_autopm;
}
hub->nerrors = 0;
hub->error = 0;
}
/* deal with port status changes */
//遍历hub中的所有port,对各个port的状态进行查看,判断port是否发生了状态变化
for (i = 1; i <= hub->descriptor->bNbrPorts; i++) {
if (test_bit(i, hub->busy_bits))
continue;
connect_change = test_bit(i, hub->change_bits);
wakeup_change = test_and_clear_bit(i, hub->wakeup_bits);
if (!test_and_clear_bit(i, hub->event_bits) &&
!connect_change && !wakeup_change)
continue;
ret = hub_port_status(hub, i,
&portstatus, &portchange);
if (ret < 0)
continue;
if (portchange & USB_PORT_STAT_C_CONNECTION) {
usb_clear_port_feature(hdev, i,
USB_PORT_FEAT_C_CONNECTION);
connect_change = 1;
}
if (portchange & USB_PORT_STAT_C_ENABLE) {
if (!connect_change)
dev_dbg (hub_dev,
"port %d enable change, "
"status %08x\n",
i, portstatus);
usb_clear_port_feature(hdev, i,
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
&& hub->ports[i - 1]->child) {
dev_err (hub_dev,
"port %i "
"disabled by hub (EMI?), "
"re-enabling...\n",
i);
connect_change = 1;
}
}
if (hub_handle_remote_wakeup(hub, i,
portstatus, portchange))
connect_change = 1;
if (portchange & USB_PORT_STAT_C_OVERCURRENT) {
u16 status = 0;
u16 unused;
dev_dbg(hub_dev, "over-current change on port "
"%d\n", i);
usb_clear_port_feature(hdev, i,
USB_PORT_FEAT_C_OVER_CURRENT);
msleep(100); /* Cool down */
hub_power_on(hub, true);
hub_port_status(hub, i, &status, &unused);
if (status & USB_PORT_STAT_OVERCURRENT)
dev_err(hub_dev, "over-current "
"condition on port %d\n", i);
}
if (portchange & USB_PORT_STAT_C_RESET) {
dev_dbg (hub_dev,
"reset change on port %d\n",
i);
usb_clear_port_feature(hdev, i,
USB_PORT_FEAT_C_RESET);
}
if ((portchange & USB_PORT_STAT_C_BH_RESET) &&
hub_is_superspeed(hub->hdev)) {
dev_dbg(hub_dev,
"warm reset change on port %d\n",
i);
usb_clear_port_feature(hdev, i,
USB_PORT_FEAT_C_BH_PORT_RESET);
}
if (portchange & USB_PORT_STAT_C_LINK_STATE) {
usb_clear_port_feature(hub->hdev, i,
USB_PORT_FEAT_C_PORT_LINK_STATE);
}
if (portchange & USB_PORT_STAT_C_CONFIG_ERROR) {
dev_warn(hub_dev,
"config error on port %d\n",
i);
usb_clear_port_feature(hub->hdev, i,
USB_PORT_FEAT_C_PORT_CONFIG_ERROR);
}
/* Warm reset a USB3 protocol port if it's in
* SS.Inactive state.
*/
if (hub_port_warm_reset_required(hub, portstatus)) {
int status;
struct usb_device *udev =
hub->ports[i - 1]->child;
dev_dbg(hub_dev, "warm reset port %d\n", i);
if (!udev ||
!(portstatus & USB_PORT_STAT_CONNECTION) ||
udev->state == USB_STATE_NOTATTACHED) {
status = hub_port_reset(hub, i,
NULL, HUB_BH_RESET_TIME,
true);
if (status < 0)
hub_port_disable(hub, i, 1);
} else {
usb_lock_device(udev);
status = usb_reset_device(udev);
usb_unlock_device(udev);
connect_change = 0;
}
}
if (connect_change)//如果port状态发生变化,则调用hub_port_connect_change
hub_port_connect_change(hub, i,
portstatus, portchange);
} /* end for i */
/* deal with hub status changes */
if (test_and_clear_bit(0, hub->event_bits) == 0)
; /* do nothing */
else if (hub_hub_status(hub, &hubstatus, &hubchange) < 0)
dev_err (hub_dev, "get_hub_status failed\n");
else {
if (hubchange & HUB_CHANGE_LOCAL_POWER) {
dev_dbg (hub_dev, "power change\n");
clear_hub_feature(hdev, C_HUB_LOCAL_POWER);
if (hubstatus & HUB_STATUS_LOCAL_POWER)
/* FIXME: Is this always true? */
hub->limited_power = 1;
else
hub->limited_power = 0;
}
if (hubchange & HUB_CHANGE_OVERCURRENT) {
u16 status = 0;
u16 unused;
dev_dbg(hub_dev, "over-current change\n");
clear_hub_feature(hdev, C_HUB_OVER_CURRENT);
msleep(500); /* Cool down */
hub_power_on(hub, true);
hub_hub_status(hub, &status, &unused);
if (status & HUB_STATUS_OVERCURRENT)
dev_err(hub_dev, "over-current "
"condition\n");
}
}
loop_autopm:
/* Balance the usb_autopm_get_interface() above */
usb_autopm_put_interface_no_suspend(intf);
loop:
/* Balance the usb_autopm_get_interface_no_resume() in
* kick_khubd() and allow autosuspend.
*/
usb_autopm_put_interface(intf);
loop_disconnected:
usb_unlock_device(hdev);
usb_put_dev(hdev);
hub_disconnected:
kref_put(&hub->kref, hub_release);
} /* end while (1) */
}
hub_events
本身是一个死循环,只要条件满足它便会一直执行。
首先判断hub_event_list
是否为空,如果为空,则跳出循环,hub_events
运行结束,会进入休眠状态;如果不为空,则从hub_event_list
列表中取出某一项,并把它从hub_event_list
中删除,通过list_entry
来获取event_list
所对应的hub,并增加hub使用计数;然后做了一些逻辑判断,判断hub是否连接,hub上是否有错误,如果有错误就重启hub,如果没有错误,接下来就对hub 上的每个port进行扫描,判断各个port是否发生状态变化;
接着遍历hub中的所有port,对各个port的状态进行查看,判断port是否发生了状态变化,如果产生了变化则调用hub_port_connect_change
进行处理;
在hub结构中存在多个标志位,用来表示port一些状态:
- 如果usb的第i个port处于resume或reset状态,则hub中
busy_bits
中第i个位置1;如果busy_bits
中第i个位置1,则退过当前port; event_bits
中第0位用来表示hub本身是否产生local power status change和是否产生过流,其它的各位用来表示hub下各个port状态是否发生变化,这些状态包括: ConnectStatusChange 连接状态发生变化,PortEnableStatusChange端口使能状态发生变化,PortSuspendStatusChange端口从挂起到恢复完成,PortOverCurrentIndicatorChange端口过流状态发生变化,PortResetStatusChange端口reset10ms置位;remove_bits
用来表示hub下各个port是否有设备连接,如果置位表示没有设备连接或设备已经断开;change_bits
用来表示hub下各个端口逻辑状态是否发生变化,它是在hub初始化的时候赋值的,这些状态主要有:1.原先port上没有设备,现在检测到有设备连接;2.原先port上有设备,由于控制器不正常关闭或禁止usb port等原图使得该设备处于NOATTACHED态,则需要断开设备;
在遍历port时,首先对这些标志进行判断,如果没有产生变化则跳过当前port,否则读取当前port的status,判断出产生状态变化的原因;
如果发生变化port的连接状态发生变化,清除相应的端口状态,设置connect_change
变量为1;
如果port的使能状态发生变化,清除相应的状态标志,如果是由于EMI电磁干扰引起的状态变化,则设置connect_change
变量为1;
……
经过一系列列port判断,如果发现port的连接状态发生变化或由于EMI导致连接使能发生变化,即connect_change=1
,则调用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,