IO Request Objects


I/O Request Objects

When a request arrives that a WDF driver is prepared to handle, WDF creates an I/O request object to represent the underlying IRP, queues the object, and eventually dispatches the request to the driver. The driver receives the request as a parameter to an I/O event callback or by calling a method on a queue.

  • In UMDF, the I/O request object exposes the IWDFIoRequest interface.

  • In KMDF, the WDFREQUEST object represents an I/O request and the driver calls WdfRequestXxx methods to perform actions on it.

The driver can call WDF methods to retrieve information about the request such as the request type, parameters, data buffers, and associated file object.

As with all other WDF objects, the WDF request object has a reference count and can have its own object context area. After a driver completes the WDF request, the framework drops its reference on the WDF request object and on any child objects, and calls their cleanup callbacks, if any. After the request object's cleanup callback returns, the underlying IRP is no longer accessible to the driver.

After the driver has completed the request, it must not attempt to access the request object or any of its child objects other than to release any outstanding references.

Important 

The WDF I/O request object's reference count applies only to the WDF request object, and not to the underlying WDM IRP. After the driver completes the I/O request, the IRP pointer is no longer valid and the driver cannot access the IRP.

A driver can create its own I/O request objects to request I/O from another device or to split an I/O request into multiple, smaller requests before completing it.

Chapter 9, "I/O Targets," provides information about creating I/O request objects in a driver.

I/O Buffers and Memory Objects

Read, write, and device I/O control requests include buffers for the input and output data associated with the request. WDF creates memory objects to encapsulate these buffers and embeds the memory objects in the I/O request objects that it creates. Each memory object contains the length of the buffer that it represents. WDF methods that copy data to and from the buffer validate the length of every transfer to prevent buffer overruns and underruns, which can result in corrupt data or security breaches.

Each memory object also controls access to the buffer. A driver can write only to a buffer that is used to receive data from the device, as in a read request. The memory object does not allow a driver to write to a buffer that only supplies data, as in a write request.

Memory objects have reference counts and persist until all references to them have been removed. The buffer that underlies the memory object, however, might not be "owned" by the object itself. For example, if the issuer of the I/O request allocated the buffer, the memory object does not "own" the buffer. In this case, the buffer pointer becomes invalid when the associated I/O request has been completed, even if the memory object still exists.

Chapter 9, "I/O Targets," provides more information on the lifetimes of memory objects and the underlying buffers.

WDF provides several ways for drivers to retrieve buffers from I/O requests:

  • UMDF drivers retrieve buffers by calling methods on the request object's IWDFIoRequest interface and on the memory object's IWDFMemory interface.

  • KMDF drivers retrieve buffers by calling the WdfRequestXxx and WdfMemoryXxx methods.

Chapter 12, "WDF Support Objects," includes general information about memory objects, the IWDFMemory interface, and the WdfMemoryXxx methods.

image from book
Are Memory Objects Necessary?

At first glance, memory objects may seem unnecessary. Why not just provide the pointer to the buffer directly instead of requiring yet another object?

In WDM drivers, a request can either have a buffer (for buffered I/O) or an MDL (for direct I/O), and your driver needs to be sure the right type is provided for each call. Changing your WDM driver from buffered I/O to direct I/O can require a major over-haul. Even worse, you can't easily find all the places that need to be changed-until you hit them at runtime.

One advantage of WDF memory objects is that we can abstract both types of data buffer (buffered and direct) so that the driver doesn't use special code for one type or the other. If you have a buffer for direct I/O and you want a pointer to the data, it's the same call sequence as if you had a buffered I/O request. WDF manages the difference for you. As a result, changing your WDF driver from buffered to direct I/O is as simple as changing the I/O type flags at initialization.

WDF memory objects have other advantages, too, as you'll surely discover as you write and debug your driver.
-Peter Wieland, Windows Driver Foundation Team, Microsoft

image from book

Retrieving Buffers in UMDF Drivers

In UMDF, a memory object encapsulates an I/O buffer that is associated with an I/O request. The memory object can be used to copy data from the driver to the buffer and vice versa. Memory objects expose the IWDFMemory interface.

