The IOUSBDeviceInterface Class

The IOUSBDeviceInterface class is a user-space class that provides equivalent functionality to the IOUSBDevice class used by kernel drivers. It's worth noting that although the user client class provides similar functionality to its kernel counterpart, it implements it through a different set of methods. Therefore, a kernel USB driver written to work with the IOUSBDevice class cannot simply be brought into a user-space USB driver.

After obtaining a reference to the user-space IOUSBDeviceInterface, the first thing an application will need to do is to configure the USB hardware. The steps an application will take to do this will closely match those explained in Chapter 8 for a kernel driver. First, the application needs to obtain exclusive access to the USB hardware and prevent the hardware's configuration from being changed by another driver, which could either be another user-space driver or a kernel driver. This is achieved by calling the IOUSBDeviceInterface method USBDeviceOpen(), as follows:

error = (*usbDevice)->USBDeviceOpen(usbDevice);

If the error code returned from the method is kIOReturnSuccess, the application has been granted exclusive access to configure the hardware. If another application or driver has already obtained exclusive access to the hardware, the call to USBDeviceOpen() will fail with the error code kIOReturnExclusiveAccess and the application should abort all further access to the device, possibly reporting an error to the user.

When an application has finished using the device, it should relinquish its exclusive access to the hardware by calling the IOUSBDeviceInterface method USBDeviceClose(), as follows:

error = (*usbDevice)->USBDeviceClose(usbDevice);

What follows is a summary of the methods provided by the IOUSBDeviceInterface class that provide access to the information contained in the USB device descriptor. In this chapter, we describe the IOUSBDeviceInterface300 class, so some of the following methods will not be present in earlier versions of the IOUSBDeviceInterface class.

  • GetDeviceClass, GetDeviceSubClass, and GetDeviceProtocol: Returns the device class (bDeviceClass), subclass (bDeviceSubClass), and protocol (bDeviceProtocol) from the USB device descriptor. Together, these three values define the function of the device based on values defined in the USB specification.
  • GetDeviceVendor: Returns the USB Vendor ID of the device (idVendor).
  • GetDeviceProduct: Returns the USB Product ID of the device (idProduct).
  • GetDeviceReleaseNumber: Returns the device release number (bcdDevice).
  • USBGetManufacturerStringIndex: Returns the index of the string for the device's manufacturer name (iManufacturer). To read the actual string from the device, an application must follow up by sending the standard device request “get descriptor” to read an entry from the device's string table.
  • USBGetProductStringIndex: Returns the index of the string for the device's product name (iProduct).
  • USBGetSerialNumberStringIndex: Returns the index of the string for the device's serial number (iSerialNumber).
  • GetNumberOfConfigurations: Returns the number of configurations the device supports at its current speed (bNumConfigurations).

The following methods allow an application to read dynamic properties that relate to the current state of the USB device:

  • GetDeviceAddress: Returns the address of the USB device, which is unique for the bus it is connected to.
  • GetDeviceSpeed: Returns the speed of the device. Possible values include kUSBDeviceSpeedLow, kUSBDeviceSpeedFull, or kUSBDeviceSpeedHigh.
  • GetLocationID: Returns a 32-bit value that uniquely identifies a USB device on the system, based on the USB hub and port the device is connected to. The Location ID won't change following a restart of the computer, but will change if the device is connected to another hub or port. Therefore, if the USB device provides a serial number string, it is a preferable way to track a device across reboots and disconnections.
  • GetBusFrameNumber: Returns the current frame number of the USB bus to which the device is connected. The function also returns the system host time that corresponds to the time at which the kernel driver handled the request. The system time may fall anywhere within the returned USB frame.
  • GetBusFrameNumberWithTime returns the current frame number of the USB bus to which the device is connected, but also returns the system host time that corresponds to the start of that frame. This method was introduced in later versions of the IOUSBDeviceInterface class and supersedes the method GetBusFrameNumber().
  • GetBusMicroFrameNumber: Returns the current microframe number of the USB bus to which the device is connected. The function also returns the system host time that corresponds to the time at which the kernel driver handled the request (and so this method behaves like GetBusFrameNumber()).

