OSRLogo
OSRLogoOSRLogoOSRLogo x Subscribe to The NT Insider
OSRLogo
x

Everything Windows Driver Development

x
x
x
GoToHomePage xLoginx
 
 

    Thu, 14 Mar 2019     118020 members

   Login
   Join


 
 
Contents
  Online Dump Analyzer
OSR Dev Blog
The NT Insider
The Basics
File Systems
Downloads
ListServer / Forum
  Express Links
  · The NT Insider Digital Edition - May-June 2016 Now Available!
  · Windows 8.1 Update: VS Express Now Supported
  · HCK Client install on Windows N versions
  · There's a WDFSTRING?
  · When CAN You Call WdfIoQueueP...ously

KMDF Support for USB Devices

As anyone who has attempted one can attest, writing a USB device driver using WDM is truly a difficult task. The PnP nature of the devices cause your driver to push at every edge of the PnP state machine, and, as if it weren't hard enough, power management becomes even more complicated with poorly documented USB-specific rules.

WDMUSB Drivers Are Dead. Long Live KMDFUSB Drivers!
That is why it is with great pleasure we are pleased to announce the demise of the WDM USB driver. Not from any sort of, "if you write your USB driver in WDM it won't load on Windows" edict, mind you, but simply as a matter of practicality. Thus, this article will tour you around the various aspects of KMDF that make writing a USB driver downright pleasant and sounds the death knell for WDM USB drivers. Trust us, folks, write a USB driver in KMDF and you will never go back.

The OSRUSBFX2 Sample
As we walk through the various parts of the framework that are relevant to USB driver writers, we may occasionally want to show some sample code of how they're used. Instead of foisting yet another sample onto the world, we've decided to simply use the OSRUSBFX2 sample provided with the WDK. While we're not 100% in love with it, it is correct and probably familiar to most of the readers out there.

The Objects
While a KMDF USB driver is likely to use a wide range of KMDF objects, there are three key objects that are specific to USB drivers:

  • WDFUSBDEVICE - This object represents a USB I/O target, which is a special type of KMDF I/O target that accepts USB control transfers.
  • WDFUSBINTERFACE - This object is KMDF's abstraction of a USB interface, which is used to get or set interface information.
  • WDFUSBPIPE - This object represents a pipe I/O target, which is a special type of KMDF I/O target that accepts USB bulk and interrupt transfers.

The following sections will discuss these objects in further detail.

WDFUSBDEVICE
During a USB driver's EvtDevicePrepareHardware processing, it will need to create the WDFUSBDEVICE object used for all control transfer operations on the device. This is done by simply making a call to WdfUsbTargetDeviceCreate, as seen in Figure 1.

//
// Create a USB device handle so that we can communicate with the
// underlying USB stack. The WDFUSBDEVICE handle is used to query,
// configure, and manage all aspects of the USB device.
// These aspects include device properties, bus properties,
// and I/O creation and synchronization.
//
status = WdfUsbTargetDeviceCreate(Device,
WDF_NO_OBJECT_ATTRIBUTES,
&pDeviceContext->UsbDevice);
if (!NT_SUCCESS(status)) {
TraceEvents(TRACE_LEVEL_ERROR, DBG_PNP,
"WdfUsbTargetDeviceCreate failed with Status code %!STATUS!\n", status);
return status;
}

Figure 1 - WdfUsbTargetDeviceCreate

Once you've created your WDFUSBDEVICE, there are several operations you're able to perform with it. However, the most common operations will be selecting a device configuration and performing control transfers.

Selecting a Device Configuration
Selecting your device's configuration is a simple matter of calling WdfUsbTargetDeviceSelectConfig and supplying an appropriate WDF_USB_DEVICE_SELECT_CONFIG_ PARAMS structure. If your device is like most devices and only uses a single interface, configuring your device is a snap. Just initialize the configuration parameters using the provided WDF_USB_DEVICE_SELECT_CONFIG_PARAMS_INIT_SINGLE_INTERFACE function and call WdfUsbTargetDeviceSelectConfig, as shown in Figure 2.

WDF_USB_DEVICE_SELECT_CONFIG_PARAMS_INIT_SINGLE_INTERFACE( &configParams);

