IOServiceNameMatching can't find dext service

I'm using the following code to find the dext service. The driver is enabled in iOS settings prior to launching the app.

    io_service_t mService = IO_OBJECT_NULL;
    kern_return_t ret = kIOReturnSuccess;
    io_iterator_t iterator = IO_OBJECT_NULL;


    if (__builtin_available(iOS 15.0, *)) {
        ret = IOServiceGetMatchingServices(kIOMainPortDefault, IOServiceNameMatching("MyDriver"), &iterator);
    } else {
        // Fallback on earlier versions
    }
    if (ret != kIOReturnSuccess)
    {
        printf("Unable to find service");
    }
    
    while ((mService = IOIteratorNext(iterator)) != IO_OBJECT_NULL)
    {
        //Only able to find service if launching the app first and then connecting the device
        ..........
    }

I noticed the call IOServiceNameMatching doesn't return the same result for the following workflows:

  1. Launch the app first and then connect the device, IOServiceGetMatchingServices can find the service.

  2. Connect the device to USB-C port first, then launch the app, the same call can't find a matching service (iterator is null). I would need to disconnect and reconnect the device while the app is running in order to find the matching dext.

Any suggestion on how to find the matching dext service for workflow #2?

Thanks

Answered by DTS Engineer in 803991022

This is an app we develop for iPadOS so I guess the IORegistryExplorer.app doesn't work in this case.

My actual recommendation is that even if you have no intention of every shipping any product for macOS... you still get your DEXT loading on running on macOS and, at least initially, focus on making everything work in that environment. Practically speaking, DriverKit for iPadOS is a subset of DriverKit for macOS broadly functions in the same way. macOS does introduce some additional complexity and edge cases, but I don't think there's any case where a DEXT will work on macOS but NOT on iPadOS (ignoring DriverKit families that aren't supported on on iPadOS). More to the point, macOS has better tools for investigation and debugging, which is why I'd start with it.

Giving a high level answer on this point:

I’m wondering if anything can be done to get it to work for the workflows I mentioned in #1 and #3 above.

  • I think case #1 is a bug in your code. I'm not sure what exactly that issue is (see below), but this is something that should work.

  • Case #3 depends on exactly what drivers are actually loading when your DEXT isn't present. My guess is that this is either a variant of #1 (so fixing #1 will also resolve #3) or that the system driver can't unload (in which case, the user would need to hot plug the device before you DEXT will match).

Covering a few specific details:

  1. With the driver up and running, if I disabled and reenabled the driver in iOS settings without disconnecting/reconnecting the device, MyDriver Start() wouldn't get called, and get matching service with IOServiceMatching("IOUserService") wouldn't find it again.

Two things to look at here:

  1. Was your driver able to teardown and destroy itself properly? If your DEXT failed to unload cleanly, then it's possible you never removed entirely, which is why you can't reload.

  2. If you managed to unload correctly, then it's possible that the system driver (which replace your DEXT) isn't unloading when you reenable your DEXT. The actual behavior here will depend on exactly what driver(s) end up attaching to the device without you DEXT being present, but there are many devices where the expected behavior is that a newly enabled DEXT won't be able to control the device until the device has been hot plugged. Historically, this was actually why so many KEXT installers asked for the system to be rebooted after installation. It wasn't that the driver COULDN'T work without reboot, it was that it was easier to tell the user "reboot after install" instead of trying to explain the nuance of which cases work and which don't.

FYI, both of these are things I would specifically investigate on macOS (NOT iPadOS), primarily because IORegistryExplorer makes it so easy to see exactly what's happening in the driver stack.

  1. When the USB device is connected before launching the app (with driver already enabled in iOS Settings), I notice my Driver Start() doesn't get called, which results in "MyDriver" not showing up when I try to get matching service with IOServiceMatching("IOUserService").

The first thing I would look at here is to make sure you're CERTAIN that's exactly what's going on. From direct, personal experience, it's very easy to misunderstand exactly what's going on, waste hours investigating the "problem", only to eventually determine that the problem you THOUGHT you had was simply not happening at all. The behavior you're describing above ("DEXT only matches and runs when my app is running") is very odd and not normal system behavior. That means one of two things is most likely going on:

  1. (unlikely) You've written your DEXT such that this is in fact "the way it works". (I think this is tricky but possible, however, I think it would be hard to implement by "accident")

  2. (more likely) Your DEXT is loading, but you're not able to find or connect to it because of some other issue/factor. (more on that below)

showing up when I try to get matching service with IOServiceMatching("IOUserService").

Frist off, note that you should definitely NOT be using a "generic" name my "MyDriver". I would recommend using the IOKit class naming convention, which is to use our standard reverse DNS/bundle ID sheme, but replacing "." with "_". In concrete terms:

Website-> example.com
BundleID-> com.example.dext.coolusbdevice
Class Name-> com_example_dext_coolusbdevice

More importantly, you should not be matching against "IOUserService", you should be matching against your driver's call name. IOUserService is the general base class used for DEXTs support (it's basically equivalent to IOService), which means you could end up with a bunch of "extra" returns that you don't need or care about. More to the point, in your while loop here:

    while ((mService = IOIteratorNext(iterator)) != IO_OBJECT_NULL)
    {
        //Only able to find "MyDriver" if launching the app first and then connecting the device
        ret = IOServiceOpen(mService, mach_task_self_, 0, &mConnection);
        ..........
    }