Drivers can retrieve the memory object from an I/O request by using the methods in Table 8-2. These methods return a pointer to the IWDFMemory interface on the object.

Table 8-2: IWDFIoRequest Methods for Memory Objects
Open table as spreadsheet

Method

Description

GetInputMemory

Retrieves the memory object that represents the input buffer for an I/O request.

GetOutputMemory

Retrieves the memory object that represents the output buffer for an I/O request.

After retrieving a pointer to the IWDFMemory interface on a memory object, a UMDF driver can get information about the buffer that the memory object describes, write data to the buffer, and read data from the buffer by using the IWDFMemory methods.

A UMDF driver takes the following two steps to access a buffer:

  1. Gets a pointer to the IWDFMemory interface that is associated with the request by calling IWDFRequest::GetInputMemory, GetOutputMemory, or both, depending on whether this is a write request, a read request, or a device I/O control request.

  2. Gets a pointer to the buffer by calling IWDFMemory::GetDataBuffer. To read and write the buffer, the driver calls IWDFMemory::CopyFromBuffer or CopyToBuffer.

When the driver calls IWDFRequest:CompleteXxx to complete the I/O request, the framework deletes the memory object. The buffer pointer is then invalid.

Important 

Currently, a UMDF driver must read all of the input data from the buffer before writing any output data to the buffer, regardless of the IOCTL type. This behavior is the same as the requirement for handling buffered device I/O control requests in KMDF or WDM drivers.

Listing 8-1 shows how the Fx2_Driver sample retrieves the memory object and the underlying buffer pointer from an I/O request object. This code is excerpted from the queue callback object's IQueueCallbackDeviceIoControl::OnDeviceIoControl method in the ControlQueue.cpp file.

Listing 8-1: Retrieving a memory object and buffer pointer in a UMDF driver

image from book
 IWDFMemory *memory = NULL; PVOID buffer; SIZE_T bigBufferCb; FxRequest->GetOutputMemory(&memory); // Get the buffer address then release the memory object. // The memory object is valid until the request is complete. ULONG bufferCb; buffer = memory->GetDataBuffer(&bigBufferCb); memory->Release(); if (bigBufferCb > ULONG_MAX) {     hr = HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER);     break; } else {     bufferCb = (ULONG) bigBufferCb; } 
image from book

To get a pointer to the memory object's IWDFMemory interface, the driver calls IWDFIoRequest::GetOutputMemory on the I/O request object. With the returned pointer, it calls the memory object's IWDFMemory::GetDataBuffer method. GetDataBuffer returns a PVOID to the data buffer, which the driver then casts to a ULONG for access to the buffer.

image from book
Conceptual Inversion in Naming Input and Output Buffers

In WDF, input and output buffers are so called based on the driver's perspective, not the application's perspective. In a WDF driver, a read request has an output buffer because the driver writes data from the device to the buffer. A write request has an input buffer because the write request supplies data to be written to the device. This naming inverts the standard application concept that read represents an input activity and write represents an output activity.

For both UMDF and KMDF drivers, methods that return the memory objects and buffers for write requests have Input in their names, and methods that return memory objects and buffers for read requests have Output in their names.

image from book

Retrieving Buffers in KMDF Drivers

In KMDF drivers, a WDF memory object represents a buffer. The object can be used to copy data to and from the driver and the buffer represented by the WDF memory handle. In addition, the driver can use the underlying buffer pointer and its length for complex access, such as casting to a known data structure. Depending on the type of I/O that the device and driver support, the buffer can be any of the following:

  • For buffered I/O, a system-allocated buffer from the nonpaged pool.

  • For direct I/O, a system-allocated MDL that points to the physical pages for DMA.

  • For METHOD_NEITHER I/O from a user-mode caller, a user-mode buffer that has been validated, mapped, and locked into the kernel virtual address space by calls to WdfRequestRetrieveUnsafeUserXxxBuffer and WdfRequestProbeAndLockUser-BufferForXxx.

The WdfRequestRetrieveXxx methods return the memory objects and buffers from a request. Table 8-3 summarizes these methods.