status = WdfUsbTargetDeviceSelectConfig(pDeviceContext->UsbDevice,
WDF_NO_OBJECT_ATTRIBUTES,
&configParams);
if(!NT_SUCCESS(status)) {
TraceEvents(TRACE_LEVEL_ERROR, DBG_PNP,
"WdfUsbTargetDeviceSelectConfig failed %!STATUS!\n",
status);
return status;
}

Figure 2 - WdfUsbTargetDeviceSelectConfig

NOTE: If you're in the boat of needing to support multiple interfaces, you're going to have to do a bit more work. We'll leave figuring that out as an exercise for the (unfortunate) reader, with the hint that the steps are documented within the WDK documentation along with some marginally useful sample code.

Once you have successfully configured your device, you can find your WDFUSBINTERFACE object within the configuration parameters structure supplied to WdfUsbTargetDeviceSelectConfig. We'll be discussing the uses of that object in a later section.

Performing Control Transfers
Once your device is configured, it's likely that you'll want to perform some control transfers on your device. There are a couple of different ways to send control transfers to your device, but no matter which method you pick, the keys to the operation will be your WDFUSBDEVICE and the WDF_ USB_CONTROL_SETUP_PACKET structure.

The WDFUSBDEVICE we already have, so it's simply a matter of initializing a WDF_USB_CONTROL_SETUP_ PACKET. Like most other structures within KMDF, there are several different functions available to you for initializing one of these structures. Currently, the following initialization functions are available:

  • WDF_USB_CONTROL_SETUP_PACKET_INIT
  • WDF_USB_CONTROL_SETUP_PACKET_INIT_ CLASS
  • WDF_USB_CONTROL_SETUP_PACKET_INIT_ FEATURE
  • WDF_USB_CONTROL_SETUP_PACKET_INIT_GET_STATUS
  • WDF_USB_CONTROL_SETUP_PACKET_INIT_ VENDOR

Which one you pick will depend on what type of control transfer you are sending. For example, on the OSRUSBFX2 device there is a vendor command available for retrieving the state of the LED bar graph. Therefore, the WDF_USB_CONTROL_SETUP_PACKET structure used to send the command is initialized with the _VENDOR flavor, as seen in Figure 3.

WDF_USB_CONTROL_SETUP_PACKET_INIT_VENDOR(&controlSetupPacket,
BmRequestDeviceToHost,
BmRequestToDevice,
USBFX2LK_READ_BARGRAPH_DISPLAY, // Request
0, // Value
0); // Index

Figure 3 - Setting up a Control Transfer

Once you've built your WDF_USB_CONTROL_SETUP_ PACKET, it's just a small matter of sending the request to your WDFUSBDEVICE. You can either do this by formatting an existing WDFREQUEST with WdfUsbTargetDeviceFormatRequestForControlTransfer and sending it with WdfRequestSend (which will give you the option of sending the request asynchronously), or you can shorthand it by simply calling WdfUsbTargetDeviceSendControlTransfer Synchronously, such as in Figure 4.

WDF_MEMORY_DESCRIPTOR_INIT_BUFFER(&memDesc,
BarGraphState,
sizeof(BAR_GRAPH_STATE));

status = WdfUsbTargetDeviceSendControlTransferSynchronously(
DevContext->UsbDevice,
WDF_NO_HANDLE, // Optional WDFREQUEST
NULL, // PWDF_REQUEST_SEND_OPTIONS
&controlSetupPacket,
&memDesc,
&bytesTransferred);

if(!NT_SUCCESS(status)) {

TraceEvents(TRACE_LEVEL_ERROR, DBG_IOCTL,
"GetBarGraphState: Failed - 0x%x \n", status);

} else {

TraceEvents(TRACE_LEVEL_VERBOSE, DBG_IOCTL,
"GetBarGraphState: LED mask is 0x%x\n", BarGraphState->BarsAsUChar);
}

Figure 4 - Sending the Control Transfer...WdfUsbTargetDeviceSendControlTransferSynchronously

Other WDFUSBDEVICE Operations
There are lots of other thing you can do with a WDFUSBDEVICE object, such as cycle the USB port it is attached to or retrieve various device descriptors. For a complete list of valid operations consult the WDK index for functions starting with WdfUsbTargetDeviceXxx.

WDFUSBINTERFACE
Again assuming a single interface, after your driver has successfully called WdfUsbTargetDeviceSelectConfig you get a handle to your WDFUSBINTERFACE object from within the supplied configuration parameters. We can see the OSRUSBFX2 sample getting its pointer in Figure 5.