...what does it do if "IOServiceOpen" returns an error? If you're breaking out of the loop when you get an error, then that might explain both the success and failure. Plugging the device in after your app is running means you're going to be the most "recent" match, which means it's more likely to be the first return* in the io_iterator_t. Having it already plugged in means that other activity can have occurred, so the code above is going to fail on IOServiceOpen.

As a side note, there's a function "KE_PrintIOObject()"in this post that I would recommend copying as a diagnostic aid. It's far more useful on macOS (where the registry is broadly accessible), but it will provide at least some insight into the object(s) the system returned to you.

I have no problem matching on the device using IOServiceMatching("IOUSBDevice") and idVendor + idProduct as keys. It doesn’t matter if the device is connected before or after launching the app, matching on the device always returns the correct value. It is the matching of the driver that I'm having problems with.

Note that if you're able to find your specific accessory, you can try manually walk "up" the registry using IORegistryEntryGetChildEntry/IORegistryEntryGetChildIterator. That might let you "see" what drivers were loading above your device in all of the cases you're looking at.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

So, my first thought here is that using "IOServiceNameMatching" is a mistake. Architecturally, the IOService "name" is basically a text string an IOService object can "attach" to itself. WHY it can do so... is not a question I can answer. It's mildly useful directly browsing the registry or when printing debugging information, but it's not something I'd recommend using for matching.

The way you'd typically handle matching is by using "IOServiceMatching" to match a particular driver class, then refining further using other keys. I have code for that in this forum post.

Any suggestion on how to find the matching dext service for workflow #2?

Aside from the code above, the other place to start here is with "IORegistryExplorer.app". If you're not familiar with that app, you can find it "Additional Tools for Xcode" from our "More Downloads" section.

IORegistryExplorer.app provides a graphical "browser" ("ioreg" is the command line tool equivalent) for the entire IORegistry, letting you search the tree, navigate the hierarchy, view properties, etc. It's the best tool I've found for understanding how IOKit actually works and what's actually happening on your system. A few quick tips when using it:

  • The first time you use this app can be a bit overwhelming. Start for searching for whatever you're "interested" in (like "USB"), select the object you want to look at, then clear the search field to restore the "full" registry. You can then browse that "section" yourself.

  • The pop up button in the top left corner changes the plane you're viewing. It can be very confusing if you flip it by accident and 99% of the time you'll want "IOService".

  • The app can save an view registry "snapshots", which is really useful when you're trying to share information or diagnose issues. Note that while the files themselves are fairly large (~50mb), they're basically plain text files, so they compress VERY well. They're easy to share once they're zip'd.

  • The app maintains a "live" view of the registry and, depending on what's happening on your system, that can cause interface glitches or stuttering. You can use "Save" to create a snapshot, which will lock the state down and make everything a bit easier to browse.

In terms of investigating the specific failure here:

