IO Queues


I/O Queues

An I/O queue object represents a queue that presents I/O requests to the driver and provides a way to control the flow of I/O requests to a driver. A WDF I/O queue is more than just a list of pending requests, however. Queue objects track requests that are active in the driver, support request cancellation, manage the concurrency of requests, can optionally synchronize their operations with the Plug and Play and power state of the device, and can optionally synchronize calls to the driver's I/O event callback functions.

The driver creates its I/O queues after it creates the device object. To create a queue, a driver calls one of the following methods:

  • A UMDF driver calls IWDFDevice::CreateIoQueue.

  • A KMDF driver calls WdfIoQueueCreate.

A driver typically creates one or more queues for each device object. Each queue can accept one or more types of requests. For each queue, the driver can specify:

  • The types of requests that are placed in the queue.

  • The power management options for the queue.

  • The dispatch method for the queue, which determines the number of requests that can be active in the driver at any given time.

  • Whether the queue accepts requests that have a zero-length buffer.

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, each queue object keeps track of which requests it owns and which requests are pending. A driver can pass a request from one queue to another by calling a method on the request object.

image from book
Queues as Building Blocks

The queues are meant to be stand-alone objects as well as building blocks for sorting and implementing more complex routing patterns. We use the term "building blocks" a lot when explaining the queue to developers, and I think it conveys the way we intended the developer to use a queue.
-Doron Holan, Windows Driver Foundation Team, Microsoft

image from book

Queue Configuration and Request Types

A driver can have any number of queues, and they can all be configured differently. For example, a driver might have separate queues for read, write, and device I/O control requests and each might use a different dispatch method. A driver can also create queues for its own internal use.

Not all types of I/O requests can be queued. The framework dispatches some requests to drivers by immediately invoking a callback function. Table 8-4 lists which types of I/O requests the framework delivers through a queue and which types it delivers immediately to a callback function without queuing.

Table 8-4: I/O Request Delivery Mechanism
Open table as spreadsheet

I/O request type

UMDF delivery mechanism

KMDF delivery mechanism

Read

Queue

Queue

Write

Queue

Queue

Device I/O control

Queue

Queue

Internal device I/O control

Queue

Queue

Create

Queue

Queue or callback

Close

Callback

Callback

Cleanup

Callback

Callback

 UMDF  Although UMDF does not deliver cleanup or cancel requests through the queues, some queuing occurs internally. The framework has a limited number of threads on which to deliver I/O requests, so it is possible for a cleanup or close request to be delayed while the framework waits for an I/O thread.

How to Specify the Request Types for a Queue

The driver specifies the types of I/O requests that a queue accepts as follows:

  • A UMDF driver calls either the device object's IWDFDevice::ConfigureRequestDispatching method or the queue object's IWDFIoQueue::ConfigureRequestDispatching method.

    In the call to ConfigureRequestDispatching, the driver specifies an I/O request type and a Boolean value that indicates whether the framework should queue requests of that type. The driver can call ConfigureRequestDispatching as many times as required to configure all the request types for the queue. Valid request types are the following:

    • WdfRequestCreate

    • WdfRequestRead

    • WdfRequestWrite

    • WdfRequestDeviceIoControl

    The driver must also implement the corresponding queue callback interfaces on the queue callback object. The framework calls QueryInterface on the callback object to determine which interfaces the queue object supports.

  • A KMDF driver calls WdfDeviceConfigureRequestDispatching for each I/O request type that the queue accepts. Valid request types are the following:

    • WdfRequestTypeCreate

    • WdfRequestTypeRead

    • WdfRequestTypeWrite

    • WdfRequestTypeDeviceControl

    • WdfRequestTypeDeviceControlInternal

    The driver must also register the I/O event callback functions for each queue by setting fields in the queue configuration structure. "KMDF Example: Creating I/O Queues" later in this chapter shows how to initialize this structure.

Queue Callbacks

Table 8-5 lists the event callback interfaces and functions that a driver can implement for an I/O queue.

Table 8-5: I/O Queue Callbacks
Open table as spreadsheet

Associated event

UMDF callback interface

KMDF callback function

Read request

IQueueCallbackRead

EvtIoRead

Write request

IQueueCallbackWrite

