How to Send an IO Request


How to Send an I/O Request

A driver can send an I/O request either synchronously or asynchronously and can specify a time-out value for the request. If the time-out period expires, the framework cancels the request. Drivers send requests using the following methods:

  • A UMDF driver calls the IWDFIoRequest::Send method on the request object.

  • A KMDF driver calls WdfRequestSend and passes a handle to the request object.

    To send a request synchronously, a KMDF driver can instead call one of the WdfIoTargetSendXxxSynchronously methods.

The driver specifies the I/O target to receive the request. If the I/O target is not the default, the driver must already have created and opened it. The framework takes out a reference on the request object to prevent associated resources from being freed while the request is pending for the target device object.

 KMDF  KMDF also supports methods that format a request and send it synchronously in a single operation.

 KMDF methods to format and send synchronous I/O requests  A KMDF driver can send a synchronous I/O request by using one of the WdfIoTargetSendXxxSynchronously methods, which format and send a request in a single call. The following KMDF methods format and send synchronous I/O requests:

  • WdfIoTargetSendInternalIoctlSynchronously

    Formats an internal device I/O control request for any I/O target, sends it to the target, and returns when the target completes the request.

  • WdfIoTargetSendInternalIoctlOthersSynchronously

    Formats a nonstandard internal device I/O control request for any I/O target, sends it to the target, and returns when the target completes the request.

  • WdfIoTargetSendIoctlSynchronously

    Formats a device I/O control request for any I/O target, sends it to the target, and returns when the target completes the request.

  • WdfIoTargetSendReadSynchronously

    Formats a read request for any I/O target, sends it to the target, and returns when the target completes the request.

  • WdfIoTargetSendWriteSynchronously

    Formats a write request for any I/O target, sends it to the target, and returns when the target completes the request.

These methods take the same parameters as the corresponding WdfIoTargetFormatRequestXxx methods as well as two additional parameters:

  • A flags parameter, as described in the following section.

  • An output parameter in which the framework returns the number of transferred bytes.

Options for Sending Requests

When a driver sends a request, it specifies the I/O target to receive the request. The driver can also specify a time-out value and flags that control how the framework sends the request.

 Flags for sending I/O requests  The following list summarizes the possible flags for sending I/O requests:

  • WDF_REQUEST_SEND_OPTION_TIMEOUT

    Cancels the request when the time-out expires.

  • WDF_REQUEST_SEND_OPTION_SYNCHRONOUS

    Sends the request synchronously.

  • WDF_REQUEST_SEND_OPTION_IGNORE_TARGET_STATE

    Sends the request whether or not the state of the I/O target allows delivery.

  • WDF_REQUEST_SEND_OPTION_SEND_AND_FORGET

    Sends the request asynchronously with no I/O completion callback.

The following sections provide more information about each of these flag values.

Time-Out Values for I/O Requests

When a driver sends a request, it can specify a time-out value that indicates how long the framework should wait before canceling the request. The driver expresses the time-out as a negative number in 100-nanosecond intervals; therefore, to specify a 10-second time-out, the driver sets the time-out value to -100,000,000. If the driver supplies a time-out value, it must also set the WDF_REQUEST_SET_OPTION_TIMEOUT flag.

  KMDF  KMDF drivers can use the framework's time conversion functions, which produce much more readable code.

To time out the I/O request

Set the time-out value to

N seconds from the system time at which the framework receives the request

A negative number equal to -N times 10,000,000

-or-

WDF_REL_TIMEOUT_IN_SEC(N). (KMDF only)

Never

Zero

-and-

Do not set the WDF_REQUEST_SET_OPTION_TIMEOUT flag.

Immediately

Zero

-and-

Set the WDF_REQUEST_SET_OPTION_TIMEOUT flag.

Chapter 12, "Support Objects," describes how KMDF drivers can use the framework's time conversion functions.

Synchronous and Asynchronous I/O Requests

