DriverKit - IOUSBHostDevice::SetProperties

I am trying to add a few properties to an IOUSBHostDevice but the SetProperties is returning kIOReturnUnsupported. The reason I am trying to modify the IOUSBHostDevice's properties is so we can support a MacBook Air SuperDrive when it is attached to our docking station devices. The MacBook Air SuperDrive needs a high powered port to run and this driver will help the OS realize that our dock can support it.

I see that the documentation for SetProperties says:

The default implementation of this method returns kIOReturnUnsupported. You can override this method and use it to modify the set of properties and values as needed. The changes you make apply only to the current service.

Do I need to override IOUSBHostDevice? This is my current Start implementation (you can also see if in the Xcode project):

kern_return_t
IMPL(MyUserUSBHostDriver, Start)
{
kern_return_t ret = kIOReturnSuccess;
OSDictionary * prop = NULL;
OSDictionary * mergeProperties = NULL;
bool success = true;

os_log(OS_LOG_DEFAULT, "> %s", __FUNCTION__);

os_log(OS_LOG_DEFAULT, "%s:%d", __FUNCTION__, __LINE__);
ret = Start(provider, SUPERDISPATCH);
__Require(kIOReturnSuccess == ret, Exit);

os_log(OS_LOG_DEFAULT, "%s:%d", __FUNCTION__, __LINE__);
ivars->host = OSDynamicCast(IOUSBHostDevice, provider);
__Require_Action(NULL != ivars->host, Exit, ret = kIOReturnNoDevice);

os_log(OS_LOG_DEFAULT, "%s:%d", __FUNCTION__, __LINE__);
ret = ivars->host->Open(this, 0, 0);
__Require(kIOReturnSuccess == ret, Exit);

os_log(OS_LOG_DEFAULT, "%s:%d", __FUNCTION__, __LINE__);
ret = CopyProperties(&prop);
__Require(kIOReturnSuccess == ret, Exit);
__Require_Action(NULL != prop, Exit, ret = kIOReturnError);

os_log(OS_LOG_DEFAULT, "%s:%d", __FUNCTION__, __LINE__);
mergeProperties = OSDynamicCast(OSDictionary, prop->getObject("IOProviderMergeProperties"));
mergeProperties->retain();
__Require_Action(NULL != mergeProperties, Exit, ret = kIOReturnError);

os_log(OS_LOG_DEFAULT, "%s:%d", __FUNCTION__, __LINE__);
OSSafeReleaseNULL(prop);
ret = ivars->host->CopyProperties(&prop);
__Require(kIOReturnSuccess == ret, Exit);
__Require_Action(NULL != prop, Exit, ret = kIOReturnError);

os_log(OS_LOG_DEFAULT, "%s:%d", __FUNCTION__, __LINE__);
os_log(OS_LOG_DEFAULT, "%s : %s", "USB Product Name", ((OSString *) prop->getObject("USB Product Name"))->getCStringNoCopy());
os_log(OS_LOG_DEFAULT, "%s : %s", "USB Vendor Name", ((OSString *) prop->getObject("USB Vendor Name"))->getCStringNoCopy());

os_log(OS_LOG_DEFAULT, "%s:%d", __FUNCTION__, __LINE__);
success = prop->merge(mergeProperties);
__Require_Action(success, Exit, ret = kIOReturnError);

os_log(OS_LOG_DEFAULT, "%s:%d", __FUNCTION__, __LINE__);
ret = ivars->host->SetProperties(prop); // this is no working
__Require(kIOReturnSuccess == ret, Exit);

Exit:
OSSafeReleaseNULL(mergeProperties);
OSSafeReleaseNULL(prop);
os_log(OS_LOG_DEFAULT, "err ref %d", kIOReturnUnsupported);
os_log(OS_LOG_DEFAULT, "< %s %d", __FUNCTION__, ret);
return ret;
}
Answered by ssmith_c in 813655022

I think you can just make a codeless kext with a plist like this one:

