serialport / serialport-rs

A cross-platform serial port library in Rust. Provides a blocking I/O interface and port enumeration including USB device information.
Other
469 stars 115 forks source link

Wrong serial number and missing interface number for FTDI multi-port device on Windows #203

Open sirhcel opened 1 month ago

sirhcel commented 1 month ago

While looking into #201, I found a wrong serial number '5' reported for a FTDI 2232D which does not have a serial number (due to not having an external EEPROM fitted):

>cargo run --example list_ports
[...]
Found 2 ports:
  COM9
    Type: USB
    VID:0403 PID:6010
     Serial Number: 5
      Manufacturer: FTDI
           Product: USB Serial Port (COM9)
  COM10
    Type: USB
    VID:0403 PID:6010
     Serial Number: 5
      Manufacturer: FTDI
           Product: USB Serial Port (COM10)

The same serial number is also reported by pySerial:

>python -m serial.tools.list_ports -v
[...]
COM9
    desc: USB Serial Port (COM9)
    hwid: USB VID:PID=0403:6010 SER=5
COM10
    desc: USB Serial Port (COM10)
    hwid: USB VID:PID=0403:6010 SER=5
3 ports found

Listing the ports on macOS gives the espected result of no serial number at all:

$ cargo run --example list_ports
[...]
  /dev/cu.usbserial-21400
    Type: USB
    VID:0403 PID:6010
     Serial Number:
      Manufacturer: FTDI
           Product: Dual RS232
  /dev/tty.usbserial-21400
    Type: USB
    VID:0403 PID:6010
     Serial Number:
      Manufacturer: FTDI
           Product: Dual RS232
[...]
sirhcel commented 1 month ago

Repeating the above test with interface number information enabled, gives no interface number for the FTDI composite device while this info is present for another composite device COM13:

>cargo run --features usbportinfo-interface --example list_ports
[...]
  COM11
    Type: USB
    VID:0403 PID:6010
     Serial Number: 6
      Manufacturer: FTDI
           Product: USB Serial Port (COM11)
         Interface:
  COM12
    Type: USB
    VID:0403 PID:6010
     Serial Number: 6
      Manufacturer: FTDI
           Product: USB Serial Port (COM12)
         Interface:
  COM13
    Type: USB
    VID:1366 PID:0105
     Serial Number: 000123456789
      Manufacturer: Microsoft
           Product: Serielles USB-Gerät (COM13)
         Interface: 00
RossSmyth commented 1 month ago

I have some ideas for possibly improving the situation w.r.t. getting device information but won't be able to work on it for a bit. The idea is basically seeing if the HID API can be used instead.

sirhcel commented 1 month ago

Thank you for chiming in @RossSmyth! No hurry.

I finally found some time to look further into it and it looks to me that there is still room for improving FTDI device enumeration on Windows just by a more educated look at the device identification string and the whole parent stack.

Instrumenting this for list_ports a bit more in test/parent-device-iteration as of 0da2c19 shows the first interesting things:

  1. Device identification stings starting with FTDIBUS don't seem to provide any interface information at all but it is provided by the parent device identification string just starting with USB

    "0: Some(\"FTDIBUS\\\\VID_0403+PID_6010+6&119857B6&0&4&2\\\\0000\")",
    "1: Some(\"USB\\\\VID_0403&PID_6010&MI_01\\\\7&19792F3E&0&0001\")",
  2. The serial number without a suffix (see #178) for a multi-port FTDI device is also reported deeper down the parent chain:

    "0: Some(\"FTDIBUS\\\\VID_0403+PID_6010+FT73U3C5A\\\\0000\")",
    "1: Some(\"USB\\\\VID_0403&PID_6010&MI_00\\\\6&26310911&0&0000\")",
    "2: Some(\"USB\\\\VID_0403&PID_6010\\\\FT73U3C5\")",
    [...]
    "0: Some(\"FTDIBUS\\\\VID_0403+PID_6010+FT73U3C5B\\\\0000\")",
    "1: Some(\"USB\\\\VID_0403&PID_6010&MI_01\\\\6&26310911&0&0001\")",
    "2: Some(\"USB\\\\VID_0403&PID_6010\\\\FT73U3C5\")",

Which leads me to the following things to think about:

  1. We likely need to look deeper into the parent device chain than just one step (like for example pySerial does)

  2. When looking deeper into the parent device chain we should limit the depth of inspection and are likely safe to stop when VID and PID no longer match as for example

    "0: Some(\"FTDIBUS\\\\VID_0403+PID_6010+6&119857B6&0&4&2\\\\0000\")",
    "1: Some(\"USB\\\\VID_0403&PID_6010&MI_01\\\\7&19792F3E&0&0001\")",
    "2: Some(\"USB\\\\VID_0403&PID_6010\\\\6&119857B6&0&4\")",
    "3: Some(\"USB\\\\VID_1A40&PID_0101\\\\5&3B109A5C&0&1\")",
    "4: Some(\"USB\\\\ROOT_HUB30\\\\4&DC91CFA&0&0\")",
  3. May be we should just skip FTDIBUS device identification strings and just look into USB ones for the information we are interested in (as we do for non-FTDI devices)

  4. May be we should add logging support to facilitate diagnosing issues with enumeration in the field when needed

  5. We should likely ignore serial number strings containing ampersands

  6. We should look at how other multi-port devices report serial numbers

  7. If we are going to report FTDI serial numbers without a suffix, we should postpone this until the next mayor release

RossSmyth commented 1 month ago

I'm trying to see if there's a more structured way of retrieving the info we would like rather than parsing the string that in the win32 docs say specifically not to parse. Here's just a dump of things I have found so far:

  1. https://github.com/ruabmbua/hidapi-rs/blob/2f37587cc7d7d56f2cdf3c948abffac860d63b5c/src/windows_native/device_info.rs#L52-L64

I tried this, but the example lshid does not show my FTDI device in the list.

  1. WMI

WMI is a large API that lets you query Windows things like a database. The incantation needed is:

Get-WmiObject -Query "SELECT * FROM Win32_PnPEntity WHERE ClassGuid=`"{4d36e978-e325-11ce-bfc1-08002be10318}`""

Which can be translated to Rust. But it does not have the fields seperated.

  1. WinUSB

https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/usbspec/ns-usbspec-_usb_device_descriptor

This struct has the fields idVendor, idProduct, iManufacturer, & iSerialNumber. I've not tested this yet.

One issue is that the serial number field is a nebulous index that I'm not sure where it indexes.

  1. Registry

This you can just view with RegEdit. The path is Computer\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Enum\FTDIBUS or something else if not FTDI.

This has the same issue of WMI in that the VID & other fields are not seperated out from the HWID.

  1. http://www.naughter.com/enumser.html

A C++ file that shows 10 different ways to get a list of serial ports. Not specific to my goal, but could have inspiration for what to look at.