============= 本系列參考 =============
《圈圈教你玩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/
代碼:linux-3.10.65/drivers/usb/core/hub.c
====================================
前言
由於USB設備是先被hub識別的, 所以這次先分析hub代碼, 與前面兩篇博文連貫起來
一、 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做了啥
二、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
三、 內核線程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); }