The following methods provide a way for an application to reset the USB device:

  • ResetDevice: Resets the USB device, returning it to the non-configured state.
  • USBDeviceReEnumerate: Instructs the hub to which this device is connected to reset the port that this device is connected to. This is equivalent to disconnecting the device from the USB port and reconnecting it.
  • USBDeviceSuspend: Despite the name of this method, it can either suspend or resume the port to which the USB device is connected, depending on the value of a Boolean parameter. If the method suspends the device, any outstanding transactions to the device will be aborted.
  • USBDeviceAbortPipeZero: Aborts any outstanding transaction on the control endpoint.

The IOUSBDeviceInterface class provides the following methods to allow an application to send control requests to the device on endpoint zero:

  • DeviceRequest: Is a synchronous method and will not return until the device request has completed. The device request is described by the same IOUSBDevRequest structure used by kernel USB drivers.
  • DeviceRequestTO: Is a synchronous method that takes two timeout values, expressed in milliseconds. The method will return once the device request has completed or a specified timeout period has elapsed (whichever comes first). The caller provides two timeout values. The device may stop sending or receiving data while handling the request, so one timeout value specifies the maximum amount of time to wait since the last data transfer before aborting the control request. The other timeout value specifies the maximum amount of time to allow for the control request to complete from start to finish. A control request is aborted if either of the specified timeout conditions occurs.
  • DeviceRequestAsync: Is an asynchronous equivalent of the method DeviceRequest. The method takes a callback function the I/O Kit uses to notify the caller once the request has completed. It's important to note that a return value of kIOReturnSuccess from this method doesn't indicate the request completed successfully; rather, it indicates the operation was successfully started. The actual result of the operation will be returned through the callback function. The value of the arg0 parameter passed to the completion callback holds the number of bytes that were either read from the device or written to the device.

Listing 15-4 lists a sample function that uses the DeviceRequest method to read the manufacturer string from the USB string table. The function starts by calling USBGetManufacturerStringIndex to obtain the index of the manufacturer string. Next, the device request structure is prepared. The bmRequestType field specifies that the request is a data read (kUSBIn), is a standard request (as opposed to a request defined by the device class or is vendor-specific), and that the recipient of the request is the device (as opposed to an interface or an endpoint). Since the string table is treated as just another descriptor table by the USB Specification, the control request that is sent to the device to read a string is the kUSBRqGetDescriptor request. The wValue field indicates we are reading a string descriptor and also contains the index of the string to be read.

Finally, if the string data is successfully read from the device, the function creates a CFStringRef from the returned data, which is returned from the device with an encoding of UTF-16 little-endian.

Listing 15-4. A Function That Demonstrates a Device Request Through the IOUSBDeviceInterface Class

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

IOReturn                PrintDeviceManufacturer (IOUSBDeviceInterface300** usbDevice)
{
        UInt8                   stringIndex;
        IOUSBDevRequest         devRequest;
        UInt8                   buffer[256];
        CFStringRef             manufString;
        IOReturn                error;
        
        // Get the index in the string table for the manufacturer.
        error = (*usbDevice)->USBGetManufacturerStringIndex(usbDevice, &stringIndex);
        if (error != kIOReturnSuccess)
                return error;
        
        // Perform a device request to read the string descriptor.

        devRequest.bmRequestType = USBmakebmRequestType(kUSBIn, kUSBStandard, kUSBDevice);
        devRequest.bRequest = kUSBRqGetDescriptor;
        devRequest.wValue = (kUSBStringDesc << 8) | stringIndex;
        devRequest.wIndex = 0x409;              // Language setting - specify US English
        devRequest.wLength = sizeof(buffer);
        devRequest.pData = &buffer[0];
        bzero(&buffer[0], sizeof(buffer));
        //
        error = (*usbDevice)->DeviceRequest(usbDevice, &devRequest);
        if (error != kIOReturnSuccess)
                return error;
        
        // Create a CFString representation of the returned data.
        int             strLength;
        strLength = buffer[0] - 2;              // First byte is length (in bytes)
        manufString = CFStringCreateWithBytes(kCFAllocatorDefault, &buffer[2], strLength,
                                                kCFStringEncodingUTF16LE, false);
        // Print the manufacturer string.
        CFShow(manufString);
        CFRelease(manufString);
        
        return error;
}

