1. Introduction to Programming I/O Queues
Drivers have several options when creating and
configuring I/O queues. The most important are which I/O requests to
queue, what type of dispatching to use, and whether to let KMDF manage
the queue. A device object can have any number of I/O queues, and each
can be configured differently.
One queue for each device object can be configured
as a default queue, into which KMDF places requests for which the
driver has not specifically configured any other queue. If the device
object has a default queue and one or more other queues, KMDF queues
specific requests to the correspondingly configured queues and queues
all other requests to the default queue. If the device object does not
have a default queue, KMDF queues only the specific request types to
the configured queues and, for a function or bus driver, fails all
other requests. (For a filter driver, it passes all other requests to
the next lower driver.)
A queue’s dispatch type determines when KMDF
presents I/O requests from the queue to the driver. KMDF supports three
dispatch types:
KMDF presents requests from a sequential queue to
the driver one at a time. When the first request arrives, KMDF calls
the event callback that was specified for the request type and queue.
After the driver completes the request, KMDF calls the event callback
for another request if the queue is not empty. A sequential queue is
thus used for synchronous I/O. By default, queues are configured as
sequential (WdfIoQueueDispatchSequential).
For a parallel queue, KMDF presents requests to the
driver as soon as they arrive. KMDF does not wait for any “inflight”
requests to complete before sending another request. An inflight
request is an I/O request that is currently active—that is, it is not
in a queue and has not been completed. A parallel queue can thus be
used for asynchronous I/O.
For a manual queue, KMDF does not present requests to the driver. Instead, the driver must call WdfIoQueueRetrieveRequest when it is ready to handle a request. A driver can thus use manual queuing for either synchronous or asynchronous I/O.
A driver can temporarily stop the delivery of requests from a sequential or parallel queue by calling WdfIoQueueStop or WdfIoQueueStopSynchronously, depending on the type of queue and the reasons for pausing delivery. To restart the queue, the driver calls WdfIoQueueStart.
By default, KMDF handles I/O cancellation for queued
I/O requests. Consequently, if the user cancels an I/O request after
KMDF has queued it but before KMDF has delivered it to the driver, KMDF
removes it from the queue and completes it with STATUS_CANCELED. The driver can request notification by registering an EvtIoCanceledOnQueue callback for the queue; otherwise, KMDF cancels the request without notifying the driver.
After a request has been dispatched to the driver,
it cannot be canceled unless the driver specifically marks it as
cancelable and registers an EvtRequestCancel callback. If the driver forwards the request to another queue, it immediately becomes cancelable again.
Also by default, KMDF handles power management for
I/O queues, and each I/O queue inherits the power state of its
associated device. During Plug and Play or power state transitions and
any time the device is not in the working state, KMDF queues incoming
I/O requests but does not dispatch them to the driver. Therefore, if
the driver creates its queues before the device enters DO, the queues are in the WDF_IO_QUEUE_STOPPED state, and KMDF queues any I/O requests targeted at the device. When the device enters the working state, KMDF resumes presenting requests. A driver can change this default when it configures each queue by setting the PowerManaged field of the configuration structure to FALSE.
A driver can also specify whether a queue
accepts I/O requests that have a zero-length buffer. By default, KMDF
does not dispatch such requests to the driver; instead, it completes
them with STATUS_SUCCESS.
2. Creating and Configuring the Queues
To create and configure a queue, a driver takes the following steps:
1. | Defines a WDF_IO_QUEUE_CONFIG structure to hold configuration settings for the queue.
|
2. | Initializes the configuration structure by calling the WDF_IO_QUEUE_CONFIG_INIT or WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE
function (for a default queue). These functions take a pointer to the
configuration structure and an enumerator that defines the dispatching
type for the queue.
|
3. | Sets the event callbacks for this queue in the WDF_IO_QUEUE_CONFIG
structure, if the queue uses sequential or parallel dispatching. A
driver can set callbacks for one or more of the following I/O events: EvtIoRead, EvtIoWrite, EvtIoDeviceIoControl, EvtIoInternalDeviceIoControl, EvtIoDefault, EvtIoStop, EvtIoResume, and EvtIoCanceledOnQueue.
|
4. | Sets Boolean values for the PowerManaged and AllowZero-LengthRequests fields in the queue configuration structure if the default values are not suitable.
|
5. | Creates the queue by calling WdfIoQueueCreate, passing a handle to the WDFDEVICE object, a pointer to the filled-in WDF_IO_QUEUE_CONFIG structure, a pointer to a WDF_OBJECT_ATTRIBUTES structure, and a pointer to receive a handle to the created queue instance.
|
6. | Specifies which I/O requests KMDF should direct to the queue by calling WdfDeviceConfigureRequestDispatching.
|
The PCIDRV sample creates two parallel queues and three manual queues, which are used as follows:
For write requests, the driver creates a
parallel queue. If a write request cannot be satisfied immediately, the
driver puts the request into an internal manual queue. (The queue is
considered internal because only the driver, and not KMDF, adds
requests to it.)
For read requests, the driver creates a manual queue.
For IOCTL requests, the driver creates a parallel queue. If the IOCTL cannot be satisfied immediately, the driver puts the request into an internal manual queue.
In this driver, each I/O queue is configured for a
particular type of I/O request. Therefore, the driver does not create a
default queue.
The code to create all the queues is excerpted from the NICAllocateSoftwareResources function (in the file cidrv\sys\hw\Nic_init.c), which is called from the driver’s EvtDriverDeviceAdd callback.
2.1. Code to Create Queues for Write Requests
The following excerpt shows how the PCIDRV
sample creates a parallel queue for incoming write requests. While
requests are in this queue, KMDF handles cancellation without notifying
the driver.
NTSTATUS status;
WDF_IO_QUEUE_CONFIG ioQueueConfig;
WDF_OBJECT_ATTRIBUTES attributes;
......
WDF_IO_QUEUE_CONFIG_INIT (
&ioQueueConfig,
WdfIoQueueDispatchParallel
);
ioQueueConfig.EvtIoWrite = PciDrvEvtIoWrite;
status = WdfIoQueueCreate (
FdoData->WdfDevice,
&ioQueueConfig,
WDF_NO_OBJECT_ATTRIBUTES,
&FdoData->WriteQueue // queue handle
);
if (!NT_SUCCESS (status))
{
return status;
}
status = WdfDeviceconfigureRequestDispatching (
FdoData->WdfDevice,
FdoData->WriteQueue,
WdfRequestTypeWrite );
if (!NT_SUCCESS (status))
{
ASSERT (NT_SUCCESS (status));
return status;
}
The driver calls WDF_IO_QUEUE_CONFIG_INIT to initialize the queue as a parallel queue. The queue holds only write requests, so the driver sets only an EvtIoWrite callback in the ioQueueConfig structure. It creates the queue, and then calls WdfDeviceConfigureRequestDispatching to configure the queue for requests of type WdfRequestTypeWrite
only. All other I/O requests are directed to a queue that is configured
for them or, if the driver has not configured a queue for them, are
handled by the framework without being sent to the driver.
The PCIDRV sample also creates a manual queue into which to place pending write requests. The driver’s EvtIoWrite callback places an I/O request in this internal queue when it cannot handle the request immediately.
The driver creates this queue as follows:
WDF_IO_QUEUE_CONFIG_INIT (
&ioQueueConfig,
WdfIoQueueDispatchManual
);
status = WdfIoQueueCreate (
FdoData->WdfDevice,
&ioQueueConfig,
WDF_NO_OBJECT_ATTRIBUTES,
&FdoData->PndingWriteQueue
);
if (!NT_SUCCESS (status))
{
return status;
}
To create the manual internal queue for its pending write requests, the driver configures a WDF_IO_QUEUE_CONFIG structure, this time setting the queue type to WdfIoDispatchManual, which indicates that the driver requests items from the queue when it is ready. It then creates the queue by calling WdfIoQueueCreate.
The driver does not configure the queue for any particular type of I/O
request because the driver itself determines which request to queue.
KMDF does not put any request in this queue because the driver did not
call WdfDeviceconfigureRequestDispatching. However, KMDF handles power management and request cancellation.