USB IO Targets


USB I/O Targets

A primary design goal for WDF was to make the driver models easy to extend to support new types of hardware. The first hardware-specific specialized I/O targets in both UMDF and KMDF support USB devices. You can use the USB I/O targets to write a fully functional Windows driver for a USB device that uses the Windows USB device stack.

Although using USB devices is generally easy, programming them can be difficult. Drivers must deal with surprise removal, state management, and cleanup because a user can remove a device at any time. Although the USB driver interface is relatively complex, the UMDF and KMDF implementations present the abstraction in an organized way and simplify many of the routine tasks that USB client drivers must perform.

About USB Devices

Data is transferred between a USB device and the USB host through an abstraction called a pipe. A pipe has an endpoint on a device, and that endpoint has an address. The other end of a pipe is always the host controller.

In USB terminology, the direction of a transfer is based on the host. Thus, IN always refers to transfers to the host from a device and OUT always refers to transfers from the host to a device. USB devices can also support bi-directional transfers of control data. Figure 9-3 shows a USB host and a device with three endpoints.

image from book
Figure 9-3: USB abstraction

The three endpoints for the USB device in Figure 9-3 include an OUT endpoint, an IN endpoint, and an endpoint that is used for bi-directional control transfers.

The endpoints on a device are grouped into functional interfaces, and a set of interfaces makes up a device configuration. For example, a one-touch USB backup device might define one group of endpoints as a HID interface that controls the one-touch backup button and another group of endpoints as an interface that provides the mass storage function for the device. The configuration of the device comprises both of these interfaces.

In addition, an individual interface can have multiple settings. Consider a Bluetooth dongle in which one interface is defined for command, control, and lossless data, and a second interface is defined for lossy voice data. The second interface has several alternate settings that provide increasing levels of voice quality and consume increasing bus bandwidth. Only one of the alternate settings is configured at any given time.

The endpoints in the current alternate setting are associated with pipes and therefore can be targets for I/O. All the other endpoints are just endpoints.

Device and Configuration Descriptors

Each USB device has a device descriptor, which provides device-specific and vendor-specific information such as the version of the USB specification that the device supports, the device class, and the device name, just to name a few. The device descriptor also contains the number of configurations that the device supports. However, WDF and the built-in Windows USB class drivers support only the first configuration on a device.

Each configuration likewise has a descriptor. The configuration descriptor describes the power and wake-up capabilities of the configuration and includes the number of interfaces in the configuration. A configuration and its interfaces follow these rules:

  • A configuration contains one or more interfaces, all of which are concurrently active.

  • Each interface can have one or more alternate settings.

    An alternate setting is a collection of endpoints. Each alternate setting in an interface can have a different number of endpoints or can have the number of endpoints that consume varying degrees of bus bandwidth.

  • An endpoint can be in only one interface within a configuration, but can be used in multiple alternates within that interface.

    All of the endpoints in an alternate setting can be in use concurrently.

Figure 9-4 shows a hypothetical device configuration.

image from book
Figure 9-4: USB configuration

In the figure, Interface 0 has two alternate settings, either one of which can be active at any time. Interface 1 has only one alternate setting.

During configuration, the driver for the USB device selects one or more or interfaces and an alternate setting for each interface. Each interface includes one or more endpoints. By choosing the interfaces and the setting within each interface, the driver indicates its support for particular device functions.

All devices support endpoint 0. Endpoint 0 is a control endpoint that is used to configure the interfaces.

Most USB devices do not provide multiple interfaces or multiple alternate settings. The OSR USB Fx2 device, for example, has one interface with one alternate setting and three endpoints.

USB Data Transfer Models

USB supports three types of data transfer I/O models-interrupt, bulk, and isochronous-as well as a separate control I/O model. All USB devices support the control mechanism, but support for data transfer mechanisms is optional. Data transfers use unidirectional endpoints, whereas control transfers use bidirectional endpoints.

The data transfer models have the following characteristics:

  • Bulk transfers Unidirectional with no guaranteed latency or bandwidth and guaranteed error-free delivery.

  • Interrupt transfers Unidirectional with guaranteed latency and error retry.

  • Isochronous transfers Guaranteed bandwidth with bounded latency that provides a constant unidirectional data rate with no error retry, which can lead to data loss.

Each endpoint is associated with a data transfer type. Interrupt, bulk, and isochronous endpoints are unidirectional. Control endpoints are bidirectional and are generally used to enumerate a USB device and select an operational configuration. Every endpoint has a unique address.

WDF supports bulk and interrupt endpoints along with the default control endpoint 0.

 KMDF  To communicate with an isochronous endpoint, a KMDF driver can create a URB, use USBD functions to format the URB, use WDF methods to insert the URB into an I/O request object, and send it to the I/O target by using WdfRequestSend. See the Isorwr.c file in the Usbsamp sample for an example.

image from book
Why KMDF Doesn't Support Isochronous Endpoints

Although it might seem like an arbitrary decision not to have native KMDF support for isochronous endpoints, we based this decision on a couple of factors. First, we surveyed the device market and found that very few devices used isochronous endpoints and most of the devices that did were covered by in-box drivers. Another factor was the lack of devices to test our implementation against; the Fx2 device is a great little test device which we used in developing KMDF to test many USB and power policy features, but it arrived well into KMDF development and, more germane to this problem, it does not have isochronous endpoints.

