linux usb枚举过程分析

插入一个 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一些状态:

  1. 如果usb的第i个port处于resume或reset状态,则hub中busy_bits中第i个位置1;如果busy_bits中第i个位置1,则退过当前port;
  2. event_bits中第0位用来表示hub本身是否产生local power status change和是否产生过流,其它的各位用来表示hub下各个port状态是否发生变化,这些状态包括: ConnectStatusChange 连接状态发生变化,PortEnableStatusChange端口使能状态发生变化,PortSuspendStatusChange端口从挂起到恢复完成,PortOverCurrentIndicatorChange端口过流状态发生变化,PortResetStatusChange端口reset10ms置位;

  3. remove_bits用来表示hub下各个port是否有设备连接,如果置位表示没有设备连接或设备已经断开;

  4. 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,
    
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值