Driver Activation failure error code 9. Maybe Entitlements? Please help

This is my first driver and I have had the devil of a time trying to find any information to help me with this. I beg help with this, since I cannot find any tutorials that will get me over this problem.

I am attempting to write a bridging driver for an older UPS that only communicates via RPC-over-USB rather than the HID Power Device class the OS requires. I have written the basic framework for the driver (details below) and am calling OSSystemExtensionRequest.submitRequest with a request object created by OSSystemExtensionRequest.activationRequest, but the didFailWithError callback is called with OSSystemExtensionErrorDomain of a value of 9, which appears to be a general failure to activate the driver. I can find no other information on how to address this issue, but I presume the issue is one of entitlements in either the entitlements file or Info.plist. I will have more code-based details below.

For testing context, I am testing this on a 2021 iMac (M1) running Sequoia 15.7, and this iMac is on MDM, specifically Jamf. I have disabled SIP and set systemextensionsctl developer on, per the instructions here, and I have compiled and am attempting to debug the app using xcode 26.2. The driver itself targets DriverKit 25, as 26 does not appear to be available in xcode despite hints on google that it's out.


For the software, I have a two-target structure in my xcode project, the main Manager app, which is a swift-ui app that both handles installation/activation of the driver and (if that finally manages to work) handles communication from the driver via its UserClient, and the driver which compiles as a dext. Both apps compile and use automated signing attached to our Apple Development team.

I won't delve into the Manager app much, as it runs even though activation fails, except to include its entitlements file in case it proves relevant

<dict>
	<key>com.apple.developer.driverkit.communicates-with-drivers</key>
	<true/>
	<key>com.apple.developer.system-extension.install</key>
	<true/>
	<key>com.apple.security.app-sandbox</key>
	<true/>
	<key>com.apple.security.files.user-selected.read-only</key>
	<true/>
</dict>

and the relevant activation code:

func request(_ request: OSSystemExtensionRequest, didFailWithError error: any Error) {
    // handling the error, which is always code value 9
}

func activateDriver() {
    let request = OSSystemExtensionRequest.activationRequest(forExtensionWithIdentifier: "com.mycompany.driver.bundle.identifier", queue: .main)
    request.delegate = self
    OSSystemExtensionManager.shared.submitRequest(request) 
    //...
}

And finally the Manager app has the following capabilities requested for its matching identifier in our Apple Developer Account:

  • DriverKit Communicates with Drivers
  • System Extension

On the Driver side, I have two major pieces, the main driver class MyDriver, and UserClient class, StatusUserClient. MyDriver derives from IDriverKit/IOService.iig but (in case this is somehow important) does not have the same name as the project/target name MyBatteryDriver. StatusUserClient derives from DriverKit/IOUserClient.iig. I have os_log(OS_LOG_DEFAULT, "trace messages") code in every method of both classes, including the initializers and Start implementations, and the log entries never seem to show up in Console, so I presume that means the OS never tried to load the driver.

Unless I'm looking in the wrong place?

Because I don't think the driver code is the current issue, I won't go into it unless it becomes necessary. As I mentioned above, I think this is a code signing / entitlements issue, but I don't know how to resolve it.

In our Apple Developer account, the Driver's matching identifier has the following capabilities requested:

  • DriverKit (development)
  • DriverKit Allow Any UserClient (development)
  • DriverKit Family HID Device (development) -- NOTE: this is planned for future use, but not yet implemented by my driver code. Could that be part of the problem?
  • DriverKit Transport HID (development)
  • DriverKit USB Transport (development)
  • DriverKit USB Transport - VendorID -- submitted, no response from Apple yet
  • HID Virtual Device -- submitted, no response from Apple. yet. This is vestigial from an early plan to build the bridge via shared memory funneling to a virtual HID device. I think I've found a way to do it with one Service, but... not sure yet. Still, that's a problem for tomorrow.

Apparently I've gone over the 7000 character maximum so I will add my entitlements and info.plist contents in a reply.

Answered by DTS Engineer in 874138022

First off, a clarification here:

I am attempting to write a bridging driver for an older UPS that only communicates via RPC-over-USB rather than the HID Power Device class the OS requires.

What's your final goal here? That is, are you:

  • Trying to create a "seamless" bridge to HID and then relying on the system’s support.