Lack of full-blown native support does not mean that you cannot leverage the generic WDFIOTARGET object features that are built into the WDFUSBPIPE object. We wanted to make sure that you could still use parts of KMDF when processing isochronous data. For instance, you can call WdfUsbTargetPipeFormatRequestForUrb or WdfRequestWdmFormatUsingStackLocation to format a request with an isochronous URB and then call WdfRequestSend to send it down the stack. By using WdfRequestSend, you get all of the request tracking and WDFIOTARGET stop logic for your isochronous data. You could also easily use these APIs to create your own continuous reader for your isochronous endpoint.
-Doron Holan, Windows Driver Foundation Team, Microsoft

image from book

Specialized USB I/O Targets in WDF

WDF drivers for USB devices should use the specialized USB I/O target support that both UMDF and KMDF provide. WDF defines three types of objects for use with USB I/O targets:

  • A USB target device object represents a USB device and provides methods for retrieving information about the device and sending control requests to the device.

  • A USB interface object represents an individual interface and supports methods with which a driver can select an alternate setting and retrieve information about the setting.

  • A USB target pipe object represents an individual pipe-that is, an endpoint that is configured in the current alternate setting for an interface.

The interfaces and methods in Table 9-7 support USB.

Table 9-7: Interfaces and Methods to Support USB
Open table as spreadsheet

USB abstraction

UMDF interface

KMDF object type and methods

USB device

IWDFUsbTargetDevice

WDFUSBDEVICE WdfUsbTargetDeviceXxx

Interface

IWDFUsbInterface

WDFUSBINTERFACE WdfUsbInterfaceXxx

Pipe

IWDFUsbTargetPipe

WDFUSBPIPE WdfUsbTargetPipeXxx

A driver creates target device, interface, and target pipe objects during USB configuration, which typically occurs in the driver's prepare hardware callback. Consequently:

  • A UMDF function driver for a USB device must implement the IPnPCallbackHardware interface on the device callback object.

  • A KMDF driver must register the EvtDeviceD0Entry, EvtDeviceD0Exit, and EvtPrepareHardware callbacks.

Important 

A UMDF driver that uses the USB I/O target must specify WinUSB as the value for the I/O dispatcher in the INF file, as described in "UMDF I/O Dispatchers" earlier in this chapter.

USB Target Device Objects

A driver calls the framework to create a USB target device object as follows:

  • A UMDF driver queries for the IWDFUsbTargetFactory interface on the device object and then uses the returned pointer to call the CreateUsbTargetDevice method. This method creates the framework USB target device object and returns a pointer to an IWDFUsbTargetDevice interface.

    The framework-created USB target device object is already configured with the default configuration and the alternate setting 0 for each interface.

  • A KMDF driver calls the WdfUsbTargetDeviceCreate method, which returns a handle to a WDFUSBDEVICE object. Unlike most of the other KMDF object creation methods, this method does not require an object configuration structure. After the KMDF driver creates the USB target device, it must call WdfUsbTargetDeviceSelectConfig to configure the device.

By default, the USB target device object is a child of the device object.

After the driver creates the USB target device object, the driver calls methods on the object to retrieve the configuration and device descriptors. WDF also supports methods that perform the following types of device-specific requests for the USB device I/O target:

  • Format and send device I/O control requests to the control pipe.

  • Retrieve various other information about the device.

  • Reset and cycle power on the port. (KMDF only)

  • Format and send WDM URBs. (KMDF only)

 KMDF  A KMDF driver can use the WdfIoTargetStart and WdfIoTargetStop methods to control USB I/O targets as for any other targets. When a KMDF driver specifies a WDFUSBDEVICE handle in a call to one of these methods, the framework starts or stops all of the currently configured pipes on all of the interfaces for the USB device object. For example, if a driver calls WdfIoTargetStop on the WDFUSBDEVICE handle, the framework stops sending I/O requests to the pipes that are part of each interface in addition to stopping I/O to the USB target device itself. Consequently, the driver is not required to iterate through all of the pipes on all of the interfaces to stop or start them individually.

USB Interface Objects

When the driver configures the device, the framework creates a USB interface object for each interface in the configuration. By default, the USB interface object is a child of the device object. A driver calls a method on the USB target device object to get access to the interface objects, as follows:

  • A UMDF driver passes an interface number to IWDFUsbTargetDevice::RetrieveUsbInterface to receive a pointer to the IWDFUsbInterface interface for that particular USB interface object.

  • A KMDF driver passes an interface number to WdfUsbTargetDeviceGetInterface to get a handle to the WDFUSBINTERFACE object for a particular interface.

After the driver selects an interface, it can select an alternate setting within that interface and then can retrieve information about the pipes in the setting. By default, the framework uses alternate setting 0 within each interface.

USB Target Pipe Objects

A pipe is an endpoint that is part of the current interface alternate setting. The framework creates a pipe object for each pipe in the setting, and a driver gets access to the pipes as follows:

  • A UMDF driver calls IWDFUsbInterface::RetrieveUsbPipeObject on the framework USB interface object to get a pointer to an IWDFUsbTargetPipe interface on a particular pipe.

  • A KMDF driver calls WdfUsbInterfaceGetConfiguredPipe to get a handle to the WDFUSBPIPE object for a particular pipe.

For pipes, WDF supports methods that return information about the pipe configuration, manage I/O on the pipe, and control pipe policy, such as limits on packet size.

