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,
                    u16 portstatus, u16 portchange)
{
    struct usb_device *hdev = hub->hdev;
    struct device *hub_dev = hub->intfdev;
    struct usb_hcd *hcd = bus_to_hcd(hdev->bus);
    unsigned wHubCharacteristics =
            le16_to_cpu(hub->descriptor->wHubCharacteristics);
    struct usb_device *udev;
    int status, i;
    unsigned unit_load;

    dev_dbg (hub_dev,
        "port %d, status %04x, change %04x, %s\n",
        port1, portstatus, portchange, portspeed(hub, portstatus));

    if (hub->has_indicators) {//如果當前的hub支持indicator指示燈,則設備指示燈爲HUB_LED_AUTO
        set_port_led(hub, port1, HUB_LED_AUTO);
        hub->indicator[port1-1] = INDICATOR_AUTO;
    }

#ifdef  CONFIG_USB_OTG
    /* during HNP, don't repeat the debounce */
    if (hdev->bus->is_b_host)
        portchange &= ~(USB_PORT_STAT_C_CONNECTION |
                USB_PORT_STAT_C_ENABLE);
#endif

    /* Try to resuscitate an existing device */
    udev = hub->ports[port1 - 1]->child;
    if ((portstatus & USB_PORT_STAT_CONNECTION) && udev &&
            udev->state != USB_STATE_NOTATTACHED) {
        usb_lock_device(udev);
        if (portstatus & USB_PORT_STAT_ENABLE) {
            status = 0;     /* Nothing to do */

#ifdef CONFIG_PM_RUNTIME
        } else if (udev->state == USB_STATE_SUSPENDED &&
                udev->persist_enabled) {
            /* For a suspended device, treat this as a
             * remote wakeup event.
             */
            status = usb_remote_wakeup(udev);
#endif

        } else {
            status = -ENODEV;   /* Don't resuscitate */
        }
        usb_unlock_device(udev);

        if (status == 0) {  //清除當前port的change_bits標誌
            clear_bit(port1, hub->change_bits);
            return;
        }
    }

    /* Disconnect any existing devices under this port */
    if (udev) {
        if (hcd->phy && !hdev->parent &&
                !(portstatus & USB_PORT_STAT_CONNECTION))
            usb_phy_notify_disconnect(hcd->phy, udev->speed);
        usb_disconnect(&hub->ports[port1 - 1]->child);
    }
    clear_bit(port1, hub->change_bits);

    /* We can forget about a "removed" device when there's a physical
     * disconnect or the connect status changes.
     */
    if (!(portstatus & USB_PORT_STAT_CONNECTION) ||
            (portchange & USB_PORT_STAT_C_CONNECTION))
        clear_bit(port1, hub->removed_bits);

    if (portchange & (USB_PORT_STAT_C_CONNECTION |
                USB_PORT_STAT_C_ENABLE)) {
        status = hub_port_debounce_be_stable(hub, port1);
        if (status < 0) {
            if (status != -ENODEV && printk_ratelimit())
                dev_err(hub_dev, "connect-debounce failed, "
                        "port %d disabled\n", port1);
            portstatus &= ~USB_PORT_STAT_CONNECTION;
        } else {
            portstatus = status;
        }
    }

    /* Return now if debouncing failed or nothing is connected or
     * the device was "removed".
     */
    if (!(portstatus & USB_PORT_STAT_CONNECTION) ||
            test_bit(port1, hub->removed_bits)) {

        /* maybe switch power back on (e.g. root hub was reset) */
        if ((wHubCharacteristics & HUB_CHAR_LPSM) < 2
                && !port_is_power_on(hub, portstatus))
            set_port_feature(hdev, port1, USB_PORT_FEAT_POWER);

        if (portstatus & USB_PORT_STAT_ENABLE)
            goto done;
        return;
    }
    if (hub_is_superspeed(hub->hdev))
        unit_load = 150;
    else
        unit_load = 100;

    status = 0;
    for (i = 0; i < SET_CONFIG_TRIES; i++) {

        /* reallocate for each attempt, since references
         * to the previous one can escape in various ways
         * 通過usb_alloc_dev爲新的USB設備申請資源,並進行一些初始化,
         * 將usb設置爲USB_STATE_ATTACHED,表示設備已經連接
         */
        udev = usb_alloc_dev(hdev, hdev->bus, port1);
        if (!udev) {
            dev_err (hub_dev,
                "couldn't allocate port %d usb_device\n",
                port1);
            goto done;
        }

        usb_set_device_state(udev, USB_STATE_POWERED);//設置usb設備爲USB_STATE_POWERED態
        udev->bus_mA = hub->mA_per_port;//設置usb設備可以從port上獲取的電流量
        udev->level = hdev->level + 1;,//設置usb設備的拓撲層級
        udev->wusb = hub_is_wusb(hub);

        /* Only USB 3.0 devices are connected to SuperSpeed hubs. */
        //如果hub支持超速,則設置usb設備的速度爲超速,否則設置usb設備速度 爲unknow
        if (hub_is_superspeed(hub->hdev))
            udev->speed = USB_SPEED_SUPER;
        else
            udev->speed = USB_SPEED_UNKNOWN;

        choose_devnum(udev);  //從usb 總線中找到一個usb地址
        if (udev->devnum <= 0) {
            status = -ENOTCONN; /* Don't retry */
            goto loop;
        }

        /* reset (non-USB 3.0 devices) and get descriptor */
        status = hub_port_init(hub, udev, port1, i);
        if (status < 0)
            goto loop;

        usb_detect_quirks(udev);
        if (udev->quirks & USB_QUIRK_DELAY_INIT)
            msleep(1000);

        /* consecutive bus-powered hubs aren't reliable; they can
         * violate the voltage drop budget.  if the new child has
         * a "powered" LED, users should notice we didn't enable it
         * (without reading syslog), even without per-port LEDs
         * on the parent.
         */ 
         //如果當前的usb設備是一個hub,它由hub供電
        if (udev->descriptor.bDeviceClass == USB_CLASS_HUB
                && udev->bus_mA <= unit_load) {
            u16 devstat;

            status = usb_get_status(udev, USB_RECIP_DEVICE, 0,
                    &devstat);
            if (status < 2) {
                dev_dbg(&udev->dev, "get status %d ?\n", status);
                goto loop_disable;
            }
            le16_to_cpus(&devstat);
            if ((devstat & (1 << USB_DEVICE_SELF_POWERED)) == 0) {
                dev_err(&udev->dev,
                    "can't connect bus-powered hub "
                    "to this port\n");
                if (hub->has_indicators) {
                    hub->indicator[port1-1] =
                        INDICATOR_AMBER_BLINK;
                    schedule_delayed_work (&hub->leds, 0);
                }
                status = -ENOTCONN; /* Don't retry */
                goto loop_disable;
            }
        }

        /* check for devices running slower than they could */ 
        //如果usb設備支持高速運行,而現在卻工作在全速,它就會通過點亮綠色指示燈來指示這種錯誤
        if (le16_to_cpu(udev->descriptor.bcdUSB) >= 0x0200
                && udev->speed == USB_SPEED_FULL
                && highspeed_hubs != 0)
            check_highspeed (hub, udev, port1);

        /* Store the parent's children[] pointer.  At this point
         * udev becomes globally accessible, although presumably
         * no one will look at it until hdev is unlocked.
         */
        status = 0;

        /* We mustn't add new devices if the parent hub has
         * been disconnected; we would race with the
         * recursively_mark_NOTATTACHED() routine.
         */
        spin_lock_irq(&device_state_lock);
        if (hdev->state == USB_STATE_NOTATTACHED)
            status = -ENOTCONN;
        else
            hub->ports[port1 - 1]->child = udev;
        spin_unlock_irq(&device_state_lock);

        /* Run it through the hoops (find a driver, etc) */
        if (!status) {
            status = usb_new_device(udev);//通過usb_new_device去獲取usb設備的配置信息,將它註冊到系統中
            if (status) {
                spin_lock_irq(&device_state_lock);
                hub->ports[port1 - 1]->child = NULL;
                spin_unlock_irq(&device_state_lock);
            }
        }

        if (status)
            goto loop_disable;

        status = hub_power_remaining(hub);
        if (status)
            dev_dbg(hub_dev, "%dmA power budget left\n", status);

        return;

loop_disable:
        hub_port_disable(hub, port1, 1);
loop:
        usb_ep0_reinit(udev);
        release_devnum(udev);
        hub_free_dev(udev);
        usb_put_dev(udev);
        if ((status == -ENOTCONN) || (status == -ENOTSUPP))
            break;
    }
    if (hub->hdev->parent ||
            !hcd->driver->port_handed_over ||
            !(hcd->driver->port_handed_over)(hcd, port1)) {
        if (status != -ENOTCONN && status != -ENODEV)
            dev_err(hub_dev, "unable to enumerate USB device on port %d\n",
                    port1);
    }

done:
    hub_port_disable(hub, port1, 1);
    if (hcd->driver->relinquish_port && !hub->hdev->parent)
        hcd->driver->relinquish_port(hcd, port1);
}

  • hub_port_connect_change分爲兩部分: 

