Windows 7 : Programming Multiple I/O Queues and Programming I/O – Retrieving Requests from a Manual Queue

1. Forwarding Requests to a Queue

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.

To forward a request to a queue, a KMDF driver calls WdfRequestForwardToIoQueue, passing as parameters the handle to the request and the handle to the queue. If the request is added successfully, KMDF returns the status STATUS_SUCCESS. A driver cannot return a request to the queue from which the driver most recently received it.

The PCIDRV sample uses this technique to delay processing IOCTL_NDISPROT_INDICATE_STATUS requests, as the following excerpt from its EvtIoDeviceControl callback (in pcidrv.c) shows:

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 (&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
          // 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.