WDF_USB_DEVICE_SELECT_CONFIG_PARAMS_INIT_SINGLE_INTERFACE( &configParams);

status = WdfUsbTargetDeviceSelectConfig(pDeviceContext->UsbDevice,
WDF_NO_OBJECT_ATTRIBUTES,
&configParams);
if(!NT_SUCCESS(status)) {
TraceEvents(TRACE_LEVEL_ERROR, DBG_PNP,
"WdfUsbTargetDeviceSelectConfig failed %!STATUS!\n",
status);
return status;
}

pDeviceContext->UsbInterface =
configParams.Types.SingleInterface.ConfiguredUsbInterface;

Figure 5 - Obtaining Handle to WDFUSBINTERFACE Object (Within Configuration Parameters)


Once you've acquired your WDFUSBINTERFACE, there are several operations you're able to perform with it. However, the most common operation will be using it to acquire handles to your various WDFUSBPIPE objects.

Acquiring Your WDFUSBPIPE Objects
Acquiring your WDFUSBPIPE objects is a simple matter of determining the number of pipes your interface has and calling KMDF to get a pointer to each pipe's WDFUSBPIPE. You'll want to store pointers to each of these WDFUSBPIPE objects within your per-device context because you'll need them later for any bulk or interrupt transfers. We can see how the OSRUSBFX2 sample does this in Figure 6.

numberConfiguredPipes = configParams.Types.SingleInterface.NumberConfiguredPipes;

//
// Get pipe handles
//
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) {
TraceEvents(TRACE_LEVEL_INFORMATION, DBG_IOCTL,
"Interrupt Pipe is 0x%p\n", pipe);
pDeviceContext->InterruptPipe = pipe;
}

if(WdfUsbPipeTypeBulk == pipeInfo.PipeType &&
WdfUsbTargetPipeIsInEndpoint(pipe)) {
TraceEvents(TRACE_LEVEL_INFORMATION, DBG_IOCTL,
"BulkInput Pipe is 0x%p\n", pipe);
pDeviceContext->BulkReadPipe = pipe;
}

if(WdfUsbPipeTypeBulk == pipeInfo.PipeType &&
WdfUsbTargetPipeIsOutEndpoint(pipe)) {
TraceEvents(TRACE_LEVEL_INFORMATION, DBG_IOCTL,
"BulkOutput Pipe is 0x%p\n", pipe);
pDeviceContext->BulkWritePipe = pipe;
}

}

Figure 6 - Acquiring WDFUSBPIPE Objects

Other WDFUSBINTERFACE Operations
There are lots of other things you can do with a WDFUSBINTERFACE object, such as select an alternate interface setting or retrieve its USB interface descriptor per the USB spec. For a complete list of valid operations consult the WDK index for functions starting with WdfUsbInterfaceXxx.

WDFUSBPIPE
The WDFUSBPIPE object is what your driver will use to perform all bulk and interrupt I/O on your device. Once you have acquired your WDFUSBPIPE objects via the WDFUSBINTERFACE object, the most common operations you will perform on the WDFUSBPIPE objects will be single I/O operations and/or continuous I/O operations.

Performing Single I/O Operations
A common scenario under which your driver might perform, say, a bulk read on the device is as follows:

A user application sends your driver a read request. Your driver converts this request into a bulk read and sends the bulk read to your device.

This is such a common scenario in fact that KMDF practically does all the work for you. In the above scenario, all your driver will need to do is call WdfUsbTargetPipeFormat RequestForRead, specifying the user's WDFREQUEST and data buffer, then forward the user request to your device with WdfRequestSend. We can see the OSRUSBFX2 driver performing these steps within its EvtIoRead callback in Figure 7.

status = WdfRequestRetrieveOutputMemory(Request, &reqMemory);
if(!NT_SUCCESS(status)){
TraceEvents(TRACE_LEVEL_ERROR, DBG_READ,
"WdfRequestRetrieveOutputMemory failed %!STATUS!\n", status);
goto Exit;
}

//
// The format call validates to make sure that you are reading or
// writing to the right pipe type, sets the appropriate transfer flags,
// creates an URB and initializes the request.
//
status = WdfUsbTargetPipeFormatRequestForRead(pipe,
Request,
reqMemory,
NULL // Offsets
);
if (!NT_SUCCESS(status)) {
TraceEvents(TRACE_LEVEL_ERROR, DBG_READ,
"WdfUsbTargetPipeFormatRequestForRead failed 0x%x\n", status);
goto Exit;
}