OR

  • Direct communication via user client to your own app.

  • In the first case, how "seamless" do you actually want this to be? Do you not want any app "at all"?

The issue here is that you don't actually need to use DriverKit to talk to a USB device. You could also use the USBHost framework, which would let your app just "talk" to the device. Unless you're specifically bridging to HID, then I wouldn't use DriverKit at all.

In addition, if you're planning to bridge to HID but you're also planning to have an app/daemon, then you could also do this by using the USBHost framework to communicate with your device and a virtual HID device to expose that device to the system.

The right choice here really depends on your final product goals. If your goal is just to expose the device via HID, then DriverKit is probably your best choice. However, as your final product "expands", I think the path above makes more and more sense, both for ease of development and general "flexibility". Note that "ease of development" is NOT a minor difference here- DriverKit is not an easy API to work with, not to mention the added development friction inherent in DEXT/KEXT development.

In any case, getting into the details:

For testing context, I am testing this on a 2021 iMac (M1) running Sequoia 15.7, and this iMac is on MDM, specifically Jamf.

Before you do anything else, I would make sure that your MDM configuration doesn't block DEXT loading. Your DEXT isn't properly configured (more on that below), but you don't want to end up wasting a bunch of time because MDM is blocking a valid configuration.

Next:

I have disabled SIP and set systemextensionsctl developer on, per the instructions here, and I have compiled and am attempting to debug the app using Xcode 26.2.

So, the first step here is to reenable SIP and basically ignore all of the instructions on our website when it comes to DriverKit testing. Technically, I think they still work [1], but the new flow is easier and (mostly) just works "better".

That leads to here:

<dict>
	<key>com.apple.developer.driverkit</key>
	<true/>
	<key>com.apple.developer.driverkit.transport.usb</key>
    <array>
        <dict>
            <key>idVendor</key>
            <integer>*</integer> <!-- I've also tried hex and decimal values here. Wildcard was a last-ditch hope --> 
        </dict>
    </array>
</dict>

This is actually the correct configuration for the development entitlement, as the "*" means "allow this DEXT as an eligible match for any vendor". Note that this is NOT the configuration you'll actually end up shipping, just the configuration you'll use for development. See this post for guidance on production signing.

That leads to here:

As I mentioned above, I think this is a code signing / entitlements issue, but I don't know how to resolve it.

Actually, I don't think that's the case. There are instructions on validating your codesign configuration here, but if your entitlement configuration is to "wrong”, then you'll end up getting a build failure.

Shifting to what is the problem:

The driver's Info.plist originally looked like this:

So, your post gave me a good excuse to do pull together a more thorough run down on DEXT matching, which you can find here. That article has the more detail on what's actually going on, but the specifics are:

(1)
Your IOProviderClass is wrong, you need to be matching against the USB device/interface you actually want to "use".

<key>IOProviderClass</key>
<string>IOUserResources</string>

(2)
You should not be including IOMatchCategory at all. It isn't causing the current failure, but it would cause problems once you actually matched.

<key>IOMatchCategory</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>

(3)
I'm not sure what your final goal here is, but if you're actually planning to use IOUserService, then I think you could just use the USBHost framework instead.

<key>IOClass</key>
<string>IOUserService</string>

In terms of the specific failure, I think what actually causes the failure is the combination of #1 and the inclusion of the "idVendor"/"idProduct" properties. Those are valid properties in for USB matching, but they don't exist on IOUserResources, so your match fails during IOKit matching, so your DEXT never launches.

My suggestion would be that you first remove those two keys, at which point I think your DEXT will start loading (just not against a USB device). That lets you confirm that the basic load process "works", after which you can correct the issues above to match your hardware target.

[1] Strictly speaking, I've never actually used the old flow, as I didn't take over DEXT support until the new flow was live.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

my driver entitlements file originally looked like this:

<dict>
	<key>com.apple.developer.driverkit</key>
	<true/>
	<key>com.apple.developer.driverkit.family.hid.device</key>
	<true/>
	<key>com.apple.developer.driverkit.transport.usb</key>
    <array/>
    <key>com.apple.developer.driverkit.allow-any-userclient-access</key>
    <true/>
</dict>

but I also tried this

