现在的位置: 首页 > 综合 > 正文

Linux设备驱动之USB hub驱动(续)

2013年02月16日 ⁄ 综合 ⁄ 共 8147字 ⁄ 字号 评论关闭

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:

抱歉!评论已关闭.