While developing the OS X driver for my Agilent DSO-X 2002A oscilloscope, I ran into a topic that I found to be poorly documented. It is about interaction with a device driver.
There are several ways to communicate with a device driver from client space (the application). One is by modifying driver properties. This method requires the application to open a connection to the driver and then to use that connection to send control requests to the driver. And, possibly, to receive status from the driver.
The other driver communication method — which is far more simpler but much less flexible — is to allow an application to read and write key–value pairs in the driver property table. On OS X, the driver property table can be easily explored with IORegistryExplorer tool. Take, for example, the generic driver property table for
This property–based, client–driver interaction is opaque for the driver. The driver does not need to know which application sent the request for setting or getting a driver property. Configuration of hardware settings fall into this category of client–driver communication. For example, setting the baud rate for a serial device, or setting the audio volume for an audio device etc.
Another possibility is to communicate via a connection. Instead of simply setting and reading properties in the driver property table, I/O Kit provides a driver–application communication method that is based on a connection between user space (“client” or “application”) and the driver. Using a connection allows the driver to determine which application generated a request and, more importantly, allows to associate a state with each connection. Moreover, one application can issue several distinct calls to a driver, thus the driver can associate each of these calls with the origin of the call. One can create complex communication protocols and state–based control routines. This category of application–driver connections is abstracted by instances of IOUserClient
class.
For each driver connection the I/O Kit creates an instance of IOUserClient
class. Each IOUserClient
object is alive until the application closes the connection to the driver (or when application exits). From the moment when the IOUserClient
object is created, all control requests made by the application to the driver are handled by the IOUserClient
object. There is a one–to–one relationship between each instance of IOUserClient
object and each application connection. In order to make this happen, the driver must implement a class that overrides the superclass implementation of IOUserClient
. On the application side, all requests will invoke a method named externalMethod()
. And here is where the things get murky.
Abstraction
To begin implementing a user client, you’ll need to add a class to your kernel extension which is a subclass of IOUserClient. The definition for IOUserClient is located in IOUserClient.h, inside Kernel.framework/IOKit.
The first method that gets called in the life of a user client is initWithTask which is where you put initialization code. (On Intel-based Macs, this is also where the user client can detect if it’s being initialized by an application running using Rosetta.) Then the start method is called which is where you should do some sanity checking to make sure that the provider is actually a member of your driver’s family. If this check fails, your start method should return false which causes the instantiation to fail.
IOUserClient provides two sets of function dispatch and parameter marshalling KPIs. The newer KPI was added in Mac OS X 10.5 in order to support 64-bit userland processes, but 32-bit processes can use it as well. The older KPI only supports 32-bit processes but is the only option on pre-Leopard systems. Both KPIs have corresponding user space APIs in IOKit.framework which will be discussed later.
Helper tools
Reaching the end of this article, a couple of words about some important helper tools for any XCode project that involves USB hardware. Apple slightly changed the availability of these tools. USBProber and the IORegistryExplorer are, probably, most important, but there are several other goodies. IORegistryExplorer is bundled in a .dmg distribution, Hardware IO Tools for Xcode which is available from Apple’s developer corner:
USBProber comes now within the debugging version of IOUSBFamily kext (IOUSBFamilyLog release), also bundled as .dmg. You need to install it and proceed to a hard reboot because it replaces the default IOUSBFamily kernel extension. Tracing USB events is not possible without this logging version of the kext:
You can even get the entire source code of the USBProber (this is available via Apple Open Source). You have to look for an older OS X version there (recommended: 10.8.5) because the latest 10.9 does not include the source of IOUSBFamily. I strongly advise on spending some (quality) time with USBProber code. It will certainly bring a great amount of insight on how USB interaction is managed under OS X, and not only (it is also a great way to understand how NSOutlineView is used etc).
struct IOExternalMethod { IOService *object; IOMethod func; IOOptionBits flags; IOByteCount count0; IOByteCount count1; };
http://lists.apple.com/archives/darwin-development/2001/May/msg00760.html
The “object” field could hold your user client object, but this is often inconvenient if you use a static IOExternalMethod
table. The way USB does it is to build a static array describing all the user client methods. Because the object isn’t available when this is set up, the “object” field is set up with a constant that is checked when the method is actually looked up to return the correct object (itself or it’s owner, depending on the constant). FireWire does it another way. It keeps a global array of IOExternalMethod
‘s, but explicitly initializes them at runtime. What I do is sort of a hybrid. I use a static array (partially like USB) that is initialised with all the static data (i.e. everything except the object field) then I fill in the object field once at runtime using a simple loop (that never needs to change as methods are added since the actual IOExternalMethod
specifics are set up in the static array). To add a new method, you can just add a new entry to the table and don’t have to write any code other than the actual method implementation.
The “func” field points to the actual user client method in your class (e.g. &MyUserClient::MyMethod
).
The “flags” field should match the type of method (i.e. what kind of input/output parameters it uses). This is mainly used by IOUserClient
to make sure the method on the kernel side matches what the user side code thinks it is calling. For example, is_io_connect_method_scalarI_scalarO
(kernel side version of io_connect_method_scalarI_scalarO
) just checks to make sure the flags field is kIOUCScalarIScalarO
. If you want to pass in scalar values and return scalar values, use kIOUCScalarIScalarO
.
The “count0
” field is the input parameter count (for scalars) or input parameter size (for structures). It is mainly used by IOUserClient
to make sure the method on the kernel side matches what the user side code thinks it is. For example, is_io_connect_method_scalarI_scalarO
compares it against the input count to make sure they match. Some method types (structure in’s and scalar in/structure in) allow you can specify 0xFFFFFFFF
to tell IOUserClient
not to perform the parameter count/size check to allow you to pass in data of varying sizes.
The “count1
” field is similar the “count0”, but mostly for output parameter count/size. If you’re using a scaler in and structure in user client method, count1 is the input structure size.
Here’s an example off the top of my head (probably doesn’t compile or work):
static const IOExternalMethod sMyMethods[] { // MyMethod { NULL, // object (fill in at runtime) (IOMethod) &MyUserClient::MyMethod, // func kIOUCScalarIStructO, // flags 2, // count0 (2 input params). 0xFFFFFFFF // count1 (variable output size) } }; static const IOItemCount sMyMethodCount = sizeof( sMyMethods ) / sizeof( *sMyMethods ) IOReturn MyUserClient::MyMethod( UInt32 inParam1, UInt32 inParam2, void * outResult, UInt32 * outResultSize ) { // Do something useful. ... }
I would suggest checking out IOKitLib.c (to see how the user side invokes the user client on the kernel side), IOUserClient.cp/h (to see how the kernel side receives user requests and dispatches them to your user client) as well as the USB and FireWire user clients (to see working user client implementations).
Leave a Reply