<dict>
	<key>com.apple.developer.driverkit</key>
	<true/>
	<key>com.apple.developer.driverkit.transport.usb</key>
    <array>
        <dict>
            <key>idVendor</key>
            <integer>*</integer> <!-- I've also tried hex and decimal values here. Wildcard was a last ditch hope --> 
        </dict>
    </array>
</dict>

to no effect. The driver's Info.plist originally looked like this:

<dict>
    <key>CFBundleShortVersionString</key>
    <string>1.0</string>
    <key>CFBundleVersion</key>
    <string>1</string>
	<key>IOKitPersonalities</key>
	<dict>
		<key>MyBatteryDriver</key>
		<dict>
            <key>CFBundleIdentifier</key>
            <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
			<key>CFBundleIdentifierKernel</key>
			<string>com.apple.kpi.iokit</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>UserClientProperties</key>
            <dict>
                <key>IOClass</key>
                <string>IOUserUserClient</string>
                <key>IOUserClass</key>
                <string>StatusUserClient</string>
            </dict>
            <key>idVendor</key>
            <integer>0x0000</integer>  <!-- showing the format, but not the actual vendor or product ID -->   
            <key>idProduct</key>
            <integer>0x0000</integer>
		</dict>
	</dict>
	<key>OSBundleUsageDescription</key>
	<string>Driver description</string>
    <key>OSBundleUsageDescriptionKey</key>
    <string>This application is trying to install a driver.</string>
</dict>

But I also tried this

<dict>
    <key>CFBundleShortVersionString</key>
    <string>2.0</string>
    <key>CFBundleVersion</key>
    <string>2</string>
    <key>OSBundleUsageDescriptionKey</key>
    <string>This application is trying to install a driver.</string>
    <key>OSBundleUsageDescription</key>
	<string>Driver description</string>
	<key>IOKitPersonalities</key>
	<dict>
		<key>MyBatteryDriver</key>
		<dict>
            <key>CFBundleIdentifier</key>
            <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
			<key>CFBundleIdentifierKernel</key>
			<string>com.apple.kpi.iokit</string>
			<key>IOClass</key>
			<string>IOUserService</string>
			<key>IOMatchCategory</key>
			<string>IOUserService</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>UserClientProperties</key>
            <dict>
                <key>IOClass</key>
                <string>IOUserUserClient</string>
                <key>IOUserClass</key>
                <string>StatusUserClient</string>
            </dict>
            <key>idVendor</key>
            <integer>2222</integer> <!-- again showing the format, which was the hex value converted to decimal -->   
		</dict>
	</dict>
</dict>

to also no effect.

First off, a clarification here:

I am attempting to write a bridging driver for an older UPS that only communicates via RPC-over-USB rather than the HID Power Device class the OS requires.

What's your final goal here? That is, are you:

  • Trying to create a "seamless" bridge to HID and then relying on the system’s support.

OR

  • Direct communication via user client to your own app.

  • In the first case, how "seamless" do you actually want this to be? Do you not want any app "at all"?

The issue here is that you don't actually need to use DriverKit to talk to a USB device. You could also use the USBHost framework, which would let your app just "talk" to the device. Unless you're specifically bridging to HID, then I wouldn't use DriverKit at all.

In addition, if you're planning to bridge to HID but you're also planning to have an app/daemon, then you could also do this by using the USBHost framework to communicate with your device and a virtual HID device to expose that device to the system.

The right choice here really depends on your final product goals. If your goal is just to expose the device via HID, then DriverKit is probably your best choice. However, as your final product "expands", I think the path above makes more and more sense, both for ease of development and general "flexibility". Note that "ease of development" is NOT a minor difference here- DriverKit is not an easy API to work with, not to mention the added development friction inherent in DEXT/KEXT development.

In any case, getting into the details:

For testing context, I am testing this on a 2021 iMac (M1) running Sequoia 15.7, and this iMac is on MDM, specifically Jamf.

Before you do anything else, I would make sure that your MDM configuration doesn't block DEXT loading. Your DEXT isn't properly configured (more on that below), but you don't want to end up wasting a bunch of time because MDM is blocking a valid configuration.

Next:

I have disabled SIP and set systemextensionsctl developer on, per the instructions here, and I have compiled and am attempting to debug the app using Xcode 26.2.

So, the first step here is to reenable SIP and basically ignore all of the instructions on our website when it comes to DriverKit testing. Technically, I think they still work [1], but the new flow is easier and (mostly) just works "better".

