1. 程式人生 > >Linux裝置驅動之USB hub驅動(續)

Linux裝置驅動之USB hub驅動(續)

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:
        usb_unlock_device(hdev);
        kref_put(&hub->kref, hub_release);

        } /* end while (1) */
}
處理完hub上的port之後,就要來處理hub本身的狀態改變了,結合程式碼中的註釋應該很容易看懂,在這裡主要是清除hub的對應Feature.
之後,將  hub->activating設為了0,如果hub是root hub,需要重新開啟root hub的中斷.
這個函式到這裡就完成了.不過,其中的幾個子函式,涉及到的操作很重要,現分析如下:
1: hub_pre_reset()函式.
該函式在裝置斷開連線的時候,將其下掛載的所有子裝置全部登出掉,程式碼如下所示:
static int hub_pre_reset(struct usb_interface *intf)
{
    struct usb_hub *hub = usb_get_intfdata(intf);
    struct usb_device *hdev = hub->hdev;
    int i;

    /* Disconnect all the children */
    for (i = 0; i maxchild; ++i) {
        if (hdev->children)
            usb_disconnect(&hdev->children);
    }
    hub_quiesce(hub);
    return 0;
}
它將裝置上所掛載的所有裝置全部都呼叫usb_disconnect()來斷開聯接.之後,再對hub呼叫hub_quiesce().
hub_quiesce()是和hub_activate()相對應的一個函式, hub_activate()在前面已經分析過了,現在來對hub_quiesce()進行分析.
程式碼如下:
static void hub_quiesce(struct usb_hub *hub)
{
    /* (nonblocking) khubd and related activity won't re-trigger */
    hub->quiescing = 1;
    hub->activating = 0;

    /* (blocking) stop khubd and related activity */
    usb_kill_urb(hub->urb);
    if (hub->has_indicators)
        cancel_delayed_work_sync(&hub->leds);
    if (hub->tt.hub)
        cancel_work_sync(&hub->tt.kevent);
}
首先,它調hub->quiescing置為1,而activating置為0.這和hub_activate()剛好是相反的動作.之後,取消hub的中斷傳輸出URB.取得TT和LED的工作佇列.
我們在後面分析的HUB中斷URB傳輸,可以知道,如果將這個URB禁用,那麼,就不會將hub->event_list新增到hub_event_list.因此,也不會進入到hub_events()函式.

usb_disconnect()用來斷開某個裝置,程式碼如下:
void usb_disconnect(struct usb_device **pdev)
{
    struct usb_device   *udev = *pdev;
    int         i;

    if (!udev) {
        pr_debug ("%s nodev\n", __FUNCTION__);
        return;
    }

    /* mark the device as inactive, so any further urb submissions for
     * this device (and any of its children) will fail immediately.
     * this quiesces everyting except pending urbs.
     */
    usb_set_device_state(udev, USB_STATE_NOTATTACHED);
    dev_info (&udev->dev, "USB disconnect, address %d\n", udev->devnum);

    usb_lock_device(udev);

    /* Free up all the children before we remove this device */
    for (i = 0; i
        if (udev->children)
            usb_disconnect(&udev->children);
    }

    /* deallocate hcd/hardware state ... nuking all pending urbs and
     * cleaning up all state associated with the current configuration
     * so that the hardware is now fully quiesced.
     */
    dev_dbg (&udev->dev, "unregistering device\n");
    usb_disable_device(udev, 0);

    usb_unlock_device(udev);

    /* Unregister the device.  The device driver is responsible
     * for removing the device files from usbfs and sysfs and for
     * de-configuring the device.
     */
    device_del(&udev->dev);

    /* Free the device number and delete the parent's children[]
     * (or root_hub) pointer.
     */
    release_address(udev);

    /* Avoid races with recursively_mark_NOTATTACHED() */
    spin_lock_irq(&device_state_lock);
    *pdev = NULL;
    spin_unlock_irq(&device_state_lock);

    usb_stop_pm(udev);

    put_device(&udev->dev);
}
很容易看出.這個函式採用深度遍歷演算法,它依次遍歷udev->children[]下的子裝置,然後依然呼叫usb_disconnect().
這個函式中的另外幾個子函式有的在前面已經分析過,有的是裝置模型中的基礎函式.很有是跟PM相關的,在這裡就不做詳細分析,來看一下release_address()函式,顧名思意,它用來釋放裝置的地址,如下示:
static void release_address(struct usb_device *udev)
{
    if (udev->devnum > 0) {
        clear_bit(udev->devnum, udev->bus->devmap.devicemap);
        udev->devnum = -1;
    }
}
我們在分析UHCI中,有關root hub的初始化時說明,設各號都是儲存在bus->devmap陣列中的.在這裡,只需要將該裝置號在陣列中的某位清了即可.
hub_pre_reset()函式就分析到這裡了.
注意到這裡呼叫的put_device(&udev->dev)沒.根據Linux裝置模型的分析,這時它會呼叫跟它繫結的driver的remove()介面,對應的,這個函式會將操作回溯到usb_driver-> disconnect().可以自行查閱這個過程.
或許,有人的疑問又來了?要是這個usb_dev沒有跟usb_driver繫結怎麼辦呢?
不要忘記我們之前的分析了,對於usb_generic_driver這個驅動是會適用所有的usb_dev的.^_^,也是說,無論如何,usb_dev都會繫結到usb_generic_driver.