第一部分主要是:確認是否有新設備插入; 
第二部分主要是:確認port口上有新設備插入後,分配設備資源,並進行枚舉操作;

對於一些已經連接的設備,進行一些類似防抖動處理,處理機制:每隔25ms去讀取port的status和change狀態,查看port連接狀態是否穩定,如果port處於穩定狀態大於100ms,則認爲當前port上的設備已經穩定,這種處理機制最長時間爲1.5s;如果port處於穩定狀態的時間小於100ms,則認爲連接是不穩定的,則跳出當前port,去執行下一個port;

對於一個新插入的設備,並已經確定它的存在,則接下來會爲這個usb設備進行枚舉

一條usb總線下總共可以有128個設備,usb總線通過位圖的形式來管理usb設備的地址,choose_devnum是從usb 總線中找到一個usb地址,usb地址在1和128之間; 
通過hub_port_init對usb設備進行reset,並設置usb設備的地址,獲取usb設備的設備描述符,這個函數相對比較重要,所以對它進行了進一步分析:


/* Reset device, (re)assign address, get device descriptor.
 * Device connection must be stable, no more debouncing needed.
 * Returns device in USB_STATE_ADDRESS, except on error.
 *
 * If this is called for an already-existing device (as part of
 * usb_reset_and_verify_device), the caller must own the device lock.  For a
 * newly detected device that is not accessible through any global
 * pointers, it's not necessary to lock the device.
 */