That leads to here:

<dict>
	<key>com.apple.developer.driverkit</key>
	<true/>
	<key>com.apple.developer.driverkit.transport.usb</key>
    <array>
        <dict>
            <key>idVendor</key>
            <integer>*</integer> <!-- I've also tried hex and decimal values here. Wildcard was a last-ditch hope --> 
        </dict>
    </array>
</dict>

This is actually the correct configuration for the development entitlement, as the "*" means "allow this DEXT as an eligible match for any vendor". Note that this is NOT the configuration you'll actually end up shipping, just the configuration you'll use for development. See this post for guidance on production signing.

That leads to here:

As I mentioned above, I think this is a code signing / entitlements issue, but I don't know how to resolve it.

Actually, I don't think that's the case. There are instructions on validating your codesign configuration here, but if your entitlement configuration is to "wrong”, then you'll end up getting a build failure.

Shifting to what is the problem:

The driver's Info.plist originally looked like this:

So, your post gave me a good excuse to do pull together a more thorough run down on DEXT matching, which you can find here. That article has the more detail on what's actually going on, but the specifics are:

(1)
Your IOProviderClass is wrong, you need to be matching against the USB device/interface you actually want to "use".

<key>IOProviderClass</key>
<string>IOUserResources</string>

(2)
You should not be including IOMatchCategory at all. It isn't causing the current failure, but it would cause problems once you actually matched.

<key>IOMatchCategory</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>

(3)
I'm not sure what your final goal here is, but if you're actually planning to use IOUserService, then I think you could just use the USBHost framework instead.

<key>IOClass</key>
<string>IOUserService</string>

In terms of the specific failure, I think what actually causes the failure is the combination of #1 and the inclusion of the "idVendor"/"idProduct" properties. Those are valid properties in for USB matching, but they don't exist on IOUserResources, so your match fails during IOKit matching, so your DEXT never launches.

My suggestion would be that you first remove those two keys, at which point I think your DEXT will start loading (just not against a USB device). That lets you confirm that the basic load process "works", after which you can correct the issues above to match your hardware target.

[1] Strictly speaking, I've never actually used the old flow, as I didn't take over DEXT support until the new flow was live.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

Kevin,

First, I wanted to apologize for not replying for a full month, as well as thank you very much for your reply. I was pulled away from this project for a couple weeks, and when I got back I was still spinning my wheels getting the driver installed.

I only today found my issue, which wasn't something you could have helped with since I hadn't thought to include the correct context in my original comment. I only found the issue after careful log monitoring in the Console app during the install process as my main installer app was running.

It turns out that my bundle ID was too long.

It was 67 characters and, apparently the maximum is 63. Seems like an arbitrarily short max in this day and age, and you'd think both XCode and the Developer Portal would catch that, but that's what it was. When I redid my bundle IDs more succinctly (and all my permissions on the Apple Developer Portal), I was able to successfully install/register/enable the driver. I have granted it permission in System Settings, it shows up under systemextensionsctl list, everything seems on the up and up.

So now I am moving on to other issues, and if you're still willing, I'll gladly accept additional help.

First, to answer your original question about my goals, the primary goal is to translate this custom USB protocol into something that an iMac will recognize as an external UPS battery.

Meaning, when the UPS data cable is plugged in, the iMac shows the battery icon widget in the Notification Center area with the correct battery percentage. Unless I'm completely wrong about it, which is entirely plausible, that means I need a driver (virtual or otherwise) that speaks HID in the Power Device class format to the operating system. And the UPS I'm working with does not speak that protocol.

Although I plan/hope to pull some of the data I'll get from the UPS battery out into user-app space to support future functionality, that's a nice-to-have. My primary goal is translating the battery into something the OS recognizes.


So, on to my current problem. The driver installs... but doesn't load. Probably because it's not matching. So I'm going to give more detailed information in the hope you can help me with this.

As I said, the device is a UPS battery, connected via USB cable from its data port. It speaks a custom RPC protocol over USB (I have internal documentation from the vendor with the bytecodes it uses). It uses a stock Silicon Labs firmware chip

When I plug the device into any iMac, I get the following item in the IO Registry / USB Tree:

