加载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;
}

 

欢迎关注我的公众号,一起交流。微信搜索“大鱼嵌入式”或者扫描下列二维码。

 

posted @ 2021-05-15 21:25  bigfish99  阅读(1121)  评论(0编辑  收藏  举报