A WDF driver uses an I/O target to send an I/O request to another driver. An I/O target is a WDF object that represents a device object that is the target of an I/O request. A UMDF driver, a KMDF driver, a WDM driver, or any other kernel-mode driver can be an I/O target.
An I/O target is more than just a pointer to a device object. Each I/O target supports methods to:
Format read, write, and device I/O control requests in a target-specific way.
Determine the Plug and Play state of the target, if the target represents a Plug and Play device object.
Query and control the operation of the target and the flow of I/O requests to the target.
By default, WDF sends I/O requests only to I/O targets that are in the working state, so that your driver can avoid sending I/O requests to a target that has been stopped or removed. A driver can override this default for a target.
A driver can send an I/O request to an I/O target for synchronous or asynchronous completion and can supply completion callbacks for asynchronous requests.
KMDF A KMDF driver can also supply callbacks through which the driver can request notification about Plug and Play state changes for orderly or surprise removal of a remote target device.
A WDF driver uses an I/O target anywhere that a WDM driver uses IoCallDriver.
The default I/O target is the next-lower driver in the device stack. To pass a request down the stack, a WDF driver uses the default I/O target. When a driver calls the framework to create a device object, the framework creates and initializes the default I/O target for the device object. Afterwards, the driver calls a framework method to obtain access to the I/O target object. The I/O target object is a child of the device object. The framework creates a default I/O target only for a function or filter driver.
A remote I/O target represents any target of an I/O request other than the next-lower device object. A KMDF driver uses a remote I/O target to send an I/O request to another device stack or to the top of its own device stack. The important distinction between local I/O targets and remote I/O targets is that I/O requests that are sent to a remote I/O target do not continue to travel down the current device stack.
For example, in some situations a driver might require information from a different device stack before it can complete an I/O request. In this case, the driver creates a remote I/O target and an I/O request, and then sends the request to the remote target. The framework directs the request to the top of the device stack for the remote I/O target.
A driver can also use a remote I/O target to send a device I/O control request to the top of its own device stack, so that all drivers in the stack have the opportunity to process the request. If the driver sends the request to the default I/O target, only drivers below it in the stack receive the request. To send such a request, the driver gets a pointer to the WDM DEVICE_OBJECT at the top of its own device stack by calling the I/O manager's IoGetAttachedDeviceReference function and then creates a remote I/O target by using the returned pointer.
In addition to categorizing I/O targets as default or remote, WDF categorizes them as general or specialized. A general I/O target can represent a device object for any kind of device. The driver that sends the I/O request is responsible for formatting the request in the way in which the target expects to receive it. Thus, the driver must fill in any device-type-specific control blocks and so forth. General I/O targets do not support any special, device-specific data formats.
Specialized I/O targets provide data formatting that is specific to a particular type of device, such as device-type-specific request blocks. If the framework implements specialized I/O targets that support your device's data format, your driver should use them. For some device types, your driver might be required to use WDM structures to properly format I/O requests for a particular target.
Specialized I/O targets enable extensions to WDF that support new hardware and protocol types. Consequently, individual drivers are not required to implement basic support for a new industry standard. Instead, Microsoft can add that support to WDF, where it can be tested and maintained for all drivers.
WDF supports specialized I/O targets for USB devices in both KMDF and UMDF. WDF sends USB request blocks (URBs) to communicate with USB I/O targets. The USB I/O target DDIs include methods that construct and send the URBs so that the driver itself is not required to build and send them.
UMDF UMDF also supports a specialized I/O target for file handles. See "File Handle I/O Targets in UMDF Drivers" later in this chapter for more information.
UMDF implements I/O targets through two special mechanisms:
I/O dispatchers, which send I/O requests from the UMDF device stack to the appropriate subsystem.
I/O target files, which represent I/O sessions for the I/O targets.
By using an I/O dispatcher, UMDF can map a generic read, write, or device I/O control request to a specific function call for an I/O target. For example, the USB dispatcher receives a generic read request and in turn calls a specific function in WinUSB.dll. The UMDF driver should not call the WinUSB functions directly, because such I/O requests immediately leave the current device stack and enter the other subsystem. The framework cannot intercept the request to ensure that lower drivers in the device stack have the opportunity to process it.
In addition, UMDF requires a file handle-and thus a session-with each request. I/O target files represent sessions for the I/O target objects.
KMDF KMDF drivers do not require either I/O dispatchers or I/O target files because kernel-mode drivers can use IRPs to communicate directly through the I/O manager. The IRP is a consistent interface that all kernel-mode device stacks use.
The UMDF I/O dispatchers direct I/O requests to I/O targets outside the user-mode device stack. An I/O dispatcher receives I/O requests that have reached the bottom of the user-mode device stack and determines how to send those requests to the kernel-mode device stack. For example, a USB dispatcher directs requests to the user-mode WinUSB.dll, which then directs them to the kernel-mode device stack for the USB device.
UMDF does not support remote I/O targets, so UMDF drivers can send I/O requests only to the next lower driver. Every I/O request passes to the bottom of the user-mode device stack unless a UMDF driver completes it. The dispatcher serves as a connector between the bottom of the UMDF device stack and the default I/O target, which is the Down device object at the top of the kernel-mode device stack.
When a UMDF driver sends an I/O request to an I/O target, the framework creates an I/O request packet and sends it to the default I/O target. When the request packet reaches the bottom of the UMDF device stack, the I/O dispatcher receives the packet and translates it to the appropriate APIs for the target subsystem.
UMDF implements several I/O dispatchers, as Figure 9-1 shows.
Figure 9-1: UMDF I/O dispatchers
As Figure 9-1 shows, the I/O dispatcher provides the connection between the UMDF host process-and thus the user-mode portion of the device stack-and the kernel-mode device stack. Each device stack can have only one dispatcher. The INF for the device stack includes the UmdfDispatcher directive to specify which of the following dispatchers to use:
Default I/O dispatcher The default I/O dispatcher sends I/O requests to the kernel-mode device stack, thus in effect combining the user-mode and kernel-mode stacks into a single logical device stack. The default I/O dispatcher is shown in the UMDF device stack on the left in Figure 9-1. This dispatcher uses the Windows API to send I/O requests to the reflector's Down device object, thus resulting in a new IRP for the kernel-mode device stack. The INF specifies the default I/O dispatcher by setting UmdfDispatcher to Default or by omitting the directive entirely.
USB dispatcher The USB dispatcher sends I/O requests to the user-mode WinUSB component, which in turn sends requests to the kernel-mode USB device stack where WinUSB.sys processes them. The USB dispatcher is shown in the UMDF device stack in the center of Figure 9-1. Device stacks that use USB I/O targets must use this dispatcher. The INF specifies this dispatcher by setting UmdfDispatcher to WinUsb.
FileHandle dispatcher The FileHandle dispatcher sends I/O requests to a subsystem or device stack that is represented by a file handle. This dispatcher is shown in the UMDF device stack on the right in Figure 9-1. For example, if a driver opens a handle to a socket, a pipe, or another file-handle-based object, the FileHandle dispatcher directs the requests to the appropriate subsystem. Device stacks that use the FileHandle I/O target must use this dispatcher. The INF specifies this dispatcher by setting UmdfDispatcher to FileHandle.
Figure 9-1 shows two kernel-mode device stacks associated with the user-mode stack that uses the FileHandle dispatcher. The dotted line indicates the default devnode for which the UMDF drivers are loaded. Requests that do not involve file I/O-such as Plug and Play and power management requests-follow the dotted line to the default devnode. The solid line indicates how a file handle makes it possible for a UMDF driver to send I/O to a device stack that is not its default devnode. For example, a driver uses a file handle I/O target to service a network-connected device to which it opens a socket for communication.
A file handle represents a logical session for I/O requests. UMDF requires that all I/O requests have session information that is represented by a file object and file context.
During I/O target initialization, the USB and FileHandle I/O targets each create an intra-stack file to represent a default I/O session. This session remains open as long as the I/O target is open. The I/O targets use the intra-stack file to send I/O requests that are not tied to a particular user I/O request. For example, a USB I/O target might send a request on this file to get a configuration descriptor during initialization or to select an interface setting during processing.
To get a pointer to the IWDFFile interface for the intra-stack file that the I/O target opened, a driver calls IWDFIoTarget::GetTargetFile on the I/O target object. The driver can then pass the interface pointer to other IWDFIoTarget methods on the I/O target object to send I/O requests to the file.
A driver can send I/O requests in the same session as the I/O target by using this file. Alternatively, a driver can create its own session by calling IWDFDevice::CreateWdfFile, thus creating its own intra-stack file.
The framework closes the intra-stack file when the driver calls IWDFObject:: DeleteWdfObject on the I/O target object or when the framework deletes the device object that is the parent of the I/O target object.
File objects are one of those kernel objects that didn't keep up with the transition to Plug and Play between Windows NT 4.0 and Windows 2000. In Windows NT 4.0, device stacks tended to be shallow, and the drivers more often formed connections between devices in layered stacks by opening the underlying device by name than by attaching to the device stack. Attachment was mostly used by filters. If the filter needed to maintain per-session state, it could piggyback that state on the lower driver's file object.
In Windows 2000, device stacks became more layered and the layering mechanism moved to device attachment. Attachment has numerous benefits but because drivers no longer "open" the lower drivers there aren't any file objects between the drivers.
With UMDF, we needed intra-stack sessions again, because the UMDF driver is seen as just another client by the kernel-mode portion of the device stack. Managing the session information is a responsibility which falls mostly on the FDO and is encapsulated in the I/O target file object.
Considering that UMDF drivers have this extra requirement, we decided to provide some benefit as well. So we provided a mechanism to support intra-stack sessions (file objects) within the user-mode portion of the device stack.
-Peter Wieland, Windows Driver Foundation Team, Microsoft