CP2102N USB to UART Bridge Controller:
  Location ID:	0x01120000
  Connection Type:	Removable
  Manufacturer:	Silicon Labs
  Serial Number:	    0c3421465321e86fcce05720eef3
  Link Speed:	12 Mb/s
  USB Vendor ID:	0x10c4
  USB Product ID:	0xea60
  USB Product Version:	0x0100

Those hex codes translate to Vendor ID 4242 and Product ID 60000.

Using Console and trawling through the logs when I plug the device in, I was able to find the following log entry:

CP2102N USB to UART Bridge Controller@01120000: IOUSBHostDevice::setConfigurationGated: AppleUSBHostCompositeDevice selected configuration 1

And immediately after that several entries involving the matching pocess, which resulted in

Picked matching dext for bundle identifier com.apple.DriverKit-AppleUSBSLCOM: Dext com.apple.DriverKit-AppleUSBSLCOM v1 in executable dext bundle com.apple.DriverKit-AppleUSBSLCOM at /System/Library/DriverExtensions/com.apple.DriverKit-AppleUSBSLCOM.dext. 

Snooping around in there I found the following in its Info.plist under the IOKitPersonalities dict:

<key>DriverKit-AppleUSBSLCOM-CP2102</key>
<dict>
    <key>CFBundleIdentifier</key>
    <string>com.apple.DriverKit-AppleUSBSLCOM</string>
    <key>CFBundleIdentifierKernel</key>
    <string>com.apple.driver.driverkit.serial</string>
    <key>IOClass</key>
    <string>IOUserSerial</string>
    <key>IOProviderClass</key>
    <string>IOUSBHostInterface</string>
    <key>IOUserClass</key>
    <string>AppleUSBSLCOM</string>
    <key>IOUserServerName</key>			
	<string>com.apple.driverkit.AppleUSBSLCOM</string>
    <key>bConfigurationValue</key>
    <integer>1</integer>
    <key>bInterfaceNumber</key>
    <integer>0</integer>
    <key>idProduct</key>
    <integer>60000</integer>
    <key>idVendor</key>
    <integer>4292</integer>
</dict>

Along with several others with different product and vendor IDs.

I tried to match it in my own Info.plist:

<key>AmstronBatteryDriver</key>
<dict>
    <key>CFBundleIdentifier</key>
    <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
    <key>CFBundleIdentifierKernel</key>
    <string>com.apple.driver.driverkit.serial</string>
    <key>IOClass</key>
    <string>IOUserService</string>
    <key>IOProviderClass</key>
    <string>IOUSBHostInterface</string>
    <key>IOUserClass</key>
    <string>AmstronBatteryDriver</string>
    <key>IOUserServerName</key>
    <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
    <key>UserClientProperties</key>
    <dict>
        <key>IOClass</key>
        <string>IOUserUserClient</string>
        <key>IOUserClass</key>
        <string>BatteryStatusUserClient</string>
    </dict>
    <key>bConfigurationValue</key>
    <integer>1</integer>
    <key>bInterfaceNumber</key>
    <integer>0</integer>
    <key>idProduct</key>
    <integer>60000</integer>
    <key>idVendor</key>
    <integer>4292</integer>
</dict>

But so far I have had no luck.

Any suggestions on how I can get my driver to be picked rather than the default one?

First, I wanted to apologize for not replying for a full month, as well as thank you very much for your reply. I was pulled away from this project for a couple weeks, and when I got back I was still spinning my wheels getting the driver installed.

Not a problem at all.

It was 67 characters and, apparently the maximum is 63. Seems like an arbitrarily short max in this day and age, and you'd think both XCode and the Developer Portal would catch that, but that's what it was.

Sigh... I'm sorry I didn't think of that, as I'm aware of that limitation and actually posted about it here. FYI, you should also be aware of the version number limitation described here, which is another arbitrary restriction the kernel enforces.

So, on to my current problem. The driver installs... but doesn't load. Probably because it's not matching. So I'm going to give more detailed information in the hope you can help me with this.

If you haven't seen it already, I have a post here that does an extended run down of the matching and loading process. Note that while the post uses PCI and SCSI for it's example, exactly the same relationship exists between USB and HID.

Moving into specifics:

Any suggestions on how I can get my driver to be picked rather than the default one?

