pololu / libusbp

The Pololu USB Library (also known as libusbp) is a cross-platform C library for accessing USB devices.
Other
104 stars 32 forks source link

macOS Port is not found for FTDI devices #8

Closed legege closed 2 years ago

legege commented 2 years ago

Hi,

On macOS, because of the interface_number += 1, the port is not found for FTDI devices. Here is how it's reported with ioreg:

+-o FT232R USB UART@14400000  <class IOUSBHostDevice, id 0x100017a14, registered, matched, active, busy 0 (78 ms), retain 25>
  | {
  |   "kUSBSerialNumberString" = "AE01EB7D"
  |   "bDeviceClass" = 0
  |   "bDeviceSubClass" = 0
  |   "iSerialNumber" = 3
  |   "Built-In" = No
  |   "IOServiceDEXTEntitlements" = (("com.apple.developer.driverkit.transport.usb"))
  |   "iProduct" = 2
  |   "USB Serial Number" = "AE01EB7D"
  |   "USB Vendor Name" = "FTDI"
  |   "USBSpeed" = 1
  |   "IOPowerManagement" = {"PowerOverrideOn"=Yes,"CapabilityFlags"=32768,"MaxPowerState"=2,"DevicePowerState"=2,"DriverPowerState"=0,"ChildrenPowerState"=2,"CurrentPowerState"=2}
  |   "bNumConfigurations" = 1
  |   "kUSBProductString" = "FT232R USB UART"
  |   "IOServiceLegacyMatchingRegistryID" = 4295064086
  |   "kUSBVendorString" = "FTDI"
  |   "USB Product Name" = "FT232R USB UART"
  |   "iManufacturer" = 1
  |   "idVendor" = 1027
  |   "Device Speed" = 1
  |   "kUSBCurrentConfiguration" = 1
  |   "idProduct" = 24577
  |   "bcdDevice" = 1536
  |   "sessionID" = 19421559036399
  |   "USB Address" = 6
  |   "non-removable" = "no"
  |   "IOCFPlugInTypes" = {"9dc7b780-9ec0-11d4-a54f-000a27052861"="IOUSBHostFamily.kext/Contents/PlugIns/IOUSBLib.bundle"}
  |   "IOClassNameOverride" = "IOUSBDevice"
  |   "USBPortType" = 0
  |   "bDeviceProtocol" = 0
  |   "locationID" = 339738624
  |   "kUSBAddress" = 6
  |   "bcdUSB" = 512
  |   "IOGeneralInterest" = "IOCommand is not serializable"
  |   "bMaxPacketSize0" = 8
  | }
  | 
  +-o AppleUSBHostLegacyClient  <class AppleUSBHostLegacyClient, id 0x100017a17, !registered, !matched, active, busy 0, retain 8>
  |   {
  |     "IOPowerManagement" = {"DevicePowerState"=0,"CurrentPowerState"=1,"CapabilityFlags"=65536,"MaxPowerState"=2,"DriverPowerState"=1}
  |   }
  |   
  +-o AppleUSBHostCompositeDevice  <class AppleUSBHostCompositeDevice, id 0x100017a1b, !registered, !matched, active, busy 0, retain 4>
  |   {
  |     "IOClass" = "AppleUSBHostCompositeDevice"
  |     "CFBundleIdentifier" = "com.apple.driver.usb.AppleUSBHostCompositeDevice"
  |     "IOProviderClass" = "IOUSBHostDevice"
  |     "kUSBPreferredConfiguration" = 1
  |     "IOProbeScore" = 50000
  |     "IOMatchedAtBoot" = Yes
  |     "IOMatchCategory" = "IODefaultMatchCategory"
  |     "bDeviceSubClass" = 0
  |     "IOPersonalityPublisher" = "com.apple.driver.usb.AppleUSBHostCompositeDevice"
  |     "CFBundleIdentifierKernel" = "com.apple.driver.usb.AppleUSBHostCompositeDevice"
  |     "IOPrimaryDriverTerminateOptions" = Yes
  |     "bDeviceClass" = 0
  |   }
  |   
  +-o FT232R USB UART@0  <class IOUSBHostInterface, id 0x100017a1d, registered, matched, active, busy 0 (13 ms), retain 8>
    | {
    |   "USBSpeed" = 1
    |   "iInterface" = 2
    |   "IOServiceLegacyMatchingRegistryID" = 4295064094
    |   "bInterfaceProtocol" = 255
    |   "bAlternateSetting" = 0
    |   "idProduct" = 24577
    |   "bcdDevice" = 1536
    |   "USB Interface Name" = "FT232R USB UART"
    |   "USB Product Name" = "FT232R USB UART"
    |   "locationID" = 339738624
    |   "bInterfaceClass" = 255
    |   "bInterfaceSubClass" = 255
    |   "IOCFPlugInTypes" = {"2d9786c6-9ef3-11d4-ad51-000a27052861"="IOUSBHostFamily.kext/Contents/PlugIns/IOUSBLib.bundle"}
    |   "USBPortType" = 0
    |   "kUSBString" = "FT232R USB UART"
    |   "bInterfaceNumber" = 0
    |   "bConfigurationValue" = 1
    |   "USB Vendor Name" = "FTDI"
    |   "idVendor" = 1027
    |   "IOServiceDEXTEntitlements" = (("com.apple.developer.driverkit.transport.usb"))
    |   "bNumEndpoints" = 2
    |   "IODEXTMatchCount" = 1
    |   "USB Serial Number" = "AE01EB7D"
    |   "IOGeneralInterest" = "IOCommand is not serializable"
    |   "IOClassNameOverride" = "IOUSBInterface"
    | }
    | 
    +-o AppleUSBFTDI  <class IOUserSerial, id 0x100017a1f, registered, matched, active, busy 0 (0 ms), retain 11>
      | {
      |   "IOClass" = "IOUserSerial"
      |   "CFBundleIdentifier" = "com.apple.DriverKit-AppleUSBFTDI"
      |   "IOProviderClass" = "IOUSBHostInterface"
      |   "IOUserServerCDHash" = "e213b87cfe13c360790f296eb70a1d2f2868de1b"
      |   "IOServiceDEXTEntitlements" = "com.apple.developer.driverkit.family.serial"
      |   "IOTTYBaseName" = "usbserial-"
      |   "idProduct" = 24577
      |   "IOProbeScore" = 89999
      |   "bConfigurationValue" = 1
      |   "IOMatchedPersonality" = {"bInterfaceNumber"=0,"IOUserClass"="AppleUSBFTDI","IOProviderClass"="IOUSBHostInterface","IOClass"="IOUserSerial","IOUserServerCDHash"="e213b87cfe13c360790f296eb70a1d2f2868de1b","CFBundleIdentifierKernel"="com.apple.driver.driverkit.serial","idProduct"=24577,"CFBundleIdentifier"="com.apple.DriverKit-AppleUSBFTDI","bConfigurationValue"=1,"idVendor"=1027,"IOUserServerName"="com.apple.DriverKit.AppleUSBFTDI"}
      |   "IOUserServerName" = "com.apple.DriverKit.AppleUSBFTDI"
      |   "HiddenPort" = Yes
      |   "IOMatchCategory" = "IODefaultMatchCategory"
      |   "IOPowerManagement" = {"CapabilityFlags"=2,"MaxPowerState"=2,"CurrentPowerState"=2}
      |   "idVendor" = 1027
      |   "CFBundleIdentifierKernel" = "com.apple.driver.driverkit.serial"
      |   "IOTTYSuffix" = "AE01EB7D"
      |   "bInterfaceNumber" = 0
      |   "IOUserClass" = "AppleUSBFTDI"
      | }
      | 
      +-o IOSerialBSDClient  <class IOSerialBSDClient, id 0x100017a22, registered, matched, active, busy 0 (0 ms), retain 5>
          {
            "IOClass" = "IOSerialBSDClient"
            "CFBundleIdentifier" = "com.apple.iokit.IOSerialFamily"
            "IOProviderClass" = "IOSerialStreamSync"
            "IOTTYBaseName" = "usbserial-"
            "IOSerialBSDClientType" = "IOSerialStream"
            "IOProbeScore" = 1000
            "IOResourceMatch" = "IOBSD"
            "IOMatchedAtBoot" = Yes
            "IOMatchCategory" = "IODefaultMatchCategory"
            "IOTTYDevice" = "usbserial-AE01EB7D"
            "IOCalloutDevice" = "/dev/cu.usbserial-AE01EB7D"
            "IODialinDevice" = "/dev/tty.usbserial-AE01EB7D"
            "IOPersonalityPublisher" = "com.apple.iokit.IOSerialFamily"
            "CFBundleIdentifierKernel" = "com.apple.iokit.IOSerialFamily"
            "IOTTYSuffix" = "AE01EB7D"
          }