Table 8-3: WdfRequestRetrieveXxx Methods
Open table as spreadsheet

Method

Description

WdfRequestRetrieveInputBuffer

Retrieves an I/O request's input buffer.

WdfRequestRetrieveInputMemory

Retrieves a handle to a WDFMEMORY object that represents an I/O request's input buffer.

WdfRequestRetrieveInputWdmMdl

Retrieves an MDL that represents an I/O request's input buffer.

WdfRequestRetrieveOutputBuffer

Retrieves an I/O request's output buffer.

WdfRequestRetrieveOutputMemory

Retrieves a handle to a WDFMEMORY object that represents an I/O request's output buffer.

WdfRequestRetrieveOutputWdmMdl

Retrieves an MDL that represents an I/O request's output buffer.

WdfRequestRetrieveUnsafeUserInputBuffer

Retrieves an I/O request's input buffer for use in METHOD_NEITHER I/O.

WdfRequestRetrieveUnsafeUserOutputBuffer

Retrieves an I/O request's output buffer for use in METHOD_NEITHER I/O.

After the driver has a handle to the WDFMEMORY object, it can use WdfMemoryXxx methods to manipulate the object and read and write its buffers. If the driver performs buffered or direct I/O, it can access the buffers in either of the following ways:

  • Get a handle to the WDFMEMORY object that is associated with the WDFREQUEST by calling WdfRequestRetrieveInputMemory for a write request or WdfRequestRetrieveOutputMemory for a read request, and then get a pointer to the buffer by calling WdfMemoryGetBuffer.

    To read and write the buffer, the driver calls WdfMemoryCopyFromBuffer or WdfMemoryCopyToBuffer.

  • Get a pointer to the buffer by calling WdfRequestRetrieveInputBuffer or WdfRequestRetrieveOutputBuffer, depending on whether this is a write or read request.

Drivers that perform direct I/O can also access the MDL underlying the buffer by calling WdfRequestRetrieveInputWdmMdl and WdfRequestRetrieveOutputWdmMdl. However, the driver must not use the user-mode buffer address specified in the MDL; instead, it must get a kernel-mode address by calling the MmGetSystemAddressForMdlSafe macro. Using the WdfRequestRetrieveInputBuffer and WdfRequestRetrieveOutputBuffer methods is easier because the framework retrieves the kernel-mode address for the driver based on the request's buffering type.

For requests to perform METHOD_NEITHER I/O, KMDF provides methods to probe and lock user-mode buffers. The driver must be running in the context of the process that sent the I/O request to probe and lock a user-mode buffer, so KMDF also defines a callback that drivers can register to be called in the context of the sending component. See "Accessing Buffers for Neither I/O in a KMDF Driver" later in this chapter for more information.

Retrieving the Buffer for Buffered or Direct I/O in a KMDF Driver

If the KMDF driver supports buffered or direct I/O, it can read and write either the buffer passed by the originator of the I/O request or a WDFMEMORY object that represents that buffer.

The framework handles all validation and addressing issues for WDFMEMORY objects and prevents the driver from writing to an input buffer. The handle to the WDFMEMORY object contains the size of the buffer, thus ensuring that buffer overruns do not occur. Thus, less code is typically required to use WDFMEMORY objects. If the driver reads and writes the buffers through the buffer pointers, the framework validates the buffer lengths initially but does not prevent overruns and underruns when the driver copies data to the buffer.

To get a handle to the WDFMEMORY object, the driver calls:

  • WdfRequestRetrieveOutputMemory for a read request.

  • WdfRequestRetrieveInputMemory for a write request.

  • Both methods for device I/O control requests.

Each of these methods returns a handle to a WDFMEMORY object that represents the corresponding buffer and is associated with the WDFREQUEST object.

Alternatively, the driver can call WdfRequestRetrieveOutputBuffer and WdfRequestRetrieveInputBuffer to get pointers to the input and output buffers themselves.

For buffered I/O, the input and output buffers are the same, so both of these methods return the same pointer. Therefore, when performing buffered I/O, a driver must read and capture all input data from the buffer before writing any output data to the buffer.