static int
hub_port_init (struct usb_hub *hub, struct usb_device *udev, int port1,
        int retry_counter)
{
    static DEFINE_MUTEX(usb_address0_mutex);

    struct usb_device   *hdev = hub->hdev;
    struct usb_hcd      *hcd = bus_to_hcd(hdev->bus);
    int         i, j, retval;
    unsigned        delay = HUB_SHORT_RESET_TIME;
    enum usb_device_speed   oldspeed = udev->speed;
    const char      *speed;
    int         devnum = udev->devnum;

    /* root hub ports have a slightly longer reset period
     * (from USB 2.0 spec, section 7.1.7.5)
     * 設置用於讀取port狀態的時間間隔,root hub需要50ms,普通的hub需要10ms,
     * 對於低速的usb設備需要200ms
     */
    if (!hdev->parent) {
        delay = HUB_ROOT_RESET_TIME;
        if (port1 == hdev->bus->otg_port)
            hdev->bus->b_hnp_enable = 0;
    }

    /* Some low speed devices have problems with the quick delay, so */
    /*  be a bit pessimistic with those devices. RHbug #23670 */
    if (oldspeed == USB_SPEED_LOW)
        delay = HUB_LONG_RESET_TIME;

    mutex_lock(&usb_address0_mutex);

    /* Reset the device; full speed may morph to high speed */
    /* FIXME a USB 2.0 device may morph into SuperSpeed on reset. */ 
    //通過hub_port_reset來reset設備
    retval = hub_port_reset(hub, port1, udev, delay, false);
    if (retval < 0)     /* error or disconnect */
        goto fail;
    /* success, speed is known */

    retval = -ENODEV;

    if (oldspeed != USB_SPEED_UNKNOWN && oldspeed != udev->speed) {
        dev_dbg(&udev->dev, "device reset changed speed!\n");
        goto fail;
    }
    oldspeed = udev->speed;

    /* USB 2.0 section 5.5.3 talks about ep0 maxpacket ...
     * it's fixed size except for full speed devices.
     * For Wireless USB devices, ep0 max packet is always 512 (tho
     * reported as 0xff in the device descriptor). WUSB1.0[4.8.1].
     * 根據usb設備的速度來初始化endpont0的最大發送數據長度,
     * 對於無線usb其maxpacketsize爲512字節,對於高速和全速爲64字節,而低速爲8個字節
     */
    switch (udev->speed) {
    case USB_SPEED_SUPER:
    case USB_SPEED_WIRELESS:    /* fixed at 512 */
        udev->ep0.desc.wMaxPacketSize = cpu_to_le16(512);
        break;
    case USB_SPEED_HIGH:        /* fixed at 64 */
        udev->ep0.desc.wMaxPacketSize = cpu_to_le16(64);
        break;
    case USB_SPEED_FULL:        /* 8, 16, 32, or 64 */
        /* to determine the ep0 maxpacket size, try to read
         * the device descriptor to get bMaxPacketSize0 and
         * then correct our initial guess.
         */
        udev->ep0.desc.wMaxPacketSize = cpu_to_le16(64);
        break;
    case USB_SPEED_LOW:     /* fixed at 8 */
        udev->ep0.desc.wMaxPacketSize = cpu_to_le16(8);
        break;
    default:
        goto fail;
    }

    if (udev->speed == USB_SPEED_WIRELESS)
        speed = "variable speed Wireless";
    else
        speed = usb_speed_string(udev->speed);

    if (udev->speed != USB_SPEED_SUPER)
        dev_info(&udev->dev,
                "%s %s USB device number %d using %s\n",
                (udev->config) ? "reset" : "new", speed,
                devnum, udev->bus->controller->driver->name);

    /* Set up TT records, if needed  */ 
    //如果hub爲高速,而usb設備爲低速或全速,則在它們之間需要有一個速度轉換設備tt
    if (hdev->tt) {
        udev->tt = hdev->tt;
        udev->ttport = hdev->ttport;
    } else if (udev->speed != USB_SPEED_HIGH
            && hdev->speed == USB_SPEED_HIGH) {
        if (!hub->tt.hub) {
            dev_err(&udev->dev, "parent hub has no TT\n");
            retval = -EINVAL;
            goto fail;
        }
        udev->tt = &hub->tt;
        udev->ttport = port1;
    }

    /* Why interleave GET_DESCRIPTOR and SET_ADDRESS this way?
     * Because device hardware and firmware is sometimes buggy in
     * this area, and this is how Linux has done it for ages.
     * Change it cautiously.
     *
     * NOTE:  If USE_NEW_SCHEME() is true we will start by issuing
     * a 64-byte GET_DESCRIPTOR request.  This is what Windows does,
     * so it may help with some non-standards-compliant devices.
     * Otherwise we start with SET_ADDRESS and then try to read the
     * first 8 bytes of the device descriptor to get the ep0 maxpacket
     * value.
     */
    for (i = 0; i < GET_DESCRIPTOR_TRIES; (++i, msleep(100))) {
        if (USE_NEW_SCHEME(retry_counter) &&
            !(hcd->driver->flags & HCD_USB3) &&
            !((hcd->driver->flags & HCD_RT_OLD_ENUM) &&
                !hdev->parent)) {
            struct usb_device_descriptor *buf;
            ushort idvendor;
            int r = 0;

#define GET_DESCRIPTOR_BUFSIZE  64
            buf = kmalloc(GET_DESCRIPTOR_BUFSIZE, GFP_NOIO);
            if (!buf) {
                retval = -ENOMEM;
                continue;
            }

            /* Retry on all errors; some devices are flakey.
             * 255 is for WUSB devices, we actually need to use
             * 512 (WUSB1.0[4.8.1]).
             */
            for (j = 0; j < 3; ++j) {
                buf->bMaxPacketSize0 = 0;
                r = usb_control_msg(udev, usb_rcvaddr0pipe(),
                    USB_REQ_GET_DESCRIPTOR, USB_DIR_IN,
                    USB_DT_DEVICE << 8, 0,
                    buf, GET_DESCRIPTOR_BUFSIZE,
                    initial_descriptor_timeout);
                switch (buf->bMaxPacketSize0) {
                case 8: case 16: case 32: case 64: case 255:
                    if (buf->bDescriptorType ==
                            USB_DT_DEVICE) {
                        r = 0;
                        break;
                    }
                    /* FALL THROUGH */
                default:
                    if (r == 0)
                        r = -EPROTO;
                    break;
                }
                if (r == 0)
                    break;
            }
            udev->descriptor.bMaxPacketSize0 =
                    buf->bMaxPacketSize0;
            idvendor = le16_to_cpu(buf->idVendor);
            kfree(buf);

            /*
             * If it is a HSET Test device, we don't issue a
             * second reset which results in failure due to
             * speed change.
             */
            if (idvendor != 0x1a0a) {
                retval = hub_port_reset(hub, port1, udev,
                             delay, false);
                if (retval < 0) /* error or disconnect */
                    goto fail;
                if (oldspeed != udev->speed) {
                    dev_dbg(&udev->dev,
                           "device reset changed speed!\n");
                    retval = -ENODEV;
                    goto fail;
                }
            }
            if (r) {
                if (r != -ENODEV)
                    dev_err(&udev->dev, "device descriptor read/64, error %d\n",
                            r);
                retval = -EMSGSIZE;
                continue;
            }
#undef GET_DESCRIPTOR_BUFSIZE
        }

        /*
         * If device is WUSB, we already assigned an
         * unauthorized address in the Connect Ack sequence;
         * authorization will assign the final address.
         */
        if (udev->wusb == 0) {
            for (j = 0; j < SET_ADDRESS_TRIES; ++j) {
                retval = hub_set_address(udev, devnum);
                if (retval >= 0)
                    break;
                msleep(200);
            }
            if (retval < 0) {
                if (retval != -ENODEV)
                    dev_err(&udev->dev, "device not accepting address %d, error %d\n",
                            devnum, retval);
                goto fail;
            }
            if (udev->speed == USB_SPEED_SUPER) {
                devnum = udev->devnum;
                dev_info(&udev->dev,
                        "%s SuperSpeed USB device number %d using %s\n",
                        (udev->config) ? "reset" : "new",
                        devnum, udev->bus->controller->driver->name);
            }

            /* cope with hardware quirkiness:
             *  - let SET_ADDRESS settle, some device hardware wants it
             *  - read ep0 maxpacket even for high and low speed,
             */
            msleep(10);
            if (USE_NEW_SCHEME(retry_counter) &&
                !(hcd->driver->flags & HCD_USB3) &&
                !((hcd->driver->flags & HCD_RT_OLD_ENUM) &&
                    !hdev->parent))
                break;
        }

        retval = usb_get_device_descriptor(udev, 8);
        if (retval < 8) {
            if (retval != -ENODEV)
                dev_err(&udev->dev,
                    "device descriptor read/8, error %d\n",
                    retval);
            if (retval >= 0)
                retval = -EMSGSIZE;
        } else {
            retval = 0;
            break;
        }
    }
    if (retval)
        goto fail;

    if (hcd->phy && !hdev->parent)
        usb_phy_notify_connect(hcd->phy, udev->speed);

    /*
     * Some superspeed devices have finished the link training process
     * and attached to a superspeed hub port, but the device descriptor
     * got from those devices show they aren't superspeed devices. Warm
     * reset the port attached by the devices can fix them.
     */
    if ((udev->speed == USB_SPEED_SUPER) &&
            (le16_to_cpu(udev->descriptor.bcdUSB) < 0x0300)) {
        dev_err(&udev->dev, "got a wrong device descriptor, "
                "warm reset device\n");
        hub_port_reset(hub, port1, udev,
                HUB_BH_RESET_TIME, true);
        retval = -EINVAL;
        goto fail;
    }

    if (udev->descriptor.bMaxPacketSize0 == 0xff ||
            udev->speed == USB_SPEED_SUPER)
        i = 512;
    else
        i = udev->descriptor.bMaxPacketSize0;
    if (usb_endpoint_maxp(&udev->ep0.desc) != i) {
        if (udev->speed == USB_SPEED_LOW ||
                !(i == 8 || i == 16 || i == 32 || i == 64)) {
            dev_err(&udev->dev, "Invalid ep0 maxpacket: %d\n", i);
            retval = -EMSGSIZE;
            goto fail;
        }
        if (udev->speed == USB_SPEED_FULL)
            dev_dbg(&udev->dev, "ep0 maxpacket = %d\n", i);
        else
            dev_warn(&udev->dev, "Using ep0 maxpacket: %d\n", i);
        udev->ep0.desc.wMaxPacketSize = cpu_to_le16(i);
        usb_ep0_reinit(udev);
    }

      //根據maxpacketsize去獲取完整的設備描述符
    retval = usb_get_device_descriptor(udev, USB_DT_DEVICE_SIZE);
    if (retval < (signed)sizeof(udev->descriptor)) {
        if (retval != -ENODEV)
            dev_err(&udev->dev, "device descriptor read/all, error %d\n",
                    retval);
        if (retval >= 0)
            retval = -ENOMSG;
        goto fail;
    }

    if (udev->wusb == 0 && le16_to_cpu(udev->descriptor.bcdUSB) >= 0x0201) {
        retval = usb_get_bos_descriptor(udev);
        if (!retval) {
            udev->lpm_capable = usb_device_supports_lpm(udev);
            usb_set_lpm_parameters(udev);
        }
    }

    retval = 0;
    /* notify HCD that we have a device connected and addressed */
    if (hcd->driver->update_device)
        hcd->driver->update_device(hcd, udev);
fail:
    if (retval) {
        hub_port_disable(hub, port1, 0);
        update_devnum(udev, devnum);    /* for disconnect processing */
    }
    mutex_unlock(&usb_address0_mutex);
    return retval;
}