DavidEGrayson commented 2 years ago

Ah, thanks for pointing that out. It looks like I knew at the time that code was kind of brittle. You can of course pass interface_number-1 to libusbp_serial_port_create as a workaround, but maybe we should think of some better way to do this.

legege commented 2 years ago

Could you detail the reason for the +1? Would you have ioreg for that device?

DavidEGrayson commented 2 years ago

I think I wanted the behavior of that function to be consistent on Windows, Linux, and macOS. The only devices I had around to test were CDC ACM devices. I don't have an ioreg printout but you can see CDC ACM USB descriptors in many different open source projects, including this one (note how it has two interfaces):

https://github.com/pololu/wixel-sdk/blob/master/libraries/src/usb_cdc_acm/usb_cdc_acm.c

For some reason, the way the macOS is designed, the serial port gets associated with the data interface which is usually (or maybe always) the second interface. For the other operating systems I think it is associated with the control interface.

Are you asking for an ioreg prinout so that you can think if there is a way to improve the code? One idea would be to detect the CDC case we are concerned about and only increment the interface number for those devices.

DavidEGrayson commented 2 years ago

The ioreg output in this thread matches the situation I remember. I found it by searching for "AppleUSBACMData ioreg":

https://bytemeta.vip/repo/RfidResearchGroup/proxmark3/issues/1616