EvtIoWrite

Device I/O control request

IQueueCallbackDeviceIoControl

EvtIoDeviceIoControl

Internal device I/O control request

Not applicable

EvtIoInternalDeviceIoControl

Create request

IQueueCallbackCreate

EvtIoDefault

I/O request for which no other callback is implemented

IQueueCallbackDefaultIoHandler

EvtIoDefault

Power-managed queue stop notification

IQueueCallbackIoStop

EvtIoStop

Power-managed queue resume notification

IQueueCallbackIoResume

EvtIoResume

Queue state change notification

IQueueCallbackStateChange

EvtIoQueueState

Queued request cancellation notification

Not applicable

EvtIoCanceledOnQueue

As the table shows, a queue can support callbacks for the following types of events in addition to I/O requests:

  • Power management changes, as described in "Power-Managed Queues" later in this chapter.

  • Queue state changes, as described in "Queue Control" later in this chapter.

  • For KMDF drivers, I/O request cancellation, as described in "Canceled and Suspended Requests" later in this chapter.

Default Queues

One queue for each device object can be created and configured as a default queue, into which the framework 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, the framework queues specific requests to the correspondingly configured queues and queues all other requests to the default queue. A driver does not call the framework's configure request dispatching method for a default queue.

If the device object does not have a default queue, the framework queues only the specified request types to the configured queues and applies default handling to other requests depending on the type of driver. For a function or bus driver, the framework fails all other requests. For a filter driver, it passes all other requests to the next lower driver.

 Note  A driver is not required to have a default I/O queue. For example, a driver that handles only read and device I/O control requests might configure one queue for read requests and another for device I/O control requests. In this scenario, if the driver receives a write request, the type of driver determines what happens to the request-for a function driver, the framework fails the request, and for a filter driver, the framework passes the request down to the default I/O target.

Queues and Power Management

Internally, WDF integrates support for queues with the Plug and Play and power management state machine. As a result, WDF can synchronize queue operations with the power state of a device 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.

Tip 

Use power-managed queues for requests that your driver can handle only while the device is in the working state. Use non-power-managed queues for requests that your driver can handle even when the device is not in the working state.

Power-Managed Queues

By default, I/O queues for function and bus drivers are power managed, which means that the state of the queue triggers power-management activities and vice versa. Power-managed queues provide several benefits for drivers:

  • If an I/O request arrives while the system is in the working state (S0) but the device is not in the working state, the I/O handler notifies the Plug and Play and power handler to restore device power.

  • The driver can implement an I/O stop callback on the queue.

    If the queue stops as a result of a device state change, the framework invokes the callback for each I/O request that the driver owns and that was dispatched by the queue. In the callback, the driver can complete, cancel, or acknowledge the I/O request before the device leaves the working state.

  • For KMDF only, the framework's I/O handler notifies its Plug and Play and power handler if a queue becomes empty so that the Plug and Play and power handler can track device usage through an idle timer. If the device supports power-down on idle, the Plug and Play and power handler can power down the device when the idle timer expires.

  • The framework pauses the delivery of requests when the device leaves the working state (D0) and resumes delivery when the device returns to the working state. Although delivery stops while the queue is paused, queuing does not stop.

For requests to be delivered, both the driver and the device power state must allow processing.

If a request arrives while

The framework

The queue is dispatching requests

Adds the request to the queue for delivery according to the queue's dispatch type.

The queue is stopped

Adds the request to the queue for delivery after the queue resumes.

The queue is stopped, the device is in a low-power idle state, and the system is in the working state

Adds the request to the queue and returns the device to the working state before delivering the request.

The queue is stopped and the system is transitioning to a sleep state

Adds the request to the queue and returns the device to the working state after the system returns to the working state.

A driver can optionally implement I/O event callbacks that the framework calls when a power-managed queue stops or restarts because of a change in the device power state. The framework calls these callbacks once for each request that the driver owns. Table 8-6 lists these callbacks.

Table 8-6: I/O Event Callbacks for Queue State Changes
Open table as spreadsheet

If a UMDF driver

Or if a KMDF driver

The framework calls the driver

Implements IQueueCallbackIoStop

Registers EvtIoStop