hub_port_init首先對port進行reset,復位成功後設備usb設備的速度和端口0最大發送數據長度,設置usb設備的地址,並獲取usb設備的設備描述符; 
通過hub_port_reset來reset設備,reset機制爲:通過set_port_feature來傳輸USB_PORT_FEAT_RESET指令,設置成功後循環延時由之前確定的時間間隔後去讀取port的status和change狀態,要確保usb設備在reset後還能正常存在,如reset還能正常,則通過port的status狀態位來確定usb設備的速度,循環延時總時間爲500ms,而usb設備reset次數爲5次, 
根據usb設備的速度來初始化endpont0的最大發送數據長度,對於無線usb其maxpacketsize爲512字節,對於高速和全速爲64字節,而低速爲8個字節; 
如果hub爲高速,而usb設備爲低速或全速,則在它們之間需要有一個速度轉換設備tt;

通過獲取usb設備的設備描述符來得到usb設備的endpoint0的最大發送數據長度,設備描述符實際 上有18個字節,而endpoint0的maxpacketsize在設備描述符裏的第8個字節位,所以只要獲取設備描述符的前8個字節數據就可以了,但有些USB設備它並不支持只獲取8個字節這樣不完整的usb請求,爲解決這種問題usb驅動裏採用了兩種策略去獲取maxpacketsize,一種是windows作法,它直接向usb設備請求64字節數據,對於一些maxpacketsize爲32或64的,它可以直接一次性把18個字節的設備描述符傳輸回來,而對於像low speed的設備它的maxpacketsize只有8個字節,就需要進行多次發送; 而Linux作法是先設置usb設備的地址,然後再發送8個字節數據請求,從返回的8個字節裏獲取endpoint0的maxpacketsize;


