How do I use IOUserSCSIPeripheralDeviceType00?

I am having similar problems to this guy on Stack Overflow over a year ago: https://stackoverflow.com/questions/77627852/functions-of-iouserscsiperipheraldevicetype00-class-in-scsiperipheralsdriverkit

There are also a few questions on this forum about this object, none of which have answers.

I can get my driver to match and instantiate, but nobody calls my UserDetermineDeviceCharacteristics (which does nothing, just returns kIOReturnSuccess)

I can attempt to call UserSuspendServices(), UserResumeServices() or UserReportMediumBlockSize() and all of them return kIOReturnUnsupported. It doesn't matter if I've unmounted the disk or not.

Is the custom driver supposed to be instantiated beside the kernel's IOSCSIPeripheralDeviceType00, or should it replace it?

What should its IOProviderClass be?

What should its IOClass be - IOUserService, or something else?

see FB19678139 and FB19677920

Answered by DTS Engineer in 855624022

So, 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:

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

  • 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 driver loads "on top" of. In your case, that would be IOSCSIPeripheralDeviceNub or one of its variants.

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

In addition, 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.

That leads to:

What should its IOClass be -

It should be the same as whatever your DEXT subclasses- either "IOUserSCSIPeripheralDeviceType00" or "IOUserSCSIPeripheralDeviceType05".

IOUserService, or something else?

...and IOUserService is exactly what causes this:

I can attempt to call UserSuspendServices(), UserResumeServices() or UserReportMediumBlockSize() and all of them return kIOReturnUnsupported.

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.

Similarly:

I can get my driver to match and instantiate, but nobody calls my UserDetermineDeviceCharacteristics (which does nothing, just returns kIOReturnSuccess)

The component that would call those methods is the kernel driver your matching dictionary "told" the system to load.

I think that should be enough to get your DEXT sorted out, but if you're still having problems, please post the contents of your IOKitPersonalities dictionary, and I'll see if I can figure out where things are going wrong.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

So, 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:

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

  • 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 driver loads "on top" of. In your case, that would be IOSCSIPeripheralDeviceNub or one of its variants.

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

In addition, 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.

That leads to:

What should its IOClass be -

It should be the same as whatever your DEXT subclasses- either "IOUserSCSIPeripheralDeviceType00" or "IOUserSCSIPeripheralDeviceType05".

IOUserService, or something else?

...and IOUserService is exactly what causes this:

I can attempt to call UserSuspendServices(), UserResumeServices() or UserReportMediumBlockSize() and all of them return kIOReturnUnsupported.

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.

Similarly:

I can get my driver to match and instantiate, but nobody calls my UserDetermineDeviceCharacteristics (which does nothing, just returns kIOReturnSuccess)

The component that would call those methods is the kernel driver your matching dictionary "told" the system to load.

I think that should be enough to get your DEXT sorted out, but if you're still having problems, please post the contents of your IOKitPersonalities dictionary, and I'll see if I can figure out where things are going wrong.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

Hi Kevin, thanks for your reply. Sorry for the delay, I was out for a while.

Here's my current personality dictionary. With this dictionary, the driver crashes, presumably in its init. The crashed thread has OSMetaClassBase::Invoke(RPC) in its stack trace, none of the code appears to be mine. I can see my driver as an object in the IORegistry called "driver" - I use SetName late in my Start_Impl() to give it a globally unique name, so I crash before this.

I also tried setting IOProviderClass to IOSCSIPeripheralDeviceNub, but my driver didn't appear to load at all then.