legege commented 2 years ago

Ok, thanks for the context! By reading your comment from lsport.cpp, would you agree that the root of this challenge is coming from the "API" that requires the user to know the interface number of the USB device in advance to determine its path(s)? I get to this conclusion because of this:

// Note: This example is slow and ugly because libusbp does not yet have // built-in support for listing serial ports; it only has support for finding // a serial port if you already know what USB device it is connected to and what // interface you expect the port to be on. This might be improved in the future.

For the use case I'm working on (https://gitlab.com/CarbonCollins/nomad-usb-device-plugin/-/issues/9), it would actually be helpful to have a method returning all the interfaces + path of a USB device. For Mac, would it be possible to scan for all IOSerialBSDClient from your experience?

DavidEGrayson commented 2 years ago

I think the current API is fine and it's pretty close to what you would want if you know what kind of device you have, and you want to connect to a specific serial port on it. (Composite devices can have multiple serial ports and specifying an interface number is how we distinguish them.) The fact that the interface numbering is not correct with FTDI devices is a bug I would like to fix though.

For applications like lsport where you don't know what type of device you have and you just want to list everything, it would be beneficial to some day add another libusbp API function that finds all the serial ports of the device, along with the interface numbers.

Once you have an io_service_t representing the USB device, you should be able to scan for all of its IOSerialBSDClient descendents. The function service_get_child_by_class basically does that, except that function only returns the first matching child, and I only use it to scan a USB interface, not a whole device (but I don't see why it wouldn't work if you pass a USB device to it).

DavidEGrayson commented 2 years ago

I'm pretty sure commit 9f4ae10 (which I just pushed to the master branch) fixes your issue. Do you want to try it out and tell me your results?