/**
 * usb_new_device - perform initial device setup (usbcore-internal)
 * @udev: newly addressed device (in ADDRESS state)
 *
 * This is called with devices which have been detected but not fully
 * enumerated.  The device descriptor is available, but not descriptors
 * for any device configuration.  The caller must have locked either
 * the parent hub (if udev is a normal device) or else the
 * usb_bus_list_lock (if udev is a root hub).  The parent's pointer to
 * udev has already been installed, but udev is not yet visible through
 * sysfs or other filesystem code.
 *
 * It will return if the device is configured properly or not.  Zero if
 * the interface was registered with the driver core; else a negative
 * errno value.
 *
 * This call is synchronous, and may not be used in an interrupt context.
 *
 * Only the hub driver or root-hub registrar should ever call this.
 */
int usb_new_device(struct usb_device *udev)
{
    int err;

    if (udev->parent) {
        /* Initialize non-root-hub device wakeup to disabled;
         * device (un)configuration controls wakeup capable
         * sysfs power/wakeup controls wakeup enabled/disabled
         */
        device_init_wakeup(&udev->dev, 0);
    }

    /* Tell the runtime-PM framework the device is active */
    pm_runtime_set_active(&udev->dev);
    pm_runtime_get_noresume(&udev->dev);
    pm_runtime_use_autosuspend(&udev->dev);
    pm_runtime_enable(&udev->dev);

    /* By default, forbid autosuspend for all devices.  It will be
     * allowed for hubs during binding.
     */
    usb_disable_autosuspend(udev);

    err = usb_enumerate_device(udev);   /* Read descriptors */
    if (err < 0)
        goto fail;
    dev_dbg(&udev->dev, "udev %d, busnum %d, minor = %d\n",
            udev->devnum, udev->bus->busnum,
            (((udev->bus->busnum-1) * 128) + (udev->devnum-1)));
    /* export the usbdev device-node for libusb */
    udev->dev.devt = MKDEV(USB_DEVICE_MAJOR,
            (((udev->bus->busnum-1) * 128) + (udev->devnum-1)));

    /* Tell the world! */
    announce_device(udev);

    if (udev->serial)
        add_device_randomness(udev->serial, strlen(udev->serial));
    if (udev->product)
        add_device_randomness(udev->product, strlen(udev->product));
    if (udev->manufacturer)
        add_device_randomness(udev->manufacturer,
                      strlen(udev->manufacturer));

    device_enable_async_suspend(&udev->dev);

    /*
     * check whether the hub marks this port as non-removable. Do it
     * now so that platform-specific data can override it in
     * device_add()
     */
    if (udev->parent)
        set_usb_port_removable(udev);

    /* Register the device.  The device driver is responsible
     * for configuring the device and invoking the add-device
     * notifier chain (used by usbfs and possibly others).
     */
    err = device_add(&udev->dev);
    if (err) {
        dev_err(&udev->dev, "can't device_add, error %d\n", err);
        goto fail;
    }

    /* Create link files between child device and usb port device. */
    if (udev->parent) {
        struct usb_hub *hub = usb_hub_to_struct_hub(udev->parent);
        struct usb_port *port_dev;

        if (!hub)
            goto fail;

        port_dev = hub->ports[udev->portnum - 1];
        if (!port_dev)
            goto fail;

        err = sysfs_create_link(&udev->dev.kobj,
                &port_dev->dev.kobj, "port");
        if (err)
            goto fail;

        err = sysfs_create_link(&port_dev->dev.kobj,
                &udev->dev.kobj, "device");
        if (err) {
            sysfs_remove_link(&udev->dev.kobj, "port");
            goto fail;
        }

        pm_runtime_get_sync(&port_dev->dev);
    }

    (void) usb_create_ep_devs(&udev->dev, &udev->ep0, udev);
    usb_mark_last_busy(udev);
    pm_runtime_put_sync_autosuspend(&udev->dev);
    return err;

fail:
    usb_set_device_state(udev, USB_STATE_NOTATTACHED);
    pm_runtime_disable(&udev->dev);
    pm_runtime_set_suspended(&udev->dev);
    return err;
}


  • usb_new_device主是要獲取usb設備的配置信息,然後將usb設備添加到系統中,這裏usb_enumerate_device相對比較重要,就是它完成了配置信息獲取及分配;