Before the device leaves the working state so that the driver can suspend processing of the request or purge and complete the request.

Implements IQueueCallbackIoResume

Registers EvtIoResume

After the device returns to the working state. However, KMDF calls the driver only if the driver's EvtIoStop callback acknowledged the stop during shutdown but did not requeue the request.

"Request Suspension" later in this chapter contains more information about the I/O stop callbacks.

Non-Power-Managed Queues

If a queue is not power managed, the queue delivers I/O requests to the driver as long as the device is present, regardless of the power state of the device. If the device is in a sleep state when an I/O request arrives, WDF does not power up the device. 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.

 KMDF  Only power-managed queues affect idle timers. If a driver supports powering down an idle device, the state of the non-power-managed queue does not determine whether the framework starts or stops the idle timer. A driver can programmatically change the idle state of the device by calling WdfDeviceStopIdle.

 Chapter 7 describes WdfDeviceStopIdle  A driver can implement an I/O stop callback, as listed previously in Table 8-6, for a non- power-managed queue. The framework invokes this callback only during device removal, and not when the device enters a lower-powered state or is stopped to rebalance resources.

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 simultaneously active in the driver. Drivers can control the concurrency of in-flight requests by configuring the dispatch type for their queues. WDF supports the following three dispatch types:

  • Sequential The queue pushes 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 or forwarded to another queue. If the driver sends the request to an I/O target, the queue does not deliver another request until the current driver completes the request. Sequential dispatching is similar to the start I/O technique in WDM.

  • Parallel The queue pushes I/O requests to the driver as soon as possible, whether or not another request is already active in the driver.

  • Manual The driver pulls requests from the queue at its own pace by calling a retrieval method on the queue.

All I/O requests that a driver receives from a queue are inherently asynchronous. The driver can complete the request within the associated I/O event callback or sometime later, after returning from the callback. The driver is not required to mark the request pending, as in a WDM driver; WDF handles this on behalf of the driver.

 Chapter 10 describes synchronization scope for queues The dispatch type controls only the number of requests that are active within a driver at one time. The dispatch type has no effect on whether the queue's I/O event callbacks are invoked sequentially or concurrently; instead, the synchronization scope of the device object controls the concurrency of callbacks. Even if the synchronization scope for a parallel queue does not allow concurrent callbacks, the queue nevertheless might have many in-flight requests.

image from book
Pending Requests and I/O Event Callback Status

If you're familiar with WDM drivers, you might wonder why the framework doesn't have a method to mark a request pending and why the driver's I/O event callbacks don't return a status.

The framework eliminated all the rules and complexity for drivers around marking an IRP pending and returning STATUS_PENDING. Internally, the framework always marks the IRP pending and returns STATUS_PENDING even if the driver routine completes the request synchronously. The I/O manager optimizes completion processing based on the thread context, so marking a request pending and completing it synchronously does not affect performance. This also led us to remove the return status from the driver's I/O event callbacks because it didn't serve any purpose. When the driver completes the request, the framework completes the IRP.
-Eliyas Yakub, Windows Driver Foundation Team, Microsoft

image from book

Queue Control

WDF provides methods that a driver can use to stop, start, drain, and purge I/O queues and to determine the state of a queue:

  • UMDF drivers control an I/O queue by calling methods in the queue object's IWDFIoQueue interface.

  • KMDF drivers control an I/O queue by calling WdfIoQueueXxx methods.

For stopping, purging, and draining a queue, WDF supports both synchronous and asynchronous methods. The synchronous methods return after the operation is complete. A drain or purge operation is complete when all of the requests in the queue have completed.

The asynchronous methods return immediately and invoke a driver-supplied callback when the operation is complete. To register the callback:

  • A UMDF driver provides a pointer to the IQueueCallbackStateChange interface on the queue callback object when it calls an asynchronous queue control method.

  • A KMDF driver provides a pointer to the EvtIoQueueState callback when it calls an asynchronous queue control method.

Table 8-7 lists the queue control methods.

Table 8-7: Queue Control Methods
Open table as spreadsheet

Operation

UMDF method in IWDFIoQueue interface

KMDF method

Stops adding requests to the queue.

Optionally notifies the driver or returns control to the driver after all pending I/O requests have been completed.