As you can see, I changed the name of IOPropertyMatch so that it won't be considered, but I would prefer to match on USB vendor and product ID, rather than a name. Does the property I'm trying to match on have to be a property of the provider, or a property of the provider or any of its parents? And is IOPropertyMatch always considered, regardless of family, or are only some family-dependent property keys considered?

 <dict>
	<key>OSBundleUsageDescription</key>
	<string>do stuff with a disk</string>
	<key>IOKitPersonalities</key>
	<dict>
		<key>driver</key>
		<dict>
			<key>CFBundleIdentifier</key>
			<string>$(PRODUCT_BUNDLE_IDENTIFIER)  </string>
			<key>CFBundleIdentifierKernel</key>
			<string>com.apple.kpi.iokit</string>
			<key>IOClass</key>
			<string>IOSCSIPeripheralDeviceType00</string>
			<key>IOProviderClass</key>
			<string>IOSCSILogicalUnitNub</string>
			<key>IOKitDebug</key>
			<integer>65535</integer>
			<key>IOResourceMatch</key>
			<string>IOKit</string>
			<key>IOUserClass</key>
			<string>driver</string>
			<key>IOUserServerName</key>
			<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
			<key>Peripheral Device Type</key>
			<integer>0</integer>
			<key>Product Identification</key>
			<string>EX400U</string>
			<key>Vendor Identification</key>
			<string>Corsair</string>
			<key>IOProbeScore</key>
			<string>5001</string>
			<key>IOMatchCategory</key>
			<string>$(PRODUCT_BUNDLE_IDENTIFIER)  </string>
			<key>xxIOPropertyMatchxx</key>
			<dict>
				<key>idVendor</key>
				<integer>6940</integer>
				<key>idProduct</key>
				<integer>6688</integer>
			</dict>
			<key>UserClientProperties</key>
			<dict>
				<key>IOClass</key>
				<string>IOUserUserClient</string>
				<key>IOUserClass</key>
				<string>userClient</string>
			</dict>
		</dict>
	</dict>

when I use this IOKitPersonality, my "driver" object appears in the IORegistry in line with the driver stack. Its parent is IOSCSILogicalUnitNub, and its child is IOBlockStorageServices. Is this correct or should there be a system supplied IOSCSIPeripheralDeviceType00 next to it in the IORegistry?

Accepted Answer

First off, here:

Here's my current personality dictionary. With this dictionary, the driver crashes, presumably in its init.

One thing I strongly recommend during early bring-up is that your driver should do as LITTLE as possible. Basically, you can log data... and nothing else. I might not even call "super".

The problem here is that until you've got a foundation that "works" in the most BASIC sense ("doesn't crash"), you'll end up wasting time looking at what you THINK is the problem without actually knowing what's "wrong".

In any case, this is wrong:

<key>IOClass</key>
<string>IOSCSIPeripheralDeviceType00</string>

And should be:

<key>IOClass</key>
<string>IOUserSCSIPeripheralDeviceType00</string>

IOSCSIPeripheralDeviceType00 is the base class driver for mass storage devices. What you want is "IOUserSCSIPeripheralDeviceType00", which is the DEXT support driver for SCSIPeripheralDriverKit. In more concrete terms, in the kernel "IOUserSCSIPeripheralDeviceType00" is a subclass of "IOSCSIPeripheralDeviceType00" which includes a bunch of additional callout hooks which call out to your DEXT. Using the wrong IOClass causes the problem I described here:

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

However, your DEXT may also just crash, depending on the methods you attempt to call and what happens after that point.

Shifting to matching issues:

Does the property I'm trying to match on have to be a property of the provider, or a property of the provider or any of its parents?

It must be a property of your provider. This kind of "recursive" matching quickly becomes VERY difficult to manage, as the collection of possible properties under any given device varies based on system version and the machine you’re running on. The layering of IOKit is also intended to isolate implementation layers from each other (so, for example, a mass storage driver doesn't care whether it's on USB or Thunderbolt), which means "digging" below your provider is typically avoided. More to the point, an IOKit that wanted to look at lower-level properties would normally do this in probe() during active matching (DriverKit handles this differently).

And is IOPropertyMatch always considered, regardless of family, or are only some family-dependent property keys considered?

Yes, always. It's actually implemented by IOService itself, so there isn't any family-specific logic. Families define what you can match against by controlling what properties they include on the nub.

When I use this IOKitPersonality, my "driver" object appears in the IORegistry in line with the driver stack. Its parent is IOSCSILogicalUnitNub, and its child is IOBlockStorageServices. Is this correct?