The lifetime of a pipe object is tied to that of the currently selected interface setting. KMDF explicitly deletes the pipe objects when the driver selects a new alternate setting. UMDF relies on reference counting and the WDF object model to delete the objects when they are no longer being used.

How to Configure a USB I/O Target

The function driver for a USB device must configure the device before the device can accept any I/O requests other than requests to the control pipe. Depending on the design of the device, configuration can involve one or more of the following:

  • Retrieving information about the current configuration, such as the number of interfaces.

  • Retrieving the interface objects.

  • Selecting an alternate setting within each interface, if the interface supports more than one setting.

  • Retrieving the pipes within each interface.

If your device has a single interface with a single alternate setting, you can skip most of these steps and simply retrieve the pipes.

The examples that follow use the OSR Fx2 device, which is configured as follows:

Number of configurations

1

Number of interfaces

1

Number of alternate settings

1

Number of endpoints

3

Data transfer types and directions

Interrupt IN

Bulk OUT

Bulk IN

UMDF Example: Configure a USB I/O Target

A UMDF driver creates a framework USB target object and configures the USB target device as part of its OnPrepareHardware method of the IPnpCallbackHardware interface on the device object. All of the code in this section appears in the Fx2_Driver's Device.cpp file. However, the code has been edited for its presentation here.

The driver's first task is to create a target device object in the framework for the USB device, as Listing 9-17 shows. The target device object in effect connects the driver to the USB subsystem.

Listing 9-17: Creating a USB target device object in a UMDF driver

