In the dynamic I/O Kit environment, a driver can be loaded and unloaded, or activated and deactivated, at any time. The constant in this flurry of activity is the set of IOService and IORegistryEntry methods that define every driver’s life cycle.
With the exception of init and free, which OSObject defines, the remaining driver life-cycle methods are IOService methods. Because these methods are well-documented elsewhere (see I/O Kit Fundamentals), this chapter does not cover them again. Instead, the following sections present the less well-known IOService methods related to the driver life cycle.
Driver Matching
Passive-Matching Keys
Driver State
Resources
User Clients
Probing
IOService includes a number of methods used in the driver-matching process, but unless you are developing a family or writing a familyless driver, you do not need to implement any of them. It is important, however, to understand the driver-matching process and how the I/O Kit uses IOService’s matching methods so you can successfully define your driver’s personality dictionary.
Table 3-1 shows the IOService matching methods and briefly describes how the I/O Kit uses them.
IOService method | Usage |
|---|---|
| Helper function used in implementing |
| Helper function used in implementing |
| In-kernel counterpart of the user-space function |
| Optionally implemented by families (or familyless drivers) to examine family-specific match properties |
| In-kernel counterpart of the user-space function |
| In-kernel matching function to create a dictionary to search for |
| In-kernel counterpart of the user-space function |
For more information on the user-space functions IOServiceGetMatchingServices, IOServiceNameMatching, and IOServiceMatching, see Accessing Hardware From Applications or the HeaderDoc documentation for IOKitLib.h in /Developer/ADC Reference Library/documentation/Darwin/Reference/IOKit.
Understanding how the I/O Kit uses your driver’s personality in the driver-matching process is an important prerequisite to crafting successful personality dictionaries. As described in I/O Kit Fundamentals, the I/O Kit uses the driver’s required IOProviderClass key during the class-matching step to eliminate all drivers with the wrong provider type. Usually, this step eliminates the majority of drivers, leaving only those that attach to the correct nub type.
In the passive-matching phase, the I/O Kit examines the remaining keys in a driver’s personality dictionary and compares them to the provider–class specific information in the device nub. It is during this phase that the family can implement the matchPropertyTable method to more closely inspect the match candidate.
When a family implements matchPropertyTable, it can interpret the match candidate’s property values in any way it chooses, without interference from the I/O Kit. Additionally, if a driver includes a family-defined property in its Info.plist file, the I/O Kit ignores it if the family does not implement the matchPropertyTable method. As a driver writer, you should be familiar with the properties your driver’s family uses. For example, the IOSCSIPeripheralDeviceNub class (in the IOSCSIArchitectureModel family) implements matchPropertyTable to rank a match candidate according to how many family-specific properties it has. Listing 3-1 shows a fragment of IOSCSIPeripheralDeviceNub’s matchPropertyTable implementation.
Listing 3-1 A matchPropertyTable implementation
bool IOSCSIPeripheralDeviceNub::matchPropertyTable ( OSDictionary * table, |
SInt32 * score ) |
{ |
bool returnValue = true; |
bool isMatch = false; |
SInt32 propertyScore = * score; |
/* Adjust a driver's initial score to avoid "promoting" it too far. */ |
/* ... */ |
/* Use IOSCSIPeripheralDeviceNub's sCompareProperty method to compare */ |
/* the driver's properties with family-specific properties. */ |
if ( sCompareProperty ( this,table, kIOPropertySCSIPeripheralDeviceType, |
&isMatch ) ) |
{ |
if ( isMatch ) { |
*score = kDefaultProbeRanking; |
} |
else { |
*score = kPeripheralDeviceTypeNoMatch; |
returnValue = false; |
} |
if ( sCompareProperty ( this, table, |
kIOPropertySCSIVendorIdentification, |
&isMatch ) ) { |
if ( isMatch ) { |
*score = kFirstOrderRanking; |
/* Continue to test for additional properties, */ |
/* promoting the driver to the next rank with each */ |
/* property found. */ |
} |
} |
} else { |
/* Take care of SCSITaskUserClient "driver" here. */ |
} |
if ( *score != 0 ) |
*score += propertyScore; |
return returnValue; |
} |
In addition to the family-specific keys the family may require, there are several passive-matching keys the I/O Kit defines:
IOProviderClass
IOPropertyMatch
IONameMatch
IOResourceMatch
IOParentMatch
IOPathMatch
IOProviderClass is required for all driver personalities because it declares the name of the nub class the driver attaches to. The provider class name also determines the remaining match keys.
The IOPropertyMatch key represents a specific list of properties that must match exactly in order for the I/O Kit to load the driver. For example, the IOBlockStorageDriver defines the following personality:
<key>IOProviderClass</key> |
<string>IOBlockStorageDevice</string> |
<key>IOClass</key> |
<string>IOBlockStorageDriver</string> |
<key>IOPropertyMatch</key> |
<dict> |
<key>device-type</key> |
<string>Generic</string> |
</dict> |
After the I/O Kit determines that the IOBlockStorageDriver is a match candidate for a nub of class IOBlockStorageDevice, it examines the value of the IOPropertyMatch key. If the nub has a device-type key with the value Generic, the I/O Kit loads this personality of the IOBlockStorageDriver.
The utility of the IOPropertyMatch key lies in the fact that the I/O Kit uses its value in the matching process regardless of whether the family implements the matchPropertyTable method. Unlike the matchPropertyTable method, however, the IOPropertyMatch key does not allow the family to interpret its value—if the IOPropertyMatch value does not match the nub’s corresponding property exactly, the I/O Kit removes the driver from the pool of match candidates.
The IONameMatch key matches on the compatible, name, or device-type properties of a provider. The value of the IONameMatch key can be an array of strings representing a list of all such properties your driver can match on. After the I/O Kit has matched and loaded your driver, it places the IONameMatched property and the value of the actual property value your driver matched on in your driver’s I/O Registry property table.
Listing 3-2 shows part of one of the personalities of the GossamerPE (a Platform Expert for the Blue and White G3 computer):
Listing 3-2 A personality of the GossamerPE
<key>GossamerPE</key> |
<dict> |
<key>IOClass</key> |
<string>GossamerPE</string> |
<key>IONameMatch</key> |
<array> |
<string>AAPL,Gossamer</string> |
<string>AAPL,PowerMac G3</string> |
<string>AAPL,PowerBook1998</string> |
<string>iMac,1</string> |
<string>PowerMac1,1</string> |
<string>PowerMac1,2</string> |
<string>PowerBook1,1</string> |
</array> |
<key>IOProviderClass</key> |
<string>IOPlatformExpertDevice</string> |
</dict> |
The IONameMatch key contains an array of several possible names. Note that there is no IONameMatched key in this personality. Listing 3-3 shows part of the I/O Registry entry for the GossamerPE after the I/O Kit matched and loaded it for a particular device:
Listing 3-3 Partial I/O Registry entry for GossamerPE
GossamerPE <class GossamerPE> |
{ |
"IOClass" = "GossamerPE" |
"IOProviderClass" = "IOPlatformExpertDevice" |
"IONameMatched" = "PowerMac1,1" |
} |
The IOResourceMatch key declares a dependency or connection between your driver and a specific resource, such as the BSD kernel or a particular resource on a device, like an audio-video jack. If you add the key-value pair
<key>IOResourceMatch</key> |
<string>IOBSD</string> |
to your driver’s personality, for example, your driver will not load until the resource, in this case the BSD kernel, is available. In this way, you can effectively stall the loading of your driver until its required resource is available. You can examine the available resources in IOResources on a running Mac OS X system using the I/O Registry Explorer application (available in /Developer/Applications). In the IOService plane (I/O Registry Explorer’s default plane), click Root, click the platform expert device for your machine (such as PowerMac3,3), and then click IOResources.
The remaining passive-matching keys, IOParentMatch and IOPathMatch, are useful for user-space driver-client matching and are very rarely used in in-kernel driver personalities. These keys depend on information about the location of an object or service in the I/O Registry rather than on the device-specific or service-specific information a nub publishes. An application developer can examine the I/O Registry to determine the location of a specific object and create a matching dictionary using that information. This is much more difficult for an in-kernel driver developer, however, because accurate I/O Registry location of objects may not be available at development time.
IOService provides some methods that give you information about an IOService object’s state which is described in the IOService private instance variable state. You can view the states of objects currently attached in the I/O Registry by typing ioreg -s on the command line.
When a driver is in the midst of registration, matching, or termination, its busy state is set to one. When these activities conclude, the busy state is reduced to zero. Any change in an IOService object’s busy state causes an identical change in its provider’s busy state, so that a driver or other IOService object is considered busy when any of its clients is busy.
A driver may need to wait for a change in another IOService object’s state. The waitQuietmethod allows a driver to block until the specified IOService object’s busy state is zero.
A driver can call the adjustBusy method to mark itself busy if, for example, it wishes to asynchronously probe a device after exiting from its start method.
Two IOService methods return information about a driver’s stage in its life cycle. The isOpen method determines if a specified client has an IOService object open. If you pass zero instead of a pointer to a particular client object, isOpen returns the open state for all clients of an object.
The second method, isInactive, indicates that an IOService object has been terminated. An inactive object does not support matching, attachments, or notifications.
The I/O Kit’s resource service uses the matching and notification methods usually used for driver objects to make resources available system-wide through IOResources, an instance of IOService attached to the root of the I/O Registry. A resource might be an audio-output jack on a device or a service, such as the BSD kernel. The I/O Kit itself appears as a resource.
A driver, or other IOService object, can publish a resource in IOResources using the IOService method publishResource. The publication of a resource triggers any notifications set on its presence, and the objects holding those notifications can then access the resource.
The IOKernelDebugger (in the IONetworking family) publishes its presence as a resource in this way:
publishResource ( "kdp" ); |
A driver can request a notification about the resource, make the resource a matching condition (as described in“Passive-Matching Keys”), or, as in the AppleUSBCDCDriver, call the waitForService method to wait until a particular resource appears:
waitForService ( resourceMatching ( "kdp" ) ); |
The waitForService method allows an IOService object to block until the object matching the specified matching dictionary is registered. Optionally, a driver can also specify a maximum time to wait.
IOService provides the newUserClient method for families that support user clients. A family that provides user clients implements the newUserClient method to create a connection between a user-space client application and an in-kernel user-client object.
The default implementation of newUserClient looks up the IOUserClientClass key in the given IOService object’s properties. If the key is found, IOService creates an instance of the class given in the value. Then, it calls the methods initWithTask, attach, and start on the newly instantiated user-client class object. If, on the other hand, there is no IOUserClientClass key (or its value is invalid) or any of the initialization methods fail, IOService returns kIOReturnUnsupported.
For more information on implementing a user-client object, see “Making Hardware Accessible to Applications.”
In addition to the probe method your driver can choose to implement to examine a device during the matching process (described in detail in I/O Kit Fundamentals), IOService provides the requestProbe method for discovering newly added or removed devices. The requestProbe method gives the families that do not automatically detect device addition and removal a way to rescan the bus and publish new devices and detach removed devices.
A family, or perhaps a bus-controller driver, implements requestProbe and passes it a set of options contained in an object of type IOOptionBits, which is not interpreted by IOService.
Last updated: 2007-03-06