USB之hub3

=============  本系列參考  =============linux

《圈圈教你玩USB》、《Linux那些事兒之我是USB》函數

協議文檔:https://www.usb.org/document-library/usb-20-specification  usb_20_20190524/usb_20.pdf工具

調試工具:Beagle USB 480 邏輯分析儀、sys/kernel/debug/usb/usbmon/post

代碼:linux-3.10.65/drivers/usb/core/hub.cui

====================================spa

前言線程

  因爲USB設備是先被hub識別的, 因此此次先分析hub代碼, 與前面兩篇博文連貫起來debug

1、 hub加載

  在USB子系統核心模塊被調用:指針

/* drivers/usb/core/usb.c */
static int __init usb_init(void)
{
    int retval;
    if (nousb) {
        pr_info("%s: USB support disabled\n", usbcore_name);
        return 0;
    }

    retval = usb_debugfs_init();
    if (retval)
        goto out;

    usb_acpi_register();
    retval = bus_register(&usb_bus_type);
    if (retval)
        goto bus_register_failed;
    retval = bus_register_notifier(&usb_bus_type, &usb_bus_nb);
    if (retval)
        goto bus_notifier_failed;
    retval = usb_major_init();
    if (retval)
        goto major_init_failed;
    retval = usb_register(&usbfs_driver);
    if (retval)
        goto driver_register_failed;
    retval = usb_devio_init();
    if (retval)
        goto usb_devio_init_failed;
    retval = usb_hub_init();
    if (retval)
        goto hub_init_failed;
    retval = usb_register_device_driver(&usb_generic_driver, THIS_MODULE);
    if (!retval)
        goto out;

    usb_hub_cleanup();
hub_init_failed:
    usb_devio_cleanup();
usb_devio_init_failed:
    usb_deregister(&usbfs_driver);
driver_register_failed:
    usb_major_cleanup();
major_init_failed:
    bus_unregister_notifier(&usb_bus_type, &usb_bus_nb);
bus_notifier_failed:
    bus_unregister(&usb_bus_type);
bus_register_failed:
    usb_acpi_unregister();
    usb_debugfs_cleanup();
out:
    return retval;
}

/*
 * Cleanup
 */
static void __exit usb_exit(void)
{
    /* This will matter if shutdown/reboot does exitcalls. */
    if (nousb)
        return;

    usb_deregister_device_driver(&usb_generic_driver);
    usb_major_cleanup();
    usb_deregister(&usbfs_driver);
    usb_devio_cleanup();
    usb_hub_cleanup();
    bus_unregister_notifier(&usb_bus_type, &usb_bus_nb);
    bus_unregister(&usb_bus_type);
    usb_acpi_unregister();
    usb_debugfs_cleanup();
}

subsys_initcall(usb_init);
module_exit(usb_exit);
MODULE_LICENSE("GPL");
/* drivers/usb/core/hub.c */
static struct usb_driver hub_driver = {
    .name =        "hub",
    .probe =    hub_probe,
    .disconnect =    hub_disconnect,
    .suspend =    hub_suspend,
    .resume =    hub_resume,
    .reset_resume =    hub_reset_resume,
    .pre_reset =    hub_pre_reset,
    .post_reset =    hub_post_reset,
    .unlocked_ioctl = hub_ioctl,
    .id_table =    hub_id_table,
    .supports_autosuspend =    1,
};

int usb_hub_init(void)
{
    if (usb_register(&hub_driver) < 0) {
        printk(KERN_ERR "%s: can't register hub driver\n",
            usbcore_name);
        return -1;
    }

    khubd_task = kthread_run(hub_thread, NULL, "khubd");
    if (!IS_ERR(khubd_task))
        return 0;

    /* Fall through if kernel_thread failed */
    usb_deregister(&hub_driver);
    printk(KERN_ERR "%s: can't start khubd\n", usbcore_name);

    return -1;
}

  

  usb_hub_init()就作兩件事, 一是註冊驅動--針對hub接口設備的驅動, 二是建立內核線程「khubd」, 咱們後續會詳解  調試

  記住hub本省也是個usb設備, 跟普通的U盤使用的都是一樣的結構體, 當有hub設備被建立時,hub驅動的probe()將會match調用, 那問題來了,一個普通設備是被hub建立的, 那hub設備是誰建立的呢?

很顯然最初的root hub設備必須是靜態建立的, 且這部分代碼沒放在hub.c, 而是放到了hcd.c, 能夠看出一個Host必然有一個root hub, 是綁定的!

int usb_add_hcd(struct usb_hcd *hcd, unsigned int irqnum, unsigned long irqflags)
{
    int retval;
    struct usb_device *rhdev;


    /* 1. 建立一個root hub設備 */
    if ((rhdev = usb_alloc_dev(NULL, &hcd->self, 0)) == NULL) {
        dev_err(hcd->self.controller, "unable to allocate root hub\n");
        retval = -ENOMEM;
        goto err_allocate_root_hub;
    }
    /* 2. 讓hcd與root hub 牢牢地綁在一塊兒! */
    hcd->self.root_hub = rhdev;



    /* 3. 註冊usb設備 */
    if ((retval = register_root_hub(hcd)) != 0)
        goto err_register_root_hub;

    return retval;

} 
EXPORT_SYMBOL_GPL(usb_add_hcd);

=========================================
/* root hub 設備默認就接在Host, 不是熱拔插 */
static int register_root_hub(struct usb_hcd *hcd)
{
    struct device *parent_dev = hcd->self.controller;
    struct usb_device *usb_dev = hcd->self.root_hub;
    int retval;

    /* 4. 有效設備地址1~127, root hub默認使用地址1 */
    usb_dev->devnum = 1;
    /* 5. 直接進入地址階段 */
    usb_set_device_state(usb_dev, USB_STATE_ADDRESS);

    /* 6. 直接設置ep0 size=64, 看來是協議規定的了 */
    usb_dev->ep0.desc.wMaxPacketSize = cpu_to_le16(64);
    /* 7. root hub 也是設備, 也要獲取各類描述符 */
    retval = usb_get_device_descriptor(usb_dev, USB_DT_DEVICE_SIZE);
    retval = usb_get_bos_descriptor(usb_dev);
    /* 8. 註冊設備(是註冊usb_device, 不是usb_interface) */
    retval = usb_new_device (usb_dev);

    return retval;
}

  

着重說明usb_generic_driver會與全部的usb_device進行match, 而後選擇合適的配置描述符,設置配置描述符時天然就設置interace, 也即建立usb_interface, 這個接口設備纔是各個驅動對應的設備, 好比咱們如今討論的hub_driver

就是針對hub的usb_interface, 不是hub的usb_device,  usb_device表明是這個設備總體抽象, usb_interface表明是具體的某樣功能, 須要具體的驅動操做。

  當註冊一個USB Host時就靜態建立root hub設備, 通過簡單初始化後註冊就會與usb_generic_driver 進行match建立具體的usb_interface, 當註冊接口設備時就會match到hub_driver.probe(), 咱們繼續看看probe作了啥

 

2、hub驅動probe()