By default, WDF sends I/O requests asynchronously. Control returns to the driver as soon as the framework queues the request for the I/O target. To be notified when the request is complete, the driver must register an I/O completion callback function. Whenever possible, a driver should send I/O requests asynchronously to avoid blocking the thread in which the driver is running.

If the driver sends the request asynchronously, the send method returns immediately. To determine whether the framework was able to queue the request for the I/O target, the driver must check the following value:

  • In a UMDF driver, the returned status of the Send method is an HRESULT that either has the value S_OK or provides an error status that indicates why the send failed.

  • In a KMDF driver, the return value of the WdfRequestSend method is a Boolean.

    If this value is FALSE, the driver must call WdfRequestGetStatus to determine the reason for the failure.

In some situations, however, a driver must send a request synchronously. For example, if the device hardware requires intervention before the driver can handle an incoming I/O request, the driver might send a device I/O control request to change the state of the hardware. To send a request synchronously, a driver sets the WDF_REQUEST_SEND_ OPTION_SYNCHRONOUS flag.

When a driver sends a synchronous request, the driver thread blocks until the I/O target has completed the request. To avoid unacceptable processing delays, drivers should set time-outs for synchronous I/O requests, if applicable.

Effect of I/O Target State

By default, WDF sends I/O requests to an I/O target only when the I/O target is in the Started state. The driver can override the default by specifying the WDF_REQUEST_SEND_OPTION_IGNORE_TARGET_STATE flag. In this case, the framework queues the request for the I/O target even if the target is in the Stopped state.

Table 9-6 summarizes what happens when a driver sends an I/O request to a target in each state.

Table 9-6: Effect of I/O Target State on I/O Request
Open table as spreadsheet

If the target state is

The framework

Started

Sends the I/O request.

Stopped

Queues the I/O request by default. Sends the request without queuing if the driver set the WDF_REQUEST_SEND_OPTION_ IGNORE_TARGET_STATE flag.

Stopped for query-remove

Fails the send operation.

Closed

Fails the send operation.

Deleted

Fails the send operation. If the I/O target object has already been deleted and the I/O target handle is invalid, a UMDF driver can issue a driver stop and a KMDF driver can issue a bug check.

If the target device has been stopped but not removed, the framework queues the request to send later after the target device resumes. If the WDF driver specifies a time-out value, the timer starts when the request is added to the queue.

If the target device has been removed, the attempt to send the request fails. The driver can determine the reason for the failure by calling the framework to get the completion status for the request. The value STATUS_INVALID_DEVICE_STATE or HRESULT_FROM_NT (STATUS_INVALID_DEVICE_STATE) indicates that the I/O target was not in the Started state.

Send and Forget Option

A driver typically sets an I/O completion callback when it sends an I/O request asynchronously. In some cases, however, a driver does not use the results of an I/O request or does not require the completion status of the request. For example, a driver that filters input might have no reason to further process a request after it sends the request down the stack.

Such a driver should set the WDF_REQUEST_SEND_OPTION_SEND_AND_FORGET flag when it sends the request and should not set an I/O completion callback. Drivers should set this flag only for I/O requests that they received from the framework, and not for driver-created I/O requests. The flag is the equivalent of the IoSkipCurrentIrpStackLocation function that WDM drivers call to pass an unchanged I/O request down the stack.

If the driver sets the send-and-forget flag, the framework does not maintain information about the request object in its list of sent or queued requests. In addition, changes in the state of the I/O target object have no effect on the request. Therefore, if the I/O target is stopped, closed, or deleted, the I/O target object does not cancel the request, so the request can remain pending for the target driver.

 UMDF  For UMDF drivers, the reflector can always find and cancel the request if the driver host process terminates unexpectedly.

UMDF Example: Send a Request to the Default I/O Target

The USB Filter sample demonstrates how a UMDF driver can filter an I/O request and then pass the request down to the default I/O target. The sample inverts the bits in read and write requests.

To get a pointer to the interface for the default I/O target object, the driver calls the GetDefaultIoTarget method on the framework's device object as follows:

 FxDevice->GetDefaultIoTarget(&m_FxIoTarget); 

