The IOUSBLib Framework

The library used to write a user-space driver for a USB device is known as IOUSBLib, which is part of the I/O Kit framework (that is, the same framework a user-space application includes if it is communicating with a user client it defined itself).

The first task a user-space driver needs to perform is to watch for the arrival of the particular USB devices it is interested in. Since USB devices can be connected and disconnected from the computer at any time, there is no guarantee the device an application is interested in will be present when the application is launched. Therefore, it's a good idea for an application to install a notification callback that watches for the arrival of the USB devices it controls.

In Chapter 5, we saw how an application can create a matching dictionary to find each instance of a specified kernel driver. This same approach is used by an application that implements a user-space USB driver to locate the devices it will control. As in Chapter 5, we begin by creating a dictionary that specifies the class name of the driver objects our application is interested in matching against. For a USB device, this can be done as follows:

matchingDictionary = IOServiceMatching(kIOUSBDeviceClassName);  // “IOUSBDevice”

This matching dictionary is far too general for most applications since it will match against all USB devices connected to the computer, including the keyboard and mouse. An application is typically interested in only one particular USB device, so a matching dictionary such as this will be inappropriate. We can narrow down the list of devices the matching dictionary satisfies by including the specific Product ID and Vendor ID of the USB device our application can support.

A sample function that will create a matching dictionary for a USB device of a specified Vendor ID and Product ID is shown in Listing 15-1.

Listing 15-1. Creating a USB Matching Dictionary

#include <IOKit/IOKitLib.h>
#include <IOKit/usb/IOUSBLib.h>
#include <CoreFoundation/CoreFoundation.h>

CFDictionaryRef         MyCreateUSBMatchingDictionary (SInt32 idVendor, SInt32 idProduct)
{
        CFMutableDictionaryRef  matchingDictionary = NULL;
        CFNumberRef             numberRef;
        
        // Create a matching dictionary for IOUSBDevice
        matchingDictionary = IOServiceMatching(kIOUSBDeviceClassName);
        if (matchingDictionary == NULL)
                goto bail;
        
        // Add the USB Vendor ID to the matching dictionary
        numberRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &idVendor);
        if (numberRef == NULL)
                goto bail;
        CFDictionaryAddValue(matchingDictionary, CFSTR(kUSBVendorID), numberRef);
        CFRelease(numberRef);
        
        // Add the USB Product ID to the matching dictionary
        numberRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &idProduct);
        if (numberRef == NULL)
                goto bail;
        CFDictionaryAddValue(matchingDictionary, CFSTR(kUSBProductID), numberRef);
        CFRelease(numberRef);
        
        // Success - return the dictionary to the caller
        return matchingDictionary;
        
bail:
        // Failure - release resources and return NULL
        if (matchingDictionary != NULL)
                CFRelease(matchingDictionary);
                
        return NULL;
}

images Note An application can narrow the matching dictionary by adding any of the keys that could be placed in the property list of a kernel-based USB driver (see Chapter 8). For example, an application may include any of the keys idVendor, idProduct, bcdDevice, bDeviceSubClass, or bDeviceProtocol.

For a composite USB device, a driver may prefer to match against a particular interface rather than the entire USB device. A user-space driver is able to do this by creating a matching dictionary for a specific instance of an IOUSBInterface object. This is because the I/O Kit defines a user client for every instance of both the IOUSBDevice class and the IOUSBInterface class that is created in the kernel, which makes both of these classes available to user processes.

Having created a matching dictionary, an application is able to use the dictionary to iterate over all kernel objects that match its specifications or to install a callback to receive notifications when such a kernel object appears. This was described in Chapter 5. An example of a function to iterate over all kernel devices described by a given matching dictionary is given in Listing 15-2.

Listing 15-2. Finding and Iterating Over Devices That Satisfy a Matching Dictionary

void    MyFindMatchingDevices (CFDictionaryRef matchingDictionary)
{
        io_iterator_t           iterator = 0;
        io_service_t            usbDeviceRef;
        kern_return_t           err;
        
        // Find all kernel objects that match the dictionary.
        err = IOServiceGetMatchingServices(kIOMasterPortDefault, matchingDictionary,
                       &iterator);
        if (err == 0)
        {
                // Iterate over all matching kernel objects.
                while ((usbDeviceRef = IOIteratorNext(iterator)) != 0)
                {
                        IOUSBDeviceInterface300**       usbDevice;

                        // Create a driver for this device instance
                        usbDevice = MyStartDriver(usbDeviceRef);
                        IOObjectRelease(usbDeviceRef);
                }
                
                IOObjectRelease(iterator);
        }
}

The iterator for a matching dictionary, such as that shown in Listing 15-2, will return a number of io_service_t objects, each of which represents a kernel object. The io_service_t object is a user- space representation of an IOUSBDevice or IOUSBInterface object that resides in the kernel. Like any I/O Kit class, both IOUSBDevice and IOUSBInterface are each implemented by a C++ class. These classes contain a public interface that defines the methods through which a kernel driver interacts with a USB hardware device.

