Basic introduction to DEXT Matching and Loading

Note: This document is specifically focused on what happens after a DEXT has passed its initial code-signing checks. Code-signing issues are dealt with in other posts.

Preliminary Guidance:

Using and understanding DriverKit basically requires understanding IOKit, something which isn't entirely clear in our documentation. The good news here is that IOKit actually does have fairly good "foundational" documentation in the documentation archive. Here are a few of the documents I'd take a look at:

Those documents do not cover the full DEXT loading process, but they are the foundation of how all of this actually works.

Understanding the IOKitPersonalities Dictionary

The first thing to understand here is that the "IOKitPersonalities" is called that because it is in fact a fully valid "IOKitPersonalities" dictionary. That is, what the system actually uses that dictionary "for" is:

  1. Perform a standard IOKit match and load cycle in the kernel.

  2. The final driver in the kernel then uses the DEXT-specific data to launch and run your DEXT process outside the kernel.

So, working through the critical keys in that dictionary:

"IOProviderClass"-> This is the in-kernel class that your in-kernel driver loads "on top" of. The IOKit documentation and naming convention uses the term "Nub", but the naming convention is not consistent enough that it applies to all cases.

"IOClass"-> This is the in-kernel class that your driver loads on top of. This is where things can become a bit confused, as some families work by:

  1. Routing all activity through the provider reference so that the DEXT-specific class does not matter (PCIDriverKit).

  2. Having the DEXT subclass a specific subclass which corresponds to a specific kernel driver (SCSIPeripheralsDriverKit).

This distinction is described in the documentation, but it's easy to overlook if you don't understand what's going on. However, compare PCIDriverKit:

"When the system loads your custom PCI driver, it passes an IOPCIDevice object as the provider to your driver. Use that object to read and write the configuration and memory of your PCI hardware."

Versus SCSIPeripheralsDriverKit:

Develop your driver by subclassing IOUserSCSIPeripheralDeviceType00 or IOUserSCSIPeripheralDeviceType05, depending on whether your device works with SCSI Block Commands (SBC) or SCSI Multimedia Commands (SMC), respectively. In your subclass, override all methods the framework declares as pure virtual.

The reason these differences exist actually comes from the relationship and interactions between the DEXT families. Case in point, PCIDriverKit doesn't require a specific subclass because it wants SCSIControllerDriverKit DEXTs to be able to directly load "above" it.

Note that the common mistake many developers make is leaving "IOUserService" in place when they should have specified a family-specific subclass (case 2 above). This is an undocumented implementation detail, but if there is a mismatch between your DEXT driver ("IOUserSCSIPeripheralDeviceType00") and your kernel driver ("IOUserService"), you end up trying to call unimplemented kernel methods. When a method is "missing" like that, the codegen system ends up handling that by returning kIOReturnUnsupported.

One special case here is the "IOUserResources" provider. This class is the DEXT equivalent of "IOResources" in the kernel. In both cases, these classes exist as an attachment point for objects which don't otherwise have a provider. It's specifically used by the sample "Communicating between a DriverKit extension and a client app" to allow that sample to load on all hardware but is not something the vast majority of DEXT will use.

Following on from that point, most DEXT should NOT include "IOMatchCategory". Quoting IOKit fundamentals:

"Important: Any driver that declares IOResources as the value of its IOProviderClass key must also include in its personality the IOMatchCategory key and a private match category value. This prevents the driver from matching exclusively on the IOResources nub and thereby preventing other drivers from matching on it. It also prevents the driver from having to compete with all other drivers that need to match on IOResources. The value of the IOMatchCategory property should be identical to the value of the driver's IOClass property, which is the driver’s class name in reverse-DNS notation with underbars instead of dots, such as com_MyCompany_driver_MyDriver."

The critical point here is that including IOMatchCategory does this:

"This prevents the driver from matching exclusively on the IOResources nub and thereby preventing other drivers from matching on it."

The problem here is that this is actually the exceptional case. For a typical DEXT, including IOMatchCategory means that a system driver will load "beside" their DEXT, then open the provider blocking DEXT access and breaking the DEXT.

DEXT Launching

The key point here is that the entire process above is the standard IOKit loading process used by all KEXT. Once that process finishes, what actually happens next is the DEXT-specific part of this process:

IOUserServerName-> This key is the bundle ID of your DEXT, which the system uses to find your DEXT target.

IOUserClass-> This is the name of the class the system instantiates after launching your DEXT. Note that this directly mimics how IOKit loading works.

Keep in mind that the second, DEXT-specific, half of this process is the first point your actual code becomes relevant. Any issue before that point will ONLY be visible through kernel logging or possibly the IORegistry.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

Basic introduction to DEXT Matching and Loading
 
 
Q