Drain

DrainSynchronously

WdfIoQueueDrain

WdfIoQueueDrainSynchronously

Returns the state of the I/O queue.

GetState

WdfIoQueueGetState

Stops adding requests to the queue.

Cancels all requests that are already in the queue and all in-flight requests that are in a cancelable state.

Notifies the driver or returns control to the driver only after all pending I/O requests have been completed.

Purge

PurgeSynchronously

WdfIoQueuePurge

WdfIoQueuePurgeSynchronously

Resumes delivery of requests from the queue.

Start

WdfIoQueueStart

Stops delivery of requests from the queue but continues to add new requests to the queue.

Stop

StopSynchronously

WdfIoQueueStop

WdfIoQueueStopSynchronously

A driver can use the queue control methods along with the self-managed I/O callbacks to manually control the state of a non-power-managed queue. "Self-Managed I/O" later in this chapter describes the self-managed I/O support in WDF.

UMDF Example: Creating I/O Queues

To create an I/O queue, a UMDF driver performs the following actions:

  1. Creates a queue callback object, if the driver implements any callback interfaces for the queue.

  2. Creates the framework queue object by calling IWDFDevice::CreateIoQueue.

  3. Configures the queue object to accept one or more types of I/O requests, unless the queue is a default queue.

CreateIoQueue takes the following six parameters:

  • pCallbackInterface

    A pointer to the IUnknown interface that the framework uses to determine which interfaces the driver implements on its queue callback object.

  • bDefaultQueue

    A BOOL value that specifies whether to create a default I/O queue for the device. TRUE indicates a default I/O queue, and FALSE indicates a secondary I/O queue.

  • DispatchType

    One of the following WDF_IO_QUEUE_DISPATCH_TYPE values, which specifies how the framework delivers requests to the driver:

    • WdfIoQueueDispatchSequential

    • WdfIoQueueDispatchParallel

    • WdfIoQueueDispatchManual

  • bPowerManaged

    A BOOL value that indicates whether the queue is power managed.

  • bAllowZeroLengthRequests

    A BOOL value that specifies whether to queue read and write requests that have zero- length buffers. FALSE indicates that the framework should automatically complete these request types for the driver. TRUE indicates that the driver receives these request types.

  • ppIoQueue

    A pointer to a variable that receives a pointer to the IWDFIoQueue interface for the framework's I/O queue object.

The Fx2_Driver sample creates the following three I/O queues:

  • A default parallel queue for read and write requests.

  • A sequential queue for device I/O control requests.

  • A manual queue into which the driver places requests that watch for changes in the state of the switches on the device.

To create each queue, the UMDF driver creates a queue callback object and then calls the framework to create the associated framework queue object. The driver's queue callback objects implement the callback interfaces that handle the I/O request types that the queue supports.

The driver creates and configures the queues after it creates the device object, as part of OnDeviceAdd processing. All three of the queues are based on the sample driver's CMyQueue base class. This class implements an Initialize method that calls the framework to create its partner queue object.

UMDF Default Queue

To create the callback object for the default queue, the Fx2_Driver sample creates a queue callback object and then calls the Initialize method of the base class to create the partner framework queue object. Listing 8-3 shows the code for this method, which appears in the Queue.cpp source file.

Listing 8-3: Creating and initializing a default UMDF queue