The IOUSBLib interface is implemented through a C++ class that wraps an io_service_t object representing either an IOUSBDevice or an IOUSBInterface. The user-space equivalent of the IOUSBDevice class is implemented by a class named IOUSBDeviceInterface and the user-space equivalent of the IOUSBInterface class is implemented by a class named IOUSBInterfaceInterface. The declaration of these classes can be found in the header file <IOKit/usb/IOUSBLib.h>. The code sample in Listing 15-3 demonstrates how a user-space application can instantiate an IOUSBDeviceInterface class from an io_service_t.

Listing 15-3. Instantiating an IOUSBDeviceInterface object from an io_service_t

IOUSBDeviceInterface300**       MyStartDriver (io_service_t usbDeviceRef)
{
        SInt32                          score;
        IOCFPlugInInterface**           plugin;
        IOUSBDeviceInterface300**       usbDevice = NULL;
        kern_return_t                   err;

        err = IOCreatePlugInInterfaceForService(usbDeviceRef, kIOUSBDeviceUserClientTypeID,
                                                   kIOCFPlugInInterfaceID, &plugin, &score);
        if (err == 0)
        {
                err = (*plugin)->QueryInterface(plugin,
                        CFUUIDGetUUIDBytes(kIOUSBDeviceInterfaceID300),
                        (LPVOID)&usbDevice);
                IODestroyPlugInInterface(plugin);
        }
        
        return usbDevice;
}

If the application had created a matching dictionary that specified an IOUSBInterface, each of the io_service_t values it receives would represent a kernel IOUSBInterface object and not an IOUSBDevice object. In this case, the user-space class the application would instantiate to represent the kernel object would be an IOUSBInterfaceInterface. This only requires one change to the parameters that are passed to two of the functions called in Listing 15-3. The call to IOCreatePlugInInterfaceForService would be called as follows.

err = IOCreatePlugInInterfaceForService(usbDeviceRef, kIOUSBInterfaceUserClientTypeID,
                                                kIOCFPlugInInterfaceID, &plugin, &score);

Similarly, the call to QueryInterface on the returned plugin object would take the following parameters:

IOUSBInterfaceInterface300**            usbInterface = NULL;
err = (*plugin)->QueryInterface(plugin,
                        CFUUIDGetUUIDBytes(kIOUSBInterfaceInterfaceID300),
                        (LPVOID)&usbInterface);

Whether the application is instantiating an IOUSBDeviceInterface or an IOUSBInterfaceInterface, the structure of the code is the same. In both cases, the first step is to call the function IOCreatePlugInInterfaceForService(), which returns an object that has the type IOCFPlugInInterface. This object serves as a factory for instantiating the user-space I/O Kit classes and serves no purpose once this has been done. In fact, as Listing 15-3 shows, the object is released with a call to IODestroyPlugInInterface() as soon as the IOUSBDeviceInterface object has been created.

The IOCFPlugInInterface class contains a method named QueryInterface() that an application uses to receive a pointer to the IOUSBDeviceInterface or the IOUSBInterfaceInterface object. The IOUSBLib provides a way of versioning classes. This allows a future release of Mac OS X to extend a class, such as IOUSBDeviceInterface, to include additional functionality while maintaining backwards compatibility with applications that were written for an older version of the class.

When an application requests an interface, such as IOUSBDeviceInterface, it must also specify the version of that class it expects to receive. The version of the class is part of the class name; for example, IOUSBDeviceInterface300 identifies the version of the IOUSBDeviceInterface class included with the IOUSBFamily version 3.0.0. This was shipped with Mac OS X 10.5. A full set of the class names and their version, and the minimum version of the operating system required to support that class is provided in the IOUSBLib.h header file.

images Tip As a general rule, the version of the IOUSBDeviceInterface and IOUSBInterfaceInterface classes you should use will be tied to the minimum version of Mac OS X your application needs to support. For example, an application that requires Mac OS X 10.5 or later should use IOUSBDeviceInterface300 and IOUSBInterfaceInterface300.

One aspect of the user client classes that can take some time to get used to is that IOUSBLib returns a pointer to the object pointer. This means that before calling a method from the object, the variable holding the interface requires an additional dereference. Another idiosyncrasy of the IOUSBLib classes is that each method requires a reference to the object to be passed as the first parameter. For example, consider the method QueryInterface() implemented by the IOCFPlugInInterface class, although you would expect to call the method with the following line of code:

plugin->QueryInterface(parameters);     // INCORRECT

Instead, because the “plugin” variable will have the type IOCFPlugInInterface** and is therefore a pointer to a pointer, the method must actually be called using the following structure:

(*plugin)->QueryInterface(plugin, parameters);

images Note If you are familiar with Microsoft's Component Object Model (COM), you will instantly recognize the method name QueryInterface(). All IOUSBLib classes are based on the COM programming model and are derived from the base class IUnknown. The biggest impact of this design on an application using IOUSBLib is that all IOUSBLib objects are reference counted; they can be retained by calling the method AddRef() and can be released by calling the method Release().

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.221.182.150