The Osrusbfx2 driver performs buffered I/O. When this driver receives a read request, it retrieves the WDFMEMORY object from the request by using the following statement:

 status = WdfRequestRetrieveOutputMemory(Request, &reqMemory); 

This call to WdfRequestRetrieveOutputMemory returns a handle to the memory object in reqMemory. The driver then validates the request and sends it to a target USB pipe.

Accessing Buffers for Neither I/O in a KMDF Driver

A KMDF driver that performs METHOD_NEITHER I/O receives a pointer to a user-mode buffer if the request originates with a user-mode caller. The driver must validate the pointer and lock the user-mode buffer into memory before accessing it. The driver should then store the buffer pointer in the request object's context area for later use in processing and completing the requested I/O. Note, however, that if the request originates in kernel mode, no such requirements apply.

To validate the buffer pointer and lock down the buffer, the driver must be running in the context of the requesting user-mode process. To ensure that it is called in the user's context, a KMDF driver that performs METHOD_NEITHER I/O must implement an EvtIoInCallerContext callback and call WdfDeviceInitSetIoInCallerContextCallback register this callback when it initializes the device object. The framework calls EvtIoInCallerContext in the caller's context every time it receives an I/O request for the device object. This function should get the buffer pointer from the request, validate it, and lock the user-mode buffer into memory.

To get the buffers that are supplied in METHOD_NEITHER I/O requests, EvtIoInCallerContext can call either of the following methods:

  • WdfRequestRetrieveUnsafeUserInputBuffer gets the buffer for a write or device I/O control request.

  • WdfRequestRetrieveUnsafeUserOutputBuffer gets the buffer for a read or device I/O control request.

These methods validate the buffer lengths that are supplied in the request and return a pointer to the user-mode buffer. They return a failure status if the buffer length is invalid, if the I/O transfer type is not METHOD_NEITHER, or if the framework finds any other errors. They must be called from within a driver's EvtIoInCallerContext callback and will fail if called from any other callback.

Before accessing the buffer, the KMDF driver must probe and lock the user-mode buffer by calling WdfRequestProbeAndLockUserBufferForRead or WdfRequestProbeAndLockUserBufferForWrite. These functions perform the following actions, in WDM terms:

  • Allocate an MDL for the user-mode buffer.

  • Call MmProbeAndLockPages to validate the user-mode buffer and lock it into memory. When the request is complete, the framework unlocks the buffer automatically.

  • Call MmGetSystemAddressForMdlSafe to map the user-mode address into a kernel-mode address.

  • Create a WDF memory object to represent the buffer.

  • Add the WDF memory object as a child object of the WDF request object.

  • Set the WDF memory object to point to the previously allocated MDL.

After the driver has locked the buffer into memory, it should store the buffer pointer in the context area of the WDFREQUEST object. If the locked buffer contains embedded pointers, the EvtIoInCallerContext callback must validate the embedded pointers in the same way. If the user's buffer contains input data, the driver should capture the data in a safe kernel-mode buffer.

The following code is from the Nonpnp sample, which uses METHOD_NEITHER for a device I/O control request. It shows how to create an object context area for the request, store the buffer pointers, and return the request to the framework for queuing. By creating a new context area for this request, instead of associating a context area with every I/O request that the framework creates, the driver ensures that memory is used only for those requests that require it.

In the header file, the driver declares the context type and names the accessor function as follows:

 typedef struct _REQUEST_CONTEXT {     WDFMEMORY InputMemoryBuffer;     WDFMEMORY OutputMemoryBuffer; } REQUEST_CONTEXT, *PREQUEST_CONTEXT; WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(REQUEST_CONTEXT, GetRequestContext) 

In its EvtIoInCallerContext function, the driver allocates a context area for this request, probes and locks the input and output buffers, stores the buffer pointers in the context area, and then returns the request to the framework for queuing. Listing 8-2 shows the driver's EvtIoInCallerContext function from the Nonpnp.c file.

Listing 8-2: EvtIoInCallerContext callback function