<?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>CFBundleDevelopmentRegion</key>
	<string>English</string>
	<key>CFBundleIconFile</key>
	<string></string>
	<key>CFBundleIdentifier</key>
	<string>com.apple.driver.AppleUSBHub1009</string>
	<key>CFBundleInfoDictionaryVersion</key>
	<string>6.0</string>
	<key>CFBundleGetInfoString</key>
	<string>1.0, Copyright © 2011 Apple Inc.  All Rights Reserved.</string>
	<key>CFBundlePackageType</key>
	<string>KEXT</string>
	<key>CFBundleShortVersionString</key>
	<string>1.0.0</string>
	<key>CFBundleSignature</key>
	<string>????</string>
	<key>CFBundleVersion</key>
	<string>1.0.0</string>
	<key>IOKitPersonalities</key>
	<dict>
		<key>Hub1009</key>
		<dict>
			<key>CFBundleIdentifier</key>
			<string>com.apple.driver.AppleUSBHostMergeProperties</string>
			<key>IOClass</key>
			<string>AppleUSBHostMergeProperties</string>
			<key>IOProviderClass</key>
			<string>IOUSBHostDevice</string>
			<key>idProduct</key>
			<real>1544</real>
			<key>idVendor</key>
			<integer>1234</integer>
			<key>IOProviderMergeProperties</key>
			<dict>
				<key>kUSBWakePortCurrentLimit</key>
				<integer>1100</integer>
				<key>kUSBSleepPortCurrentLimit</key>
				<integer>1100</integer>
				<key>kUSBHubPowerSupply</key>
				<integer>1300</integer>
			</dict>
		</dict>
	</dict>
	<key>OSBundleLibraries</key>
	<dict>
		<key>com.apple.iokit.IOUSBHostFamily</key>
		<string>1.01</string>
		<key>com.apple.kpi.iokit</key>
		<string>10.4.2</string>
	</dict>
	<key>OSBundleRequired</key>
	<string>Root</string>
</dict>
</plist>

I added a tiny amount of code. My driver is a subclass of IOService, and overrides Start(), just so I can see it logging. I think, but have not checked, that the AppleUSBHostMergeProperties driver returns an error from its Start (but first merges the properties).

The downside of course is that dexts are not loaded until after boot time, so your dext usually has no effect because the system has already loaded a driver for the hub by the time your dext is available. If you plug the hub in after boot time, the codeless dext will be instantiated. (FB12048885)

Accepted Answer

I think you can just make a codeless kext with a plist like this one:

<?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>CFBundleDevelopmentRegion</key>
	<string>English</string>
	<key>CFBundleIconFile</key>
	<string></string>
	<key>CFBundleIdentifier</key>
	<string>com.apple.driver.AppleUSBHub1009</string>
	<key>CFBundleInfoDictionaryVersion</key>
	<string>6.0</string>
	<key>CFBundleGetInfoString</key>
	<string>1.0, Copyright © 2011 Apple Inc.  All Rights Reserved.</string>
	<key>CFBundlePackageType</key>
	<string>KEXT</string>
	<key>CFBundleShortVersionString</key>
	<string>1.0.0</string>
	<key>CFBundleSignature</key>
	<string>????</string>
	<key>CFBundleVersion</key>
	<string>1.0.0</string>
	<key>IOKitPersonalities</key>
	<dict>
		<key>Hub1009</key>
		<dict>
			<key>CFBundleIdentifier</key>
			<string>com.apple.driver.AppleUSBHostMergeProperties</string>
			<key>IOClass</key>
			<string>AppleUSBHostMergeProperties</string>
			<key>IOProviderClass</key>
			<string>IOUSBHostDevice</string>
			<key>idProduct</key>
			<real>1544</real>
			<key>idVendor</key>
			<integer>1234</integer>
			<key>IOProviderMergeProperties</key>
			<dict>
				<key>kUSBWakePortCurrentLimit</key>
				<integer>1100</integer>
				<key>kUSBSleepPortCurrentLimit</key>
				<integer>1100</integer>
				<key>kUSBHubPowerSupply</key>
				<integer>1300</integer>
			</dict>
		</dict>
	</dict>
	<key>OSBundleLibraries</key>
	<dict>
		<key>com.apple.iokit.IOUSBHostFamily</key>
		<string>1.01</string>
		<key>com.apple.kpi.iokit</key>
		<string>10.4.2</string>
	</dict>
	<key>OSBundleRequired</key>
	<string>Root</string>
</dict>
</plist>

I added a tiny amount of code. My driver is a subclass of IOService, and overrides Start(), just so I can see it logging. I think, but have not checked, that the AppleUSBHostMergeProperties driver returns an error from its Start (but first merges the properties).

The downside of course is that dexts are not loaded until after boot time, so your dext usually has no effect because the system has already loaded a driver for the hub by the time your dext is available. If you plug the hub in after boot time, the codeless dext will be instantiated. (FB12048885)

