Search code examples
macosserial-portusbcore-foundationiokit

IOKIT Detecting BSD(unix) name for USB Serial Device with PID and VID


I am working with USB serial devices on macOS.

How can I detect BSD(unix) name have for my USB Serial device on macOS using IOKit? I want to get device name like : "IODialinDevice" = "/dev/tty.usbmodemMyDeviceName"

My USB device is USB serial COM port. Also I want to detect when device was attached to machine.

I can detect when new USB device with my VID and PID was connected. This code allow me to do it

CFMutableDictionaryRef keywordDict = IOServiceMatching(kIOSerialBSDServiceValue);

kern_return_t result = IOServiceGetMatchingServices(kIOMasterPortDefault, keywordDict, &iterator);

while ((port = IOIteratorNext(iterator)))
{
        io_object_t parent = 0;                     
        io_object_t current_device = port;
        while (KERN_SUCCESS == IORegistryEntryGetParentEntry(current_device, kIOServicePlane, &parent))
        {
            CFTypeRef vendor_Id = IORegistryEntryCreateCFProperty(parent, CFSTR(kUSBVendorID), kCFAllocatorDefault, 0);
            CFTypeRef pr_Id = IORegistryEntryCreateCFProperty(parent, CFSTR(kUSBProductID), kCFAllocatorDefault, 0);

            if((vendor_id==MY_VENDOR_ID) && (pr_ID==MY_PRODUCT_ID))
            {
                // MY SERIAL DEVICE DETECTED !! 
                CFTypeRef deviceName = IORegistryEntryCreateCFProperty(device, key, CFSTR(kIOTTYDeviceKey), 0);
                CFTypeRef callOutDevice = IORegistryEntryCreateCFProperty(device, key, CFSTR(kIOCalloutDeviceKey), 0);
                CFTypeRef dialInDevice = IORegistryEntryCreateCFProperty(device, key, CFSTR(kIODialinDeviceKey), 0);

            }
        }
}

But the problem that this code not allows me to detect the new device. I just enumerate existing kIOSerialBSDServiceValue devices here and then I check parents VID and PID If parent have MY_VID and MY_PID I assume that I have found correct serial device.

Another code allows me to detect new USB device

This is Apple example LUSBPrivateDataSample.c I can detect my device using VID and PID with the following code

   int main()
   {
    ...
    ...
    ...

    // Create a CFNumber for the idVendor and set the value in the dictionary
    numberRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &usbVendor);
    CFDictionarySetValue(matchingDict, 
                         CFSTR(kUSBVendorID), 
                         numberRef);
    CFRelease(numberRef);

    // Create a CFNumber for the idProduct and set the value in the dictionary
    numberRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &usbProduct);
    CFDictionarySetValue(matchingDict, 
                         CFSTR(kUSBProductID), 
                         numberRef);
    CFRelease(numberRef);

    ...
    ...
    ...

        // Now set up a notification to be called when a device is first matched by I/O Kit.
    kr = IOServiceAddMatchingNotification(gNotifyPort,                  // notifyPort
                                          kIOFirstMatchNotification,    // notificationType
                                          matchingDict,                 // matching
                                          DeviceAdded,                  // callback
                                          NULL,                         // refCon
                                          &gAddedIter                   // notification
                                          );    

    }

    void DeviceAdded(void *refCon, io_iterator_t iterator)
    {
        io_service_t        usbDevice;
        while ((usbDevice = IOIteratorNext(iterator)))
        {
                // I have device here but I can't get IODialinDevice device name 
        }
    }

The problem with this code that I can't get IODialinDevice device name I have also checked all child nodes for this usbDevice. None of them contain IODialinDevice property or none of them is kIOSerialBSDServiceValue device.

Looks like I am doing it wrong. Looks like serial devices and USB live in different IOKit registry branches. The question is how can go from USB device identified by VID and PID to serial device (kIOSerialBSDServiceValue) with IODialinDevice ("/dev/tty.usbmodemMyDeviceName") property .

Any help appreciated!

Update: This is ioreg tree:
I can detect CDC ACM Data device in my application. (top device in this tree) In ioreg I see this picture. So there are children, AppleUSBACMData and IOSerialBSDClient who has IODialinDevice property. But I can't detect AppleUSBACMData and IOSerialBSDClient in my application . Probably because AppleUSBACMData and IOSerialBSDClient are not devices, but USB Interfaces or USB Endpoints. So this is my problem.