/**
 * usb_enumerate_device - Read device configs/intfs/otg (usbcore-internal)
 * @udev: newly addressed device (in ADDRESS state)
 *
 * This is only called by usb_new_device() and usb_authorize_device()
 * and FIXME -- all comments that apply to them apply here wrt to
 * environment.
 *
 * If the device is WUSB and not authorized, we don't attempt to read
 * the string descriptors, as they will be errored out by the device
 * until it has been authorized.
 */
static int usb_enumerate_device(struct usb_device *udev)
{
    int err;

    if (udev->config == NULL) {
        err = usb_get_configuration(udev);
        if (err < 0) {
            if (err != -ENODEV)
                dev_err(&udev->dev, "can't read configurations, error %d\n",
                        err);
            return err;
        }
    }

    /* read the standard strings and cache them if present */
    udev->product = usb_cache_string(udev, udev->descriptor.iProduct);
    udev->manufacturer = usb_cache_string(udev,
                          udev->descriptor.iManufacturer);
    udev->serial = usb_cache_string(udev, udev->descriptor.iSerialNumber);

    err = usb_enumerate_device_otg(udev);
    if (err < 0)
        return err;

    usb_detect_interface_quirks(udev);

    return 0;
}

通過usb_get_configuration獲取和分配usb設備的配置,接口,端口信息

/*
 * Get the USB config descriptors, cache and parse'em
 *
 * hub-only!! ... and only in reset path, or usb_new_device()
 * (used by real hubs and virtual root hubs)
 */