image from book
 HRESULT CMyQueue::Initialize(     __in WDF_IO_QUEUE_DISPATCH_TYPE DispatchType,     __in bool Default,     __in bool PowerManaged     ) {     IWDFIoQueue *fxQueue;     HRESULT hr; {   // Create the I/O Queue object.     IUnknown *callback = QueryIUnknown();     hr = m_Device->GetFxDevice()->CreateIoQueue( callback,                                                  Default,                                                  DispatchType,                                                  PowerManaged,                                                  FALSE,                                                  &fxQueue                                                  );     callback->Release(); }     if (SUCCEEDED(hr)){         m_FxQueue = fxQueue;         fxQueue->Release();     }     return hr; } 
image from book

The Initialize method calls IWDFDevice::CreateIoQueue to create the framework's I/O queue object. The Fx2_Driver sample configures the default I/O queue for parallel dispatching and framework power management and does not receive zero-length requests from the queue.

For each queue callback object, the driver implements one or more callback interfaces to handle I/O requests. To dispatch an I/O request, UMDF calls a method in the corresponding callback interface. The sample driver's default queue supports the following UMDF callback interfaces:

  • IQueueCallbackRead

  • IQueueCallbackWrite

  • IRequestCallbackRequestCompletion

For example, to dispatch a read request, UMDF calls the IQueueCallbackRead::OnRead method on the queue callback object. A queue callback object can implement interfaces that are specific to a single request type-such as IQueueCallbackRead-and can also optionally implement the default IQueueCallbackDefaultIoHandler interface. UMDF calls methods on the IQueueCallbackDefaultIoHandler interface when it receives a create, read, write, or device I/O control request for which the driver has not implemented any other interface. However, the sample driver does not implement this interface, so the default queue accepts only read and write requests. The framework fails other types of requests for which the driver does not configure a queue.

The default queue also implements the IRequestCallbackRequestCompletion interface so that the driver can provide the default queue as the completion callback object for requests that the driver sends to the I/O target.

UMDF Nondefault Queue

A driver creates a nondefault queue in the same way that it creates a default queue, with two exceptions:

  • In its call to IWDFDevice::CreateIoQueue, the driver sets the bDefaultQueue parameter to FALSE.

  • After the driver creates a framework queue object, it must call a ConfigureRequestDispatching method to specify the types of requests that the framework places in the queue.

ConfigureRequestDispatching takes a value that indicates the type of request and a Boolean that indicates whether to enable or disable queuing of the specified request type. Both the IWDFDevice and IWDFIoQueue interfaces include this method. The only difference between the two methods is that IWDFDevice::ConfigureRequestDispatching takes an additional parameter that supplies a pointer to the IWDFIoQueue interface for the queue.

The Fx2_Driver sample configures a queue to receive only device I/O control requests by calling IWDFDevice::ConfigureRequestDispatching as follows:

 hr = m_FxDevice->ConfigureRequestDispatching( m_ControlQueue->GetFxQueue(),                                               WdfRequestDeviceIoControl,                                               true                                               ); 

To configure the queue to accept an additional type of request, the driver can call ConfigureRequestDispatching again. Any request types that the driver does not specify in a call to ConfigureRequestDispatching are directed to the default queue or, if the driver has not created a default queue, are either failed or passed to the next lower driver, as previously described.

UMDF Manual I/O Queue

As the driver receives I/O requests from the device I/O control queue, it processes them and forwards to the manual queue any requests to report changes in the state of the device's switches. Only the driver places requests in the switch-state queue; the framework does not. The driver completes all of these requests when the user flips a switch. Such an I/O control request is a simple way for a driver to send information to an application.

The driver configures the queue for manual dispatching, so the driver must call the framework to retrieve requests from the queue. The IWDFIoQueue::RetrieveNextRequest and IWDFIoQueue::RetrieveNextRequestByFileObject methods return I/O requests from a manual queue. The framework does not call back to the driver to deliver requests from the queue.

Listing 8-4 shows how the Fx2_Driver creates this queue.

Listing 8-4: Creating a manual queue in a UMDF driver

image from book
 IWDFIoQueue * m_SwitchChangeQueue; IUnknown * pQueueCallbackInterface = this->QueryIUnknown(); hr = m_FxDevice->CreateIoQueue(pQueueCallbackInterface,                                FALSE, // Not a default queue                                WdfIoQueueDispatchManual,                                false, // Not power managed                                false, // No zero-length buffers                                &m_SwitchChangeQueue                                ); // Release the reference that QueryIUnknown took SAFE_RELEASE(pQueueCallbackInterface); 
image from book

The driver does not implement any callback interfaces for the manual queue object, so it passes a pointer to the IUnknown interface on the device callback object. The CreateIoQueue method requires a pointer for this parameter so the driver must not pass NULL.

KMDF Example: Creating I/O Queues

To create and configure an I/O queue, a KMDF 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.

    These functions take a pointer to the configuration structure and an enumeration value that defines the dispatching type for the queue.

  3. Sets the event callbacks for the queue in the WDF_IO_QUEUE_CONFIG structure, and sets whether the queue uses sequential, manual, or parallel dispatching.

  4. Sets Boolean values for the PowerManaged and AllowZeroLengthRequests 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 that owns the queue, 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.

  6. Specifies which I/O requests KMDF should direct to the queue by calling WdfDeviceConfigureRequestDispatching, if the queue is not a default queue. A driver can also use this method to configure a default queue to accept create requests.

The Osrusbfx2 sample creates a parallel default queue and two sequential queues, which are used as follows:

  • For IOCTLs, the driver configures the default parallel queue.

  • For read requests, the driver creates a sequential queue.

  • For write requests, the driver creates another sequential queue.

The code to create all the queues is excerpted from the OsrFxDeviceAdd function in the Device.c file.

KMDF Default Queue

Listing 8-5 shows how the Osrusbfx2 sample creates a default parallel queue for incoming device I/O control requests.

Listing 8-5: Creating a default queue in a KMDF driver

image from book
 WDF_IO_QUEUE_CONFIG ioQueueConfig; WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE(&ioQueueConfig, WdfIoQueueDispatchParallel); ioQueueConfig.EvtIoDeviceControl = OsrFxEvtIoDeviceControl; status = WdfIoQueueCreate(device,                          &ioQueueConfig,                          WDF_NO_OBJECT_ATTRIBUTES,                          &queue);// handle to default queue if (!NT_SUCCESS(status)) {     return status; } 
image from book

Because this is a default queue, the driver calls WDF_IO_QUEUE_CONFIG_ INIT_ DEFAULT_QUEUE to initialize the configuration structure. It passes a pointer to the configuration structure and the WdfIoQueueDispatchParallel enumeration value, which specifies parallel dispatching. It then registers an I/O event callback function for device I/O control requests in the EvtIoDeviceControl field of the configuration structure. It does not register any other I/O event callbacks, so the framework fails any I/O requests for which the driver does not configure another queue. Finally, to create the queue, it calls WdfIoQueueCreate.

If a request is canceled while it is in this queue and before it is dispatched to the driver, the framework handles cancellation without notifying the driver.

KMDF Nondefault Queue

The Osrusbfx2 driver also creates separate sequential queues for read and write requests. Listing 8-6 shows the source code that creates and configures these two queues.

Listing 8-6: Creating a nondefault queue in a KMDF driver

image from book
 // Create a sequential queue for read requests and register EvtIoStop to // acknowledge requests that are pending at the I/O target. WDF_IO_QUEUE_CONFIG_INIT(&ioQueueConfig, WdfIoQueueDispatchSequential); ioQueueConfig.EvtIoRead = OsrFxEvtIoRead; ioQueueConfig.EvtIoStop = OsrFxEvtIoStop; status = WdfIoQueueCreate( device,                            &ioQueueConfig,                            WDF_NO_OBJECT_ATTRIBUTES,                            &queue // queue handle                            ); if (!NT_SUCCESS (status)) {     return status; } status = WdfDeviceConfigureRequestDispatching(device, queue, WdfRequestTypeRead); if(!NT_SUCCESS (status)){     return status; } // Create another sequential queue for write requests. WDF_IO_QUEUE_CONFIG_INIT(&ioQueueConfig, WdfIoQueueDispatchSequential); ioQueueConfig.EvtIoWrite = OsrFxEvtIoWrite; ioQueueConfig.EvtIoStop = OsrFxEvtIoStop; status = WdfIoQueueCreate(device,                           &ioQueueConfig,                           WDF_NO_OBJECT_ATTRIBUTES,                           &queue // queue handle                           ); if (!NT_SUCCESS (status)) {         return status; } status = WdfDeviceConfigureRequestDispatching(device, queue, WdfRequestTypeWrite); if(!NT_SUCCESS (status)){     return status; } 
image from book

As Listing 8-6 shows, the driver follows the same steps to create both the read queue and the write queue. It calls WDF_IO_QUEUE_CONFIG_INIT to initialize the queues as sequential, nondefault queues. The first queue accepts only read requests, so the driver sets an EvtIoRead callback in the ioQueueConfig structure and calls WdfDeviceConfigureRequestDispatching to specify that the framework add only read requests (WdfRequestTypeRead) to the queue. The second queue accepts only write requests, so the driver repeats this procedure, this time setting an EvtIoWrite callback and specifying WdfRequestTypeWrite.

In addition to the event callbacks for read and write requests, the driver configures an EvtIoStop callback for both queues so that it can properly handle pending requests when the queue stops. The framework calls EvtIoStop for each request that the driver owns when the device is leaving the working state.

This function driver configures the sequential queues for read and write requests and supplies only an EvtIoDeviceControl callback for its default queue. Therefore, the driver framework fails internal device I/O requests with the status STATUS_INVALID_DEVICE_REQUEST.

Retrieving Requests from a Manual Queue

When a driver is ready to handle a request from a manual queue, it calls a method on the queue object to retrieve one, as follows:

  • UMDF drivers call methods on the queue object's IWDFIoQueue interface to retrieve requests.

  • KMDF drivers call WdfIoQueueXxx methods.

Table 8-8 summarizes the methods that drivers can use to retrieve requests from manual I/O queues.

Table 8-8: Retrieval Methods for Manual I/O Queues
Open table as spreadsheet

Operation

UMDF method

KMDF method

Retrieve the next request from the queue.

IWDFIoQueue:: RetrieveNextRequest

WdfIoQueueRetrieveNextRequest

Retrieve the next request for a particular file object.

IWDFIoQueue::RetrieveNextRequestByFileObject

WdfIoQueueRetrieveRequestByFileObject

Search the queue for a particular request and then retrieve that request.

None

WdfIoQueueFindRequest followed by WdfIoQueueRetrieveFoundRequest

By default, the queues operate on a first-in, first-out (FIFO) basis. A driver can retrieve the next request in the queue or the next request that specifies a particular file object.

A driver can receive notification when a request arrives at a queue as follows:

  • A UMDF driver implements the IQueueCallbackStateChange callback on the queue callback object. When the state of the queue changes, the framework calls the OnStateChange method with a value that identifies the new state of the queue.

  • A KMDF driver calls WdfIoQueueReadyNotify and passes a pointer to an EvtIoQueueState function. The framework calls EvtIoQueueState when a request arrives.

 KMDF  A KMDF driver can search the queue for a particular request by calling WdfIoQueueFindRequest. This method takes a reference on the request and returns a handle to the request but does not remove the request from the queue. The driver can inspect the request to determine whether it is the one that the driver was seeking. If not, the request stays in the queue and the driver can search again. If so, the driver can dequeue the request by calling WdfIoQueueRetrieveFoundRequest. The driver must then release the reference that WdfIoQueueFindRequest took on the request.

After a driver has removed a request from the queue, the driver "owns" that request. The driver must complete the request, send it to another driver, or add it to a different queue.

Listing 8-7 shows how the Pcidrv sample searches its manual device I/O control queue for a request with a particular function code and then retrieves that request. The code is from the Pcidrv\sys\hw\nic_req.c source file and has been slightly abridged.

Listing 8-7: Finding a request in a manual KMDF queue

image from book
 NTSTATUS NICGetIoctlRequest(     IN WDFQUEUE Queue,     IN ULONG FunctionCode,     OUT WDFREQUEST*  Request     ) {     NTSTATUS            status = STATUS_UNSUCCESSFUL;     WDF_REQUEST_PARAMETERS params;     WDFREQUEST          tagRequest;     WDFREQUEST          prevTagRequest;     WDF_REQUEST_PARAMETERS_INIT(&params);     *Request = NULL;     prevTagRequest = tagRequest = NULL;     do {        WDF_REQUEST_PARAMETERS_INIT(&params);        status = WdfIoQueueFindRequest(Queue,                                       prevTagRequest,                                       NULL,                                       &params,                                       &tagRequest);        // WdfIoQueueFindRequest takes an extra reference on tagRequest to prevent        // the memory from being freed. However, tagRequest is still in the queue        // and can be cancelled or removed by another thread and completed.        if(prevTagRequest) {            WdfObjectDereference(prevTagRequest);        }        if(status == STATUS_NO_MORE_ENTRIES) {            status = STATUS_UNSUCCESSFUL;            break;        }        if(status == STATUS_NOT_FOUND) {            // The prevTagRequest disappeared from the queue. Either it was            // cancelled or dispatched to the driver. Other requests might match            // our criteria so restart the search.            prevTagRequest = tagRequest = NULL;            continue;        }        if( !NT_SUCCESS(status)) {            status = STATUS_UNSUCCESSFUL;            break;        }        if(FunctionCode == params.Parameters.DeviceIoControl.IoControlCode){            status = WdfIoQueueRetrieveFoundRequest(Queue, tagRequest, Request);            WdfObjectDereference(tagRequest);            if(status == STATUS_NOT_FOUND) {                // The prevTagRequest disappeared from the queue.                // Restart the search.                prevTagRequest = tagRequest = NULL;                continue;            }            if( !NT_SUCCESS(status)) {                status = STATUS_UNSUCCESSFUL;                break;            }                // Found a request. Drop the extra reference before returning.                ASSERT(*Request == tagRequest);                status =  STATUS_SUCCESS;                break;            }            else {                // This is not the request we need. Drop the reference                // on the tagRequest after looking for the next request.                prevTagRequest = tagRequest;                continue;            }        } while (TRUE);        return status; } 
image from book

The sample driver starts by calling WDF_REQUEST_PARAMETERS_INIT to initialize a WDF_REQUEST_PARAMETERS structure. Later, when the driver calls WdfIoQueueFindRequest, KMDF returns the parameters for the request in this structure.

Next, the driver initializes the variables that it uses to keep track of the requests it has searched through. The prevTagRequest variable holds a handle to the previous request that the driver inspected, and tagRequest holds a handle to the current request. The driver initializes both values to NULL before it starts searching.

The search is conducted in a loop. Each time NICGetIoctlRequest calls WdfIoQueueFindRequest, it passes prevTagRequest to indicate where KMDF should start searching and passes a pointer to tagRequest to receive the handle to the current request. WdfIoQueueFindRequest also takes a handle to the queue, a handle to the related file object, and a pointer to the initialized WDF_REQUEST_PARAMETERS structure. The Pcidrv sample does not use file objects, so it passes NULL for the file object handle. Note that the driver reinitializes the WDF_REQUEST_PARAMETERS structure before each call, thus ensuring that it does not receive old data.

On the first iteration of the loop, prevTagRequest is NULL. Therefore, the search starts at the beginning of the queue. WdfIoQueueFindRequest searches the queue and returns the request's parameters (in the Params variable) and a handle to the request (in tagRequest). To prevent another component from deleting the request while the driver inspects it, WdfIoQueueFindRequest takes out a reference on request.

NICGetIoctlRequest compares the function code value that was returned in the request's parameters structure with the function code that the caller passed in. If the codes match, the driver calls WdfIoQueueRetrieveFoundRequest to dequeue the request. WdfIoQueueRetrieveFoundRequest takes three parameters: the handle to the queue, the handle returned by WdfIoQueueFindRequest that indicates which request to dequeue, and a pointer to a location that will receive a handle to the dequeued request.

When WdfIoQueueRetrieveFoundRequest returns successfully, the driver "owns" the retrieved request. It deletes the extra reference previously taken on the request by calling WdfObjectDereference. It then exits from the loop, and the NICGetIoctlRequest function returns a handle to the retrieved request. The caller can then perform the I/O operations that are required to satisfy the request.

If the function codes do not match, the driver sets prevTagRequest to tagRequest so that the search starts at its current location in the queue. However, the driver does not yet dereference the request object that prevTagRequest represents. It must maintain this reference until WdfIoQueueFindRequest has returned on the next iteration of the loop. The loop then executes again. This time, if WdfIoQueueFindRequest successfully finds a request, the driver deletes the reference that WdfIoQueueFindRequest acquired for the prevTagRequest and then compares the function codes as it did in the previous iteration.

If the request is no longer in the queue, WdfIoQueueFindRequest returns STATUS_NOT_FOUND. For example, the request might not be in the queue if it was canceled or was already retrieved by another thread. WdfIoQueueRetrieveFoundRequest can also return this same status if the handle passed in tagRequest is not valid. If either of these errors occurs, the driver restarts the search at the beginning. If either of these methods fails for any other reason, such as exhausting the queue, the driver exits from the loop.




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