Yes, that sounds correct. Your DEXT is taking over the device by replacing IOSCSIPeripheralDeviceType00 with IOUserSCSIPeripheralDeviceType00. IOUserSCSIPeripheralDeviceType00 then implements all of the hooks your DEXT needs to function.

Or should there be a system-supplied IOSCSIPeripheralDeviceType00 next to it in the IORegistry?

This would be "wrong", both in this particular case and across the broader system. The general design "philosophy" of IOKit is that there should only be one child connecting to a given nub. Multiple children "work" (in the sense that IOKit will load that configuration), but that doesn't mean it's actually "useful". Most drivers (particularly in the mass storage stack) will only accept a single connection, so you'll end up attached to a provider that won't actually allow you to interact with it in any meaningful way.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

Splendid! Thank you Kevin, thank you for pointing out my spelling mistake. Also, thanks for the extra information about matching.

It looks like some IOService subclasses copy properties from their providers to themselves, presumably to facilitate matching - is that so?

When you say "DriverKit handles this differently", what do you mean? In a kext, I can crawl down the provider chain to the root, inspecting properties as I go. That doesn't seem possible with DriverKit, which (by design) severely limits access from a dext to its associated objects (at least, those that lie outside its own address space).

Splendid! Thank you, Kevin, for pointing out my spelling mistake. Also, thanks for the extra information about matching.

It looks like some IOService subclasses copy properties from their providers to themselves, presumably to facilitate matching - is that so?

Yes, this is very common, either by directly copying the key-value pair or by copying the underlying value into a different key. Note how "USB Product Name" and "USB Vendor Name" in the USB layer become "Product Identification" and "Vendor Identification".

One thing to understand here is that the purpose of IOKit layering is to use abstraction to separate different implementation layers from each other. The reason the SCSI layer uses "named" based matching (Product/Vendor Identification strings) instead of idProduct/idVendor is actually really simple- both of those concepts are USB specific, and the whole point of the SAM (SCSI Architecture Model) layer is to provide a "generic" SCSI implementation that can operate on any bus. You'll note that the key transition I mentioned above happens in the "IOSCSILogicalUnitNub" that's created by "IOUSBMassStorageDriver". That's because the role of "IOUSBMassStorageDriver" is to act as the bridge that both understand USB (so it knows which USB keys to get the data from) and SAM (so it knows what keys to add).

Historically, this was a lot more visible as FireWire, USB, ATAPI, and SCSI were all in widespread use.

When you say "DriverKit handles this differently", what do you mean? In a kext, I can crawl down the provider chain to the root, inspecting properties as I go.

SO, KEXT matching is a three-phase process:

  1. Passive matching, which uses the IOKitPersonalities dictionary to identify "possible" drivers.

  2. Active matching, where each driver from #1 is loaded into the kernel and IOService::probe() is run* to confirm that the match is "valid" and rank the candidates.

  3. Once active matching has finished, "start" is called on the highest probe candidate and that driver takes control of the device. The other drivers are then unloaded.

*As a side note, this is actually how codeless KEXT/DEXT's actually work. How "AppleUSBHostMergeProperties" actually works is that its probe function adds the contents of "IOProviderMergeProperties" to provider, then intentionally fails probe.

Note that, in practice, this wasn't really necessary for most cases- typically there was either only one match (the vendor driver) or there was a default system driver and one vendor driver (that "always" took control of whatever it passively matched against). The major exception here is the high-level storage family, where things like IOPartitionScheme have to read the device in order to figure out if they can interpret that device.

The difference in DriverKit is that #2 doesn't really exist. Strictly speaking, you can fail Start(), but that isn't really the same as failing probe.

That doesn't seem possible with DriverKit, which (by design) severely limits access from a dext to its associated objects (at least, those that lie outside its own address space).

In practice, it's less limited than you think. You can't search or directly walk the entire registry in the same way a KEXT (or user space app) can, but IOService::SearchProperty(...) lets you search your parent chain for properties, which is 99% of what you actually need/want.

Also, it's rarely used, but strictly speaking, IOServiceNotificationDispatchSource will actually let you find and monitor for other drivers. There's a practical example of using it here.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

How do I use IOUserSCSIPeripheralDeviceType00?
 
 
Q