In the short term, the most direct way is to use the IOProbeScore to force your score higher. We say you're not supposed to that, but I'm fairly sure it will "work". However, the problem here is that doing this is going claim other hardware as well, which could obviously be problematic. In the longer term, you need to be able to differentiate your hardware from "everyone else”. Can you do that, perhaps by operating at the device instead of interface level?

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

Hello again, Kevin, and thank you again for your help. I notice that you commented 18 hrs ago, but I could have sworn I checked yesterday and there wasn't anything here.

I ended up getting matching working by also specifying version number, which increased my probe score. And...

you should also be aware of the version number limitation described here, which is another arbitrary restriction the kernel enforces.

you definitely predicted my next issue. I prefer date-based build and version numbers, and this one was problematic, but your link helped me fit a date based number into that parsing code.

If anyone else wants to try that, I used BUILD_NUMBER=$(date '+%Y.%m%d.%H%Mf%S') in my update script, which yields a version number e.g. 2026.0303.1749f09. The arbitrary "f" enables me to get past that 4[letter]3 format. I picked "f" only because it looks similar to an "s".

Before I move onto my current problem...

Can you do that, perhaps by operating at the device instead of interface level?

Could you explain what you mean by that? Do you mean the difference between picking IOUSBHostDevice vs IOUSBHostInterface for the IOProviderClass? If so, I currently have IOUSBHostDevice selected.

I've been trying to learn this API and I don't recall running into anything that discusses the fundamental differences between those two and why I would chose which. Any links you happen to have will be happily devoured.


So now my new problem, and again I'll thank you in advance for any help you provide.

In short, my driver matches and tries to load, but fails to load. In the logs I see the matching log entry for my driver, and it goes through some setup that mentions my driver and some entries that do not, and eventually ends with "provider entitlements check failed", at which point the logs show that the OS switched to the default driver.

There are two entries with "Sandbox: kernelmanagerd(358) deny(1) file-write-create /private/var/folders/zz/zyxvpxvq6csfxvn_n0000000000000/T/com.apple.kernelmanagerd/TemporaryItems" sandwiched between mentions of my driver, then are a bunch of entries from the kernel process that just say "<private>", and then the notification that entitlements check failed.

How do I go about troubleshooting this?

For more context, my current entitlements file is this:

<dict>
   <key>com.apple.developer.driverkit</key>
   <true/>
   <key>com.apple.developer.driverkit.allow-any-userclient-access</key>
   <true/>
   <key>com.apple.developer.driverkit.transport.usb</key>
   <array>
       <dict>
           <key>idVendor</key>
           <integer>*</integer>
       </dict>
   </array>
</dict>

And I'm using automated signing with our apple developer team selected, and in the Apple Developer Oortal, the App ID configuration I have that maps to the driver's bundle ID has the following checked:

  • DriverKit
  • DriverKit (development)
  • DriverKit Allow Any UserClient (development)
  • DriverKit USB Transport (development)
  • DriverKit USB Transport - VendorID
  • In App Purchases (automatically added by the portal)

Am I missing something? Could my IOKitPersonalities be wrong? If that will help, it's currently set to

<dict>
    <key>CFBundleIdentifier</key>
    <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
    <key>CFBundleIdentifierKernel</key>
    <string>com.apple.driver.driverkit.serial</string>
    <key>IOClass</key>
    <string>IOUserService</string>
    <key>IOProviderClass</key>
    <string>IOUSBHostDevice</string>
    <key>IOUserClass</key>
    <string>AmstronBatteryDriver</string>
    <key>IOUserServerName</key>
    <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
    <key>UserClientProperties</key>
    <dict>
        <key>IOClass</key>
        <string>IOUserUserClient</string>
        <key>IOUserClass</key>
        <string>BatteryStatusUserClient</string>
    </dict>
    <key>bcdDevice</key>
    <integer>256</integer>
    <key>bConfigurationValue</key>
    <integer>1</integer>
    <key>bInterfaceNumber</key>
    <integer>0</integer>
    <key>idProduct</key>
    <integer>60000</integer>
    <key>idVendor</key>
    <integer>4292</integer>
</dict>

Again, thanks so much for your time and responses. I very much appreciate it.

Accepted Answer

How do I go about troubleshooting this?

So, the place to start is actually with this log message:

eventually ends with "provider entitlements check failed"

