某設備提供了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; }
歡迎關注我的公眾號,一起交流。微信搜索“大魚嵌入式”或者掃描下列二維碼。