2: hub_port_connect_change()函式
這個函式是一個很核心的操作,它的程式碼如下:
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);
    u16 wHubCharacteristics = le16_to_cpu(hub->descriptor->wHubCharacteristics);
    int status, i;

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

    //HUB LED
    if (hub->has_indicators) {
        set_port_led(hub, port1, HUB_LED_AUTO);
        hub->indicator[port1-1] = INDICATOR_AUTO;
    }

    /* Disconnect any existing devices under this port */
    //如果對應埠已經有裝置連線,先將其斷開
    if (hdev->children[port1-1])
        usb_disconnect(&hdev->children[port1-1]);
    //將hub_change_bits中的對應位清零,以免下次進來的時候,還會檢測到
    //hub_port_logical_disconnect()對該值的設定
    clear_bit(port1, hub->change_bits);

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

    //連線發生改變
    //連線反彈的處理,實際上就是除抖動
    if (portchange & USB_PORT_STAT_C_CONNECTION) {
        status = hub_port_debounce(hub, port1);
        if (status
            if (printk_ratelimit())
                dev_err (hub_dev, "connect-debounce failed, "
                        "port %d disabled\n", port1);
            goto done;
        }
        portstatus = status;
    }
在這裡,我們忽略掉HUB LED燈的操作,然後,將HUB對應埠下面掛載的裝置斷開.經過前面的分析,進入到這個函式的可能有多種情況(在hub_events()中分析的五種情況).可以分為三大類:
一類是之前有連線之後沒聯接的,在這裡,將hub 對應埠下的裝置全部斷開是無可非議的.
第二類是之前沒有,之後有連線的,在這裡,if(hdev->children[port-1])的判斷是不會滿足的.
第三類是需要重置的埠,在這裡先將裝置斷開,然後再將它聯連上去好了.

接下來,將hub->change_bits的對應位清掉,該位是在函式hub_port_logical_disconnect()中被置的,在這裡將其清除,免得下次在進入hub_events()的時候,再次檢測到這個位發生改變.

忽略掉CONFIG_USB_OTG的處理,這個巨集我們在前面分析過很多次了,這裡不再贅述.
如果該埠的連線發生改變(從有連線到無接接,或者從無連線到有連線),就有一個除抖動的過程,usb2.0 spec上規定,除抖動的時間為100ms.
也許有人會有這樣的想法: 那檢測到移除了一個裝置,但它在100ms又插上去了,這裡適不適合這裡的抖動檢測的情況呢?
我們先從程式碼的流程看,檢測到連線發生改變,進入到hub_port_connect_change(),它首先就會將埠上的裝置移除.這樣,就算你在100ms上連線上去了,也得要再次建立.
從usb2.0的協議看來,裝置移除後,usb裝置裡儲存的資訊(例如選擇的配置,給它分配的地址)全部都丟失了,必須要重新進行配置過程才能夠使用.
在這裡,順便將hub_port_debounce()列出,來看一下具體的除抖過程是怎麼樣實現的.
static int hub_port_debounce(struct usb_hub *hub, int port1)
{
    int ret;
    int total_time, stable_time = 0;
    u16 portchange, portstatus;
    unsigned connection = 0xffff;

    for (total_time = 0; ; total_time += HUB_DEBOUNCE_STEP) {
        ret = hub_port_status(hub, port1, &portstatus, &portchange);
        if (ret
            return ret;

        if (!(portchange & USB_PORT_STAT_C_CONNECTION) &&
             (portstatus & USB_PORT_STAT_CONNECTION) == connection) {
            stable_time += HUB_DEBOUNCE_STEP;
            if (stable_time >= HUB_DEBOUNCE_STABLE)
                break;
        } else {
            stable_time = 0;
            connection = portstatus & USB_PORT_STAT_CONNECTION;
        }

        if (portchange & USB_PORT_STAT_C_CONNECTION) {
            clear_port_feature(hub->hdev, port1,
                    USB_PORT_FEAT_C_CONNECTION);
        }

        if (total_time >= HUB_DEBOUNCE_TIMEOUT)
            break;
        msleep(HUB_DEBOUNCE_STEP);
    }

    dev_dbg (hub->intfdev,
        "debounce: port %d: total %dms stable %dms status 0x%x\n",
        port1, total_time, stable_time, portstatus);

    if (stable_time
        return -ETIMEDOUT;
    return portstatus;
}
函式中的stable_time表示隱定的時間.在hub_events()的程式碼分析時,我們看到了,在檢測到連線狀態發生改變的時候,會發送Clear_Feature.因此,如果在這裡檢測到有USB_PORT_STAT_C_CONNECTION,就說明之後又有一次連線狀態發生改變了.
分析這個函式的時候,要注意有這樣的情況,埠的連線狀態,一直在波動,即時有連線,時末有連線.
還有注意, connection的初始值是0xffff, 所以(portstatus & USB_PORT_STAT_CONNECTION) == connection這個判斷是肯定不會滿足的,因為hub_port_status()取得的portstatus裡面還有一些保留位.所以,在第一次進入這個迴圈的時候,就會進入到else中,就會將stable_time置0,而connection也儲存了這一次的連線資訊.
如果埠維持前一個狀態,那迴圈中的流程就會滿足第一個if,在這個if的操作裡,會增加stable_time的值.
如果埠的狀態發生了改變,那迴圈中的流程就會滿足else,又將stable_time和connection初始化了.另外,要記得在狀態發生改變的時候,要傳送Clear_Feature,將狀態清除.
在函式裡,定義的測試時間是1500ms.如果在這個時間內,埠還末處於穩定狀態,就會返回-ETIMEDOUT.
如果已經處於穩定狀態了,就會返回穩定狀態下的portstatus.

/* Return now if nothing is connected */
    //如果介面上沒有連線了,可以直接退出了
    if (!(portstatus & USB_PORT_STAT_CONNECTION)) {

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

        if (portstatus & USB_PORT_STAT_ENABLE)
            goto done;
        return;
    }
經過去抖後,埠穩定的處於斷開連線狀態.說明埠已經沒有裝置了.然後,再判斷hub是否有電源開關((wHubCharacteristics & HUB_CHAR_LPSM)
如果埠依然處理enable狀態,就會跳轉到標號done處,就埠disalbe.

    //如果介面上面有了聯接,需要為聯接在埠上裝置建立連線
    for (i = 0; i
        struct usb_device *udev;

        /* reallocate for each attempt, since references
         * to the previous one can escape in various ways
         */
        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);
        udev->speed = USB_SPEED_UNKNOWN;
        udev->bus_mA = hub->mA_per_port;
        udev->level = hdev->level + 1;

        /* set the address */
        choose_address(udev);
        if (udev->devnum
            status = -ENOTCONN; /* Don't retry */
            goto loop;
        }

        /* reset and get descriptor */
        status = hub_port_init(hub, udev, port1, i);
        if (status
            goto loop;

        /* 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.
         */
        if (udev->descriptor.bDeviceClass == USB_CLASS_HUB
                && udev->bus_mA
            u16 devstat;

            status = usb_get_status(udev, USB_RECIP_DEVICE, 0,
                    &devstat);
            if (status
                dev_dbg(&udev->dev, "get status %d ?\n", status);
                goto loop_disable;
            }
            le16_to_cpus(&devstat);
            if ((devstat & (1
                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 */
        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
            hdev->children[port1-1] = udev;
        spin_unlock_irq(&device_state_lock);

        /* Run it through the hoops (find a driver, etc) */
        if (!status) {
            status = usb_new_device(udev);
            if (status) {
                spin_lock_irq(&device_state_lock);
                hdev->children[port1-1] = 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:
        ep0_reinit(udev);
        release_address(udev);
        usb_put_dev(udev);
        if ((status == -ENOTCONN) || (status == -ENOTSUPP))
            break;
    }
如果埠隱定處於連線狀態,那就需要連線埠下的裝置了.首先看到的是一個for迴圈,是用來配置裝置的兩種方式.我們知道,在配置裝置的時候,首先要去取裝置的描述符,這個過程是在ep0上完成的.而這個ep0支援的最大傳輸出資料又是在裝置描述符的bMaxPacketSize0中所定義的.
因此就對應有兩種處理方式:
第一種是傳輸8個位元組,取得描述符的前面一部份,從而就可以取得bMaxPacketSize0.此後再reset裝置,再根據這個bMaxPacketSize0的長度去取它的裝置描述符.
第二種是一次傳輸64位元組,取得裝置描述符的bMaxPacketSize0欄位
關於這兩種方式的描述,詳見fudan_abc的>.
有關這個for迴圈的作用就解釋到這裡.
在這段程式碼裡,它首先分配一個usb_dev的結構,然後將其置為USB_STATE_POWERED狀態.接著,為裝置指定一個地址.
然後就呼叫hub_port_init()對這個usb_dev結構進行一系的初始化,在這個函式中會處理:Get_Description,Set_address.等操作,這個函式接下來我們再詳細分析.
接著,將分配的struct usb_dev結構跟他的父結構關聯起來,也就是說新增到它的父結構的usb_dev-> children[]陣列.
最後再呼叫usb_new_device()來取這個裝置的配置項.這個函式我們在分析UHCI的時候已經分析過了.
中間是關於一些電流的判斷處理,這部份比較簡單,自行檢視就可以看懂,這裡不再分析.
注意,這裡在分配usb_dev結構的時候,跟root hub是不相同的,如下示:
    udev = usb_alloc_dev(hdev, hdev->bus, port1)
在為root hub分配struct usb_dev的時候,它的第一個引數,也就是它的父結點是為NULL.
我們來觀察一下它在sysfs中的命名方式:
如下所示:
在沒有插入U盤之前:
[
[email protected]
devices]# pwd
/sys/bus/usb/devices
[[email protected] devices]# ls
1-0:1.0  usb1
[[email protected] devices]#
插入U盤之後:
[[email protected] devices]# ls
1-0:1.0  1-1  1-1:1.0  usb1
增加的兩個目是:
1-1和1-1:1.0
表示,U盤對應的裝置目錄是1-1.結合之前UHCI分析中,對usb_alloc_dev()應該很容易理解.
1-1:1.0 :只有這樣的目錄,表示該U盤只有一個介面,當前選取的是第0號設定項.

done:
    hub_port_disable(hub, port1, 1);
    if (hcd->driver->relinquish_port && !hub->hdev->parent)
        hcd->driver->relinquish_port(hcd, port1);
}
Done標號是對應上述處理失敗的處理,它禁用掉該埠(因為該埠沒有連線裝置或者是埠上的裝置配置失敗),如果是root hub,且USB控制器器驅動中又定義了relinquish_port.呼叫它.

照例,還是分析一下這個函式中涉及到的重要的子函式.
第一個要分析的函式是choose_address()
該函式用來為裝置選擇一個地址,程式碼如下所示:
static void choose_address(struct usb_device *udev)
{
    int     devnum;
    struct usb_bus  *bus = udev->bus;

    /* If khubd ever becomes multithreaded, this will need a lock */

    /* Try to allocate the next devnum beginning at bus->devnum_next. */
//從bus->devnum_next開始找到一個末被使用的位
    devnum = find_next_zero_bit(bus->devmap.devicemap, 128,
            bus->devnum_next);
    //如果搜尋到了最末尾,(128是不能被佔用的),則從1起開始搜尋
    if (devnum >= 128)
        devnum = find_next_zero_bit(bus->devmap.devicemap, 128, 1);
    //更新bus->devnum_next
    bus->devnum_next = ( devnum >= 127 ? 1 : devnum + 1);
    //如果找到了合適位,將該位設為佔用,然後更新udev->devnum為找到的裝置號
    if (devnum
        set_bit(devnum, bus->devmap.devicemap);
        udev->devnum = devnum;
    }
}
這個函式的原理我們在之前說過了多次,它是到所屬的usb bus的bus->devmap中找到沒有使用的那一位.在這裡設定bus->devnum_next項是一個搜尋的優化,它不必每次都從第1位起開始搜尋.最後將找到的值存放在udev->devnum中.

第二個要分析的函式是hub_port_disable().
這個函式將hub對應的埠禁用,程式碼如下:
static int hub_port_disable(struct usb_hub *hub, int port1, int set_state)
{
    struct usb_device *hdev = hub->hdev;
    int ret = 0;
    //將接在該埠下的裝置設為末連線
    if (hdev->children[port1-1] && set_state)
        usb_set_device_state(hdev->children[port1-1],
                USB_STATE_NOTATTACHED);
    //傳送enable 的Clear_Feature請求.
    if (!hub->error)
        ret = clear_port_feature(hdev, port1, USB_PORT_FEAT_ENABLE);
    if (ret)
        dev_err(hub->intfdev, "cannot disable port %d (err = %d)\n",
                port1, ret);
    return ret;
}
該函式的邏輯很簡單,就是該端點下的聯接裝置斷開,如果埠有裝置連線的話.然後清除埠的enable.

第三個要分析的函式是hub_port_init().
將它列到最後,並不是因為它最輕微,而是因為它太複雜.^_^
程式碼分段分析如下:
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;
    int         i, j, retval;
    unsigned        delay = HUB_SHORT_RESET_TIME;
    enum usb_device_speed   oldspeed = udev->speed;
    char            *speed, *type;
    int         devnum = udev->devnum;

    /* root hub ports have a slightly longer reset period
     * (from USB 2.0 spec, section 7.1.7.5)
     */
     //設定port 的重置等待時間
    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 */
    //將port reset
    retval = hub_port_reset(hub, port1, udev, delay);
    if (retval
        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;
首先為埠重置選擇一個合適的延時,即在這個延時過後,埠的Reset應該完成了.usb2.0 spec上規定,root hub的延時值是50ms,高速裝置是10ms,而低速裝置是100ms.從程式碼上看,這個延時都是從udev引數中來的,這個引數就是表示在埠上連線的裝置.其實,所謂的Reset埠,就是Reset埠上連線的裝置.
由於我們現在要對這個裝置進行配置,因此,先將它復原成初始值.
另外,如果重置之後,裝置的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].
     */
    switch (udev->speed) {
    case USB_SPEED_VARIABLE:    /* fixed at 512 */
        udev->ep0.desc.wMaxPacketSize = __constant_cpu_to_le16(512);
        break;
    case USB_SPEED_HIGH:        /* fixed at 64 */
        udev->ep0.desc.wMaxPacketSize = __constant_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 = __constant_cpu_to_le16(64);
        break;
    case USB_SPEED_LOW:     /* fixed at 8 */
        udev->ep0.desc.wMaxPacketSize = __constant_cpu_to_le16(8);
        break;
    default:
        goto fail;
    }
根據裝置的speed來設定ep0的MaxPacketSize.這個只是spec上規定的值.另外對於Full Speed的設來說,它的MaxPacketSize有四種情況,即8.16.32和64實際的值要在裝置描述符的bMaxPacketSize0欄位才能知道.

    type = "";
    switch (udev->speed) {
    case USB_SPEED_LOW: speed = "low";  break;
    case USB_SPEED_FULL:    speed = "full"; break;
    case USB_SPEED_HIGH:    speed = "high"; break;
    case USB_SPEED_VARIABLE:
                speed = "variable";
                type = "Wireless ";
                break;
    default:        speed = "?";    break;
    }
    dev_info (&udev->dev,
          "%s %s speed %sUSB device using %s and address %d\n",
          (udev->config) ? "reset" : "new", speed, type,
          udev->bus->controller->driver->name, devnum);
這段程式碼無關緊要,只是打印出了一個Debug資訊,

    /* Set up TT records, if needed  */
    if (hdev->tt) {
        udev->tt = hdev->tt;
        udev->ttport = hdev->ttport;
    } else if (udev->speed != USB_SPEED_HIGH
            && hdev->speed == USB_SPEED_HIGH) {
        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
        if (USE_NEW_SCHEME(retry_counter)) {
            struct usb_device_descriptor *buf;
            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
                buf->bMaxPacketSize0 = 0;
                r = usb_control_msg(udev, usb_rcvaddr0pipe(),
                    USB_REQ_GET_DESCRIPTOR, USB_DIR_IN,
                    USB_DT_DEVICE
                    buf, GET_DESCRIPTOR_BUFSIZE,
                    USB_CTRL_GET_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;
            kfree(buf);

            retval = hub_port_reset(hub, port1, udev, delay);
            if (retval
                goto fail;
            if (oldspeed != udev->speed) {
                dev_dbg(&udev->dev,
                    "device reset changed speed!\n");
                retval = -ENODEV;
                goto fail;
            }
            if (r) {
                dev_err(&udev->dev, "device descriptor "
                        "read/%s, error %d\n",
                        "64", r);
                retval = -EMSGSIZE;
                continue;
            }
#undef GET_DESCRIPTOR_BUFSIZE
        }

        for (j = 0; j
            retval = hub_set_address(udev, devnum);
            if (retval >= 0)
                break;
            msleep(200);
        }
        if (retval
            dev_err(&udev->dev,
                "device not accepting address %d, error %d\n",
                devnum, retval);
            goto fail;
        }

        /* 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))
            break;

        retval = usb_get_device_descriptor(udev, 8);
        if (retval
            dev_err(&udev->dev, "device descriptor "
                    "read/%s, error %d\n",
                    "8", retval);
            if (retval >= 0)
                retval = -EMSGSIZE;
        } else {
            retval = 0;
            break;
        }
    }
這個for迴圈是一個很重要的操作,首先,我們來看一下USE_NEW_SCHEME巨集的定義.如下示:
((i) / 2 == old_scheme_first), old_scheme_first預設為0,也就是說,當i為0,1的時候,這個巨集會返回1.那就是說,對於之前分析的兩種機制,每種機制嘗試兩次.
區分一下這兩種機制的不同:
對於第一種機制,它先用64的buffer去取裝置描述符.而第二種機制,是以長度8的快取區,取裝置描述符的前半部份.
另外,第一種機制,去取裝置描述符之前沒有設定裝置的地址,因此使用地址0來表示裝置的地址,在程式碼中,用usb_rcvaddr0pipe()表示.而在第二種機制中,它在取裝置描述符之前已經設定了裝置的地址.
疑問:可能有人就有這樣的疑問,既然地址0可以表示沒有設定地址的裝置地址,那如果有多個沒有set address的裝置,這個地址0到底是表示那個裝置呢?
實際上,從程式碼上看,Linux是每開啟一個hub的埠就初始連在這個埠上的裝置.之後這連線上的裝置設定好地址之後再開啟hub的另外的埠進行配置,因此,在同一條usb bus上,不會出現多個末配置的活動裝置.

    if (retval)
        goto fail;

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

    retval = usb_get_device_descriptor(udev, USB_DT_DEVICE_SIZE);
    if (retval descriptor)) {
        dev_err(&udev->dev, "device descriptor read/%s, error %d\n",
            "all", retval);
        if (retval >= 0)
            retval = -ENOMSG;
        goto fail;
    }

    retval = 0;

fail:
    if (retval) {
        hub_port_disable(hub, port1, 0);
        udev->devnum = devnum;  /* for disconnect processing */
    }
    mutex_unlock(&usb_address0_mutex);
    return retval;
}
在上面獲得的裝置描述符的bMaxPacketSize0欄位,也就是ep0的MaxPacketSize.但如果這個值不和我們之前根據spec為ep0設定的MaxPacketSize值相等,且不是Full speed的話,就會有錯誤了.因為只有Full Speed的裝置的ep0 的MaxPacketSize在spec上並沒有一個明確的定義值.
有了確定的ep0 的MaxPacketSize值,就可以取得完整的裝置描述符了.

第四個要分析的函式是hub_port_reset().
這個函式將埠重置並等待埠重置完成.程式碼如下:
static int hub_port_reset(struct usb_hub *hub, int port1,
                struct usb_device *udev, unsigned int delay)
{
    int i, status;

    /* Block EHCI CF initialization during the port reset.
     * Some companion controllers don't like it when they mix.
     */
    down_read(&ehci_cf_port_reset_rwsem);

    /* Reset the port */
    //嘗試5次
    for (i = 0; i
        //傳送Reset 的Set_Feature
        status = set_port_feature(hub->hdev,
                port1, USB_PORT_FEAT_RESET);
        //傳送錯誤
        if (status)
            dev_err(hub->intfdev,
                    "cannot reset port %d (err = %d)\n",
                    port1, status);
        else {
            //傳送Clear_Feature成功,等待埠重置完成
            status = hub_port_wait_reset(hub, port1, udev, delay);
            if (status && status != -ENOTCONN)
                dev_dbg(hub->intfdev,
                        "port_wait_reset: err = %d\n",
                        status);
        }

        /* return on disconnect or reset */
        switch (status) {
        //成功   
        case 0:
            /* TRSTRCY = 10 ms; plus some extra */
            msleep(10 + 40);
            udev->devnum = 0;   /* Device now at address 0 */
            /* FALL THROUGH */
        //埠沒有連線   
        case -ENOTCONN:
        //要傳送的裝置不存在   
        case -ENODEV:
            clear_port_feature(hub->hdev,
                port1, USB_PORT_FEAT_C_RESET);
            /* FIXME need disconnect() for NOTATTACHED device */
            usb_set_device_state(udev, status
                    ? USB_STATE_NOTATTACHED
                    : USB_STATE_DEFAULT);
            goto done;
        }

        dev_dbg (hub->intfdev,
            "port %d not enabled, trying reset again...\n",
            port1);
        //將延遲設至最長,再試一次
        delay = HUB_LONG_RESET_TIME;
    }

    dev_err (hub->intfdev,
        "Cannot enable port %i.  Maybe the USB cable is bad?\n",
        port1);

done:
    up_read(&ehci_cf_port_reset_rwsem);
    return status;
}
這個函式的程式碼看清淅,首先將埠重置,然後等待埠重置完成.在成功返回或者是發錯致命錯誤的時候就會在清除掉RESET Feature,設定裝置狀態之後返回.這個所謂的致命包括:
1:傳送Clear_Feature時,返回-ENODEV,表示裝置不存在
2:在hub_port_wait_reset()後返回的-ENOTCONN,表示埠上末連線裝置.
另外,在這裡哆嗦的重複一句,只有在裝置有這個Feature的時候,才能Clear_Feature.在上面的程式碼中,只有程式碼中,如果Reset不成功,是不需要Clear USB_PORT_FEAT_C_RESET 這個Feature的.只有在已經設定成功的情況,才能將其Clear(-ENODEV的情況,無所謂,這個錯誤在submit urb前期就能測檢出來,不會跟硬體互動,而-ENOTCONN則表示埠Reset完成,但尚末檢測到連線裝置,這種情況下,也是需要Clear_Feature的).
另外,裡面還呼叫了一個子函式, hub_port_wait_reset().程式碼如下:
static int hub_port_wait_reset(struct usb_hub *hub, int port1,
                struct usb_device *udev, unsigned int delay)
{
    int delay_time, ret;
    u16 portstatus;
    u16 portchange;

    //最長等待時間是500
    for (delay_time = 0;
            delay_time
            delay_time += delay) {
        /* wait to give the device a chance to reset */
        msleep(delay);

        /* read and decode port status */
        ret = hub_port_status(hub, port1, &portstatus, &portchange);
        if (ret
            return ret;

        /* Device went away? */
        //埠已經沒有連線了,說明連線的裝置在某個時刻被撥下來了
        if (!(portstatus & USB_PORT_STAT_CONNECTION))
            return -ENOTCONN;

        /* bomb out completely if the connection bounced */
        //連線狀態發生了改變,則說明連線狀態不穩定.因為斷開之後,再聯上是需要重新配置的
        //退出
        if ((portchange & USB_PORT_STAT_C_CONNECTION))
            return -ENOTCONN;

        /* if we`ve finished resetting, then break out of the loop */
        //如果Reset已經完成,且埠處於enable狀態,設定speed成員就可以返回了
        if (!(portstatus & USB_PORT_STAT_RESET) &&
            (portstatus & USB_PORT_STAT_ENABLE)) {
            if (hub_is_wusb(hub))
                udev->speed = USB_SPEED_VARIABLE;
            else if (portstatus & USB_PORT_STAT_HIGH_SPEED)
                udev->speed = USB_SPEED_HIGH;
            else if (portstatus & USB_PORT_STAT_LOW_SPEED)
                udev->speed = USB_SPEED_LOW;
            else
                udev->speed = USB_SPEED_FULL;
            return 0;
        }

        /* switch to the long delay after two short delay failures */
        //失敗兩次,將延時時間設為最長的時間
        if (delay_time >= 2 * HUB_SHORT_RESET_TIME)
            delay = HUB_LONG_RESET_TIME;

        dev_dbg (hub->intfdev,
            "port %d not reset yet, waiting %dms\n",
            port1, delay);
    }

    return -EBUSY;
}
注意到在上面為speed成員賦值的時候,出現了一個hub_is_wusb().該巨集用來判斷hcd是否是一個無線的USB主機控制器.如果hcd 是一個無線的,那其下的所有裝置的speed均為USB_SPEED_VARIABLE.這個是屬於usb2.5 spec裡面定義的.
到這裡,hub_thread()函式已經分析完了.它已經將hub下連線的所有新裝置都初始化並新增進了裝置模型.

5.2.3:HUB中斷URB傳輸完成的處理
在之前分析中斷URB初始化的時候,曾分析到,如果中斷URB傳輸完成,就會呼叫hub_irq().在分析這個函式之前,我們先從spec上了解一下,對於hub的中斷傳輸到底會傳些什麼樣的東西:
如下圖所示:


Bit0表示hub的連線狀態發生了改變,而bit1~bitN表示的是各埠連線狀態的改變.如果1表示改變,為0表示末改變.
現在可以看該函式的程式碼了,如下:
static void hub_irq(struct urb *urb)
{
    struct usb_hub *hub = urb->context;
    int status = urb->status;
    int i;
    unsigned long bits;

    switch (status) {
    case -ENOENT:       /* synchronous unlink */
    case -ECONNRESET:   /* async unlink */
    case -ESHUTDOWN:    /* hardware going away */
        return;

    default:        /* presumably an error */
        /* Cause a hub reset after 10 consecutive errors */
        dev_dbg (hub->intfdev, "transfer --> %d\n", status);
        if ((++hub->nerrors error)
            goto resubmit;
        hub->error = status;
        /* FALL THROUGH */

    /* let khubd handle things */
    case 0:         /* we got data:  port status changed */
        bits = 0;
        for (i = 0; i actual_length; ++i)
            bits |= ((unsigned long) ((*hub->buffer)))
                    
        hub->event_bits[0] = bits;
        break;
    }

    hub->nerrors = 0;

    /* Something happened, let khubd figure it out */
    kick_khubd(hub);

resubmit:
    if (hub->quiescing)
        return;

    if ((status = usb_submit_urb (hub->urb, GFP_ATOMIC)) != 0
            && status != -ENODEV && status != -EPERM)
        dev_err (hub->intfdev, "resubmit --> %d\n", status);
}
從上面的程式碼可以看出,就將是設HUB中斷傳輸的資訊儲存在hub->event_bits中,然後又將此URB再次提交,再次提交的結果是,可以輪詢獲得hub的狀態,另外,還會呼叫kick_khubd().這樣, hub_events()就又會呼叫,又可以處理HUB埠的狀態改變.

六:小結
在本小結裡,對HUB的處理過程做了一個詳盡的分析,在這一節裡,也瞭解到了USB的驅動架構以及USB裝置的列舉過程.
在下一節裡,我們以特定的USB裝置分例,來分析USB驅動程式的架構.

相關推薦

Linux裝置驅動USB hub驅動()

5.2.2:介面驅動中的hub_thread()函式 我們之前在分析usb_hub_init()的程式碼的時候,忽略掉了一部份. 程式碼片段如下所示: int usb_hub_init(void) {    ……     khubd_task = kthread_run(hub_thread, NULL, "

Linux裝置模型tty&&uart驅動架構分析

五: uart_add_one_port()操作 在前面提到.在對uart裝置檔案過程中.會將操作轉換到對應的port上,這個port跟uart_driver是怎麼關聯起來的呢?這就是uart_add_ont_port()的主要工作了. 顧名思義,這個函式是在uart_driver增加一個port.程式碼如

linux裝置驅動USB資料傳輸分析(五)

也許,有人會有這樣的疑問: 對於控制傳輸,它不也是基於toggle的糾錯麼,為什麼它就不需要修改後續的包的toggle值呢? 這是因為,控制傳輸的toggle都是從1開始的,刪除掉當前的urb,也不會對後面的發包造成影響. 之後,處理完之後,將無用的td刪除. 跟

linux裝置驅動USB主機控制器驅動分析 (一)

一:前言 Usb是一個很複雜的系統.在usb2.0規範中,將其定義成了一個分層模型.linux中的程式碼也是按照這個分層模型來設計的.具體的分為 usb裝置,hub和主機控制器三部份.在閱讀程式碼的時候,必須要參考相應的規範.最基本的就是USB2.0的spec.

Linux裝置模型tty驅動架構分析

------------------------------------------ 本文系本站原創,歡迎轉載!轉載請註明出處:http://ericxiao.cublog.cn/------------------------------------------一:前言Tty這個名稱源於電傳打位元組的簡稱。

linux裝置模型uart驅動架構分析

一:前言 接著前面的終端控制檯分析,接下來分析serial的驅動.在linux中,serial也對應著終端,通常被稱為串列埠終端.在shell上,我們看到的/dev/ttyS*就是串列埠終端所對應的裝置節點. 在分析具體的serial驅動之前.有必要先分析uart

Linux裝置驅動button按鍵驅動學習與小結

button按鍵驅動,相對於前面的LED驅動來說。增加了中斷處理以及阻塞與非阻塞方式等新知識點。 先上學習的驅動程式碼。 核心:linux3.0 板子:fl2440 /***************************************************

linux驅動USB驅動程式框架

USB驅動程式框架: app: -----------------------------------------------------------------------                              USB裝置驅動程式         

Linux驅動usb滑鼠

/*  * 參考drivers\hid\usbhid\usbmouse.c  */ #include <linux/kernel.h> #include <linux/slab.h> #include <linux/module.h> #include <linux/

linux裝置模型匯流排 裝置驅動

《Linux核心修煉之道》讀書筆記 1、 裝置模型的上層建築由匯流排(bus) 、裝置(device)、 驅動(device_driver)這3個數據結構構成,裝置模型表示了它們之間的連線關係。

tiny4412 裝置SD卡驅動(三)

開發板:tiny4412(1611) 核心:linux4.4 編譯器:arm-none-linux-gnueabi-gcc (gcc version 4.8.3 20140320) 在linux核心中,SD卡屬於MMC子系統,簡單的介紹: http://blog.

使用USB gadget驅動測試USB晶片驅動功能

在核心中使能Mass Storage Gadget ,如下圖,配置路徑是Linux/arm64 4.14.0 Kernel Configuration → Device Drivers → USB support → USB Gadget Support ->Mass Stor

第18章 ARM Linux裝置四(常用的OF API)

18.4 常用的OF API除了前文介紹的of_machine_is_compatible()、of_device_is_compatible()等常用函式以外,在Linux的BSP和驅動程式碼中,經常會使用到一些Linux中其他裝置樹的API,這些API通常被冠以of_字首

linux裝置模型I2C子系統

=============================== 本文系本站原創,歡迎轉載! 轉載請註明出處:http://blog.csdn.net/gdt_a20 ===============================       I2c子系統將i2c控制器(

linux裝置模型spi子系統

=============================== 本文系本站原創,歡迎轉載! 轉載請註明出處:http://www.cnblogs.com/gdt-a20 ===============================       相比於前面介紹的i2c子系統,spi子系

linux裝置模型mmc,sd子系統

struct mmc_host { 171         struct device           *parent;          172         struct device           class_dev; 173         int                    

linux裝置gpio

#include <linux/init.h> #include <linux/module.h> #include <linux/kernel.h> #include <linux/version.h> #include &

SPI驅動主控制器驅動程式

 嵌入式微處理器訪問SPI裝置有兩種方式:使用GPIO模擬SPI介面的工作時序或者使用SPI控制器。使用GPIO模擬SPI介面的工作時序是非常容易實現的,但是會導致大量的時間耗費在模擬SPI介面的時序上,訪問效率比較低,容易成為系統瓶頸。這裡主要分析使用SPI控制器的情

linux裝置模型bus,device,driver分析二

=============================== 本文系本站原創,歡迎轉載! 轉載請註明出處:http://blog.csdn.net/gdt_a20 ===============================   上篇分析了bus,driver的註冊過程

linux裝置模型Class

    參考:http://www.wowotech.net/device_model/class.html     剛開始寫字元裝置驅動程式的時候,老師教我們自動建立裝置節點,“要先建立類,在類下面建立裝置,類名字不重要“。         firstdrv_class