The log message is the final, generic, error message printed by IOUserService when the entitlement check process fails. A few things to understand about what this tells you about what HASN'T gone wrong:

  1. "Basic" entitlement validation happens outside the kernel well before this point. So getting to this point means that your DEXT is at least somewhat coherently signed, even if it doesn't actually "work".

  2. IOKit passive matching happens at the same time as entitlement checking (just before actually), so this message means that either your passive match failed or your entitlement configuration was wrong.

So, the next step here is to actually take a closer look at this logging:

In the logs I see the matching log entry for my driver, and it goes through some setup that mentions my driver and some entries that do not.

Focusing on the entitlement checking side, each IOKit family actually implements its own entitlement validation. IOUSBHostFamily isn't open source, but the IOPCIFamily so, for reference, you can actually see what that validation code looks like here.

The details are obviously different for IOUSBHostFamily but the basic idea is that the driver checks each entry entitlement against "itself" and fails the match if it can't find a matching entitlement configuration. All of our driver families should be pretty good about logging this process so, in the case of the USB family you should see one of these three log messages:

(1) It worked!

"The com.apple.developer.driverkit.transport.usb entitlements array has matched at index <number corresponding to entry in your entitlement array>"

(2) Every entry was checked but nothing matched:

"The com.apple.developer.driverkit.transport.usb entitlements property doesn't contain a matching entitlement."

(3) Something is wrong with the format of that entitlement's overall structure, so the checking code couldn't find an array to iterate:

"The com.apple.developer.driverkit.transport.usb entitlements property is malformed."

In terms of validating your entitlements, my post here describes that process in detail. The command I'd highlight there is the "security" command. It's often unclear from our documentation exactly what an entitlement value should actually "be". That entitlement prints the contents of the provisioning profile the portal generated for you, so whatever it shows IS what your entitlement value should be.

You should run through that entire post and do your own validation, but I do have one question here:

<key>com.apple.developer.driverkit.transport.usb</key>
<array>
   <dict>
	   <key>idVendor</key>
	   <integer>*</integer>
   </dict>
</array>

Is that the EXACT contents of your entitlement file? And, if so, how are you actually managing your project and building it? The problem here is that this:

<integer>*</integer>

...is fundamentally invalid XML, as "*" is not "a number". You can't create that configuration with Xcode, and I'd expect any other XML editor to reject it as well (my personal favorite, PlistEdit Pro does). You can force it into the file using a text editor, but Xcode will throw this alert when you try to view it:

Failed to open property list: Unknown character '*' (0x2a) in <integer> on line 13

...and then offer to "Open as Source Code". It looks like Xcode will let you build with that invalid configuration, but if you run through the checks in my post above, you'll find that it's actually stripped "com.apple.developer.driverkit.transport.usb" from your app signing data. In any case, the correct value for this entry is:

<key>idVendor</key>
<string>*</string>

Finally, moving to here:

Could my IOKitPersonalities be wrong?

So, if you're not seeing any of the three messages above, then one of two things is going on:

  1. Your code signing is broken, probably due to the issue I just described. The way to test that is with the process I outlined here.

  2. You're failing passive matching, meaning something in your IOKitPersonalities dictionary is "wrong".

The basic process for #2 is comparing the keys in your matching dictionary with the keys in the IORegistry, making sure they EXACTLY match. You can and should go through that process, but you can also pull back to this more basic match:

<key>idProduct</key>
<integer>60000</integer>
<key>idVendor</key>
<integer>4292</integer>
<key>IOProbeScore</key>
<integer>9000</integer>

That's a simpler configuration to verify, and the IOProbeScore key will bump your match high enough that you'll beat any other driver. You shouldn't actually SHIP that configuration, but in my experience, it's always easier to modify a working configuration than it is to try and make it "right" from the very beginning.

As a side note on that point, I wouldn't include ANY "functional" code in your initial DEXT implementation. At this point, your focus should JUST be on getting a minimal DEXT attached to your target hardware, not having that DEXT actually "do" anything useful. My experience has been that it's FAR too easy to confuse yourself about what's actually happening in your driver, so you end up wasting a bunch of time investigating a "loading" problem when the ACTUAL problem was that your driver loaded fine... then messed up in a way that caused it to fail.