image from book
 HRESULT                 hr; IWDFUsbTargetFactory *  pIUsbTargetFactory = NULL; IWDFUsbTargetDevice *   pIUsbTargetDevice = NULL; ULONG                   length; UCHAR                   m_Speed; hr = m_FxDevice->QueryInterface (IID_PPV_ARGS(&pIUsbTargetFactory)); if (FAILED(hr)) {     . . . //error handling omitted } if (SUCCEEDED(hr)) {     hr = pIUsbTargetFactory->CreateUsbTargetDevice(&pIUsbTargetDevice); length = sizeof(UCHAR); hr = m_pIUsbTargetDevice->RetrieveDeviceInformation(DEVICE_SPEED,                                                     &length,                                                     &m_Speed                                                     ); } 
image from book

To create the USB target device object in the framework, the driver must use the IWDFUsbTargetFactory interface. It queries for this interface on the device object and uses the returned pointer to call CreateUsbTargetDevice to create the framework device object. CreateUsbTargetDevice returns a pointer to a IWDFUsbTargetDevice interface. The driver can then call RetrieveDeviceInformation to get the speed of the device.

Next the driver determines the number of USB interfaces in the device and retrieves an interface pointer. Listing 9-18 shows this code.

Listing 9-18: Retrieving a USB interface in a UMDF driver

image from book
 IWDFUsbInterface *      pIUsbInterface = NULL; UCHAR                   NumEndPoints = 0; UCHAR NumInterfaces = pIUsbTargetDevice->GetNumInterfaces(); WUDF_TEST_DRIVER_ASSERT(1 == NumInterfaces); hr = pIUsbTargetDevice->RetrieveUsbInterface(0, &pIUsbInterface); if (FAILED(hr)) {     . . . //error handling omitted } NumEndPoints = pIUsbInterface->GetNumEndPoints(); if (NumEndPoints != NUM_OSRUSB_ENDPOINTS) {     hr = E_UNEXPECTED; } 
image from book

To find the number of interfaces in the device, the driver calls IWDFUsbTargetDevice::GetNumInterfaces, which returns the number of interfaces in the default configuration. This driver assumes that the device has one interface and asserts an error otherwise. Interfaces are numbered starting at zero, so when the driver calls IWDFUsbTargetDevice::RetrieveUsbInterface, it passes 0 as the first parameter to direct the framework to return an IWDFUsbInterface pointer for the first interface. Next, the driver can get the number of endpoints in the interface by calling IWDFUsbInterface::GetNumEndPoints.

The driver now has the necessary information to configure the pipes. Remember, a pipe is an endpoint that is used in the current alternate interface setting. Listing 9-19 shows the code to configure the pipes.

Listing 9-19: Configuring the USB pipes in a UMDF driver

image from book
 IWDFUsbTargetPipe *     pIUsbPipe = NULL; IWDFUsbTargetPipe *     pIUsbInputPipe = NULL; IWDFUsbTargetPipe *     pIUsbOutputPipe = NULL; IWDFUsbTargetPipe *     pIUsbInterruptPipe = NULL; for (UCHAR PipeIndex = 0; PipeIndex < NumEndPoints; PipeIndex++) {     hr = pIUsbInterface->RetrieveUsbPipeObject(PipeIndex, &pIUsbPipe);     if (FAILED(hr)) {         . . . //error handling omitted     }     else {         if ( pIUsbPipe->IsInEndPoint() ) {             if ( UsbdPipeTypeInterrupt == pIUsbPipe->GetType() ) {                 pIUsbInterruptPipe = pIUsbPipe;             }             else if ( UsbdPipeTypeBulk == pIUsbPipe->GetType() ) {                 pIUsbInputPipe = pIUsbPipe;             }             else {                 SAFE_RELEASE(pIUsbPipe);             }         }         else if ( pIUsbPipe->IsOutEndPoint()                   && (UsbdPipeTypeBulk == pIUsbPipe->GetType()) ) {              pIUsbOutputPipe = pIUsbPipe;         }         else {             SAFE_RELEASE(pIUsbPipe);         }     } } if (NULL == pIUsbInputPipe || NULL == pIUsbOutputPipe) {     hr = E_UNEXPECTED; } 
image from book

The endpoint numbers, also called pipe indexes, start at zero. As Listing 9-19 shows, the driver loops through the endpoints, retrieving a pointer to the IWDFUsbTargetPipe interface for the associated pipe and then determining the following information for each pipe:

  • Whether this is an input pipe or an output pipe.

  • Whether the pipe supports interrupt or bulk transfers.

The driver retrieves the IWDFUsbTargetPipe interface pointer by calling IWDFUsbInterface::RetrieveUsbPipeObject. The driver uses the returned pointer to call the IWDFUsbTargetPipe::IsInEndPoint, IsOutEndPoint, and GetType methods. The driver is designed for the OSR USB Fx2 device, so it expects to find an interrupt IN pipe, a bulk IN pipe, and a bulk OUT pipe.

IsInEndPoint returns TRUE for an IN pipe, and the driver calls the GetType method to determine the type of data transfer that the pipe supports. GetType returns one of the following USBD_PIPE_TYPE values:

  • UsbdPipeTypeControl

  • UsbdPipeTypeIsochronous

  • UsbdPipeTypeBulk

  • UsbdPipeTypeInterrupt

If the pipe supports interrupt or bulk data transfers, the driver saves the pointer to the target pipe interface as pIUsbInterruptPipe or pIUsbInputPipe, respectively.

If this is the bulk OUT pipe, the driver saves the pointer to the target pipe interface as pIUsbOutputPipe.

At the end of the loop, if the driver has not found the expected pipes, it sets an error status.

The driver now configures the pipes, as Listing 9-20 shows.

Listing 9-20: Configuring USB pipes in a UMDF driver

image from book
 LONG                    timeout; timeout = ENDPOINT_TIMEOUT; hr = m_pIUsbInputPipe->SetPipePolicy( PIPE_TRANSFER_TIMEOUT,                                       sizeof(timeout),                                       &timeout                                       ); if (FAILED(hr)) {     . . . //error handling omitted } hr = m_pIUsbOutputPipe->SetPipePolicy( PIPE_TRANSFER_TIMEOUT,                                        sizeof(timeout),                                        &timeout                                        ); if (FAILED(hr)) {     . . . //error handling omitted } 
image from book

UMDF supports pipe policy settings to control numerous aspects of device operation, including time-out values and how the device responds to stalled data transfers, among several others. The WinUSB Winusbio.h header file defines the constants that identify the policy types.

 Tip  See "WinUsb_SetPipePolicy" in the WDK for information on pipe policy-online at http://go.microsoft.com/fwlink/?LinkId=80619.

The Fx2_Driver sets time-out values for the input and output pipes to the constant ENDPOINT_TIMEOUT, which is defined as 10000 (10 seconds) in the Device.h header file. WinUSB cancels transfers that do not complete within the time-out period.

KMDF Example: Configure a USB I/O Target

A KMDF driver creates and configures a USB I/O target device object in its EvtDevicePrepareHardware callback function. The sample code in this section is based on the Osrusbfx2 sample's Device.c file.

To create a USB I/O target device object, a KMDF driver calls WdfUsbTargetDeviceCreate as Listing 9-21 shows.

Listing 9-21: Creating a USB target device object in a KMDF driver

image from book
 NTSTATUS                          status; PDEVICE_CONTEXT                   pDeviceContext; WDF_USB_DEVICE_INFORMATION        deviceInfo; pDeviceContext = GetDeviceContext(Device); status = WdfUsbTargetDeviceCreate(Device,                                   WDF_NO_OBJECT_ATTRIBUTES,                                   &pDeviceContext->UsbDevice                                   ); 
image from book

The WdfUsbTargetDeviceCreate method takes as input parameters a handle to the device object and a pointer to a WDF_OBJECT_ATTRIBUTES structure and returns a handle to a WDFUSBDEVICE object.

Select the Configuration

If the framework successfully creates the USB target device object, the driver selects the device configuration and retrieves information from the device configuration descriptor by calling WdfUsbTargetDeviceSelectConfig. This method configures the device, creates WDF USB interface and pipe objects, and returns information about the specified configuration.

WdfUsbTargetDeviceSelectConfig requires a WDF_USB_DEVICE_SELECT_ CONFIG_PARAMS structure as an input and output parameter. On input, the WDF_USB_ DEVICE_SELECT_CONFIG_PARAMS structure selects a configuration. On output, the structure contains information about the selected configuration from the device configuration descriptor.

The device configuration descriptor contains information about many aspects of the device, its configurations, their interfaces, and so forth, and KMDF provides great flexibility in the way that a driver configures the device. Consequently, the framework provides several WDF_USB_DEVICE_SELECT_CONFIG_PARAMS_INIT_XXX functions to initialize this structure. Table 9-8 lists the variations of initialization functions.

Table 9-8: Initialization Functions for WDF_USB_DEVICE_SELECT_CONFIG_PARAMS Structure
Open table as spreadsheet

Function version

Description

DECONFIG

Deconfigures the device, thus indicating that no interfaces are selected.

INTERFACES_DESCRIPTORS

Configures the device by specifying a configuration descriptor and an array of interface descriptors.

MULTIPLE_INTERFACES

Configures the device to use multiple interfaces.

SINGLE_INTERFACE

Configures the device to use a single interface. Drivers for most devices can use this function.

URB

Configures the device by specifying a URB.

 Note  KMDF provides additional methods that a driver can call to get specific information about a USB device before the device has been configured. Such methods include-but are not limited to-WdfUsbTargetDeviceRetrieveConfigDescriptor, WdfUs-bTargetDeviceGetDeviceDescriptor, and WdfUsbTargetDeviceGetInterface, among others.

Listing 9-22 shows how the driver selects a configuration and retrieves information about it.

Listing 9-22: Selecting a USB device configuration in a KMDF driver

image from book
 WDF_USB_DEVICE_SELECT_CONFIG_PARAMS configParams; NTSTATUS                            status; PDEVICE_CONTEXT                     pDeviceContext; UCHAR                               numberConfiguredPipes; pDeviceContext = GetDeviceContext(Device); WDF_USB_DEVICE_SELECT_CONFIG_PARAMS_INIT_SINGLE_INTERFACE(&configParams); status = WdfUsbTargetDeviceSelectConfig( pDeviceContext->UsbDevice,                                          WDF_NO_OBJECT_ATTRIBUTES,                                          &configParams                                          ); if(!NT_SUCCESS(status)) return status; pDeviceContext->UsbInterface =         configParams.Types.SingleInterface.ConfiguredUsbInterface; numberConfiguredPipes =         configParams.Types.SingleInterface.NumberConfiguredPipes; 
image from book

The Osrusbfx2 driver selects the first interface in the first configuration descriptor on the USB device by calling WdfUsbTargetDeviceSelectConfig and passing a WDF_USB_DEVICE_SELECT_CONFIG_PARAMS structure.

The driver initializes the structure by using the WDF_USB_DEVICE_SELECT_CONFIG_PARAMS_INIT_SINGLE_INTERFACE function. This function indicates that the device has a single USB interface.

WdfUsbTargetDeviceSelectConfig returns a handle to the selected interface in the Types.SingleInterface.ConfiguredUsbInterface field of the WDF_USB_DEVICE_SELECT_CONFIG_PARAMS structure. The driver saves this handle in the UsbInterface field of the device context area.

The OSR USB Fx2 device has only one interface with one alternate setting, so the driver is not required to select an alternate setting.

Enumerate the Pipes

Every interface is associated with one or more alternate settings, and each alternate setting is associated with one or more endpoints. Each endpoint in the selected setting is a unidirectional pipe that can perform specific types of data transfers. The WdfUsbTargetDeviceSelectConfig method creates a WDFUSBPIPE object for each pipe in the interface and returns the number of configured pipes in the Types.SingleInterface.NumberConfiguredPipes field of the WDF_USB_DEVICE_SELECT_CONFIG_PARAMS structure. The driver saves this value in a local variable named numberConfiguredPipes.

Next, the sample driver enumerates all of the USB pipe handles that are associated with the selected interface, as shown in Listing 9-23. This step is not strictly necessary, but it shows how to access the WDFUSBPIPE collection that is associated with a USB interface.

Listing 9-23: Enumerating pipes for a USB interface in a KMDF driver

image from book
 WDFUSBPIPE                          pipe; WDF_USB_PIPE_INFORMATION            pipeInfo; UCHAR                               index; for(index=0; index < numberConfiguredPipes; index++) {     WDF_USB_PIPE_INFORMATION_INIT(&pipeInfo);     pipe = WdfUsbInterfaceGetConfiguredPipe(pDeviceContext->UsbInterface,                                             index, //PipeIndex,                                             &pipeInfo                                             );     // Tell the framework that it's okay to read less than MaximumPacketSize     WdfUsbTargetPipeSetNoMaximumPacketSizeCheck(pipe);     if(WdfUsbPipeTypeInterrupt == pipeInfo.PipeType) {         pDeviceContext->InterruptPipe = pipe;     }     if(WdfUsbPipeTypeBulk == pipeInfo.PipeType                              && WdfUsbTargetPipeIsInEndpoint(pipe)) {         pDeviceContext->BulkReadPipe = pipe;     }     if(WdfUsbPipeTypeBulk == pipeInfo.PipeType                              && WdfUsbTargetPipeIsOutEndpoint(pipe)) {         pDeviceContext->BulkWritePipe = pipe;     } } // If we didn't find all 3 pipes, fail the start. if(!(pDeviceContext->BulkWritePipe         && pDeviceContext->BulkReadPipe         && pDeviceContext->InterruptPipe)) {     status = STATUS_INVALID_DEVICE_STATE;     return status; } 
image from book

WdfUsbInterfaceGetConfiguredPipe requires three parameters:

  • A USB interface handle that indicates which interface contains the pipe.

  • The zero-based index of the pipe.

  • An optional output parameter that points to storage for a WDF_USB_PIPE_INFORMATION structure.

For each USB interface handle, the framework maintains a collection of configured pipes on the current setting. The index variable value passed as the second parameter is the zero-based index of the pipe about which to get information.

The third parameter is optional and is an output parameter that points to storage for a WDF_USB_PIPE_INFORMATION structure. If the driver supplies the structure, the framework fills it in with information about the specified pipe. The function initializes a WDF_USB_PIPE_INFORMATION structure and passes the address of this structure as the third parameter to WdfUsbInterfaceGetConfiguredPipe.

The driver iterates through the collection until it has retrieved information about all the pipes and then ascertains that the pipes match the device configuration that the driver expected to find.

By default, the framework reports an error if a driver uses a read buffer that is not an integral multiple of the pipe's maximum packet size. This buffer-size check helps to prevent the driver from receiving "babble"-that is, extra data as a result of unexpected bus activity. Within the loop, the driver disables this check by calling WdfUsbTargetPipeSetNoMaximumPacketSizeCheck for each pipe.

In addition to the collection of configured pipes, the framework maintains a collection of alternate settings for each interface. Each alternate setting is a collection of endpoints. A driver can get information about this collection and the endpoints in it by calling WdfUsbInterfaceGetNumEndpoints and WdfUsbInterfaceGetEndpointInformation either before or after configuring the device.

Get Device Traits

After it configures the pipes, the Osrusbfx2 driver calls the framework to return additional information about the device, as Listing 9-24 shows.

Listing 9-24: Retrieving USB device information in a KMDF driver

image from book
 WDF_USB_DEVICE_INFORMATION deviceInfo; ULONG waitWakeEnable; WDF_USB_DEVICE_INFORMATION_INIT(&deviceInfo); status = WdfUsbTargetDeviceRetrieveInformation( pDeviceContext->UsbDevice,                                                &deviceInfo                                                ); waitWakeEnable = deviceInfo.Traits & WDF_USB_DEVICE_TRAIT_REMOTE_WAKE_CAPABLE; if(waitWakeEnable){     status = OsrFxSetPowerPolicy(Device);     if (!NT_SUCCESS (status)) return status; } 
image from book

In Listing 9-24, the driver initializes a WDF_USB_DEVICE_INFORMATION structure and passes this structure to WdfUsbTargetDeviceRetrieveInformation to get information about the device. When the method returns, the structure contains information about the version of USB that the device and its host controller driver (HCD) support, the capabilities of the HCD, and a set of flags that indicate whether the device is self powered, capable of remote wakeup, and operating at high speed.

If the device can support wakeup, the driver enables this feature in its power policy settings. The driver tests the value of the wakeup bit returned in the Traits field of the structure and calls a helper function that sets the device power policy if required.

How to Send an I/O Request to a USB I/O Target

To send an I/O request to a USB I/O target, a driver follows the same steps as for any other I/O target:

  1. Create the request or use a request that the framework delivered.

  2. Set up the memory objects and buffers for the request.

  3. Format the request.

  4. Set an I/O completion callback for the request, if appropriate.

  5. Send the request.

WDF provides USB-specific methods to format the request, to send certain types of requests, and to retrieve completion parameters.

UMDF Example: Send a Synchronous Request to a USB I/O Target

To send a device I/O control request to a USB I/O target, a UMDF driver uses the IWDFIoRequest::Send method, just as it does to send a request to any other kind of I/O target. The difference is that the driver uses USB-specific UMDF interfaces to format the request.

The IWDFUsbTargetDevice::FormatRequestForControlTransfer method for a USB I/O target object formats a request for a USB I/O target. This method takes a pointer to the IWDFIoRequest interface for the request, a pointer to a WINUSB_SETUP_PACKET structure, a pointer to the IWDFMemory interface for the memory object that contains the buffer for the request, and an optional buffer offset.

The WINUSB_SETUP_PACKET structure is defined in the Winusbio.h header file that is included in the WDK. The driver fills in the WINUSB_SETUP_PACKET structure with information about the request and then calls FormatRequestForControlTransfer to format the request as a device I/O control request. If the framework successfully formats the request, the sample driver calls IWDFIoRequest::Send to send the request to the USB I/O target.

Listing 9-25 shows how the Fx2_Driver sample formats and sends the request.

Listing 9-25: Sending a device I/O control request to a USB device in a UMDF driver

image from book
 hr = m_pIUsbTargetDevice->FormatRequestForControlTransfer(pWdfRequest,                                                           SetupPacket,                                                           FxMemory,                                                           NULL      //TransferOffset                                                           ); } if (SUCCEEDED(hr))  {     hr = pWdfRequest->Send (m_pIUsbTargetDevice,                             WDF_REQUEST_SEND_OPTION_SYNCHRONOUS,                             0); //Timeout } 
image from book

The driver has already created the I/O request object and memory object to use in the request and has saved pointers to their IWDFIoRequest and IWDFMemory interfaces in pWdfRequest and FxMemory, respectively. The FormatRequestForControlTransfer method requires these two pointers along with a pointer to a WINUSB_SETUP_PACKET structure. The driver passes NULL for a transfer offset to indicate that the transfer starts at the beginning of the buffer that the memory object describes.

If the framework successfully formats the request, the driver calls IWDFIoRequest::Send to send it to the USB I/O target, specifying the flag for a synchronous request.

When the request is complete, the driver retrieves the completion status and USB-specific completion information, as Listing 9-26 shows.

Listing 9-26: Retrieving results from a USB I/O request in a UMDF driver

image from book
 *LengthTransferred = 0; IWDFRequestCompletionParams * FxComplParams = NULL; IWDFUsbRequestCompletionParams * FxUsbComplParams = NULL; pWdfRequest->GetCompletionParams(&FxComplParams); hr = FxComplParams->GetCompletionStatus(); if (SUCCEEDED(hr)){     HRESULT hrQI =        FxComplParams->QueryInterface(IID_PPV_ARGS(&FxUsbComplParams));        FxUsbComplParams->GetDeviceControlTransferParameters( NULL,                                                              LengthTransferred,                                                              NULL,                                                              NULL                                                              ); } SAFE_RELEASE(FxUsbComplParams); SAFE_RELEASE(FxComplParams); 
image from book

In the listing, the driver calls IWDFIoRequest::GetCompletionParams on the request object to get a pointer to the IWDFRequestCompletionParams interface for a completion parameters object. With the returned pointer, it can get the completion status for the request by calling IWDFRequestCompletionParams::GetCompletionStatus.

If the request completed successfully, the driver queries for the IWDFUsbRequestCompletionParams interface, which supports methods that provide USB-specific completion data. The GetDeviceControlTransferParameters method returns a pointer to the IWDFMemory interface for the output buffer, the number of transferred bytes, the offset into the output buffer, and a pointer to the setup packet for the request. The driver is interested only in the number of transferred bytes, so it calls the method with a pointer to a variable to receive that value and NULL for all the other parameters. It then releases the interface pointers for the request and USB parameters.

KMDF Example: Send an Asynchronous Request to a USB I/O Target

To send a request to a USB I/O target, a KMDF driver uses the methods shown in Table 9-9.

Table 9-9: Methods for Sending a Request to a USB I/O Target
Open table as spreadsheet

To send this type of request

Use this method

Cycle power on port (asynchronous)

WdfUsbTargetDeviceFormatRequestForCyclePort and x4 WdfRequestSend

Cycle power on port (synchronous)

WdfUsbTargetDeviceCyclePortSynchronously

Device I/O control (asynchronous)

WdfUsbTargetDeviceFormatRequestForControlTransfer and x4 WdfRequestSend

Device I/O control (synchronous)

WdfUsbTargetDeviceSendControlTransferSynchronously

Get string descriptor (synchronous or asynchronous)

WdfUsbTargetDeviceFormatRequestForString and X5 WdfRequestSend

Reset port (synchronous only)

WdfUsbTargetDeviceResetPortSynchronously

URB (asynchronous)

WdfUsbTargetDeviceFormatRequestForRead and X5 WdfRequestSend

URB (synchronous)

WdfUsbTargetDeviceSendUrbSynchronously

If the driver uses WdfRequestSend, it must format the request before sending it by calling a WdfUsbTargetDeviceFormatXxx or WdfUsbTargetPipeFormatXxx method for the target USB device or pipe, respectively.

Table 9-10 shows the methods a KMDF driver uses to send a request to a USB target pipe.

Table 9-10: Methods for Sending a Request to a USB Target Pipe
Open table as spreadsheet

To send this type of request

Use this method

Abort synchronous)

WdfUsbTargetPipeAbortSynchronously

Abort (asynchronous)

WdfUsbTargetPipeFormatRequestForAbort and X5 WdfRequestSend

Read (asynchronous)

WdfUsbTargetPipeFormatRequestForRead and X5 WdfRequestSend

Read (synchronous)

WdfUsbTargetPipeReadSynchronously

Reset (asynchronous)

WdfUsbTargetPipeFormatRequestForReset and X5 WdfRequestSend

Reset (synchronous)

WdfUsbTargetPipeResetSynchronously

URB (asynchronous)

WdfUsbTargetPipeFormatRequestForUrb and X5 WdfRequestSend

URB (synchronous)

WdfUsbTargetPipeSendUrbSynchronously

Write (asynchronous)

WdfUsbTargetPipeFormatRequestForWrite and X5 WdfRequestSend

Write (synchronous)

WdfUsbTargetPipeWriteSynchronously

The example in this section shows how a driver sends a read request to a USB target pipe and then retrieves the results of the request. The source code is derived from Osrusbfx2\Sys\Final\Bulkrwr.c.

When a read request arrives for the Osrusbfx2 driver, the framework adds it to a sequential queue that the driver created and calls the driver's EvtIoRead callback function. The EvtIoRead callback, in turn, sends the request to the USB target pipe. Listing 9-27 shows the EvtIoRead callback function in its entirety, except for some trace statements.

Listing 9-27: Sending an asynchronous read request to a USB target pipe in a KMDF driver

image from book
 VOID OsrFxEvtIoRead(     IN WDFQUEUE         Queue,     IN WDFREQUEST       Request,     IN size_t           Length     ) {     WDFUSBPIPE          pipe;     NTSTATUS            status;     WDFMEMORY           reqMemory;     PDEVICE_CONTEXT     pDeviceContext;     UNREFERENCED_PARAMETER(Queue);     if (Length > TEST_BOARD_TRANSFER_BUFFER_SIZE) {         status = STATUS_INVALID_PARAMETER;         goto Exit;     }     pDeviceContext = GetDeviceContext(WdfIoQueueGetDevice(Queue));     pipe = pDeviceContext->BulkReadPipe;     status = WdfRequestRetrieveOutputMemory(Request, &reqMemory);     if(!NT_SUCCESS(status)){         goto Exit;     }     status = WdfUsbTargetPipeFormatRequestForRead(pipe,                                                   Request,                                                   reqMemory,                                                   NULL // Offsets                                                   );     if (!NT_SUCCESS(status)) {         goto Exit;     }     WdfRequestSetCompletionRoutine( Request,                                    EvtRequestReadCompletionRoutine,                                    pipe                                   );     if (WdfRequestSend(Request,                        WdfUsbTargetPipeGetIoTarget(pipe),                        WDF_NO_SEND_OPTIONS) == FALSE) {         status = WdfRequestGetStatus(Request);         goto Exit;     } Exit:     if (!NT_SUCCESS(status)) {         WdfRequestCompleteWithInformation(Request, status, 0);     }     return; } 
image from book

In Listing 9-27, the driver validates the parameters to ensure that the number of requested bytes does not exceed the capabilities of the device. If the Length parameter is within the correct range, the driver gets a pointer to its device context area, where it has stored a handle to the USB pipe object.

The driver must format the I/O request before sending it to the pipe. Therefore, the driver retrieves the output memory object from the incoming I/O request object and calls WdfUsbTargetPipeFormatRequestForRead. The driver passes a handle to the pipe object, a handle to the I/O request object, a handle to the memory object, and an offset in the call.

The driver next sets an I/O completion callback for the request by calling WdfRequestSetCompletionRoutine, and then calls WdfRequestSend to send the request to the pipe. The driver specifies NO_SEND_OPTIONS to indicate that the request should be sent asynchronously and without a time-out. Note that the driver must call WdfUsbTargetPipeGetIoTarget to get a handle to the I/O target object for the pipe. The framework creates an I/O target object that is associated with each pipe, but the pipe object itself is not an I/O target.

If WdfRequestSend fails, the driver calls WdfRequestGetStatus to determine why the operation failed and then completes the I/O request with the failure status.

When the request is complete, the framework calls the I/O completion callback. This callback retrieves the completion parameters and completes the request. Listing 9-28 shows the I/O completion callback.

Listing 9-28: Retrieving USB request completion parameters in a KMDF driver

image from book
 VOID EvtRequestReadCompletionRoutine(     IN WDFREQUEST                  Request,     IN WDFIOTARGET                 Target,     PWDF_REQUEST_COMPLETION_PARAMS CompletionParams,     IN WDFCONTEXT                  Context     ) {     NTSTATUS    status;     size_t      bytesRead = 0;     PWDF_USB_REQUEST_COMPLETION_PARAMS usbCompletionParams;     UNREFERENCED_PARAMETER(Target);     UNREFERENCED_PARAMETER(Context);     status = CompletionParams->IoStatus.Status;     usbCompletionParams = CompletionParams->Parameters.Usb.Completion;     bytesRead = usbCompletionParams->Parameters.PipeRead.Length;     WdfRequestCompleteWithInformation(Request, status, bytesRead);     return; } 
image from book

The framework calls the I/O completion callback with several parameters, the most interesting of which is a pointer to a WDF_REQUEST_COMPLETION_PARAMS structure. This structure contains a union that supplies the completion parameters for various types of I/O requests. For a request to a USB device or pipe I/O target, the driver uses the Parameters.Usb member to access the returned data.

The Parameters.Usb field contains a WDF_USB_REQUEST_COMPLETION_PARAMS structure, which is also a union in which each member describes the results for a different combination of request types and target types. To access the results of a read request sent to a pipe, the driver uses the Parameters.PipeRead member. Within this member, the Length field contains the number of transferred byes. The driver retrieves this value and supplies it in the call to WdfRequestCompleteWithInformation.

USB Continuous Reader in KMDF

KMDF provides a continuous reader through which a driver can asynchronously and continuously read data from a USB pipe. The continuous reader ensures that a read request is always available on the pipe and therefore that the driver is always ready to receive data from the device.

A driver configures a continuous reader for an input pipe by including specialized code in several callbacks:

  • The EvtDevicePrepareHardware callback function must call the WdfUsbTargetPipeConfigContinuousReader method. This method queues a set of read requests to the device's I/O target.

  • The EvtDeviceD0Entry callback function must call WdfIoTargetStart to start the continuous reader.

  • The EvtDeviceD0Exit callback function must call WdfIoTargetStop to stop the continuous reader.

Each time that data is available from the device, the I/O target completes a read request and the framework calls one of the following callback functions:

  • EvtUsbTargetPipeReadComplete, if the I/O target successfully read the data.

  • EvtUsbTargetPipeReadersFailed, if the I/O target reported an error.

image from book
When to Start the Continuous Reader

When I joined the KMDF group, I knew a continuous reader had to be implemented for the USB pipe I/O target. I had worked on and debugged several custom implementations before I joined the team, and this was always a problematic area. The issue I faced in the framework's continuous reader was when to start reading. There were three possibilities:

  • EvtDevicePrepareHardware when the reader was created. The problem was that the device was not yet powered on, so we should not start I/O at this time.

  • Immediately before EvtDeviceD0Entry was called. The problem here was that the driver could be receiving I/O completions while the device was powering up which placed an undue burden on the driver writer.

  • Immediately after EvtDeviceD0Entry returned. The problem here was that if the data from the reader was needed to implement power up in EvtDeviceD0Entry, it would not be available.

After sorting through all of the scenarios, we realized that what was important was not that the framework would automatically change the state of the target, but rather that the framework would give driver writers the ability to start and stop reading synchronously (since stopping the reader tended to be where a lot of the race conditions occurred for me in earlier projects) at their own discretion. By giving the driver writer the proper continuous reader building blocks, the framework enabled a lot of scenarios that would have been precluded if it had only offered automatic behavior.
-Doron Holan, Windows Driver Foundation Team, Microsoft

image from book




Developing Drivers with the Microsoft Windows Driver Foundation
Developing Drivers with the Windows Driver Foundation (Pro Developer)
ISBN: 0735623740
EAN: 2147483647
Year: 2007
Pages: 224

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net