IO Event Callbacks


I/O Event Callbacks

The framework can perform any one of the following actions when it receives an I/O request:

  • Place the request in a queue.

  • Call one of the driver's callback functions without queuing the request.

  • Handle the request for the driver.

  • Forward the request to the default I/O target.

  • Fail the request.

The action that the framework performs depends on the type of request, the configuration of the driver's queues, and the type of driver. Drivers should implement the I/O event callbacks and configure their queues so that WDF handles each type of request in a manner that is appropriate for the request and the device.

File Objects for I/O

From the point of view of a user-mode application, all I/O takes place through a file handle. From the point of view of a driver, the file is generally unimportant, unless the driver must maintain session information. However, if multiple clients can access the device simultaneously, the file provides a way for the driver to manage client-specific requests and perform client-specific actions, such as completing all of the pending I/O requests from a particular client.

The Windows I/O manager creates a file object when an application opens a file handle. WDF likewise creates a framework file object to represent the I/O manager's file object and defines three file-specific requests: create, cleanup, and close. A driver can opt to handle these requests or can let the framework handle them on the driver's behalf.

UMDF drivers must have a valid file object for every I/O request. KMDF drivers can perform I/O without an associated file.

image from book
File Objects and I/O Requests in UMDF

Although kernel-mode drivers can send and receive I/O requests without using a file object, UMDF requires a valid file object with every I/O request. You might wonder why.

A file object represents a logical session in which the I/O occurs. By requiring a file object for every I/O request, UMDF ensures that any driver in the UMDF device stack can obtain session information for the I/O request and drivers can assume that a corresponding file object is open for each I/O request. In addition, all I/O requests from the user-mode device stack to the kernel-mode device stack must go through the Windows API, which requires a file. This makes file objects much more important for intra-stack I/O than they are in KMDF.
-Praveen Rao, Windows Driver Foundation Team, Microsoft

image from book

Automatic Forwarding of Create, Cleanup, and Close

Automatic forwarding is useful for drivers that process some types of I/O requests, but not other types of requests. For example, a UMDF filter driver might inspect the data that is being written to a file but might not look at create, cleanup, or close requests. Therefore, it would have a callback interface for write requests but would enable automatic forwarding for create, cleanup, and close.

The frameworks do not create WDF request objects to represent cleanup and close notifications. The only way a driver can forward such requests to the default I/O target is to configure automatic forwarding. For a create request, the framework creates a WDF request object only if the driver provides a create callback.

Whether the framework dispatches, forwards, or completes create, cleanup, and close depends on the following:

  • The setting of the automatic-forwarding flag for the device object.

  • Whether this is a filter driver or a function driver.

  • Whether the driver implements an event callback for the request type.

  • For create requests only, whether the driver configures a queue for the requests.

UMDF  A UMDF driver sets automatic forwarding for create, cleanup, and close by calling the AutoForwardCreateCleanupClose method on the IWDFDeviceInitialize interface before it creates the device object.

 KMDF  A KMDF driver sets automatic forwarding for create, cleanup, and close by setting the AutoForwardCleanupClose field in the WDF_FILEOBJECT_CONFIG structure before it creates the device object.

A driver specifies automatic forwarding by using an enumeration constant of the WDF_TRI_STATE type:

  • WdfDefault Indicates that the framework should use its defaults for forwarding. The defaults differ for filter and function drivers, as the following sections describe.

  • WdfTrue Indicates that the framework should forward requests to the default I/O target.

  • WdfFalse Indicates that the framework should not forward the requests.

The following sections on create, cleanup, and close provide more information on automatic forwarding.

image from book
Unbalanced Create and Cleanup/Close Requests

If a driver implements a create callback, it must handle all create requests consistently. That is, the driver must either complete all such requests or send all such requests to the default I/O target. It must not complete some create requests and send others to the default I/O target.

Moreover, the driver must handle the create requests in the same way in which the framework handles the cleanup and close requests. If the framework forwards the cleanup and close requests to the default I/O target, the driver must similarly send the create requests to that driver. If the framework completes cleanup and close requests, the driver must complete the create requests.

The reason is that the AutoForwardCreateCleanupClose flag in UMDF and the AutoForwardCleanupClose flag in KMDF apply to all such requests for the device object. The framework has no way to determine which create requests the driver completed and which it sent to another driver. Lower drivers in the device stack must receive the same number of create requests and cleanup/close requests.

image from book

I/O Event Callbacks for Create Requests

A driver should handle create requests if its device can have more than one file or user at a time, and if the driver must perform different operations for each file or user. By handling create requests, the driver gains access to the file object that the framework creates in response to the request. The file object provides a way for the driver to distinguish the users and provides a user-specific storage area in the file object context area.