Clarifying the architecture of all this, the first thing to understand is that IOKit uses the idea of "setting properties" for two separate and distinct operations:

  1. A KEXT (or other in kernel client) manipulating the values of it's own properties in the IORegistry. The IOKit method for this is IORegistryEntry.setProperty(...), as well as the variety of type specific variation.

  2. A KEXT receiving a "set property" REQUEST from some external source, particularly user space through IOConnectSetCFProperty and IOConnectSetCFProperties. The IOKit method for this is IORegistryEntry.setProperties(), however, note that "setProperties" is an optional method. Nothing requires KEXTs to support this method and many do not.

The key thing to understand here is that these are fundamentally different operations. The first operation is a straightforward key/value "set". The second operation is a send operation that passes data into the client and, in fact, it's most can be used as an alternative to a full UserClient implementation*. Similarly, note that #2 does NOT actually modify that the contents of the IORegistry. A KEXT certainly CAN modify the registry if it chooses to (through #1), but it's under no obligation to do so.

With that context, we move to DriverKit.

DriverKit's only has a "SetProperties" method, the documentation of which is not very helpful (r.139745784). Structurally, what it actually "does" is a variation of #2, but it's implementation actually crosses both cases. The source for this is actually here, but the basic summary is that it:

  1. Perform #1 on each individual, failing as soon as any setProperty fails.

  2. Perform #2 with the entire dictionary. Note tha failing this is common.

  3. If #2 fails, then the contents of the dictionary are merge into the gIOUserServicePropertiesKey/kIOUserServicePropertiesKey dictionary.

In practical terms, it should allow you to set properties on the object your trying to modify unless that object doesn't want you to.

Looking at your case here:

ret = ivars->host->SetProperties(prop);

I wasn't able to figure out exactly what was going on, but I think the issue here is that you're dealing with an indirect reference instead of going through your base class. I can see that there are many DEXT classes (for example, the storage family) that use SetProperties() (going through the base class) without any special implementation, so I think the issue is specific to USB or the property itself.

That leads to here:

I see that the documentation for SetProperties says:

The default implementation of this method returns kIOReturnUnsupported. You can override this method and use it to modify the set of properties and values as needed. The changes you make apply only to the current service.

Honestly, I can't explain where that came from or what it's trying to communicate. My best guess is that there was a mixup caused by confusing "IOService (IOKit)" and "IOService (DriverKit)". Strictly speaking, I believe IOService itself does fail by returning kIOReturnUnsupported, but that doesn't really apply because DEXT are always working through a subclass of IOUserService. Similarly, I don't know why the comment on overriding is is there. It's certainly possible to override it, but as far as I can tell almost none of our own DEXT use that functionality and I'm not sure what the goal of doing so would be.

I think you can just make a codeless kext with a plist like this one:

To clarify here, there are actually two different things going on:

  1. A "basic" codeless DEXT/KEXT works by matching against specific device and then using the "IOClass" property to change the driver that would normally match against a particular device. The codeless DEXT can also insert/modify it's "own" properties, which will the change the behavior of drivers that load on top of it.

  2. AppleUSBHostMergeProperties is actually a specialized variation of a standard "codeless" DEXT. What the AppleUSBHostMergeProperties driver actually does is:

  • Match against a particular device in the standard way.

  • In "probe". it takes the contents of the "IOProviderMergeProperties"* and applies them to it's provider, NOT "itself".

  • It then (intentionally) fails probe(), removing itself from the match so that other drivers can load against the device.

*Note that "IOProviderMergeProperties" key is actually an "informal" key (it isn't actually defined in XNU) that specific families have chosen to reuse for this, so it isn't usable at "all" layers of the system. Similarly, modifying your provider liek this DIRECTLY violates the expected IOKit behavior, since KEXTs in probe should generally NOT be making any persistant modification to their provider.

I added a tiny amount of code. My driver is a subclass of IOService, and overrides Start(), just so I can see it logging. I think, but have not checked, that the AppleUSBHostMergeProperties driver returns an error from its Start (but first merges the properties).

To be honest, I'm not sure what's actually happening when you add a DEXT into the mix here. AppleUSBHostMergeProperties cannot complete the normal IOKit match process- probe() has been hardcoded to return "NULL" and it doesn't even implement start(). The entire class is only ~150 lines of code, so there isn't much to it.

My guess as to what's going on is that your DEXT is ending up somewhat "orphaned" and may actually be holding a reference to the AppleUSBHostMergeProperties instance, preventing it from fully unloading. It resource usage is so tiny that I don't think you'd see any immediate issue from that, but if you're shipping or plan to ship a DEXT that does this, I would recommend taking the time to verify that everything is tearing down properly.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

DriverKit - IOUSBHostDevice::SetProperties
 
 
Q