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 allows 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 .



This is part of my 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  
      | {
      |   "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  
        | {
        |   "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  
        {
           "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"
        }

Any help appreciated!

Replies

The way I typically approach this problem is to turn things upside down. Rather than watch the registry for the specific device showing up, I watch the registry for all serial devices (

IOSerialBSDClient
) and then I filter any found devices to identify the specific one I’m interested in. You have a couple of ways to do that:
  • You can iterate up looking for the node of interest (

    IORegistryEntryCreateIterator
    ,
    kIORegistryIterateParents
    ) and then get properties on that node (
    IORegistryEntryCreateCFProperty
    ).
  • You can use

    IORegistryEntrySearchCFProperty
    to search ‘up’ the registry for a specific property.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

I know I'm late to this IOKit party, but the following Swift code (I'm new to Swift, so point out any better practises if you see I'm doing something horrendously wrong) works for me to retrieve the IODialinDevice...

var dict : Unmanaged<CFMutableDictionary>?       if (IORegistryEntryCreateCFProperties(usbDevice, &dict, kCFAllocatorDefault, 0) == KERN_SUCCESS)       {         let hardDict = (dict?.takeRetainedValue()) as! NSMutableDictionary         DispatchQueue.main.async {           self.deviceLabel.stringValue = self.deviceLabel.stringValue + (hardDict["IODialinDevice"] as! String) + "\n"         }       }

For my test purposes I had the deviceLabel (an NSTextField) updating just to make sure I was receiving Add and Remove Messages.

Hope this helps others as the Apple docs on IOKit are really abysmal in helping people with this low level stuff.

so point out any better practises if you see I'm doing something horrendously wrong

Well, there’s nothing horrendously wrong, but let me suggest a few tweaks:

var dictQ: Unmanaged<CFMutableDictionary>? = nil
let err = IORegistryEntryCreateCFProperties(usbDevice, &dictQ, kCFAllocatorDefault, 0)
guard err == KERN_SUCCESS else {
    … handle error …
}
let dict = dictQ!.takeRetainedValue() as! [String: Any]
guard let dialInDevice = dict["IODialinDevice"] as? String else {
    … handle error …
}

Note:

  • I force unwrap dictQ because the API contract is that it won’t be nil if IORegistryEntryCreateCFProperties succeeds.

  • When working with non-Swift APIs I prefer to get things into ‘Swift space’ as quickly as possible, hence my cast to [String: Any] rather than NSMutableDictionary.

  • I use kIODialinDeviceKey rather than the hard coded strings. To gat that constant, you need to import IOKit.serial.

  • You do want to check the result of the final as? String because it’s possible that the property isn’t their or is of the wrong type (which would be weird but it’s best not to crash in that case).

the Apple docs on IOKit are really abysmal in helping people with this low level stuff

Most of the good I/O Kit docs are in the Documentation Archive. I specifically recommend Accessing Hardware From Applications. With that info, it’s just a ‘simple’ matter of understanding how to call these C APIs from Swift. There’s a bunch of good info on that front in the Language Interoperability topic group on the main Swift doc page.

ps If you use the Code Block button (or triple backquote delimiters) that’ll make your code much easier to read.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"