To handle create requests:

  •  UMDF  UMDF drivers implement the IQueueCallbackCreate interface on a queue callback object or implement the IQueueCallbackDefaultIoHandler interface on the default queue's callback object to receive create requests.

  •  KMDF  KMDF drivers can register an EvtDeviceFileCreate callback to receive create requests without queuing or can implement an EvtIoDefault callback and configure a queue to receive create requests.

If the driver does not handle create requests, WDF takes a default action, as described in the following sections.

Handling Create Requests in a UMDF Driver

Whether UMDF dispatches, forwards, succeeds, or fails a create request depends on whether the target device object represents a filter driver or a function driver and whether the driver implements the IQueueCallbackCreate interface on one of its queue callback objects. Table 8-9 summarizes the UMDF actions.

Table 8-9: Handling Create Requests in a UMDF Driver
Open table as spreadsheet

If the driver

The user-mode framework

Implements the IQueueCallbackCreate interface on a queue callback object

-or-

Implements the IQueueCallbackDefaultIoHandler interface for the default queue's callback object

Queues create requests.

Sets AutoForwardCreateCleanupClose to FALSE (the default for a function driver)

Opens a file object and completes the request with S_OK.

Sets AutoForwardCreateCleanupClose to TRUE (the default for a filter driver)

Forwards create requests to the default I/O target.

In a UMDF filter driver, the IQueueCallbackCreate interface should perform any required filtering tasks and then either forward the request to the default I/O target or complete the request. A typical filter driver forwards the request so that lower drivers in the stack have an opportunity to complete it. However, a filter driver might complete the request if a serious error occurs.

For function drivers that do not implement an IQueueCallbackCreate interface, UMDF by default opens a file object to represent the device and completes the request with S_OK.

Listing 8-8 shows how a UMDF function driver can implement the OnCreateFile method on a queue callback object. This example is based on the WpdWudfSampleDriver code in the Queue.cpp file.

Listing 8-8: UMDF driver's OnCreateFile method

image from book
 STDMETHODIMP_ (void) CQueue::OnCreateFile(     /*[in]*/ IWDFIoQueue*       pQueue,     /*[in]*/ IWDFIoRequest*     pRequest,     /*[in]*/ IWDFFile*          pFileObject     ) {     HRESULT hr = S_OK;     ClientContext* pClientContext = new ClientContext ();     . . . //Code omitted.     if(pClientContext != NULL) {         hr = pFileObject->AssignContext (this, (void*)pClientContext);         // Release the client context if we cannot set it         if(FAILED(hr)) {            pClientContext->Release();            pClientContext = NULL;         }     }     else {         hr = E_OUTOFMEMORY;     }     pRequest->Complete(hr);     return; } 
image from book

As the listing shows, the OnCreateFile method receives pointers to the framework's queue, I/O request, and file interfaces. In the sample driver, this method allocates an object context area to hold client-specific data. The driver calls the IWDFObject::AssignContext method on the file object to associate the context area with the framework's file object. If the AssignContext method succeeds, the driver completes the request by calling the IWDFIoRequest::Complete method with the S_OK status. If the driver cannot allocate the context area or assign it to the file object, the driver completes the request with an appropriate failure status.

Impersonation in UMDF Drivers

By default, UMDF drivers run in the LocalService security context, which has minimum security privileges on the local computer and presents anonymous credentials on the network. This context provides adequate privileges for most UMDF driver actions. Occasionally, however, a driver receives a request that requires privileges that are not available in the LocalService context. In this situation, a UMDF driver can impersonate the client and temporarily use the client's security context. The framework enables a driver to impersonate the client process to handle I/O requests, but not Plug and Play, power, or other system requests.

At driver installation, the INF file specifies the maximum impersonation level that the driver can use by including the UmdfImpersonationLevel directive in the DDInstall.Wdf section, as follows:

 [OsrUsb_Install.NT.Wdf] . . . UmdfImpersonationLevel=Impersonation 

The UmdfImpersonationLevel directive takes one of the following four levels:

  • Anonymous

  • Identification

  • Impersonation

  • Delegation

By default, the maximum is Identification, which means that the driver can use the identity and privileges of the client process but cannot impersonate its entire security context. These levels are the same as the SECURITY_IMPERSONATION_LEVEL enumeration constants named SecurityXxx.

In a CreateFile call, an application can specify the highest impersonation level that the driver is allowed to use. The impersonation level determines the operations that the driver can perform in the context of the application process. UMDF always uses the minimum level possible. If the level that the application specifies is different from the minimum that the INF provides, the reflector-which sets up the security context information for the driver host process-uses the lower of the two levels.

Properly handling impersonation is key to writing a secure UMDF driver. To determine whether to implement impersonation in your driver, you must know the security requirements of the operations that your driver performs. For example, if you want to leave a good audit trail so that an administrator can later determine the user on whose behalf the driver performed a particular action, you would use the SecurityIdentification level of impersonation. Remember, however, that the SecurityIdentification level could reveal some personal information, so you should use it only if the audit trail is valuable.

As another example, if your driver requires access to a file in the user's Documents folder, you would need to use SecurityImpersonation because the user's file likely has an access control list (ACL) that protects it from access in the LocalService context.

 Tip  See "SECURITY_IMPERSONATION_LEVEL" on MSDN for more information about impersonation levels and user-mode security-online at http://go.microsoft.com/fwlink/?LinkId=82952.

A driver requests impersonation by calling the IWDFIoRequest::Impersonate method with the required impersonation level, a pointer to the driver's IImpersonateCallback interface, and a pointer to context data. The driver can implement the IImpersonateCallback interface on whichever callback object is most convenient, typically the callback object that contains the context data. The Impersonate method calls the appropriate Windows API functions to impersonate the client at the requested impersonation level and then calls the driver's IImpersonateCallback::OnImpersonation callback, passing the context pointer. When OnImpersonation returns, the Impersonate method finishes the impersonation on behalf of the driver by calling the Windows API to revert to its own identity.

Listing 8-9 shows how the Fx2_Driver sample uses impersonation to open a user file that contains data to write to the device's seven-segment display. This code is in the Device.cpp file.

Listing 8-9: Using impersonation

image from book
 HRESULT CMyDevice::PlaybackFile(     __in PFILE_PLAYBACK PlayInfo,     __in IWDFIoRequest *FxRequest     ) {     PLAYBACK_IMPERSONATION_CONTEXT context = {PlayInfo, NULL, S_OK};     . . . //Additional declarations omitted     HRESULT hr;     // Impersonate and open the playback file.     hr = FxRequest->Impersonate( SecurityImpersonation,                                  this->QueryIImpersonateCallback(),                                  &context                                  );     this->Release();     hr = context.Hr;     if (FAILED(hr)) {         goto exit;     }     . . . //Omit code to read file and encode for 7-segment display     return hr; } 
image from book

 Note  This code is from an updated sample that was not in the current WDK release when this book was printed. See "Developing Drivers with WDF: News and Update" on the WHDC Web site for information about updated sample code-online at http://go.microsoft.com/fwlink/?LinkId=80911.

The driver calls the PlaybackFile method in Listing 8-9 in response to the IOCTL_OSRUSBFX2_PLAY_FILE device I/O control request. The input buffer for this request contains the name of the file. After the driver validates the file name, it calls PlaybackFile to open the file, read the data, and write it on the device's display.

PlaybackFile initializes the context data for the OnImpersonate method. The following three fields represent information the driver requires to open the file and return a handle and status:

  • PlaybackInfo is the name of the file to open and is initialized to PlayInfo.

  • FileHandle is the handle to the opened file and is initialized to NULL.

  • Hr is the result of the operation and is initialized to S_OK.

The function then calls IWDFIoRequest::Impersonate, passing SecurityImpersonation as the requested impersonation level, a pointer to the IImpersonateCallback interface, and a pointer to the context data.

UMDF impersonates the client at the SecurityImpersonation level and then calls the driver's OnImpersonation method. OnImpersonation should perform only the tasks that require impersonation and should not call any other framework methods. Typically an OnImpersonation method performs one simple task, such as opening a file that is protected by an ACL. After the file is open, the driver can read and write it without impersonating the client.

Listing 8-10 shows the OnImpersonation method that the previous example calls. This example is also from Device.cpp.

Listing 8-10: OnImpersonation callback

image from book
 VOID CMyDevice::OnImpersonate(     __in PVOID Context     ) {     PPLAYBACK_IMPERSONATION_CONTEXT context;     context = (PPLAYBACK_IMPERSONATION_CONTEXT) Context;     context->FileHandle = CreateFile(context->PlaybackInfo->Path,                                      GENERIC_READ,                                      FILE_SHARE_READ,                                      NULL,                                      OPEN_EXISTING,                                      FILE_ATTRIBUTE_NORMAL,                                      NULL);     if (context->FileHandle == INVALID_HANDLE_VALUE) {         DWORD error = GetLastError();         context->Hr = HRESULT_FROM_WIN32(error);     }     else {         context->Hr = S_OK;     }     return; } 
image from book

As the listing shows, the OnImpersonation method calls the Windows CreateFile function to open the file and does nothing more. It returns the file handle and the status in the context area.

Handling Create Requests in a KMDF Driver

KMDF dispatches, forwards, or completes a create request based on whether the device object represents a filter driver, whether the driver registers the EvtDeviceFileCreate method for the file object, and whether the driver configures a queue to receive create requests. Table 8-10 summarizes the possible KMDF actions in response to a create request.

Table 8-10: Handling Create Requests in a KMDF Driver
Open table as spreadsheet

If the driver

The kernel-mode framework

Configures a queue for WdfRequestTypeCreate and registers EvtIoDefault for the queue

Queues create requests.

Registers EvtDeviceFileCreate by calling WdfDeviceInitSetFileObjectConfig from EvtDriverDeviceAdd

Invokes the EvtDeviceFileCreate callback and does not queue create requests.

Is a function driver and neither configures a queue to receive create requests nor registers the EvtDeviceFileCreate callback

Opens a file object and completes create requests with STATUS_SUCCESS.

Is a filter driver and neither configures a queue to receive create requests nor registers the EvtDeviceFileCreate callback

Forwards create requests to the default I/O target.

The framework does not automatically add create requests to a default queue. Instead, the driver must call WdfDeviceConfigureRequestDispatching to explicitly configure the default queue to accept create requests and must implement the EvtIoDefault callback for the queue.

If a KMDF filter driver does not handle create requests, KMDF by default forwards all create, cleanup, and close requests to the default I/O target. Filter drivers that handle create requests should perform any required filtering tasks and then forward such requests to the default I/O target.

If a filter driver completes create requests instead of forwarding them, it should set AutoForwardCleanupClose to WdfFalse in the file object configuration structure so that KMDF completes cleanup and close requests for the file object instead of forwarding them.

If a KMDF bus or function driver does not either register the create callback or configure a queue for create requests, KMDF handles create requests for the driver by completing the request with STATUS_SUCCESS. However, this default has an undesireable effect. Even if the driver does not register a device interface, a malicious application could attempt to open the device by using the name of its PDO and the framework would comply and complete the request successfully. As a result, drivers that do not support create requests must register an EvtDeviceFileCreate callback that explicitly fails such requests. This behavior is different from WDM, where the default is to fail such requests.

Important 

Any bus or function driver that does not accept create or open requests from user-mode applications-and thus does not register a device interface-must register an EvtDeviceFileCreate callback that explicitly fails such requests. Supplying a callback to fail create requests ensures that a rogue user-mode application cannot access the device.

Listing 8-11 shows how the Featured Toaster sample registers its EvtDeviceFileCreate callback. This code appears in the sample's EvtDriverDeviceAdd callback (that is, ToasterEvtDeviceAdd) in the Toaster.c file.

Listing 8-11: Registering the EvtDeviceFileCreate callback

image from book
 WDF_FILEOBJECT_CONFIG      fileConfig; WDF_FILEOBJECT_CONFIG_INIT(&fileConfig,                             ToasterEvtDeviceFileCreate,                             ToasterEvtFileClose,                             WDF_NO_EVENT_CALLBACK // no Cleanup                             ); WdfDeviceInitSetFileObjectConfig(DeviceInit,                                  &fileConfig,                                  WDF_NO_OBJECT_ATTRIBUTES                                  ); 
image from book

A driver registers its callbacks before it creates the device object. As the listing shows, the Toaster sample registers create and close callbacks by using the WDF_FILEOBJECT_ CONFIG_INIT function. This function takes a pointer to a driver-allocated WDF_ FILEOBJECT_CONFIG structure as its first parameter, followed by pointers to the three possible file object callback methods: EvtDeviceFileCreate, EvtFileClose, and EvtFileCleanup. The driver then calls WdfDeviceInitSetFileObjectConfig to initialize these settings in the WDFDEVICE_INIT structure that was passed to the EvtDriverDeviceAdd callback.

If the EvtDeviceFileCreate callback sends the create request to the default I/O target, the I/O completion callback must not change the completion status of the create request. The reason is that drivers lower in the device stack have no way to find out that the status changed. If the KMDF driver changes the status from failure to success, lower drivers will not be able to handle subsequent I/O requests. Conversely, if the KMDF driver changes the status from success to failure, lower drivers cannot determine that the file handle was not created and will never receive a cleanup or close request for the file object.

I/O Event Callbacks for Cleanup and Close

WDF can call the driver to handle cleanup and close notifications or can automatically forward them to the default I/O target. Neither KMDF nor UMDF queues cleanup or close notifications.

To handle file cleanup and close notifications:

  • A UMDF driver implements the IFileCallbackCleanup and IFileCallbackClose interfaces on the device callback object.

  • A KMDF driver registers the EvtFileCleanup and EvtFileClose event callbacks.

A driver can support one, both, or neither of these callbacks, depending on its particular requirements for cleanup and close operations.

Table 8-11 summarizes the actions that WDF takes when a cleanup or close request arrives.

Table 8-11: Handling Cleanup and Close Requests
Open table as spreadsheet

If a UMDF driver

Or if a KMDF driver

WDF …

Implements IFileCallbackCleanup or IFileCallbackClose

Registers EvtFileCleanup or EvtFileClose during EvtDriverDeviceAdd processing

Invokes a callback for a cleanup or close request.

Does not implement a callback for cleanup or close and is a function driver

-or-

Sets AutoForwardCreateCleanupClose to WdfFalse, does not implement the corresponding callback interface, and is a filter driver

Does not register a callback for cleanup or close and is a function driver

-or-

Sets AutoForwardCleanupClose in the FILE_OBJECT_CONFIG structure to WdfFalse, does not implement the corresponding callback function, and is a filter driver

Completes the request with S_OK. (UMDF only)

-or-

Completes the request with STATUS_SUCCESS. (KMDF only)

Does not implement a callback for cleanup or close and is a filter driver

-or-

Sets AutoForwardCreateCleanupClose to WdfTrue and is a function driver

Does not register a callback for cleanup or close and is a filter driver

-or-

Sets AutoForwardCleanupClose to WdfTrue and is a function driver

Forwards the cleanup or close request to the default I/O target.

The framework calls the driver's file cleanup callback when the last handle to the file object has been closed and released, so the file has no additional clients. The cleanup callback should cancel all outstanding I/O requests for the file.

The framework calls the file close callback after the cleanup callback has returned and after all the outstanding I/O requests for the file have been completed. The device might not be in the working state when the file cleanup callback runs. The file close callback should deallocate any resources that the driver allocated for the file.

 KMDF  For KMDF drivers, the framework calls EvtFileClose synchronously, in an arbitrary thread context.

Listing 8-11 in the previous section shows how a KMDF driver registers the create and close callbacks.

I/O Event Callbacks for Read, Write, and Device I/O Control Requests

For read, write, device I/O control, and internal device I/O control requests, the driver creates one or more queues and configures each queue to receive one or more types of I/O requests. Figure 8-5 shows how the framework determines what to do with requests of these types.

As Figure 8-5 shows, when a read, write, device I/O control, or internal device I/O control request arrives, the following actions occur:

  1. The framework determines whether the driver has configured a queue for this type of request. If not, the framework fails the request if this is a function or bus driver. If this is a filter driver, the framework passes the request to the default I/O target.

  2. The framework determines whether the queue is accepting requests. If not, the framework fails the request.

  3. If the queue is accepting requests, the framework creates a WDF request object to represent the request and adds it to the queue.

  4. If the device is not in the working state, the framework notifies the Plug and Play and power handler to power up the device.

The following sections provide additional details and examples of callbacks for read, write, and device I/O control requests.

Callbacks for Read and Write Requests

Drivers handle read and write requests through the callbacks in Table 8-12.

Table 8-12: Callbacks for Read and Write Requests
Open table as spreadsheet

Type of request

UMDF queue callback interfaces

KMDF event callback functions

Read

IQueueCallbackRead or

IQueueCallbackDefaultIoHandler

EvtIoRead or EvtIoDefault

Write

IQueueCallbackWrite or

IQueueCallbackDefaultIoHandler

EvtIoWrite or EvtIoDefault

The read and write callback methods are called with the following information:

  • The queue that is dispatching the request, expressed as a pointer to the queue's IWDFQueue interface or a handle to the WDFQUEUE object.

  • The request itself, expressed as a pointer to the request object's IWDFIoRequest interface or a handle to the WDFREQUEST object.

  • The number of bytes to read or write.

Within the callback method, the driver can call methods on the request object to retrieve additional information about the request, such as a pointer to the input or output buffer. After the driver has retrieved the information that it requires, it can initiate the I/O operation.

"I/O Buffers and Memory Objects" earlier in this chapter has more information about retrieving parameters.

UMDF Example: IQueueCallbackWrite::OnWrite method

Listing 8-12 shows the IQueueCallbackWrite::OnWrite method that the Fx2_Driver sample implements for its read-write queue callback object. The source code for this example appears in the ReadWriteQueue.cpp file.

Listing 8-12: IQueueCallbackWrite::OnWrite method in a UMDF driver

image from book
 STDMETHODIMP_ (void) CMyReadWriteQueue::OnWrite(     /* [in] */ IWDFIoQueue *pWdfQueue,     /* [in] */ IWDFIoRequest *pWdfRequest,     /* [in] */ SIZE_T BytesToWrite     ) {     UNREFERENCED_PARAMETER(pWdfQueue);     HRESULT hr = S_OK;     IWDFMemory * pInputMemory = NULL;     IWDFUsbTargetPipe * pOutputPipe = m_Device->GetOutputPipe();     pWdfRequest->GetInputMemory(&pInputMemory);     hr = pOutputPipe->FormatRequestForWrite( pWdfRequest,                                              NULL, //pFile                                              pInputMemory,                                              NULL, //Memory offset                                              NULL  //DeviceOffset                                             );     if (FAILED(hr)) {         pWdfRequest->Complete(hr);     }     else {         ForwardFormattedRequest(pWdfRequest, pOutputPipe);     }     SAFE_RELEASE(pInputMemory);     return; } 
image from book

The OnWrite method is called with three parameters: a pointer to the IWDFIoQueue interface for the framework queue object, a pointer to the IWDFIoRequest interface for the framework request object, and the number of bytes to write.

The sample driver accepts a write request from the user and then sends the request to a USB pipe I/O target, which performs the device I/O. Consequently, this method starts by initializing some variables and then calls IWDFIoRequest::GetInputMemory to get a pointer to the IWDFMemory interface of the memory object that is embedded in the request. Before the driver can send the request to the USB pipe I/O target, it must call the I/O target's IWDFIoTarget::FormatRequestForWrite method to set up the request appropriately, and this method requires the IWDFMemory pointer. If the request is successfully formatted, the driver forwards it to the I/O target and returns.

Chapter 9, "I/O Targets," provides more information on using USB I/O targets.

KMDF Example: EvtIoRead Callback

The Osrusbfx2 driver configures an EvtIoRead event callback for one of its sequential queues as follows:

 WDF_IO_QUEUE_CONFIG_INIT(&ioQueueConfig, WdfIoQueueDispatchSequential);     ioQueueConfig.EvtIoRead = OsrFxEvtIoRead;     ioQueueConfig.EvtIoStop = OsrFxEvtIoStop;     status = WdfIoQueueCreate( device,                                &ioQueueConfig,                                WDF_NO_OBJECT_ATTRIBUTES,                                &queue                                ); 

When a read request arrives for the device object, the framework places it in the sequential queue. When the driver has completed handling the previous request from that queue, the framework calls the EvtIoRead callback to dispatch the request. Listing 8-13 shows the source code for this function.

Listing 8-13: EvtIoRead callback function in a KMDF driver

image from book
 VOID OsrFxEvtIoRead(     IN WDFQUEUE         Queue,     IN WDFREQUEST       Request,     IN size_t           Length     ) {     WDFUSBPIPE                  pipe;     NTSTATUS                    status;     WDFMEMORY                   reqMemory;     PDEVICE_CONTEXT             pDeviceContext;     UNREFERENCED_PARAMETER(Queue);     // First validate input parameters.     if (Length > TEST_BOARD_TRANSFER_BUFFER_SIZE) {         status = STATUS_INVALID_PARAMETER;         goto Exit;     }     pDeviceContext = GetDeviceContext(WdfIoQueueGetDevice(Queue));     pipe = pDeviceContext->BulkReadPipe;     status = WdfRequestRetrieveOutputMemory(Request, &reqMemory);     if(!NT_SUCCESS(status)){         goto Exit;     }     // The format call validates that you are reading or     // writing to the right pipe type, sets the transfer flags,     // creates an URB and initializes the request.     status = WdfUsbTargetPipeFormatRequestForRead(pipe,                                                   Request,                                                   reqMemory,                                                   NULL // Offsets                                                   );     if (!NT_SUCCESS(status)) {         goto Exit;     }     WdfRequestSetCompletionRoutine( Request,                                     EvtRequestReadCompletionRoutine,                                     pipe                                    );     // Send the request asynchronously.     if (WdfRequestSend (Request,                         WdfUsbTargetPipeGetIoTarget(pipe),                         WDF_NO_SEND_OPTIONS)                         == FALSE) {         // Framework couldn't send the request for some reason.         status = WdfRequestGetStatus(Request);         goto Exit;     } Exit:     if (!NT_SUCCESS(status)) {         WdfRequestCompleteWithInformation(Request, status, 0);     }     return; } 
image from book

As the listing shows, the EvtIoRead function is called with a handle to the I/O queue, a handle to the request object, and a length that indicates the number of bytes to read. The driver first validates the length and then gets information that is stored in the device context area. It retrieves the buffer into which it will read the data by calling WdfRequestRetrieveOutputMemory and then formats the request for the USB target pipe. After the driver sets an I/O completion callback, it sends the request to the target pipe. If any errors occur, the driver completes the request with a failure status.

"Retrieving Buffers in KMDF Drivers" earlier in this chapter has more information on retrieving buffers from I/O requests. Chapter 9, "I/O Targets," provides more information on sending requests and using I/O targets.

Callbacks for Device I/O Control Requests

Drivers handle device I/O control requests through the callbacks in Table 8-13.

Table 8-13: Callbacks for Device I/O Control Requests
Open table as spreadsheet

Type of request

UMDF callback interfaces

KMDF event callback function

Device I/O control

IQueueCallbackDeviceIoControl or IQueueCallbackDefaultIoHandler

EvtIoDeviceControl or EvtIoDefault

Internal device I/O control

None

EvtIoInternalDeviceControl or EvtIoDefault

The device I/O control callbacks are passed the following information:

  • Queue that is dispatching the request

    Expressed as a pointer to the queue object's IWDFQueue interface or a handle to the WDFQUEUE object.

  • Request itself

    Expressed as a pointer to the request object's IWDFIoRequest interface or a handle to the WDFREQUEST object.

  • I/O control code

    Expressed as a ULONG value.

  • Length of the input buffer

    Expressed as a SIZE_T value.

  • Length of the output buffer

    Expressed as a SIZE_T value.

UMDF Example: OnDeviceIoControl

Listing 8-14 shows the IQueueCallbackDeviceIoControl::OnDeviceIoControl method that the Fx2_Driver sample implements on its I/O control queue callback object. This function appears in the ControlQueue.cpp source file. For simplicity, the version shown here is from Step3, rather than from the Final version of the driver.

Listing 8-14: IQueueCallbackDeviceIoControl::OnDeviceIoControl method in a UMDF driver

image from book
 VOID STDMETHODCALLTYPE CMyControlQueue::OnDeviceIoControl(     __in IWDFIoQueue *FxQueue,     __in IWDFIoRequest *FxRequest,     __in ULONG ControlCode,     __in SIZE_T InputBufferSizeInBytes,     __in SIZE_T OutputBufferSizeInBytes     ) {     UNREFERENCED_PARAMETER(FxQueue);     UNREFERENCED_PARAMETER(OutputBufferSizeInBytes);     IWDFMemory *memory = NULL;     PVOID buffer;     SIZE_T bigBufferCb;     ULONG information = 0;     bool completeRequest = true;     HRESULT hr = S_OK;     switch (ControlCode)     {         case IOCTL_OSRUSBFX2_SET_BAR_GRAPH_DISPLAY: {             // Make sure the buffer is big enough.             if (InputBufferSizeInBytes < sizeof(BAR_GRAPH_STATE)){                 hr = HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER);             }             else {                 FxRequest->GetInputMemory(&memory);             }             // Get the data buffer and use it to set the bar graph.             if (SUCCEEDED(hr)) {                 buffer = memory->GetDataBuffer(&bigBufferCb);                 memory->Release();                   hr = m_Device->SetBarGraphDisplay((PBAR_GRAPH_STATE) buffer);               }               break;           }           default: {               hr = HRESULT_FROM_WIN32(ERROR_INVALID_FUNCTION);               break;          }     }     if (completeRequest) {          FxRequest->CompleteWithInformation(hr, information);     }     return; } 
image from book

After initializing some local variables, the method checks the value of the control code to determine what to do. This version of the driver handles only one control code, which sets the light bar. If this is the code in the request, the driver validates the buffer size against the device capabilities. If the buffer is large enough, the driver retrieves the memory object from the request by calling IWDFMemory::GetInputMemory on the memory object and then retrieves the buffer from the memory object by calling IWDFMemory::GetDataBuffer. It then sets the display and completes the request.

"Retrieving Buffers in UMDF Drivers" earlier in this chapter has more information on retrieving memory objects and data buffers and completing I/O requests.

KMDF Example: EvtIoDeviceControl Callback

Listing 8-15 shows how a KMDF driver handles a device I/O control request. This example is from the Featured Toaster sample's Toaster.c file.

Listing 8-15: EvtIoDeviceControl callback in a KMDF driver

image from book
 VOID ToasterEvtIoDeviceControl(     IN WDFQUEUE     Queue,     IN WDFREQUEST   Request,     IN size_t       OutputBufferLength,     IN size_t       InputBufferLength,     IN ULONG        IoControlCode     ) {     NTSTATUS             status= STATUS_SUCCESS;     WDF_DEVICE_STATE     deviceState;     WDFDEVICE            hDevice = WdfIoQueueGetDevice(Queue);     UNREFERENCED_PARAMETER(OutputBufferLength);     UNREFERENCED_PARAMETER(InputBufferLength);     PAGED_CODE();     switch (IoControlCode) {     case IOCTL_TOASTER_DONT_DISPLAY_IN_UI_DEVICE:         // Please remove this code when you adapt this sample for your hardware.         WDF_DEVICE_STATE_INIT(&deviceState);         deviceState.DontDisplayInUI = WdfTrue;         WdfDeviceSetDeviceState(hDevice, &deviceState);         break;     default:         status = STATUS_INVALID_DEVICE_REQUEST;     }     // Complete the Request.     WdfRequestCompleteWithInformation(Request, status, (ULONG_PTR) 0); } 
image from book

The Toaster sample handles only one IOCTL, which disables the display of the toaster in Device Manager. When a device I/O control request arrives for the driver, the framework queues the request to the driver's default queue and calls the EvtIoDeviceControl callback that the driver configured for the queue.

The ToasterEvtIoDeviceControl callback inspects the value that the framework passed in the IoControlCode parameter. If the code is IOCTL_TOASTER_DONT_DISPLAY_ IN_UI_DEVICE, the driver initializes a WDF_DEVICE_STATE structure, sets the DontDisplayInUI field to WdfTrue, and calls WdfDeviceSetDeviceState to set this new value. If IoControlCode contains any other value, the driver sets a failure status. The driver then completes the request.

Default I/O Callbacks

Both KMDF and UMDF drivers can implement default I/O callbacks that are invoked for I/O request types for which the driver has configured a queue but has not provided any other callback. The default I/O callbacks receive only read, write, device I/O control, and internal device I/O control requests; they cannot handle create requests.

The default I/O callbacks receive the following two pieces of information:

  • Queue that is dispatching the request Expressed as a pointer to the queue object's IWDFIoQueue interface or a handle to the WDFQUEUE object.

  • Request Expressed as a pointer to the request object's IWDFIoRequest interface or a handle to the WDFREQUEST object.

A default callback must retrieve the parameters from the request object to determine the type of request and thus how to handle it.

UMDF Example: IQueueCallbackDefaultIoHandler

Listing 8-16 shows the default I/O handler for the Usb\Filter sample. This driver creates one queue and implements two callbacks on the queue callback object: IQueueCallbackWrite for write requests and IQueueCallbackDefaultIoHandler for all other requests. The driver filters write requests as they arrive and filters read requests after lower drivers have completed them. The driver implements the default I/O handler interface so that it can set an I/O completion callback for the read requests.

Listing 8-16: IQueueCallbackDefaultIoHandler::OnDefaultIoHandler in a UMDF driver

image from book
 void CMyQueue::OnDefaultIoHandler(     __in IWDFIoQueue*    FxQueue,     __in IWDFIoRequest*  FxRequest     ) {     UNREFERENCED_PARAMETER(FxQueue);     // Forward the request down the stack. When the device below completes     // the request, OnComplete will be called to complete the request.     ForwardRequest(FxRequest); } 
image from book

The IQueueCallbackDefaultIoHandler::OnDefaultIoHandler method in the listing is quite simple. It does no processing whatsoever on the request. It calls a helper function to send the request to the default I/O target and then exits. The helper function sets an I/O completion callback, however, because it filters read requests after lower drivers have completed them.

Chapter 9, "I/O Targets," provides more information on I/O completion callbacks.

KMDF Example: EvtIoDefault

Listing 8-17 shows the EvtIoDefault callback from the AMCC5933 sample driver. The code is excerpted from the AMCC5933\Sys\Transfer.c file.

Listing 8-17: EvtIoDefault callback in KMDF driver

image from book
 VOID AmccPciEvtIoDefault(     IN WDFQUEUE       Queue,     IN WDFREQUEST     Request     ) {     PAMCC_DEVICE_EXTENSION    devExt;     REQUEST_CONTEXT         * transfer;     NTSTATUS                  status;     size_t                    length;     WDF_DMA_DIRECTION         direction;     WDFDMATRANSACTION         dmaTransaction;     WDF_REQUEST_PARAMETERS    params;     WDF_REQUEST_PARAMETERS_INIT(&params);     WdfRequestGetParameters(Request, &params);     // Get the device context area.     devExt = AmccPciGetDevExt(WdfIoQueueGetDevice(Queue));     // Validate and gather parameters.     switch (params.Type) {         case WdfRequestTypeRead:             length    = params.Parameters.Read.Length;             direction = WdfDmaDirectionReadFromDevice;             break;         case WdfRequestTypeWrite:             length    = params.Parameters.Write.Length;             direction = WdfDmaDirectionWriteToDevice;             break;         default:             WdfRequestComplete(Request, STATUS_INVALID_PARAMETER);             return;     }     // The length must be non-zero.     if (length == 0) {         WdfRequestComplete(Request, STATUS_INVALID_PARAMETER);         return;     }     // Code continues to set up DMA operation.     . . .     return; } 
image from book

The EvtIoDefault callback has only two parameters: a handle to the queue object and a handle to the I/O request object. In the AMCC5933 driver, EvtIoDefault handles read, write, and device I/O control requests, so the first task of the callback is to get the request parameters by calling WdfGetRequestParameters. This method returns a pointer to a WDF_REQUEST_PARAMETERS structure. The Type field of this structure identifies the request type, which in turn determines the direction of the I/O operation. If this is not a read or write request or if the requested data transfer length is zero, the driver completes the request with an error.




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