5.2.2:接口驱动中的hub_thread()函数
我们之前在分析usb_hub_init()的代码的时候,忽略掉了一部份.
代码片段如下所示:
int usb_hub_init(void)
{
……
khubd_task = kthread_run(hub_thread, NULL, "khubd");
……
}
Kthread_run()是kernel中用来启动一个新kernel线程的接口,它所要执行的函数就是后面跟的第一个参数.在这里,也就是hub_thread().另外,顺带提一句,要终止kthread_run()创建的线程,可以调用kthread_stop().
Hub_thread()的代码如下:
static int hub_thread(void *__unused)
{
set_freezable();
do {
hub_events();
wait_event_freezable(khubd_wait,
!list_empty(&hub_event_list) ||
kthread_should_stop());
} while (!kthread_should_stop() || !list_empty(&hub_event_list));
pr_debug("%s: khubd exiting\n", usbcore_name);
return 0;
}
在上面的代码中, kthread_should_stop()用来判断是否有kthread_stop()将其终止.
在这里,我们终止看到,我们在前面要唤醒的等待队列khubd_wait,也就是在这个地方了.
这个函数的核心处理是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;
/*
* 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 */
//如果hub_event_list为空,退出
spin_lock_irq(&hub_event_lock);
if (list_empty(&hub_event_list)) {
spin_unlock_irq(&hub_event_lock);
break;
}
//取hub_event_list中的后一个元素,并将其断链
tmp = hub_event_list.next;
list_del_init(tmp);
hub = list_entry(tmp, struct usb_hub, event_list);
kref_get(&hub->kref);
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);
//如果hub断开了,继续hub_event_list中的下一个
if (unlikely(hub->disconnected))
goto loop;
/* If the hub has died, clean up after it */
//设备没有连接上
if (hdev->state == USB_STATE_NOTATTACHED) {
hub->error = -ENODEV;
//将下面的子设备全部disable
hub_pre_reset(intf);
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 */
//hub 暂停
if (hub->quiescing)
goto loop_autopm;
//hub 有错误发生?
if (hub->error) {
dev_dbg (hub_dev, "resetting for error %d\n",
hub->error);
ret = usb_reset_composite_device(hdev, intf);
if (ret) {
dev_dbg (hub_dev,
"error resetting hub: %d\n", ret);
goto loop_autopm;
}
hub->nerrors = 0;
hub->error = 0;
}
首先,从hub_event_list摘下第一个元素,根据我们之前在接口驱动probe过程的kick_khubd()函数分析中,有将hub-> event_list添加到hub_event_list.因此,就可以顺藤摸瓜找到hub,再根据hub结构,找到接口结构和所属的usb 设备结构.
然后,进行第一个重要的判断.如果hub被断开了,则,断开hub下面所连接的所有端口,这是在hub_pre_reset()中完成的.
最后,进行第二个重要的判断,如果hub发生了错误,则reset它下面的所有端口,这是在usb_reset_composite_device()中完成的.
/* deal with port status changes */
//遍历hub中的每一个port
for (i = 1; i descriptor->bNbrPorts; i++) {
{
if (test_bit(i, hub->busy_bits))
continue;
connect_change = test_bit(i, hub->change_bits);
if (!test_and_clear_bit(i, hub->event_bits) &&
!connect_change && !hub->activating)
continue;
//Get_Port_Status:取得端口状态.
//会取得port的改变值和状态值
ret = hub_port_status(hub, i,
&portstatus, &portchange);
if (ret
continue;
//如果对应端口没有在设备树上,且端口显示已经连接上
//将connect_change置为1
if (hub->activating && !hdev->children[i-1] &&
(portstatus &
USB_PORT_STAT_CONNECTION))
connect_change = 1;
//端口的连接状态发生了改变.需要发送Clear_Feature
if (portchange & USB_PORT_STAT_C_CONNECTION) {
clear_port_feature(hdev, i,
USB_PORT_FEAT_C_CONNECTION);
connect_change = 1;
}
//端口的状态从enable 变为了disable
if (portchange & USB_PORT_STAT_C_ENABLE) {
if (!connect_change)
dev_dbg (hub_dev,
"port %d enable change, "
"status %08x\n",
i, portstatus);
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
&& hdev->children[i-1]) {
dev_err (hub_dev,
"port %i "
"disabled by hub (EMI?), "
"re-enabling...\n",
i);
connect_change = 1;
}
}
//Resume完成
if (portchange & USB_PORT_STAT_C_SUSPEND) {
clear_port_feature(hdev, i,
USB_PORT_FEAT_C_SUSPEND);
//如果端口连接了设备,就将设备唤醒
if (hdev->children[i-1]) {
ret = remote_wakeup(hdev->
children[i-1]);
if (ret
connect_change = 1;
}
//如果端口没有连接设备,就将端口禁用
else {
ret = -ENODEV;
hub_port_disable(hub, i, 1);
}
dev_dbg (hub_dev,
"resume on port %d, status %d\n",
i, ret);
}
//有过流保护,需要对hub power on
if (portchange & USB_PORT_STAT_C_OVERCURRENT) {
dev_err (hub_dev,
"over-current change on port %d\n",
i);
clear_port_feature(hdev, i,
USB_PORT_FEAT_C_OVER_CURRENT);
hub_power_on(hub);
}
//Reset状态已经完成了
if (portchange & USB_PORT_STAT_C_RESET) {
dev_dbg (hub_dev,
"reset change on port %d\n",
i);
clear_port_feature(hdev, i,
USB_PORT_FEAT_C_RESET);
}
if (connect_change)
hub_port_connect_change(hub, i,
portstatus, portchange);
}
这段代码就是最核心的操作了,首先要说明的是,在struct usb_dev中,有一个struct usb_device *children[USB_MAXCHILDREN]的成员,它是表示对应端口序号上所连接的usb设备.
在这里,它遍历hub上的每一个端口,如果端口的连接会生了改变(connect_change等于1)的情况,就会调用hub_port_connect_change().我们来看一下,什么情况下, hub_port_connect_change才会被设为1.
1:端口在hub->change_bits中被置位.搜索整个代码树,发生在设置hub->change_bits的地方,只有在hub_port_logical_disconnect()中手动将端口禁用,会将对应位置1.
2:hub上没有这个设备树上没有这个端口上的设备.但显示端口已经连上了设备
3:hub这个端口上的连接发生了改变,从端口有设备连接变为无设备连接,或者从无设备连接变为有设备连接.
4:hub的端口变为了disable,此时这个端口上连接了设备,但被显示该端口已经变禁用,需要将connect_change设为1.
5:端口状态从SUSPEND变成了RESUME,远程唤醒端口上的设备失败,就需要将connect_change设为1.
另外hub_port_connect_change()函数我们放在后面再来讨论
//对HUB的处理
/* deal with hub status changes */
//如果hub状态末变化,不需要做任何处理
if (test_and_clear_bit(0, hub->event_bits) == 0)
; /* do nothing */
//Get_hub_status 失败?
else if (hub_hub_status(hub, &hubstatus, &hubchange)
dev_err (hub_dev, "get_hub_status failed\n");
else {
//这里是对应hub 状态发生了改变,且Get_hub_status正常返回的情况
//如果hub的本地电源供电发生了改变
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;
}
//如果hub 发生过电源保护,需要对hub power on
if (hubchange & HUB_CHANGE_OVERCURRENT) {
dev_dbg (hub_dev, "overcurrent change\n");
msleep(500); /* Cool down */
clear_hub_feature(hdev, C_HUB_OVER_CURRENT);
hub_power_on(hub);
}
}
hub->activating = 0;
/* If this is a root hub, tell the HCD it's okay to
* re-enable port-change interrupts now. */
if (!hdev->parent && !hub->busy_bits[0])
usb_enable_root_hub_irq(hdev->bus);
loop_autopm:
/* Allow autosuspend if we're not going to run again */
if (list_empty(&hub->event_list))
usb_autopm_enable(intf);
loop: