加載usbserial驅動后,為什么adb不可用了?


​某設備提供了USB串口功能,上位機(Host端)可以通過USB串口與之通信。對於Linux上位機,比如Ubuntu,自帶usbserial驅動,當安裝usbserial驅動后,上位機就會生成ttyUSBx(x=0~n)設備,通過ttyUSBx就能與設備端進行USB串口通信。

 

該設備不僅提供了USB串口,也同時提供了adb口的功能。實際應用中發現,將設備通過USB線連接到Ubuntu PC后,有生成adb口,adb能正常使用;但是串口驅動默認沒有加載,手動insmod usbserial后,串口可以正常使用,但是adb口卻不通了。

 

usbserial的使用方法可參考kernel document:Documentation/usb/usb-serial.txt

insmod usbserial vendor=0x#### product=0x####
####是vendor id和product id。

 

調查發現,Ubuntu加載usbserial驅動后,原來的adb口會被識別為USB串口,生成新的ttyUSBx設備,導致Ubuntu端的adb無法正常與設備端通信。從這個現象看,是Ubuntu端usbserial驅動錯誤地匹配了本來不應該屬於它匹配的設備。具體情況如下。

 

成功執行adb shell后,加載usbserial驅動之前,在Ubuntu上執行 lsusb -t 指令得到的結果截取如下:

Bus 03.Port 1: Dev 1, Class=root_hub, Driver=xhci_hcd/10p, 480M

|__ Port 6: Dev 4, If 5, Class=Vendor Specific Class, Driver=, 480M

|__ Port 6: Dev 4, If 6, Class=Vendor Specific Class, Driver=usbfs, 480M

 

加載usbserial驅動之后,再次插拔USB,在Ubuntu上執行 lsusb -t 指令得到的結果截取如下:

Bus 03.Port 1: Dev 1, Class=root_hub, Driver=xhci_hcd/10p, 480M

|__ Port 6: Dev 4, If 5, Class=Vendor Specific Class, Driver=usbserial_generic, 480M

|__ Port 6: Dev 4, If 6, Class=Vendor Specific Class, Driver=usbserial_generic, 480M

 

If 5對應的是串口,If 6對應的是adb口;正常情況下,adb口匹配的驅動是usbfs ,異常情況下,adb口匹配的驅動是 usbserial_generic 。

 

為什么usbserial驅動會錯誤地匹配了本來不應該屬於它匹配的設備呢?這個需要從源碼碼中去尋找答案(Linus名言:Read the fu*k source code  ^_^)。

 

代碼路徑在 drivers/usb/serial/*,關鍵代碼如下,從match_flags可以看出,usb generic serial驅動是簡單地通過vendor id和product id來匹配設備。

 

drivers/usb/serial/generic.c

int usb_serial_generic_register(void)
{
int retval = 0;

#ifdef CONFIG_USB_SERIAL_GENERIC
generic_device_ids[0].idVendor = vendor;
generic_device_ids[0].idProduct = product;
generic_device_ids[0].match_flags =
USB_DEVICE_ID_MATCH_VENDOR | USB_DEVICE_ID_MATCH_PRODUCT;

retval = usb_serial_register_drivers(serial_drivers,
"usbserial_generic", generic_device_ids);
#endif
return retval;
}

drivers/usb/serial/usb-serial.c

static int __init usb_serial_init(void)
{
int result;

usb_serial_tty_driver = alloc_tty_driver(USB_SERIAL_TTY_MINORS);
...
/* register the generic driver, if we should */
result = usb_serial_generic_register();

 

由於該設備的adb口和串口都是基於同樣的USB設備,具有同樣的vendor id和product id,那adb口被識別成串口,也就能理解了。

 

原因找到了,那有沒有辦法解決這個問題呢?

 

1)上位機先執行adb shell把adb口占住,再加載usbserial驅動,則adb口不會被重新匹配serial驅動;但是重新插拔USB后,又需要卸載usbserial驅動,再重復此過程。臨時用用倒是可以。

 

2)修改usbserial驅動。在開發嵌入式產品時,一般上位機也是需要源碼級開發的,在這種場景下修改usbserial驅動比較適用。

 

看看adb和generic串口的接口描述符信息。在Ubuntu上執行lsusb -v指令后得到的adb接口描述符如下:

    Interface Descriptor:

      bLength                 9

      bDescriptorType         4

      bInterfaceNumber        6

      bAlternateSetting       0

      bNumEndpoints           2

      bInterfaceClass       255 Vendor Specific Class

      bInterfaceSubClass     66

      bInterfaceProtocol      1

      iInterface             10 ADB Interface

 

usb generic串口的接口描述符如下:

    Interface Descriptor:

      bLength                 9

      bDescriptorType         4

      bInterfaceNumber        5

      bAlternateSetting       0

      bNumEndpoints           2

      bInterfaceClass       255 Vendor Specific Class

      bInterfaceSubClass     0

      bInterfaceProtocol      0

 

可以看到兩者的 bInterfaceClass 是一樣的,都是 Vendor Specific Class ,但是 bInterfaceSubClass 和 bInterfaceProtocol 不一樣。所以可以考慮修改usbserial驅動的匹配規則,除了vendor id和product id之外,再加入按interface class和interface protocol聯合匹配。

 

除了USB generic serial驅動外,還有一些廠商自定義的USB serial驅動,比如 drivers/usb/serial/option.c,該驅動就在probe函數里面設置了一些判斷條件,避免匹配上不該匹配的設備,可用來參考。如下,代碼注釋得相當詳細,不再贅述。

 

static int option_probe(struct usb_serial *serial,
            const struct usb_device_id *id)
{
    struct usb_interface_descriptor *iface_desc =
                &serial->interface->cur_altsetting->desc;
    unsigned long device_flags = id->driver_info;

    /* Never bind to the CD-Rom emulation interface    */
    if (iface_desc->bInterfaceClass == USB_CLASS_MASS_STORAGE)
        return -ENODEV;

    /*
     * Don't bind reserved interfaces (like network ones) which often have
     * the same class/subclass/protocol as the serial interfaces.  Look at
     * the Windows driver .INF files for reserved interface numbers.
     */
    if (device_flags & RSVD(iface_desc->bInterfaceNumber))
        return -ENODEV;

    /*
     * Allow matching on bNumEndpoints for devices whose interface numbers
     * can change (e.g. Quectel EP06).
     */
    if (device_flags & NUMEP2 && iface_desc->bNumEndpoints != 2)
        return -ENODEV;

    /* Store the device flags so we can use them during attach. */
    usb_set_serial_data(serial, (void *)device_flags);

    return 0;
}

 

歡迎關注我的公眾號,一起交流。微信搜索“大魚嵌入式”或者掃描下列二維碼。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM