2. I/O Queues
A WDFQueue object represents a queue that presents requests from KMDF to the driver. A WDFQUEUE
is more than just a list of pending requests; however, it tracks
requests that are active in the driver, supports request cancellation,
manages the concurrency of requests, and can optionally synchronize
calls to the driver’s I/O event callback functions.
A driver typically creates
one or more queues, each of which can accept one or more types of
requests. The driver configures the queues when it creates them. For
each queue, the driver can specify
The types of requests that are placed in the queue.
The event callback functions that are registered to handle I/O requests from the queue.
The power management options for the queue.
The dispatch method for the queue, which determines the number of requests that are serviced at a given time.
Whether the queue accepts requests that have a zero-length buffer.
A driver can have any number
of queues, and they can all be configured differently. For example, a
driver might have a parallel queue for read requests and a sequential
queue for write requests.
While a request is in a queue and
has not yet been presented to the driver, the queue is considered the
“owner” of the request. After the request has been dispatched to the
driver, it is “owned” by the driver and is considered an in-flight request internally, and each WDFQUEUE
object keeps track of which requests it owns and which requests are
pending. A driver can forward a request from one queue to another by
calling a method on the request object.
2.1. Queues and Power Management
KMDF provides rich
control of queues. The framework can manage the queues for the driver,
or the driver can manage queues on its own. Power management is
configurable on a per-queue basis. A driver can use both power-managed
and non-power-managed queues and can sort requests based on the
requirements for its power model.
By default, queues for FDOs
and PDOs are power managed, which means that the state of the queue can
trigger power-management activities. Such queues have several
advantages:
If
an I/O request arrives while the system is in the working state (SO)
but the device is not, KMDF notifies the Plug and Play/power handler so
that it can restore device power.
When
a queue becomes empty, KMDF notifies the Plug and Play/power handler so
that it can track device usage through its idle timer.
If the device power state begins to change while the driver “owns” an I/O request, KMDF can notify the driver through the EvtIoStop
callback. The driver must complete, cancel, or acknowledge all the I/O
requests that it owns before the device can leave the working state.
For power-managed queues,
KMDF pauses the delivery of requests when the device leaves the working
state (DO) and resumes delivery when the device returns to the working
state. Although delivery stops while the queue is paused, queuing does
not. If KMDF receives a request while the queue is paused, KMDF adds the
request to the queue for delivery after the queue resumes. If an I/O
request arrives while the device is idle and the system is in the
working state, KMDF returns the device to the working state so that it
can handle the request. If an I/O request arrives while the system is
transitioning to a sleep state, however, KMDF does not return the device
to the working state until the system returns to the working state.
For requests to be
delivered, both the driver and the device power state must allow
processing. The driver can pause delivery manually by calling WdfIoQueueStopWdfIoQueueStart. and resume delivery by calling
If a queue is not power
managed, the state of the queue has no effect on power management, and
conversely, KMDF delivers requests to the driver any time the system is
in the working state, regardless of the power state of the device. KMDF
does not start an idle timer when the queue becomes empty, and it does
not power up a sleeping device when I/O arrives for the queue.
Drivers should use
non-power-managed queues to hold requests that the driver can handle
even while its device is not in the working state.
2.2. Dispatch Type
A queue’s dispatch type
determines how and when I/O requests are delivered to the driver and, as
a result, whether multiple I/O requests from a queue are active in the
driver at one time. Drivers can control the concurrency of in-flight requests by configuring the dispatching method for their queues. KMDF supports three dispatch types:
Sequential— A
queue that is configured for sequential dispatching delivers I/O
requests to the driver one at a time. The queue does not deliver another
request to the driver until the previous request has been completed.
(Sequential dispatching is similar to the start-I/O technique in WDM.)
Parallel—
A queue that is configured for parallel dispatching delivers I/O
requests to the driver as soon as possible, whether or not another
request is already active in the driver.
Manual—
A queue that is configured for manual dispatching does not deliver I/O
requests to the driver. Instead, the driver retrieves requests at its
own pace by calling a method on the queue.
The dispatch type controls
only the number of requests that are active within a driver at one time.
It has no effect on whether the queue’s I/O event callbacks are invoked
sequentially or concurrently; instead, the concurrency of callbacks is
controlled by the synchronization scope of the device object. Even if
the synchronization scope for a parallel queue does not allow concurrent
callbacks, the queue nevertheless might have many in-flight requests.
All I/O requests that a driver
receives from a queue are inherently asynchronous. The driver can
complete the request within the event callback or sometime later, after
returning from the callback.
3. I/O Request Objects
The WDFREQUEST object is the KMDF representation of an IRP. When an I/O request arrives, the I/O handler creates a WDFREQUEST object, queues the object, and eventually passes the object to the driver in its I/O callback function.
The properties of the WDFREQUEST object represent the fields of the IRP. The object also contains additional information. Like all other KMDF objects, the WDFREQUEST
object has a reference count and can have its own object context area.
When the driver completes the I/O request that the object represents,
KMDF automatically frees the object and any child resources such as
associated memory buffers or memory descriptor lists (MDLs). After the
driver has called WdfRequestComplete, the driver must not attempt to access the handle to the WDFREQUEST object or any of its child resources. A driver can create its own WDFREQUEST objects to request I/O from another device or to split an I/O request into multiple, smaller requests before completing it.
4. Retrieving Buffers from I/O Requests
The WDFMEMORY
object encapsulates the I/O buffers that are supplied for an I/O
request. To enable device drivers to handle complicated requests with
widely scattered buffers, any number of WDFMEMORY objects may be associated with a WDFREQUEST.
The WDFMEMORY
object represents a buffer that the framework manages. The object can
be used to copy memory to and from the driver and the buffer represented
by the WDFMEMORY
handle. In addition, the driver can use the underlying buffer pointer
and its length for complex access, such as casting to a known data
structure.
Like other KMDF objects, WDFMEMORY objects have reference counts and persist until all references to them have been removed. The buffer that underlies the WDFMEMORY
object, however, might not be “owned” by the object itself. For
example, if the issuer of the I/O request allocated the buffer or if the
driver called WdfMemoryCreatePreallocated to assign an existing buffer to the object, the WDFMEMORY
object does not “own” the buffer. In this case, the buffer pointer
becomes invalid when the associated I/O request has been completed, even
if the WDFMEMORY object still exists.
Each WDFMEMORY
object contains the length of the buffer that it represents. KMDF
methods that copy data to and from the buffer validate the length of
every transfer to prevent buffer over- and underruns, which can result
in corrupt data or security breaches.
Depending on the type of I/O that the device and driver support, the underlying buffer might be any of the following:
For buffered I/O, a system-allocated buffer from the nonpaged pool.
For direct I/O, a system-allocated MDL that points to the physical pages for DMA.
For neither buffered nor direct I/O, an unmapped and unverified user mode memory address.
The WDFMEMORY
object supports methods that return each type of buffer from the object
and methods to read and write the buffers. For device I/O control
requests (IOCTLs), KMDF provides methods to probe and lock user mode
buffers. The driver must be running in the context of the process that
sent the I/O request to probe and lock a user mode buffer, so KMDF also defines a callback that drivers can register to be called in the context of the sending component.
Each WDFMEMORY
object also controls access to the buffer and allows the driver to
write only to buffers that support I/O from the device to the buffer. A
buffer that is used to receive data from the device (as in a read
request) is writable. The WDFMEMORY object does not allow write access to a buffer that only supplies data (as in a write request).
5. I/O Targets
Drivers send I/O
requests by creating or reusing an I/O request object, creating an I/O
target, and sending the request to the target. Drivers can send requests
either synchronously or asynchronously. A driver can specify a time-out
value for either type of request.
An I/O target represents a
device object to which an I/O request is directed. If a driver cannot
complete an I/O request by itself, it typically forwards the request to
an I/O target. An I/O target can be a KMDF driver, a WDM driver, or any
other Kernel Mode Driver.
Before a driver forwards an existing I/O request or sends a new request, it must create a WDFIOTARGET
object to represent either a local or remote target for the I/O
request. The local I/O target is the next lower driver in the device
stack and is the default target or a filter or FDO device object. A
remote I/O target is any other driver that might be the target of an I/O
request. A driver might use a remote I/O target if it requires data
from another device to complete an I/O request. A function driver might
also use a remote I/O target to send a device I/O control request to its
bus driver. In this case, the I/O request originates with the function
driver itself, rather than originating with some other process.
The WDFIOTARGET
object formats I/O requests to send to other drivers, handles changes
in device state, and defines callbacks through which a driver can
request notification about target device removal. A driver can call
methods on the WDFIOTARGET to
Open a device object or device stack by name.
Format read, write, and device I/O control requests to send to the target. Some types of targets, such as WDFUSBDEVICE and WDFUSBPIPE, can format bus-specific requests in addition to the standard request types.
Send read, write, and device I/O control requests synchronously or asynchronously.
Determine the Plug and Play state of the target.
Internally, KMDF calls IoCallDriver to send the request. It takes out a reference on the WDFREQUEST object to prevent the freeing of associated resources while the request is pending for the target device object.
The WDFIOTARGET
object racks queued and sent requests and can cancel them when the
state of the target device or of the issuing driver changes. From the
driver’s perspective, the I/O target object behaves like a cancel-safe
queue that retains forwarded requests until KMDF can deliver them. KMDF
does not free the WDFIOTARGET object until all the I/O requests that have been sent to it are complete.
By default, KMDF sends a
request only when the target is in the proper state to receive it.
However, a driver can also request that KMDF ignore the state of the
target and send the request anyway. If the target device has been
stopped (but not removed), KMDF queues the request to send later after
the target device resumes. If the issuing driver specifies a time-out
value, the timer starts when the request is added to the queue.
If the device that is associated
with a remote I/O target is removed, KMDF stops and closes the I/O
target object, but does not notify the driver unless the driver has
registered an EvtIoTargetXxx
callback. If the driver must perform any special processing of I/O
requests that it sent to the I/O target, it should register one or more
such callbacks. When the removal of the target device is queried,
canceled, or completed, KMDF calls the corresponding functions and then
processes the target state changes on its own.
For local I/O targets,
no such callbacks are defined. Because the driver and the target device
are in the same device stack, the driver is notified of device removal
requests through its Plug and Play and power management callbacks.
6. Creating Buffers for I/O Requests
Drivers that issue I/O
requests must supply buffers for the results of those requests. The
buffers in a synchronous request can be allocated from any type of
memory, such as the nonpaged pool or an MDL, as well as a WDFMEMORY object. Asynchronous requests must use WDFMEMORY object so that KMDF can ensure that the buffers persist until the I/O request has completed back to the issuing driver.
If the driver uses a WDFMEMORY object, the I/O target object takes out a reference on the WDFMEMORY
object when it formats the object to send to the I/O target. The target
object retains this reference until one of the following occurs:
The request has been completed.
The driver reformats the WDFREQUEST object.
The driver calls WdfRequestReuse to send a request to another target.
A driver can retrieve a WDFMEMORY object from an incoming WDFREQUEST
and reuse it later in a new request to a different target. However, if
the driver has not yet completed the original request, the original I/O
target still has a reference on the WDFMEMORY object. To avoid a bug check, the driver must call WdfRequestReuse in its I/O completion routine before it completes the original request.
7. Canceled and Suspended Requests
Windows I/O is inherently
asynchronous. The system can request that a driver stop processing an
I/O request at any time for many reasons, of which these are the most
common:
The thread or process that issued the request cancels it or exits.
A system Plug and Play or power event such as hibernation occurs.
The device is being, or has been, removed.
The actions that a driver
takes to stop processing an I/O request depend on the reason for
suspension or cancellation. In general, the driver can either cancel the
request or complete it with an error. In some situations, the system
might request that a driver suspend (temporarily pause) processing; the
system notifies the driver later when to resume processing.
To provide a good user
experience, drivers should provide callbacks to handle cancellation and
suspension of any I/O request that might take a long time to complete or
that might not complete, such as a request for asynchronous input.
7.1. Request Cancellation
How KMDF proceeds to cancel an I/O request depends on whether the request has already been delivered to the target driver:
If the request has
never been delivered—either because KMDF has not yet queued it or
because it is still in a queue—KMDF cancels or suspends it
automatically. If the original IRP has been canceled, KMDF completes the
request with a cancellation status.
If
the request has been delivered and then requeued, KMDF notifies the
driver of cancellation only if the driver has registered an EvtIoCanceledOnQueue callback for the queue.
After a request has been
delivered, it cannot be canceled unless the driver that owns it
explicitly marks it cancelable by calling the WdfRequestMarkCancelableEvtRequestCancel) for the request. method on the request and registering a cancellation callback (
A driver should mark a request cancelable and register an I/O cancellation callback if either of the following is true:
The request involves a long-term operation.
The request might never succeed; for example, the request is waiting for synchronous input.
In the EvtRequestCancel
callback, the driver must perform any tasks that are required to cancel
the request, such as stopping any device I/O operations that are in
progress and canceling any related requests that it has already
forwarded to an I/O target. Eventually, the driver must complete the
request with the status STATUS_CANCELLED.
Requests that are marked
cancelable cannot be forwarded to another queue. Before requeuing a
request, the driver must first make it noncancelable by calling WdfRequestUnmarkCancelable.
After the request has been added to the new queue, KMDF once again
considers it cancelable until that queue dispatches it to the driver.
If the driver does not mark a request cancelable, it can call WdfRequestIsCanceled
to determine whether the I/O manager or original requester has
attempted to cancel the request. A driver that processes data on a
periodic basis might use this approach. For example, a driver involved
in image processing might complete a transfer request in small chunks
and poll for cancellation after processing each chunk. In this case,
the driver supports cancellation of the I/O request, but only after each
discrete chunk of processing is complete. If the driver determines that
the request has been canceled, it performs any required cleanup and
completes the request with the status STATUS_CANCELLED.
7.2. Request Suspension
When the system
transitions to a sleep state—typically because the user has requested
hibernation or closed the lid on a laptop—a driver can complete,
requeue, or continue to hold any in-flight requests. KMDF notifies the driver of the impending power change by calling the EvtIoStop callback for each in-flight
request. Each call includes flags that indicate the reason for stopping
the queue and whether the I/O request is currently cancelable.
Depending on the value of the
flags, the driver can complete the request, requeue the request,
acknowledge the event but continue to hold the request, or ignore the
event if the current request will complete in a timely manner. If the
queue is stopping because the device is being removed (either by an
orderly removal or a surprise removal), the driver must complete the
request immediately.
Drivers should handle EvtIoStop
events for any request that might take a long time to complete or that
might not complete, such as a request for asynchronous input. Handling EvtIoStop provides a good user experience for laptops and other power-managed systems.
8. Completing I/O Requests
To complete an I/O request, a driver calls WdfRequestComplete. In response, KMDF completes the underlying IRP and then deletes the WDFREQUEST object and any child objects. If the driver has set an EvtCleanupCallback for the WDFREQUEST
object, KMDF invokes the callback before completing the underlying IRP,
so that the IRP itself is still valid when the callback runs.
After WdfRequestComplete returns, the WDFREQUEST
object’s handle is invalid and its resources have been released. The
driver must not attempt to access the handle or any of its resources,
such as parameters and memory buffers that were passed in the request.
If
the request was dispatched from a sequential queue, the driver’s call
to complete the IRP might cause KMDF to deliver the next request in the
queue. (If the queue is configured for parallel dispatching, KMDF can
deliver another request at any time.) If the driver holds any locks
while it calls WdfRequestComplete,
it must ensure that its event callbacks for the queue do not use the
same locks because a deadlock might occur. In practice, this is
difficult to ensure, so the best practice is not to call WdfRequestComplete while holding a lock.
9. Self-Managed I/O
Although the I/O support
that is built into KMDF is recommended for most drivers, some drivers
have I/O paths that do not pass through queues or are not subject to
power management. KMDF provides self-managed I/O features for this
purpose. For example, the PCIDRV sample uses self-managed I/O callbacks
to start and stop a watchdog timer DPC.
The self-managed I/O callbacks
correspond directly to WDM Plug and Play and power management state
changes. These routines are called with a handle to the device object
and no other parameters. If a driver registers these callbacks, KMDF
calls them at the designated times so that the driver can perform
whatever actions it requires.
10. Accessing IRPs and WDM Structures
KMDF includes a
mechanism nicknamed “the great escape” through which a driver can access
the underlying WDM structures and the I/O request packet as it was
delivered from the operating system. Although this mechanism exposes the
driver to all the complexity of the WDM model, it can often be useful
in converting a WDM driver to KMDF, such as processing for some types of
IRPs. Such drivers can use KMDF for most features but can rely on the
“great escape” to gain access to the WDM features that they require.
To use the “great escape,” a driver calls WdfDeviceInitAssignWdmIrpPreprocessCallback to register an EvtDeviceWdmIrpPreprocess
event callback function for an IRP major function code. When KMDF
receives an IRP with that function code, it invokes the callback. The
driver must then handle the request just as a WDM driver would, by using
I/O manager functions such as IoCallDriver to forward the request and IoCompleteRequest to complete it. The Serial driver sample shows how to use this feature.
In addition to the “great escape,”
KMDF provides methods with which a driver can access the WDM objects
that the KMDF objects represent. For example, a driver can access the
IRP that underlies a WDFREQUEST object, the WDM device object that underlies a WDFDEVICE object, and so forth.