static int hub_probe(struct usb_interface *intf, const struct usb_device_id *id)
{
    struct usb_host_interface *desc;
    struct usb_endpoint_descriptor *endpoint;
    struct usb_device *hdev;
    struct usb_hub *hub;

    desc = intf->cur_altsetting;
    /* 1. 要求除了ep0, 必須有且只有一個ep 仍是INT型 */
    if (desc->desc.bNumEndpoints != 1)
        goto descriptor_error;

    endpoint = &desc->endpoint[0].desc;
    if (!usb_endpoint_is_int_in(endpoint))
        goto descriptor_error;

    /* 2. 這是和普通設備的區別, 除了用usb_device描述外,hub還會建立usb_hub和usb_port */
    hub = kzalloc(sizeof(*hub), GFP_KERNEL);
    
    dev_info (&intf->dev, "USB hub found\n");
    if (hub_configure(hub, endpoint) >= 0)
        return 0;
}

        static int hub_configure(struct usb_hub *hub, struct usb_endpoint_descriptor *endpoint)
        {
            /* 3. 主要獲取hub有多少個port */
            ret = get_hub_descriptor(hdev, hub->descriptor);
            hdev->maxchild = hub->descriptor->bNbrPorts;
            /* 4. 這也是和普通外設的區別, 會建立port, 這裏只是建立指針, 真正建立在後面 */
            hub->ports = kzalloc(hdev->maxchild * sizeof(struct usb_port *), GFP_KERNEL);

            /* 5. hub內部高速與全/低速不可兼容, 因此須要兩部分傳輸電路, 根據須要進行切換
                  SINGLE_TT指的是hub只有一個轉換電路針對整個hub,MULTI_TT表示每一個port都有個TT轉換電路能夠針對每一個port(土豪)
                  TTTT指的是轉換電路後須要多少時間才穩定, 只有穩定了才能夠傳輸數據*/
            switch (hdev->descriptor.bDeviceProtocol) {
            case USB_HUB_PR_FS:
                break;
            case USB_HUB_PR_HS_SINGLE_TT:
                dev_dbg(hub_dev, "Single TT\n");
                hub->tt.hub = hdev;
                break;
            case USB_HUB_PR_HS_MULTI_TT:
                ret = usb_set_interface(hdev, 0, 1);
                if (ret == 0) {
                    dev_dbg(hub_dev, "TT per port\n");
                    hub->tt.multi = 1;
                } else
                    dev_err(hub_dev, "Using single TT (err %d)\n",
                        ret);
                hub->tt.hub = hdev;
                break;
            case USB_HUB_PR_SS:
                /* USB 3.0 hubs don't have a TT */
                break;
            default:
                dev_dbg(hub_dev, "Unrecognized hub protocol %d\n",
                    hdev->descriptor.bDeviceProtocol);
                break;
            }

            /* Note 8 FS bit times == (8 bits / 12000000 bps) ~= 666ns */
            switch (wHubCharacteristics & HUB_CHAR_TTTT) {
                case HUB_TTTT_8_BITS:
                    if (hdev->descriptor.bDeviceProtocol != 0) {
                        hub->tt.think_time = 666;
                        dev_dbg(hub_dev, "TT requires at most %d "
                                "FS bit times (%d ns)\n",
                            8, hub->tt.think_time);
                    }
                    break;
                case HUB_TTTT_16_BITS:
                    hub->tt.think_time = 666 * 2;
                    dev_dbg(hub_dev, "TT requires at most %d "
                            "FS bit times (%d ns)\n",
                        16, hub->tt.think_time);
                    break;
                case HUB_TTTT_24_BITS:
                    hub->tt.think_time = 666 * 3;
                    dev_dbg(hub_dev, "TT requires at most %d "
                            "FS bit times (%d ns)\n",
                        24, hub->tt.think_time);
                    break;
                case HUB_TTTT_32_BITS:
                    hub->tt.think_time = 666 * 4;
                    dev_dbg(hub_dev, "TT requires at most %d "
                            "FS bit times (%d ns)\n",
                        32, hub->tt.think_time);
                    break;
            }

            /* 7. 申請一個urb並填充, 後續在hub_activate會調用usb_submit_urb */
            hub->urb = usb_alloc_urb(0, GFP_KERNEL);
            usb_fill_int_urb(hub->urb, hdev, pipe, *hub->buffer, maxp, hub_irq, hub, endpoint->bInterval);

            /* 8. 真正建立usb_port設備 */
            for (i = 0; i < hdev->maxchild; i++) {
                ret = usb_hub_create_port_device(hub, i + 1);
                if (ret < 0) {
                    dev_err(hub->intfdev,
                        "couldn't create port%d device.\n", i + 1);
                    hdev->maxchild = i;
                    goto fail_keep_maxchild;
                }
            }

            /* 9. 激活 */ hub_activate(hub, HUB_INIT);
            return 0;
        }


                                static void hub_activate(struct usb_hub *hub, enum hub_activation_type type)
                                {
                                    struct usb_device *hdev = hub->hdev;
                                    for (port1 = 1; port1 <= hdev->maxchild; ++port1) {
                                        struct usb_device *udev = hub->ports[port1 - 1]->child;
                                        u16 portstatus, portchange;

                                        portstatus = portchange = 0;
                                        status = hub_port_status(hub, port1, &portstatus, &portchange);

                                            if (udev || (portstatus & USB_PORT_STAT_CONNECTION) || (portstatus & USB_PORT_STAT_OVERCURRENT))
                                                /* 10. 前面讀取portstatus&portchange 就是爲了標誌change_bits, 由於內核線程會讀取看有沒有變化
                                                    其實內核線程也會調用hub_port_status,我以爲能夠不須要change_bits, 之因此存在是線程khubd
                                                    一運行就第一時間知道有沒有設備插入, 或者線程讀以前是否是狀態又發生了變化*/
                                                set_bit(port1, hub->change_bits);
                                    
                                    }
                                    
                                    /* 11. 第一次提交urb,後續的提交就在urb的回調函數hub_irq()裏調用 */
                                    status = usb_submit_urb(hub->urb, GFP_NOIO);

                                    /* 12. 其實submit後就會調用回調函數hub_irq(), 裏面就會調用kick_khubd(hub),不知道爲什麼這裏重複調用一次 */
                                    kick_khubd(hub);
                                }

 

  整個核心就是建立usb_hub結構體、獲取hub描述符知道多少個port後又建立usb_port結構體、初始化TT轉換電路、而後順便讀取狀態看有沒有外設插入, 之因此說是順即是由於內核線程khubd纔是主業作這個查詢狀態的, 不管是此次順帶讀仍是