int usb_get_configuration(struct usb_device *dev)
{
    struct device *ddev = &dev->dev;
    int ncfg = dev->descriptor.bNumConfigurations;
    int result = 0;
    unsigned int cfgno, length;
    unsigned char *bigbuffer;
    struct usb_config_descriptor *desc;

    cfgno = 0;
    result = -ENOMEM; 
    //如果usb設備的配置個數大於最大允許配置數8,則發出警告,並把設備的配置個數改成最大允許配置數8
    if (ncfg > USB_MAXCONFIG) {
        dev_warn(ddev, "too many configurations: %d, "
            "using maximum allowed: %d\n", ncfg, USB_MAXCONFIG);
        dev->descriptor.bNumConfigurations = ncfg = USB_MAXCONFIG;
    }

    //如果usb設備沒有配置,則不允許,直接返回錯誤信息
    if (ncfg < 1) {
        dev_err(ddev, "no configurations\n");
        return -EINVAL;
    }

    //根據配置數,申請usb配置結構usb_host_config,併爲用於存放配置結構的指針數據申請空間
    length = ncfg * sizeof(struct usb_host_config);
    dev->config = kzalloc(length, GFP_KERNEL);
    if (!dev->config)
        goto err2;

    length = ncfg * sizeof(char *);
    dev->rawdescriptors = kzalloc(length, GFP_KERNEL);
    if (!dev->rawdescriptors)
        goto err2;

    //申請用於暫時存放配置描述符的結構空間
    desc = kmalloc(USB_DT_CONFIG_SIZE, GFP_KERNEL);
    if (!desc)
        goto err2;

    result = 0;
    //依次獲取usb設備的配置信息
    for (; cfgno < ncfg; cfgno++) {
        /* We grab just the first descriptor so we know how long
         * the whole configuration is */
        result = usb_get_descriptor(dev, USB_DT_CONFIG, cfgno,
            desc, USB_DT_CONFIG_SIZE);
        if (result < 0) {
            dev_err(ddev, "unable to read config index %d "
                "descriptor/%s: %d\n", cfgno, "start", result);
            if (result != -EPIPE)
                goto err;
            dev_err(ddev, "chopping to %d config(s)\n", cfgno);
            dev->descriptor.bNumConfigurations = cfgno;
            break;
        } else if (result < 4) {
            dev_err(ddev, "config index %d descriptor too short "
                "(expected %i, got %i)\n", cfgno,
                USB_DT_CONFIG_SIZE, result);
            result = -EINVAL;
            goto err;
        }
        length = max((int) le16_to_cpu(desc->wTotalLength),
            USB_DT_CONFIG_SIZE);//得到wTotallLength

        /* Now that we know the length, get the whole thing */ 
        //根據得到的wTotallLength,申請用於存放這些信息的內存空間
        bigbuffer = kmalloc(length, GFP_KERNEL);
        if (!bigbuffer) {
            result = -ENOMEM;
            goto err;
        }

        if (dev->quirks & USB_QUIRK_DELAY_INIT)
            msleep(100);

        //根據得到的wTotallLength,去獲取完整的配置,接口,端口等描述符信息,把這些信息存放到bigbuffer裏
        result = usb_get_descriptor(dev, USB_DT_CONFIG, cfgno,
            bigbuffer, length);
        if (result < 0) {
            dev_err(ddev, "unable to read config index %d "
                "descriptor/%s\n", cfgno, "all");
            kfree(bigbuffer);
            goto err;
        }
        if (result < length) {
            dev_warn(ddev, "config index %d descriptor too short "
                "(expected %i, got %i)\n", cfgno, length, result);
            length = result;
        }

    //把用於存放配置等信息的地址保存在之前申請的rawdescriptors裏
        dev->rawdescriptors[cfgno] = bigbuffer;

    //通過usb_parse_configuration函數,把獲得的配置等信息按照類型進行分類 
        result = usb_parse_configuration(dev, cfgno,
            &dev->config[cfgno], bigbuffer, length);
        if (result < 0) {
            ++cfgno;
            goto err;
        }
    }
    result = 0;

err:
    kfree(desc);
    dev->descriptor.bNumConfigurations = cfgno;
err2:
    if (result == -ENOMEM)
        dev_err(ddev, "out of memory\n");
    return result;
}

這個函數主要分成二部分,第一部分是向usb device側獲取設備的配置,接口,端口信息。首先,它爲這些信息申請存放空間,然後像之前獲取設備描述符一樣,先發送一個9 個字節的請求,用來獲取配置,接口,端口等描述符的總長度,最後根據得到的總長度去得到完成 的設備配置,接口,端口信息;第二部分是把獲取到的這個信息按照類別分別進行分類,並存到相應的結構中;

通過usb_parse_configuration函數,把獲得的配置等信息按照類型進行分類


static int usb_parse_configuration(struct usb_device *dev, int cfgidx,
    struct usb_host_config *config, unsigned char *buffer, int size)
{
    struct device *ddev = &dev->dev;
    unsigned char *buffer0 = buffer;
    int cfgno;
    int nintf, nintf_orig;
    int i, j, n;
    struct usb_interface_cache *intfc;
    unsigned char *buffer2;
    int size2;
    struct usb_descriptor_header *header;
    int len, retval;
    u8 inums[USB_MAXINTERFACES], nalts[USB_MAXINTERFACES];
    unsigned iad_num = 0;

    memcpy(&config->desc, buffer, USB_DT_CONFIG_SIZE);
    if (config->desc.bDescriptorType != USB_DT_CONFIG ||
        config->desc.bLength < USB_DT_CONFIG_SIZE ||
        config->desc.bLength > size) {
        dev_err(ddev, "invalid descriptor for config index %d: "
            "type = 0x%X, length = %d\n", cfgidx,
            config->desc.bDescriptorType, config->desc.bLength);
        return -EINVAL;
    }
    cfgno = config->desc.bConfigurationValue;

    buffer += config->desc.bLength;
    size -= config->desc.bLength;

    nintf = nintf_orig = config->desc.bNumInterfaces;
    if (nintf > USB_MAXINTERFACES) {
        dev_warn(ddev, "config %d has too many interfaces: %d, "
            "using maximum allowed: %d\n",
            cfgno, nintf, USB_MAXINTERFACES);
        nintf = USB_MAXINTERFACES;
    }