WdfRequestSetCompletionRoutine(
Request,
EvtRequestReadCompletionRoutine,
pipe);
//
// Send the request asynchronously.
//
if (WdfRequestSend(Request,
WdfUsbTargetPipeGetIoTarget(pipe),
WDF_NO_SEND_OPTIONS) == FALSE) {
...

Figure 7 - Performing a Bulk Read

Performing Continuous I/O Operations
It is a common paradigm in USB drivers to "hang" an I/O request on the USB device so that there is always a request waiting when the device has data to present. When the I/O request completes, your driver performs device specific processing on the data and then resubmits the I/O request to the device. Further complicating the matter, drivers typically need to have multiple I/O requests pending on the device so that they don't lose the next set of data while working on the previous set of data.

In order to facilitate USB devices that require this programming model be used, KMDF supports something called a continuous reader on both bulk read and interrupt read pipes. The idea behind the continuous read is that your driver supplies a callback to be called when the device has reported bulk or interrupt data, depending on the configured pipe type. This way, your driver deals with the data reported by the device while the framework deals with keeping a set of requests active on the device and ready to receive the data.

The OSRUSBFX2 device supports an interrupt endpoint that monitors changes to the device's switch pack. The sample driver configures a continuous reader that receives the device reported data at the time of the interrupt. In Figure 8 we can see how the sample driver initializes the continuous reader on the WDFUSBPIPE, while Figure 9 shows the actual code of the continuous reader callback.

WDF_USB_CONTINUOUS_READER_CONFIG_INIT(&contReaderConfig,
OsrFxEvtUsbInterruptPipeReadComplete,
DeviceContext, // Context
sizeof(UCHAR)); // TransferLength
//
// Reader requests are not posted to the target automatically.
// Driver must explictly call WdfIoTargetStart to kick start the
// reader. In this sample, it's done in D0Entry.
// By defaut, framework queues two requests to the target
// endpoint. Driver can configure up to 10 requests with CONFIG macro.
//
status = WdfUsbTargetPipeConfigContinuousReader(DeviceContext->InterruptPipe,
&contReaderConfig);

if (!NT_SUCCESS(status)) {
TraceEvents(TRACE_LEVEL_ERROR, DBG_PNP,
"OsrFxConfigContReaderForInterruptEndPoint failed %x\n",
status);
return status;
}

Figure 8 - Initialization of the Continuous Reader

VOID
OsrFxEvtUsbInterruptPipeReadComplete(
WDFUSBPIPE Pipe,
WDFMEMORY Buffer,
size_t NumBytesTransferred,
WDFCONTEXT Context
)
{
...
switchState = WdfMemoryGetBuffer(Buffer, NULL);

TraceEvents(TRACE_LEVEL_INFORMATION, DBG_INIT,
"OsrFxEvtUsbInterruptPipeReadComplete SwitchState %x\n",
*switchState);

pDeviceContext->CurrentSwitchState = *switchState;
...

}

Figure 9 - The Continuous Reader Callback

Other WDFUSBPIPE Operations
The WDFUSBPIPE object supports a rich set of operations that can be performed on the pipe, including routines for performing synchronous I/O on the pipe and the ability to send your own USB Request Block (URB) to the pipe. For a complete list of valid operations consult the WDK index for functions starting with WdfUsbTargetPipeXxx.

It Rocks!
Clearly the KMDF designers had USB on the brain when creating the framework. There's so much built in support for typical USB operations that you almost forget how much of a pain WDM was. And, don't forget, this is on top of having all your PnP and power management code written for you. So, get crackin' and convert your old, crufty WDM drivers to KMDF!

Related Articles
You're Testing Me - Testing WDM/Win2K Drivers
Who Cares? You Do! - Implementing PnP for WDM/NT V5
Converting Windows NT V4 Drivers to WDM/Win2K
A New Interface for Driver Writing -- The Windows Driver Framework
USB 2.0 Debugging
I/O Manager & Vista
Starting Out: Should You Learn WDM or WDF?
Ten Things You Need To Know About KMDF
18 Months Later: Release The KMDF Source Code!
When is a Queue not just a Queue? WDFQUEUEs

User Comments
Rate this article and give us feedback. Do you find anything missing? Share your opinion with the community!
Post Your Comment

Post Your Comments.
Print this article.
Email this article.
bottom nav links