khubd讀總得有個urb, 因此就申請一個urb,採用中斷傳輸模式, 並觸發第一次submit提交, 而後在回調函數再次提交urb

 

3、 內核線程khubd

  上面probe()是針對每一個hub設備都會有的行爲, 建立usb_hub、usb_port,申請一個urb, 但有一個共同操做, 就是內核線程khubd, 因此它放在usb_hub_init()

hub_thread()
    -> hub_events():
            /* 1. 處理每一個hub, 並從鏈表刪除這個hub */
            while (1) {
                /* 2. 若是鏈表空了就退出 */
                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);
                hub_dev = hub->intfdev;
                intf = to_usb_interface(hub_dev);

                /* 3. 處理每一個hub的每一個port */
                for (i = 1; i <= hub->descriptor->bNbrPorts; i++) {
                    /* 4. 早前提到的順帶讀取狀態會操做change_bits */
                    connect_change = test_bit(i, hub->change_bits);
                    ret = hub_port_status(hub, i, &portstatus, &portchange);
                    printk("ret=%d, portstatus=0x%x, portchange=0x%x\n", ret, portstatus, portchange);

                    if (portchange & USB_PORT_STAT_C_CONNECTION) {
                        usb_clear_port_feature(hdev, i,
                            USB_PORT_FEAT_C_CONNECTION);
                        connect_change = 1;
                    }

                    /* 5. 若是有設備插入, 則建立新的設備 */
                    if (connect_change)
                        hub_port_connect_change(hub, i, portstatus, portchange);
                } /* end for i */
            } /* end while (1) */

  線程就是不斷從鏈表取出hub, 而後掃描hub的全部port看有沒有外設插入, 有的話就經過hub_port_connect_change()建立, 這樣全部的hub,全部的port就都被訪問到了, 那鏈表的hub是何時掛上去的呢?

這就是上面提到的urb, 每一個hub外設都會申請一個urb, 採用中斷傳輸, 傳輸的內容就是讀取hub的狀態, 若是狀態改變, 則將這個hub掛到鏈表上去, 同時啓動內核線程, 線程天然就會處理這些hub了!

  

static void hub_port_connect_change(struct usb_hub *hub, int port1, u16 portstatus, u16 portchange)
{
        udev = usb_alloc_dev(hdev, hdev->bus, port1);
        choose_devnum(udev);
        hub_port_init(hub, udev, port1, i);
            -> hub_port_reset()
            -> usb_get_device_descriptor
            -> hub_port_reset
            -> usb_get_device_descriptor
        usb_new_device(udev);
}
相關文章
相關標籤/搜索