In some situations, a driver must requeue requests
on its own, after it has received them from KMDF. For example, a driver
might be able to respond to some device I/O control requests
immediately, but might have to handle others at a later time. Because
the requests are all of the same type (WdfRequestTypeDeviceControl), KMDF cannot deliver some to one
queue and some to another. Instead, the driver must sort the requests
as KMDF delivers them and place any that it cannot satisfy immediately
into a manual, internal queue to handle later.
case IOCTL_NDISPROT_INDICATE_STATUS:
status = WdfRequestForwardToIoQueue (Request,
fdoData->PendingIoctlQueue);
ASSERT (status == STATUS_WDF_FORWARDED);
break;
2. Retrieving Requests from a Manual Queue
When the driver is ready to handle a request from a
manual queue, it calls a method on the queue object to retrieve one. A
KMDF driver can
Retrieve the next request from the queue.
Retrieve the oldest request in the queue that pertains to a particular file object.
Search the queue until it finds a particular request and then retrieve that request.
To remove the next item from a manual queue, a driver calls WdfIoQueueRetrieveNextRequest with a handle to the queue and a pointer to a location to receive the handle to the request.
To remove the oldest request that specifies a particular file object, a driver calls WdfIoQueueRetrieveRequestByFileObject.
The driver passes a handle to the file object along with the handle to
the queue and a pointer to a location to receive the handle to the
request. This method updates an internal queue pointer, so that the
next time the driver calls it, it returns the next-oldest item, and so
forth.
To search the queue for a particular request, the driver calls WdfIoQueueFindRequest.
This method 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.
After the driver has removed a request from the
queue, the driver “owns” that request. The driver must complete the
request, forward it to another driver, or forward it to a different
queue.
2.1. Code to Find a Request
The following function 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 source file pcidrv\sys\hw\nic_req.c, and it has been slightly abridged.)
NICGETIoctlRequest (
IN WDFQUEUE Queue,
IN ULONG FunctionCode,
OUT WDFREQUEST* Request
)
{
NTSTATUS status = STATUS_UNSUCCESSFUL
WDF_REQUEST_PARAMETERS params;
WDFREQUEST tagRequest;
WDFREQUEST prevTagRequest;
WDF_REQUEST_PARAMETER_INIT (¶ms);
*Request = NULL;
prevTagRequest = tagRequest = NULL;
do
{
WDF_REQUEST_PARAMETERS_INIT (¶ms);
status = WdfIoQueueFindRequest (Queue,
prevTagRequest,
NULL,
¶ms,
&tagRequest);
// WdfIoQueueFindRequest takes an extra reference on
// the returned tagRequest to prevent the memory
// being freed. However, the tagRequest is still
// in the queue and can be canceled 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 for some reason – either it was
// canceled or dispatched to the driver. There
// might be other requests that 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, // TagRequest
Request
);
WdfObjectDereference (tagRequest);
if (status == STATUS_NOT_FOUND)
{
//
// The TagRequest disappeared
// for some reason – either it was
// canceled 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;
}
//
// 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;
}
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 variable prevTagRequest 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, preTagRequest 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 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 extra reference previously taken on the request by calling WdfObjectDeference. 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 the 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.