The framework creates the default I/O target when it creates the device object, so the driver can call GetDefaultIoTarget any time that it has a valid pointer to a framework device object interface. The USB Filter driver calls this method after it successfully creates the default I/O queue.

Listing 9-9 shows how the USB Filter driver sends a request to the default I/O target. The sample code shown here is excerpted from the Queue.cpp file.

Listing 9-9: Sending a request to the default I/O target in a UMDF driver

image from book
 void CMyQueue::ForwardRequest(     __in IWDFIoRequest* FxRequest ) {     //Set a completion callback     IRequestCallbackRequestCompletion *completionCallback =         QueryIRequestCallbackRequestCompletion();     FxRequest->SetCompletionCallback( completionCallback, NULL );     completionCallback->Release();     //Set up the request for forwarding.     FxRequest->FormatUsingCurrentType( );     //Send the request down to the default I/O target.     HRESULT hrSend = S_OK;     hrSend = FxRequest->Send( m_FxIoTarget,   //Default I/O target                               0,              //No flag                               0               //No timeout                               );     //If send failed, complete the request with failure status.     if (FAILED(hrSend)) {         FxRequest->CompleteWithInformation(hrSend, 0);     }     return; } 
image from book

The ForwardRequest function shown in the listing performs the following three tasks:

  • Sets a completion callback.

  • Formats the request for the I/O target.

  • Sends the request to the I/O target.

To set the request completion callback, the driver queries for a pointer to its IRequestCallbackRequestCompletion interface and passes this pointer to the IWDFIoRequest::SetCompletionCallback method. It then releases the reference on the interface.

Next, the driver calls IWDFIoRequest::FormatUsingCurrentType to prepare the request to be sent to the I/O target. This method sets up the next I/O stack location in the underlying user-mode IRP for the target driver. If the driver does not set the WDF_REQUEST_SEND_OPTION_SEND_AND_FORGET flag, it must call one of the format methods to set up the I/O stack location.

Finally, the driver sends the request by calling IWDFIoRequest::Send. The Send method takes three parameters: an interface pointer to the default I/O target, a set of flags, and a time-out value. The driver passes zero for both the flags and the time-out. The zero flag value means the following:

  • The driver does not specify a time-out for the request.

  • The framework sends the request asynchronously.

  • The framework sends the request only if the target is in a valid state-that is, not stopped or deleted; otherwise, Send returns an error status.

  • The driver has set an I/O completion routine for the request.

After lower drivers complete the request, the framework calls the OnCompletion method of the driver's IRequestCallbackRequestCompletion interface. Listing 9-10 shows this function.

Listing 9-10: Completing a sent request in a UMDF driver

image from book
 Void CMyQueue::OnCompletion(     /* [in] */ IWDFIoRequest*                 FxRequest,     /* [in] */ WDFIoTarget*                   FxIoTarget,     /* [in] */ IWDFRequestCompletionParams*   CompletionParams,     /* [in] */ PVOID                          Context) {     // For a read request, invert the bits read.     if (WdfRequestRead == FxRequest->GetType()){     . . . //Code omitted for brevity}     else  {         // Complete the request object with the same parameters         // with which the lower drivers completed it.         FxRequest->CompleteWithInformation(                 CompletionParams->GetCompletionStatus(),                 CompletionParams->GetInformation());     } } 
image from book

For a read request, the I/O completion callback processes the returned data to invert the bits and then completes it. For any other type of request, the callback simply calls IWDFIoRequest::CompleteWithInformation. It retrieves the request completion status and number of transferred bytes by calling methods on the IWDFRequestCompletionParams interface and then passes these values to CompleteWithInformation.

KMDF Example: Send and Forget

The KMDF Kbfiltr sample filters keyboard input and then passes the internal device I/O control requests that it receives down to the default I/O target. The sample code shown here is excerpted from the Kbfiltr.c file.

To get a pointer to the default I/O target object, the driver calls the WdfDeviceGetIoTarget method as follows:

 KbFilter_ForwardRequest(Request, WdfDeviceGetIoTarget(hDevice)); 

The framework creates and initializes the default I/O target when it creates the device object, so the driver can call WdfDeviceGetIoTarget any time that it has a valid pointer to a framework device object. The Kbfiltr driver calls this method from its EvtIoInternalDeviceControl callback after it parses the incoming request.

Listing 9-11 shows how the driver sends the request to the default I/O target.

Listing 9-11: Using the send-and-forget option in a KMDF driver

image from book
 VOID KbFilter_ForwardRequest(     IN WDFREQUEST Request,     IN WDFIOTARGET Target) {     WDF_REQUEST_SEND_OPTIONS options;     BOOLEAN ret;     NTSTATUS status;     WDF_REQUEST_SEND_OPTIONS_INIT(&options,                                   WDF_REQUEST_SEND_OPTION_SEND_AND_FORGET);     ret = WdfRequestSend(Request, Target, &options);     if (ret == FALSE) {         status = WdfRequestGetStatus (Request);         WdfRequestComplete(Request, status);     }     return; } 
image from book

In this function, the driver initializes a WDF_REQUEST_SEND_OPTIONS structure and then sends the request. By setting the WDF_REQUEST_SEND_OPTION_SEND_AND_FORGET option, the driver directs the framework to send the request asynchronously without notification if the request is completed or canceled. Drivers must not complete requests that they send with this option and they cannot set I/O completion callbacks for these requests.

After it initializes the options, the driver calls WdfRequestSend to send the request to the default I/O target. The driver passes a handle to the request, a handle to the I/O target, and a pointer to the WDF_REQUEST_SEND_OPTIONS structure.

If the framework fails to send the request, WdfRequestSend returns FALSE and the driver calls WdfRequestGetStatus to get the reason for the failure and WdfRequestComplete to complete the request with the failure status.

If the framework successfully sends the request, WdfRequestSend returns TRUE and the driver's processing of the I/O request is complete.

 Note  In this example, the driver does not call a format method before it sends the request because it sets the WDF_REQUEST_SEND_OPTION_SEND_AND_FORGET flag.

KMDF Example: Format and Send an I/O Request to an I/O Target

The following example shows how a KMDF driver can format and send an I/O request to an I/O target. The code in Listing 9-12 is from the Toastmon.c file. The Toastmon sample creates and opens a remote I/O target and preallocates I/O request objects for read and write requests. It sets the I/O target object as the parent of both request objects.

Listing 9-12: Formatting and sending a read request in a KMDF driver

image from book
 NTSTATUS ToastMon_PostReadRequests(     IN WDFIOTARGET IoTarget     ) {     WDFREQUEST                  request;     NTSTATUS                    status;     PTARGET_DEVICE_INFO         targetInfo;     WDFMEMORY                   memory;     WDF_OBJECT_ATTRIBUTES       attributes;     targetInfo = GetTargetDeviceInfo(IoTarget);     request = targetInfo->ReadRequest;     WDF_OBJECT_ATTRIBUTES_INIT(&attributes);     status = WdfMemoryCreate( &attributes,                               NonPagedPool,                               DRIVER_TAG,                               READ_BUF_SIZE,                               &memory,                               NULL); // buffer pointer     if (!NT_SUCCESS(status)) return status;     status = WdfIoTargetFormatRequestForRead( IoTarget,                                               request,                                               memory,                                               NULL, // Buffer offset                                               NULL); // OutputBufferOffset     if (!NT_SUCCESS(status)) return status;     WdfRequestSetCompletionRoutine(request,                                    Toastmon_ReadRequestCompletionRoutine,                                    targetInfo);     // Clear the ReadRequest field in the context.     targetInfo->ReadRequest = NULL;     if(WdfRequestSend(request, IoTarget, WDF_NO_SEND_OPTIONS) == FALSE) {        status = WdfRequestGetStatus(request);        targetInfo->ReadRequest = request;     }     return status; } 
image from book

The ToastMon_PostReadRequests function in Listing 9-12 formats and sends read requests to a remote I/O target. The function receives a handle to the I/O target as a parameter. The driver earlier stored the handle to the I/O request object in the I/O target object's context area, so the first action that the function takes is to get a pointer to the context area by calling the accessor function (that is, GetTargetDeviceInfo) and then retrieving the I/O request object from the context area.

Next, the driver initializes an object attributes structure. It passes this structure to WdfMemoryCreate to create a new WDF memory object and allocate a new buffer from nonpaged pool for the request.

If the driver creates the memory object and buffer successfully, it formats the read request. It passes the I/O target handle, the I/O request object handle, and the memory object handle to WdfIoTargetFormatRequestForRead. The driver passes NULL for the last two parameters, which provide offsets into the output buffer and device.

After the driver formats the I/O request, it sets an I/O completion callback so that it can retrieve the results of the request. When the I/O target completes the request, the framework calls the I/O completion callback. The driver then deletes the request handle from the context area and calls WdfRequestSend to send the request to the I/O target.

In the call to WdfRequestSend, the driver passes the handle to the request object, the handle to the I/O target object, and WDF_NO_SEND_OPTIONS, which indicates that the driver accepts all the WDF defaults for sending the I/O request. That is, the framework sends the request asynchronously, without a time-out, and fails the request if the I/O target is stopped. WdfRequestSend returns a Boolean to indicate whether the framework successfully queued the request for the target. If the send failed or if the framework did not queue the request, the driver calls WdfRequestGetStatus to retrieve the failure status and sets the formatted request object in the context area. The ToastMon_PostReadRequests function then returns.

Although the sample driver uses a remote I/O target, a driver sends a request to the default I/O target in the same way. The only difference is that the driver calls the framework to get a handle to the default I/O target instead of creating and opening a remote I/O target.

How to Split an I/O Request into Smaller Requests

If your driver receives an I/O request to read or write more data than the hardware can manage in a single operation, the driver can split the request into multiple, smaller subrequests.

For example, assume that the I/O target can handle at most MAX_BYTES data in one operation. If the driver receives an I/O request to read more than MAX_BYTES, it can take the following steps to split the request:

  1. Retrieve the memory object from the incoming I/O request object.

  2. Create a new I/O request object or reuse an I/O request object that the driver previously created. (Only KMDF drivers can reuse I/O request objects.)

  3. Determine the number of bytes and the offset into the buffer for this subrequest.

    The first subrequest typically specifies a buffer length of MAX_BYTES and an offset of zero. The second subrequest specifies an offset of MAX_BYTES and a buffer length that is MAX_BYTES or the number of bytes remaining to be read or written, whichever is smaller. Any additional subrequests follow the same pattern.

  4. Format the subrequest object with the existing memory object from the incoming request and the calculated buffer length and offset.

  5. Send the subrequest.

  6. Create, format, and send additional subrequests as required to complete the I/O.

  7. Complete the original incoming request after all the smaller subrequests have completed.

    The memory object-and thus the buffer supplied in it-already contains any required output data.

To retrieve the memory object from the incoming I/O request:

  • A UMDF driver calls IWDFRequest::GetInputMemory or GetOutputMemory.

  • A KMDF driver calls WdfRequestRetrieveInputMemory or WdfRequestRetrieveInputMemory.

If the driver uses the memory object from the incoming I/O request in each of the subrequests, the buffer already contains any output data from the I/O target. The driver is not required to copy data into the buffer before completing the original, incoming I/O request. It must, however, fill in the I/O completion status and number of transferred bytes when it calls the framework to complete the request.

However, if the driver has not yet completed the original request, the framework holds a reference on the memory object in the I/O target on behalf of the driver. The driver must release this reference before it completes the original request, as follows:

  • A UMDF driver must implement the IRequestCallbackRequestCompletion interface on the subrequest and, in the OnCompletion method, must call Release on the memory object before the driver completes the original request.

  • A KMDF driver must register a CompletionRoutine callback for the subrequest, and this callback must call WdfRequestReuse on the subrequest before the driver completes the original request.

    If the driver does not call WdfRequestReuse before completing the original request, the framework generates a bug check because of the outstanding references on the memory object in the original request.

KMDF Example: Reuse an I/O Request Object

If your KMDF driver sends many I/O requests, you can design it to preallocate one or more I/O request objects and reuse those objects, instead of creating a new I/O request object for every request. Reusing I/O request objects can improve performance, particularly if the driver sends many similar asynchronous requests. Because the memory is already allocated, the driver does not spend time in memory allocation for each request. By sending the requests asynchronously, the driver does not block the thread while waiting for a response.

A KMDF driver can reuse an I/O request object only if the object was created by calling WdfRequestCreate or WdfRequestCreateFromIrp. A driver cannot reuse an I/O request object that it received from the framework.

Before a driver reuses an I/O request object, the driver should retrieve the results of the previous I/O request and completely finish processing that request. Then, to reuse the I/O request object, the driver calls WdfRequestReuse after the request has completed back to the driver. WdfRequestReuse reinitializes the request object so that none of the information it previously contained is available to the driver. Therefore, the driver should call this method only if the I/O request object does not contain any information that the driver requires. The driver must reformat the request before sending it again and must reregister the I/O completion callback for the request.

Important 

Your driver can reinitialize the request object in its I/O completion callback but in some cases should not send the next request from the same callback. Doing so could lead to recursion and a subsequent system crash if the system runs out of stack space.

If lower drivers complete the request synchronously, the framework can call the driver's I/O completion callback again before the callback has finished processing the first request.

Listing 9-13 shows how the Ndisedge driver reuses an I/O request object to send an asynchronous IOCTL request. This example is from Ndisedge\60\Request.c.

Listing 9-13: Reusing an I/O request object in a KMDF driver

image from book
 NTSTATUS NICSendOidRequestToTargetAsync(     IN WDFIOTARGET        IoTarget,     IN WDFREQUEST         Request,     IN PFILE_OBJECT       FileObject,     IN ULONG              IoctlControlCode,     IN OUT PVOID          InputBuffer,     IN ULONG              InputBufferLength,     IN OUT PVOID          OutputBuffer,     IN ULONG              OutputBufferLength,     OUT PULONG            BytesReadOrWritten     ) {     NTSTATUS                status;     PREQUEST_CONTEXT        reqContext;     WDF_REQUEST_REUSE_PARAMS    params;     WDFMEMORY               inputMem, outputMem;     UNREFERENCED_PARAMETER(FileObject);     WDF_REQUEST_REUSE_PARAMS_INIT(&params,                                   WDF_REQUEST_REUSE_NO_FLAGS,                                   STATUS_SUCCESS                                   );     status = WdfRequestReuse(Request, &params);     if (!NT_SUCCESS(status))  return status;     // Assign the new buffers to the preallocated memory objects     reqContext = GetRequestContext(Request);     inputMem = outputMem = NULL;     if (InputBuffer != NULL) {         status = WdfMemoryAssignBuffer(reqContext->InputMemory,                                        InputBuffer,                                        InputBufferLength                                        );         if (!NT_SUCCESS(status)) {             return status;         }         inputMem = reqContext->InputMemory;     }     if (OutputBuffer != NULL) {         status = WdfMemoryAssignBuffer(reqContext->OutputMemory,                                        OutputBuffer,                                        OutputBufferLength);         if (!NT_SUCCESS(status)) return status;         outputMem = reqContext->OutputMemory;     }     status = WdfIoTargetFormatRequestForIoctl( IoTarget,                                                Request,                                                IoctlControlCode,                                                inputMem,                                                NULL, // InputBufferoffsets                                                outputMem,                                                NULL  // OutputBufferOffset                                               );     if (!NT_SUCCESS(status)) return status;     WdfRequestSetCompletionRoutine(Request,               NICSendOidRequestToTargetAsyncCompletionRoutine,               BytesReadOrWritten);     if (WdfRequestSend (Request, IoTarget, WDF_NO_SEND_OPTIONS) == FALSE) {         status = WdfRequestGetStatus(Request);     }     return status; } 
image from book

A driver must not try to reuse an I/O request object until the previous request has completed. For this reason, many drivers call the WdfRequestReuse method to reinitialize the I/O request object from their I/O completion callbacks. The driver shown in Listing 9-13 operates differently, however; it calls WdfRequestReuse immediately before sending the next I/O request. The reason is that this driver sends I/O requests from a work item that waits for an event that the I/O completion callback sets. Therefore, even though the driver sends I/O requests asynchronously, its design ensures that the previous I/O request is complete when NICSendOidRequestToTargetAsync runs. To avoid including special-case code for the first request, this driver calls WdfRequestReuse on the newly created request, too. The request does not contain any useful information at that time, so calling the reuse method does no harm.

Before the driver calls WdfRequestReuse, it must initialize a WDF_REQUEST_ REUSE_PARAMS structure to pass as a parameter. This structure contains a set of flags and an initial NTSTATUS value to set in the request. Currently, the only possible flag applies to I/O request objects that a driver created by calling WdfRequestCreateFromIrp. The Ndisedge driver did not create the request from an IRP so it specifies no flags and STATUS_SUCCESS.

Next, the sample driver calls WdfRequestReuse, passing a handle to the I/O request object and a pointer to the reuse parameters structure.

If WdfRequestReuse succeeds, the driver sets up the buffers for the I/O request. The sample driver has previously called WdfMemoryCreatePreallocated to create WDF memory objects to describe the input and output buffers and has stored handles to those objects in the request object's context area. The context area is accessible through the pointer returned by the driver's accessor function named GetRequestContext.

If the caller of NICSendOidRequestToTargetAsync supplied a valid pointer in InputBuffer, the driver calls WdfMemoryAssignBuffer to associate this buffer with the InputMemory object that was stored in the request's context area. The driver repeats this step for the OutputBuffer.

After the driver sets up the input and output buffers, it calls WdfIoTargetFormatRequestForIoctl to format the I/O request. The driver supplies handles to the I/O target object and the I/O request object, along with the I/O control code that the caller passed and handles to the input and output memory objects. If this function returns successfully, the request is almost ready to send.

The one remaining task before the driver sends the I/O request is to set an I/O completion callback. The driver calls WdfRequestSetCompletionRoutine and then calls WdfRequestSend to send the request. It specifies WDF_NO_SEND_OPTIONS to accept all the defaults for sending a request. If the I/O target is not in the Started state, WdfRequestSend returns FALSE and the driver calls WdfRequestGetStatus to determine the failure status. Otherwise, the NICSendOidRequestToTargetAsyn function returns. When the I/O target completes the request, the framework calls the driver's I/O request completion callback, where the driver retrieves the results of the request.

How to Cancel a Sent Request

If the target device is being stopped or if the user removes the device unexpectedly, your driver might be required to cancel I/O requests that it has already sent to an I/O target.

  • A UMDF driver calls IWDFIoRequest::CancelSentRequest to cancel a single request.

    To cancel all the requests that have been sent for a particular file, a UMDF driver calls IWDFIoTarget::CancelSentRequestsForFile.

  • A KMDF driver calls WdfRequestCancelSentRequest.

If the request is queued for delivery to the I/O target but has not yet been delivered, the framework cancels the request. If the request has been delivered to the next driver, the request is canceled only if that driver supports cancellation of requests.

Any time an I/O request is being canceled, a race condition can occur. The request might be completed-and therefore the WDF request object deleted-between the time your driver calls the cancellation method and the time that method begins to run. As long as there is an outstanding reference on the WDF request object, the WDF request object's handle is valid and so in most cases the driver is not required to implement synchronization to call the cancellation method. The framework ensures that the underlying IRP is still valid before canceling it.

image from book
How UMDF Cancels Requests

In Windows Vista and later versions, UMDF uses the Windows CancelIoEx function to cancel an individual I/O request when the driver calls CancelSentRequest.

Earlier versions of Windows, however, do not support CancelIoEx. On these versions, UMDF implements request cancellation through the reflector for requests that the UMDF device stack forwards to the Down device object through the default I/O target. If a UMDF driver creates a new I/O request, UMDF cannot cancel it individually on these versions of Windows, although the driver can cancel all of the requests that it has sent.

image from book

UMDF Example: How to Cancel All I/O Requests for a File

A UMDF driver typically cancels all pending I/O for a file in its IFileCallbackCleanup::OnCleanupFile callback. The framework calls this method when an application calls the Windows CloseHandle function.

Listing 9-14 shows how the Usb\Echo_driver cancels all of the requests that it has sent for a particular file target. This driver implements the IFileCallbackCleanup interface on the device object. The device object's OnCleanupFile method simply calls the OnCleanupFile method that is shown in Listing 9-14, which is implemented on the default queue callback object. Consequently, the sample code appears in the Queue.cpp file.

Listing 9-14: Canceling all requests for a file in a UMDF driver

image from book
 Void CMyQueue::OnCleanupFile(         /*[in]*/ IWDFFile*      pFileObject         ) {     m_Parent->GetInputPipe()->CancelSentRequestsForFile(pFileObject);     m_Parent->GetOutputPipe()->CancelSentRequestsForFile(pFileObject);     return; } 
image from book

The code in Listing 9-14 is quite straightforward. The driver provides GetInputPipe and GetOutputPipe helper functions, which return interface pointers to the I/O targets for its input and output pipes. OnCleanupFile simply calls the framework's IWDFIoTarget:: CancelSentRequestsForFile method through these pointers, passing a pointer to the IWDFFile interface for the file object.

KMDF Example: How to Cancel I/O Requests

The Osrusbfx2 driver receives requests from the framework through a queue and sends them to a USB I/O target pipe. When the OSR USB Fx2 device leaves the working state or is removed, the framework calls the driver's I/O queue stop callback once for each inflight request that the driver has received from the queue. Depending on the reason for the call, the driver either acknowledges the callback for the request or cancels the request.

Listing 9-15 shows how the driver acknowledges or cancels a request in the Bulkrwr.c file.

Listing 9-15: Canceling a request in a KMDF driver

image from book
 VOID OsrFxEvtIoStop(     IN WDFQUEUE         Queue,     IN WDFREQUEST       Request,     IN ULONG            ActionFlags     ) {     . . . //Code omitted     if (ActionFlags == WdfRequestStopActionSuspend ) {         WdfRequestStopAcknowledge(Request, FALSE); // Don't requeue     }     else if(ActionFlags == WdfRequestStopActionPurge) {         WdfRequestCancelSentRequest(Request);     }     return; } 
image from book

In the listing, the driver checks the value of the ActionFlags parameter, which indicates why the framework called the EvtIoStop callback. If the device is merely leaving the working state, the framework passes WdfRequestStopActionSuspend. The driver acknowledges the callback and leaves the request inflight at the I/O target. However, if the framework passes WdfRequestStopActionPurge, the device is being removed, so the driver calls WdfRequestCancelSentRequest to cancel the request.




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

Similar book on Amazon
Windows Internals, Part 1: Covering Windows Server 2008 R2 and Windows 7
Windows Internals, Part 1: Covering Windows Server 2008 R2 and Windows 7
Windowsu00ae Internals: Including Windows Server 2008 and Windows Vista, Fifth Edition (Pro Developer)
Windowsu00ae Internals: Including Windows Server 2008 and Windows Vista, Fifth Edition (Pro Developer)
Advanced Windows Debugging
Advanced Windows Debugging
The Windows 2000 Device Driver Book: A Guide for Programmers (2nd Edition)
The Windows 2000 Device Driver Book: A Guide for Programmers (2nd Edition)

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net