My recommendation is that you first get a non-functional driver loading and unloading cleanly (both in terms of hot plug and being able to update your driver), then start working on the "real" implementation. Getting that most basic configuration working first means you know that anytime something breaks, it's because of something your DEXT did and not an issue with the "basic" configuration.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

You've set IOProviderClass to IOUSBHostDevice, but your IOKitPersonality includes the interface number, which a device doesn't have. You probably want to match against the interface (set IOProviderClass to IOUSBHostInterface)

If you really want to talk to the IOUSBHostDevice, not the IOUSBHostInterface, reduce your matching criteria to include only the items in this table:

https://developer.apple.com/library/archive/documentation/DeviceDrivers/Conceptual/USBBook/USBOverview/USBOverview.html#//apple_ref/doc/uid/TP40002644-BBIDGCHB

The table is ancient, but still relevant. If you over-specify matching criteria, you won't match at all. You're right not to put an IOProbeScore in there. The matching process calculates an IOProbeScore (more criteria -> higher probe score). The system's driver is unlikely to be as specific as yours, so you will outmatch it anyway. The only reason to manually specify an IOProbeScore is to outmatch another driver which would otherwise have the same score based on its matching criteria. That is unlikely here.

You asked about the difference between IOUSBHostDevice and IOUSBHostInterface. Start here:

https://developer.apple.com/documentation/usbdriverkit

If you match against the device, you can only send requests to the control pipe, and you prevent the OS from building any kernel abstractions which can talk to the interfaces, unless your driver makes that happen.

If you need to operate on anything more than the control pipe, you're better off matching against the interface you need. This gives you access to CopyPipe. Once you have a pipe, you can send or receive data through that pipe.

You've set IOProviderClass to IOUSBHostDevice, but your IOKitPersonality includes the interface number, which a device doesn't have.

Nice catch!

The matching process calculates an IOProbeScore (more criteria -> higher probe score). The system's driver is unlikely to be as specific as yours, so you will outmatch it anyway. The only reason to manually specify an IOProbeScore is to outmatch another driver which would otherwise have the same score based on its matching criteria.

Very good advice. My suggestion is ONLY for early development when you're still trying to figure out exactly how everything fits together. It lets you use the simplest match criteria without worrying about edge cases like other drivers. Once you've gotten your driver matched and loading, you can then add in other properties, which catches issues like this:

your IOKitPersonality includes the interface number, which a device doesn't have.

...since adding that property will immediately break the load.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

Kevin, ssmith_c, thank you both so much for your help. Thanks to your help and patience, I am finally able to get my driver to load.

It errors out almost immediately, but it's writing to the logs, which is the most functionality I've gotten out of this enterprise since I started writing in December.

FWIW, I think it was the <string>*</string> that did it, so I'll be accepting that answer for this question.

Based on ssmith_c's reply I suspect I have some re-thinking to do with respect to my programmatic approach, but I think that's going to be the subject of a different question.

For additional context on how I got there... if you care... I had initially switched over to <integer>number<integer> for vendor ID in my entitlements file but it continued to fail the entitlements check.

Eventually I compared the output of security cms -D -i <profile embedded in dext> against the output of codesign -d --entitlements <dext> and discovered a discrepancy -- the codesign result included the integer vendor ID, while the embedded profile included a wildcard.

I had been using automated signing, so I manually created and downloaded a profile, and received the same wildcard in it, even though we had requested and gotten approved for the vendor ID capability request with the specific vendor ID.

I'm not going to sweat that right now, since I have programming to do again, but if either of you happen to know why that is, I'd appreciate the info. I presume it's something I'll have to deal with when this is ready to roll out

It errors out almost immediately, but it's writing to the logs.

Congratulations! Having been through this many times, I'm well aware of the mixed joy of FINALLY getting a driver to the point where it can log its own failure.

I had been using automated signing, so I manually created and downloaded a profile, and received the same wildcard in it, even though we had requested and gotten approved for the vendor ID capability request with the specific vendor ID.

My post here has all the background and the ultimate solution, but the summary is that you'll be using this for development builds:

<string>*</string>

...and then resigning DEXT builds with the numeric value you were approved for once you're ready to distribute code.

I wouldn't bother trying to sort out that process now, just keep in mind that you'll need to revisit this issue once you get closer to shipping.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

Driver Activation failure error code 9. Maybe Entitlements? Please help
 
 
Q