The following methods are used by an application to examine and set the device configuration, and to iterate the device's interfaces. These methods are usually called by an application to initialize the USB device when it is first detected:

  • GetConfigurationDescriptionPtr: Returns a pointer to the descriptor for the specified configuration; note that although the caller receives a pointer to an IOUSBConfigurationDescriptorPtr structure, the buffer is owned by the IOUSBDeviceInterface object and should not be released by the caller.
  • GetConfiguration: Returns the active configuration number of the device. Note that this is not the index of the configuration, but rather the value of bConfigurationValue from the active configuration description.
  • SetConfiguration: Sets the active configuration of the device. The configuration is specified by passing the bConfigurationValue from the desired configuration description.
  • CreateInterfaceIterator: Creates an iterator over the device's interfaces. Like its kernel equivalent, the caller provides an IOUSBFindInterfaceRequest structure that specifies the properties that returned interfaces must match.

Having examined the functionality provided by the IOUSBDeviceInterface class, we are now in a position to consider the steps an application will typically take to initialize a new USB device that has been attached to the system. A sample initialization function is given in Listing 15-5.

Listing 15-5. A Sample Function for Configuring a USB Device During Initialization

IOReturn         MyConfigureDevice (IOUSBDeviceInterface300** usbDevice)
{

        UInt8                            numConfigurations;
        IOUSBConfigurationDescriptorPtr  configDesc;
        IOUSBFindInterfaceRequest        interfaceRequest;
        io_iterator_t                    interfaceIterator;
        io_service_t                     usbInterfaceRef;
        IOReturn                         error;
        
        // Get the count of the device's configurations.
        error = (*usbDevice)->GetNumberOfConfigurations(usbDevice, &numConfigurations);
        if (error != kIOReturnSuccess)
                return error;
        // Ensure the device has at least one configuration
        if (numConfigurations == 0)
                return kIOReturnError;
        
        // Read the descriptor for the first configuration.
        error = (*usbDevice)->GetConfigurationDescriptorPtr(usbDevice, 0, &configDesc);
        if (error != kIOReturnSuccess)
                return error;
        
        // Make the first configuration the active configuration.
        error = (*usbDevice)->SetConfiguration(usbDevice, configDesc->bConfigurationValue);
        if (error != kIOReturnSuccess)
                return error;
        
        // Create an iterator over all interfaces in the active configuration.
        interfaceRequest.bInterfaceClass = kIOUSBFindInterfaceDontCare;
        interfaceRequest.bInterfaceSubClass = kIOUSBFindInterfaceDontCare;
        interfaceRequest.bInterfaceProtocol = kIOUSBFindInterfaceDontCare;
        interfaceRequest.bAlternateSetting = kIOUSBFindInterfaceDontCare;
        
        error = (*usbDevice)->CreateInterfaceIterator(usbDevice, &interfaceRequest,
                     &interfaceIterator);
        if (error != kIOReturnSuccess)
                return error;
        
        // Iterate over all interfaces.
        while ((usbInterfaceRef = IOIteratorNext(interfaceIterator)) != 0)
        {
                MySetupInterface(usbInterfaceRef);
                IOObjectRelease(usbInterfaceRef);
        }
        IOObjectRelease(interfaceIterator);
        
        return kIOReturnSuccess;
}

The code in Listing 15-5 begins by setting the active configuration of the device to a known configuration, in this case the device's first configuration. This step is necessary because the device may have been used by another application before our application was launched, so the device may be in an unknown state.

Next, the application iterates over all interfaces in the active configuration. If we were interested in a particular interface, we could narrow down the list of interfaces returned by the iterator by specifying the desired class, subclass, protocol, or alternate setting for the interface. Obtaining a USB interface object is particularly important, since the only way an application can access the device's endpoints, other than the control endpoint, is through the IOUSBInterfaceInterface class.

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

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