I noticed the call IOServiceNameMatching doesn't return the same result for the following workflows:

  1. Launch the app first and then connect the device, IOServiceGetMatchingServices can find the service.

  2. Connect the device to USB-C port first, then launch the app, the same call can't find a matching service (iterator is null). I would need to disconnect and reconnect the device while the app is running in order to find the matching dext.

One of two things is going on here:

  1. In the initial (#1) run, your DEXT isn't actually loading because something else is. Why exactly that would be the case depends on exactly what driver loaded instead.

  2. Your DEXT is loading in both cases, but in the first run it's getting a completely different name.

I suspect that #2 is actually the issue, however, you'd investigate both cases by capturing a snapshots from both state, then finding the difference(s) between them.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

Thank you for your comment.

This is an app we develop for iPadOS so I guess the IORegistryExplorer.app doesn't work in this case.

Just a bit more info on the problem we are facing.

  1. When the USB device is connected before launching the app (with driver already enabled in iOS Settings), I notice my Driver Start() doesn't get called, which results in "MyDriver" not showing up when I try to get matching service with IOServiceMatching("IOUserService"). Others such as AppleBCMWLANBusInterfacePCIe, IO80211ReporterProxy, AppleBCMWLANCore are available but MyDriver. I tried using match notifications but got the same result as IOServiceGetMatchingServices.

  2. If I launch the app first and then connect my USB device, MyDriver Start() is called and MyDriver is available. This allows me to find the driver and call IOServiceOpen on it. Everything then works as expected.

  3. With the driver up and running, if I disabled and reenabled the driver in iOS settings without disconnecting/reconnecting the device, MyDriver Start() wouldn't get called, and get matching service with IOServiceMatching("IOUserService") wouldn't find it again.

I have no problem matching on the device using IOServiceMatching("IOUSBDevice") and idVendor + idProduct as keys. It doesn’t matter if the device is connected before or after launching the app, matching on the device always returns the correct value. It is the matching of the driver that I'm having problems with.

In the driver info.plist file we use IOUSBHostDevice for IOProviderClass since our device is a non-HID USB device. From what I’m seeing, looks like the device would need to be physically connected after launching the app for the driver to start and the matching to happen properly. I’m wondering if anything can be done to get it to work for the workflows I mentioned in #1 and #3 above.

Thanks!

MyDriver Info.plist file

<plist version="1.0">
<dict>
    <key>IOKitPersonalities</key>
    <dict>
        <key>MyDriver</key>
        <dict>
            <key>CFBundleIdentifier</key>
            <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
            <key>CFBundleIdentifierKernel</key>
            <string>com.apple.kpi.iokit</string>
            <key>CFBundleName</key>
            <string>$(PRODUCT_NAME)</string>
            <key>IOClass</key>
            <string>IOUserService</string>
            <key>IOMatchCategory</key>
            <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
            <key>IOProviderClass</key>
            <string>IOUserResources</string>
            <key>IOResourceMatch</key>
            <string>IOKit</string>
            <key>IOUserClass</key>
            <string>MyDriver</string>
            <key>IOUserServerName</key>
            <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
            <key>MyDriverUserClientProperties</key>
            <dict>
                <key>IOClass</key>
                <string>IOUserUserClient</string>
                <key>IOUserClass</key>
                <string>MyDriverClient</string>
            </dict>
            <key>bConfigurationValue</key>
            <integer>1</integer>
            <key>bInterfaceNumber</key>
            <integer>0</integer>
            <key>idProduct</key>
            <integer>1234</integer>
            <key>idVendor</key>
            <integer>5678</integer>
        </dict>
    </dict>
</dict>
</plist>

MyDriver.entitlements

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>com.apple.developer.driverkit</key>
    <true/>
    <key>com.apple.developer.driverkit.transport.usb</key>
    <array>
        <dict/>
    </array>
    <key>com.apple.security.app-sandbox</key>
    <true/>
</dict>
</plist>

code to get matching service and open service

  io_service_t mService = IO_OBJECT_NULL;
    kern_return_t ret = kIOReturnSuccess;
    io_iterator_t iterator = IO_OBJECT_NULL;

    //temporary checking for all services
    //I refined it further using other keys
    CFMutableDictionaryRef matchingDict = IOServiceMatching("IOUserService");
    
    if (__builtin_available(iOS 15.0, *)) {
        ret = IOServiceGetMatchingServices(kIOMainPortDefault, matchingDict, &iterator);
    } else {
        // Fallback on earlier versions
    }
    if (ret != kIOReturnSuccess)
    {
        printf("Unable to find service");
    }
    
    while ((mService = IOIteratorNext(iterator)) != IO_OBJECT_NULL)
    {
        //Only able to find "MyDriver" if launching the app first and then connecting the device
        ret = IOServiceOpen(mService, mach_task_self_, 0, &mConnection);
        ..........
    }
Accepted Answer

This is an app we develop for iPadOS so I guess the IORegistryExplorer.app doesn't work in this case.

My actual recommendation is that even if you have no intention of every shipping any product for macOS... you still get your DEXT loading on running on macOS and, at least initially, focus on making everything work in that environment. Practically speaking, DriverKit for iPadOS is a subset of DriverKit for macOS broadly functions in the same way. macOS does introduce some additional complexity and edge cases, but I don't think there's any case where a DEXT will work on macOS but NOT on iPadOS (ignoring DriverKit families that aren't supported on on iPadOS). More to the point, macOS has better tools for investigation and debugging, which is why I'd start with it.

Giving a high level answer on this point:

I’m wondering if anything can be done to get it to work for the workflows I mentioned in #1 and #3 above.

  • I think case #1 is a bug in your code. I'm not sure what exactly that issue is (see below), but this is something that should work.

  • Case #3 depends on exactly what drivers are actually loading when your DEXT isn't present. My guess is that this is either a variant of #1 (so fixing #1 will also resolve #3) or that the system driver can't unload (in which case, the user would need to hot plug the device before you DEXT will match).

Covering a few specific details:

  1. With the driver up and running, if I disabled and reenabled the driver in iOS settings without disconnecting/reconnecting the device, MyDriver Start() wouldn't get called, and get matching service with IOServiceMatching("IOUserService") wouldn't find it again.

Two things to look at here:

  1. Was your driver able to teardown and destroy itself properly? If your DEXT failed to unload cleanly, then it's possible you never removed entirely, which is why you can't reload.

  2. If you managed to unload correctly, then it's possible that the system driver (which replace your DEXT) isn't unloading when you reenable your DEXT. The actual behavior here will depend on exactly what driver(s) end up attaching to the device without you DEXT being present, but there are many devices where the expected behavior is that a newly enabled DEXT won't be able to control the device until the device has been hot plugged. Historically, this was actually why so many KEXT installers asked for the system to be rebooted after installation. It wasn't that the driver COULDN'T work without reboot, it was that it was easier to tell the user "reboot after install" instead of trying to explain the nuance of which cases work and which don't.

FYI, both of these are things I would specifically investigate on macOS (NOT iPadOS), primarily because IORegistryExplorer makes it so easy to see exactly what's happening in the driver stack.

  1. When the USB device is connected before launching the app (with driver already enabled in iOS Settings), I notice my Driver Start() doesn't get called, which results in "MyDriver" not showing up when I try to get matching service with IOServiceMatching("IOUserService").

The first thing I would look at here is to make sure you're CERTAIN that's exactly what's going on. From direct, personal experience, it's very easy to misunderstand exactly what's going on, waste hours investigating the "problem", only to eventually determine that the problem you THOUGHT you had was simply not happening at all. The behavior you're describing above ("DEXT only matches and runs when my app is running") is very odd and not normal system behavior. That means one of two things is most likely going on:

  1. (unlikely) You've written your DEXT such that this is in fact "the way it works". (I think this is tricky but possible, however, I think it would be hard to implement by "accident")

  2. (more likely) Your DEXT is loading, but you're not able to find or connect to it because of some other issue/factor. (more on that below)

showing up when I try to get matching service with IOServiceMatching("IOUserService").

Frist off, note that you should definitely NOT be using a "generic" name my "MyDriver". I would recommend using the IOKit class naming convention, which is to use our standard reverse DNS/bundle ID sheme, but replacing "." with "_". In concrete terms:

Website-> example.com
BundleID-> com.example.dext.coolusbdevice
Class Name-> com_example_dext_coolusbdevice

More importantly, you should not be matching against "IOUserService", you should be matching against your driver's call name. IOUserService is the general base class used for DEXTs support (it's basically equivalent to IOService), which means you could end up with a bunch of "extra" returns that you don't need or care about. More to the point, in your while loop here:

    while ((mService = IOIteratorNext(iterator)) != IO_OBJECT_NULL)
    {
        //Only able to find "MyDriver" if launching the app first and then connecting the device
        ret = IOServiceOpen(mService, mach_task_self_, 0, &mConnection);
        ..........
    }

...what does it do if "IOServiceOpen" returns an error? If you're breaking out of the loop when you get an error, then that might explain both the success and failure. Plugging the device in after your app is running means you're going to be the most "recent" match, which means it's more likely to be the first return* in the io_iterator_t. Having it already plugged in means that other activity can have occurred, so the code above is going to fail on IOServiceOpen.

As a side note, there's a function "KE_PrintIOObject()"in this post that I would recommend copying as a diagnostic aid. It's far more useful on macOS (where the registry is broadly accessible), but it will provide at least some insight into the object(s) the system returned to you.

I have no problem matching on the device using IOServiceMatching("IOUSBDevice") and idVendor + idProduct as keys. It doesn’t matter if the device is connected before or after launching the app, matching on the device always returns the correct value. It is the matching of the driver that I'm having problems with.

Note that if you're able to find your specific accessory, you can try manually walk "up" the registry using IORegistryEntryGetChildEntry/IORegistryEntryGetChildIterator. That might let you "see" what drivers were loading above your device in all of the cases you're looking at.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

thanks a lot for the detailed answer!

I found a bug in our driver client free() function (double releasing a reference to a callback Action) that caused the driver to crash when the app was closed. Since the driver wasn't cleaned up properly, when I tried to relaunch the app with the usbc device still connected, my driver couldn’t reload.

Fixing this bug helped fix the issue we were seeing with the workflow in case #1.

Case #3 depends on exactly what drivers are actually loading when your DEXT isn't present. My guess is that this is either a variant of #1 (so fixing #1 will also resolve #3) or that the system driver can't unload (in which case, the user would need to hot plug the device before you DEXT will match).

If you managed to unload correctly, then it's possible that the system driver (which replace your DEXT) isn't unloading when you reenable your DEXT.

with the fix for case#1, case #3 still doesn't work properly. I tried to walk up the registry and noticed when I disabled my driver with the device still connected, AppleUSBHostCompositeDevice ended up matching with the device, and it didn't get unloaded when I reenabled my driver.

We display a message asking the user to disconnect and reconnect the device but just wondering if anything else can be done for this particular case.

Thanks a lot!

with the fix for case#1, case #3 still doesn't work properly. I tried to walk up the registry and noticed when I disabled my driver with the device still connected, AppleUSBHostCompositeDevice ended up matching with the device, and it didn't get unloaded when I reenabled my driver.

We display a message asking the user to disconnect and reconnect the device but just wondering if anything else can be done for this particular case.

No, not really*. In theory the USB stack could implement something like this, however, there are two major issues with it:

  1. The way drivers are layered makes it much more complicated than it looks. A HID device needs to be handled very differently than a mass storage device, but the "base" USB stack intentionally operates in relatively "generic" terms.

  2. Lots of USB device/drivers don't handle this way. USB device operate as complex state machines, so you can't simply "swap" one driver for another (as the device and driver would be in different states). You can reset things... but there is a long history of devices doing weird/broken things when they're unexpectedly reset.

Basically, the issues #2 would end up exposing weird edge cases, which you'd end up solving by hot plugging, so it's always been easier to say "hot plug the device".

*Strictly speaking, the IOUSBHost framework can be used to "force" the kind of reset #2 would involved, after which a newly enabled DEXT would load. However, IOUSBHost is only available on macOS and, in practice, most apps on the mac end up telling the user to hot plug.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

thanks for the info!

IOServiceNameMatching can't find dext service
 
 
Q