    /* Go through the descriptors, checking their length and counting the
     * number of altsettings for each interface */
    n = 0;
    for ((buffer2 = buffer, size2 = size);
          size2 > 0;
         (buffer2 += header->bLength, size2 -= header->bLength)) {

        if (size2 < sizeof(struct usb_descriptor_header)) {
            dev_warn(ddev, "config %d descriptor has %d excess "
                "byte%s, ignoring\n",
                cfgno, size2, plural(size2));
            break;
        }

        header = (struct usb_descriptor_header *) buffer2;
        if ((header->bLength > size2) || (header->bLength < 2)) {
            dev_warn(ddev, "config %d has an invalid descriptor "
                "of length %d, skipping remainder of the config\n",
                cfgno, header->bLength);
            break;
        }

        if (header->bDescriptorType == USB_DT_INTERFACE) {
            struct usb_interface_descriptor *d;
            int inum;

            d = (struct usb_interface_descriptor *) header;
            if (d->bLength < USB_DT_INTERFACE_SIZE) {
                dev_warn(ddev, "config %d has an invalid "
                    "interface descriptor of length %d, "
                    "skipping\n", cfgno, d->bLength);
                continue;
            }

            inum = d->bInterfaceNumber;

            if ((dev->quirks & USB_QUIRK_HONOR_BNUMINTERFACES) &&
                n >= nintf_orig) {
                dev_warn(ddev, "config %d has more interface "
                    "descriptors, than it declares in "
                    "bNumInterfaces, ignoring interface "
                    "number: %d\n", cfgno, inum);
                continue;
            }

            if (inum >= nintf_orig)
                dev_warn(ddev, "config %d has an invalid "
                    "interface number: %d but max is %d\n",
                    cfgno, inum, nintf_orig - 1);

            /* Have we already encountered this interface?
             * Count its altsettings */
            for (i = 0; i < n; ++i) {
                if (inums[i] == inum)
                    break;
            }
            if (i < n) {
                if (nalts[i] < 255)
                    ++nalts[i];
            } else if (n < USB_MAXINTERFACES) {
                inums[n] = inum;
                nalts[n] = 1;
                ++n;
            }

        } else if (header->bDescriptorType ==
                USB_DT_INTERFACE_ASSOCIATION) {
            if (iad_num == USB_MAXIADS) {
                dev_warn(ddev, "found more Interface "
                           "Association Descriptors "
                           "than allocated for in "
                           "configuration %d\n", cfgno);
            } else {
                config->intf_assoc[iad_num] =
                    (struct usb_interface_assoc_descriptor
                    *)header;
                iad_num++;
            }

        } else if (header->bDescriptorType == USB_DT_DEVICE ||
                header->bDescriptorType == USB_DT_CONFIG)
            dev_warn(ddev, "config %d contains an unexpected "
                "descriptor of type 0x%X, skipping\n",
                cfgno, header->bDescriptorType);

    }   /* for ((buffer2 = buffer, size2 = size); ...) */
    size = buffer2 - buffer;
    config->desc.wTotalLength = cpu_to_le16(buffer2 - buffer0);

    if (n != nintf)
        dev_warn(ddev, "config %d has %d interface%s, different from "
            "the descriptor's value: %d\n",
            cfgno, n, plural(n), nintf_orig);
    else if (n == 0)
        dev_warn(ddev, "config %d has no interfaces?\n", cfgno);
    config->desc.bNumInterfaces = nintf = n;

    /* Check for missing interface numbers */
    for (i = 0; i < nintf; ++i) {
        for (j = 0; j < nintf; ++j) {
            if (inums[j] == i)
                break;
        }
        if (j >= nintf)
            dev_warn(ddev, "config %d has no interface number "
                "%d\n", cfgno, i);
    }

    /* Allocate the usb_interface_caches and altsetting arrays */
    for (i = 0; i < nintf; ++i) {
        j = nalts[i];
        if (j > USB_MAXALTSETTING) {
            dev_warn(ddev, "too many alternate settings for "
                "config %d interface %d: %d, "
                "using maximum allowed: %d\n",
                cfgno, inums[i], j, USB_MAXALTSETTING);
            nalts[i] = j = USB_MAXALTSETTING;
        }

        len = sizeof(*intfc) + sizeof(struct usb_host_interface) * j;
        config->intf_cache[i] = intfc = kzalloc(len, GFP_KERNEL);
        if (!intfc)
            return -ENOMEM;
        kref_init(&intfc->ref);
    }

    /* FIXME: parse the BOS descriptor */

    /* Skip over any Class Specific or Vendor Specific descriptors;
     * find the first interface descriptor */
    config->extra = buffer;
    i = find_next_descriptor(buffer, size, USB_DT_INTERFACE,
        USB_DT_INTERFACE, &n);
    config->extralen = i;
    if (n > 0)
        dev_dbg(ddev, "skipped %d descriptor%s after %s\n",
            n, plural(n), "configuration");
    buffer += i;
    size -= i;

    /* Parse all the interface/altsetting descriptors */
    while (size > 0) {
        retval = usb_parse_interface(ddev, cfgno, config,
            buffer, size, inums, nalts);
        if (retval < 0)
            return retval;

        buffer += retval;
        size -= retval;
    }

    /* Check for missing altsettings */
    for (i = 0; i < nintf; ++i) {
        intfc = config->intf_cache[i];
        for (j = 0; j < intfc->num_altsetting; ++j) {
            for (n = 0; n < intfc->num_altsetting; ++n) {
                if (intfc->altsetting[n].desc.
                    bAlternateSetting == j)
                    break;
            }
            if (n >= intfc->num_altsetting)
                dev_warn(ddev, "config %d interface %d has no "
                    "altsetting %d\n", cfgno, inums[i], j);
        }
    }

    return 0;
}

這裏寫圖片描述

發佈了16 篇原創文章 · 獲贊 21 · 訪問量 6萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章