+-o CDC ACM Data@3  <class IOUSBHostInterface, id 0x100090fff, registered, matched, active, busy 0 (514 ms), retain 7>
  | {
  |   "USBPortType" = 0
  |   "IOCFPlugInTypes" = {"2d9786c6-9ef3-11d4-ad51-000a27052861"="IOUSBFamily.kext/Contents/PlugIns/IOUSBLib.bundle"}
  |   "Product Name" = "DevName"
  |   "bcdDevice" = 1044
  |   "USBSpeed" = 3
  |   "idProduct" = 260
  |   "bConfigurationValue" = 1
  |   "bInterfaceSubClass" = 0
  |   "locationID" = 338886656
  |   "IOGeneralInterest" = "IOCommand is not serializable"
  |   "IOServiceLegacyMatchingRegistryID" = 4295561221
  |   "IOClassNameOverride" = "IOUSBInterface"
  |   "AppleUSBAlternateServiceRegistryID" = 4295561221
  |   "idVendor" = 7777
  |   "bInterfaceProtocol" = 0
  |   "bAlternateSetting" = 0
  |   "bInterfaceNumber" = 3
  |   "bInterfaceClass" = 10
  | }
  | 
  +-o AppleUSBACMData  <class AppleUSBACMData, id 0x100091018, registered, matched, active, busy 0 (0 ms), retain 6>
    | {
    |   "IOClass" = "AppleUSBACMData"
    |   "CFBundleIdentifier" = "com.apple.driver.usb.cdc.acm"
    |   "IOProviderClass" = "IOUSBHostInterface"
    |   "IOTTYBaseName" = "usbmodem"
    |   "idProduct" = 260
    |   "IOProbeScore" = 49999
    |   "bInterfaceSubClass" = 0
    |   "HiddenPort" = Yes
    |   "IOMatchCategory" = "IODefaultMatchCategory"
    |   "idVendor" = 7777
    |   "IOTTYSuffix" = "DevName_B3"
    |   "bInterfaceClass" = 10
    | }
    | 
    +-o IOSerialBSDClient  <class IOSerialBSDClient, id 0x100091020, registered, matched, active, busy 0 (0 ms), retain 5>
    {
       "IOClass" = "IOSerialBSDClient" 
        "CFBundleIdentifier" = "com.apple.iokit.IOSerialFamily"
        "IOProviderClass" = "IOSerialStreamSync"
        "IOTTYBaseName" = "usbmodem"
        "IOSerialBSDClientType" = "IORS232SerialStream"
        "IOProbeScore" = 1000
        "IOCalloutDevice" = "/dev/cu.usbmodemDevName_B3"
        "IODialinDevice" = "/dev/tty.usbmodemDevName_B3"
        "IOMatchCategory" = "IODefaultMatchCategory"
        "IOTTYDevice" = "usbmodemDevName_B3"
        "IOResourceMatch" = "IOBSD"
        "IOTTYSuffix" = "DevName_B3"
    }

Solution

  • The solution is quite simple. It always was on my desk. You should subscribe to receive notifications about new kIOSerialBSDServiceValue devices appears in system Like this. This code also based on Apple USBPrivateDataSample.c

    https://developer.apple.com/library/archive/samplecode/USBPrivateDataSample/Introduction/Intro.html#//apple_ref/doc/uid/DTS10000456

    CFMutableDictionaryRef matchingDict = IOServiceMatching(kIOSerialBSDServiceValue);
    ....
        // Now set up a notification to be called when a device is first matched by I/O Kit.
    kr = IOServiceAddMatchingNotification(gNotifyPort,                  // notifyPort
                                          kIOFirstMatchNotification,    // notificationType
                                          matchingDict,                 // matching
                                          DeviceAdded,                  // callback
                                          NULL,                         // refCon
                                          &gAddedIter                   // notification
                                          );
    

    When you get kIOSerialBSDServiceValue (in DeviceAdded() function) traverse up ( using IORegistryEntryGetParentEntry ) on the IOKit tree until your find your VID and PID. (See my first code fragment in question.) If you can't find and device tree is over this is not your device.