image from book
 VOID NonPnpEvtDeviceIoInCallerContext(     IN WDFDEVICE Device,     IN WDFREQUEST Request     )     NTSTATUS                   status = STATUS_SUCCESS;     PREQUEST_CONTEXT           reqContext = NULL;     WDF_OBJECT_ATTRIBUTES      attributes;     size_t                     inBufLen, outBufLen;     PVOID                      inBuf, outBuf;     . . . //Code omitted     // Allocate a context for this request to store the     // memory objects created for the input and output buffer.     WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, REQUEST_CONTEXT);     status = WdfObjectAllocateContext(Request, &attributes, &reqContext);     if(!NT_SUCCESS(status)) {         goto End;     }     status = WdfRequestProbeAndLockUserBufferForRead(Request,                             inBuf,                             inBufLen,                             &reqContext->InputMemoryBuffer                             );     if(!NT_SUCCESS(status)) {         goto End;     }     status = WdfRequestProbeAndLockUserBufferForWrite(Request,                             outBuf,                             outBufLen,                             &reqContext->OutputMemoryBuffer                             );     status = WdfDeviceEnqueueRequest(Device, Request);     if(!NT_SUCCESS(status)) {         goto End;     }     return; End:     WdfRequestComplete(Request, status);     return; } 
image from book

The WdfDeviceEnqueueRequest method causes the framework's I/O handler to follow its usual pattern to queue the request, as described in Figure 8-5.

image from book
Figure 8-5: Flow of I/O requests through the framework

If the driver has configured parallel or sequential dispatching for the queue, the framework later invokes the I/O event callback for the I/O type that is specified in the request. If the driver has configured manual dispatching, the driver calls one of the WdfIoQueueRetrieveXxx methods when it is ready to receive and handle the request. Before the driver accesses the buffer, it uses the accessor function for the WDFREQUEST object (in the example, GetRequestContext) to get a pointer to the context area from which it can retrieve the buffer pointer.

Because the request has not yet been queued, it can be canceled while EvtIoInCallerContext runs. Usually, the driver merely locks the buffer, stores the pointer, and returns the request to the framework. If the request is canceled during this time, the cancellation does not affect the driver and the framework simply completes the request later with STATUS_CANCELLED. When the request is complete, the framework unmaps the buffers that the driver mapped with WdfRequestProbeAndLockUserBufferXxx. No cleanup is required in the driver.

However, if the driver allocates resources related to the request or performs other actions that require cleanup when the request is canceled, the driver should register EvtObjectCleanup for the WDF request object.

If the request is canceled after the driver calls WdfDeviceEnqueueRequest, the framework invokes the EvtIoCanceledOnQueue callback, if the driver registered one. The driver should register this callback if it must perform cleanup that is related to the progress of the request since the driver queued it. If the driver registers an EvtIoCanceledOnQueue callback, the driver typically also registers an EvtObjectCleanup callback for the WDF request object. EvtIoCanceledOnQueue and EvtObjectCleanup are required only if the driver allocates resources related to the request or performs other actions that require cleanup when the request is canceled.

The EvtIoInCallerContext code in Listing 8-2 calls WdfDeviceEnqueueRequest to return the request to the framework for queuing. If the driver does not return the request to the framework for queuing, it must be prepared to handle cancellation and must provide any other required synchronization. Generally, drivers should use framework queuing and queue management instead of attempting to implement their own.

Request, Memory, and Buffer Pointer Lifetimes

As previously mentioned, the lifetime of a memory object is related to the lifetime of the I/O request object that is its parent. However, the lifetime of the underlying buffer pointer might be different.

In the simplest scenario, the framework dispatches a request to the driver and the driver performs I/O and completes the request. In this case, the underlying buffer might have been created by a user-mode application, by another driver, or by the operating system itself. When the driver completes the I/O request, the framework deletes the memory objects that are associated with the request. The buffer pointer is then invalid.

If the driver created the buffer, the memory object, or the request, or if the driver forwarded the request to an I/O target, lifetimes become more complicated.

Chapter 9, "I/O Targets," provides details about the lifetimes of memory objects and buffer pointers in requests that the driver sends to an I/O target.




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