Figure 8-1 shows the general path that an I/O request follows from an application through the system and the device stack. The device stack can contain any combination of UMDF, KMDF, and other kernel-mode drivers. The sections that follow Figure 8-1 describe the details of processing through UMDF and KMDF drivers.
Figure 8-1: Overview of I/O request path from application to device
In Figure 8-1, an application issues an I/O request. The numbers refer to the following major steps in this path:
An application issues an I/O request by calling a Windows API function.
The Windows API calls the kernel-mode subsystems, which call the corresponding I/O processing function of the I/O manager.
The I/O manager builds an IRP that describes the I/O request and sends it to the top driver in the kernel-mode device stack for the target device.
The top driver in the kernel-mode device stack processes and completes the request if it can.
If the device has a UMDF driver, the top driver in the kernel-mode device stack is the reflector, and its Up device object receives the IRP. The reflector packages the request and sends it to the user-mode driver host process so that the UMDF driver can act on it. If the device does not have a UMDF driver, no user-mode processing occurs.
The following section, "I/O Request Path through the UMDF Device Stack," describes the details of user-mode processing.
If the IRP is complete as a result of processing by either the UMDF device stack or the kernel-mode driver, steps 4 and 5 do not occur and processing continues with step 6.
If the IRP is not yet complete, the kernel-mode driver sends it to the next-lower kernel-mode driver in the device stack. That driver processes the IRP and, in turn, sends it to the next-lower driver and so on down the device stack, until a driver completes the IRP.
Any driver that sends the IRP down the stack can register an I/O completion callback to have the opportunity to process the IRP again after lower drivers have completed it.
Finally, the IRP reaches a driver that completes it. In Figure 8-1, this is the PDO, but it could be any driver in the stack.
After the IRP is complete, the I/O manager calls the I/O completion callbacks that drivers set as the request passed down the device stack, in reverse order.
The driver that completes the IRP does not require an I/O completion callback because it already "knows" that the request is complete and has completed its processing.
The I/O manager retrieves the I/O status from the IRP, translates the status to a Windows error code, returns completion information and, if necessary, copies data to the requesting application's data buffer.
The I/O manager then returns control to the Windows API.
The Windows API returns the results of the operation to the requesting application.
Figure 8-2 shows the path that the I/O request takes from the reflector through the UMDF device stack.
Figure 8-2: I/O request path through the UMDF device stack
Figure 8-2 shows how the UMDF device stack processes an I/O request. The numbers refer to the following major steps in this path:
The I/O manager delivers the IRP to the top driver in the kernel-mode device stack. If the device has one or more UMDF drivers, the top driver in the kernel-mode device stack is the reflector and its Up device object is its target for I/O requests from the I/O manager.
The reflector packages the request and sends it to the driver host process, which creates a user-mode IRP. In the driver host process, the framework validates the request parameters and processes the request, starting with the top driver in the user-mode stack. The figure shows two UMDF drivers, but a device could have more or less than two.
If the UMDF driver is a filter driver that does not handle requests of this type, as the figure shows, the framework forwards the user-mode IRP to the driver's default I/O target, which is the next lower driver in the device stack. The framework then determines whether that driver can handle such requests. "I/O Request Flow within the Frameworks" later in this chapter explains how the framework makes this determination.
If the UMDF driver is a function driver that does not handle requests of this type, the framework fails the request with STATUS_INVALID_DEVICE_REQUEST. Steps 3 through 6 do not occur, and processing continues at step 7.
When the request reaches a driver that can handle it, the framework creates a WDF request object and adds it to the appropriate queue or calls the appropriate callback method. The driver then processes the request.
If the driver cannot complete the WDF request, the driver sends the request to the default I/O target, which is the next-lower driver in the stack. This action passes the request back to the framework, which stores a pointer to the WDF request object so that the framework can determine later whether the driver registered a request completion callback. The framework calls the next driver, if any, in the user-mode device stack to handle the request, and so forth down the stack, in the same way as kernel-mode drivers do.
Any UMDF driver that sends the request to the default I/O target can register an I/O completion callback for notification when the request is eventually completed.
If the driver can complete the WDF request, it does so by calling an IWDFIoRequest::CompleteXxx method on the request object. When a driver completes the request, processing continues at step 7.
If all of the UMDF drivers process the request and then send it to the default I/O target, the framework calls the dispatcher. The dispatcher issues a new Windows I/O request for the Down device object, so the Down device object receives a different IRP from the IRP that the application originally sent. The Down device object is the default I/O target of the bottom driver in the user-mode device stack.
The Down device object receives the new IRP from the I/O manager and sends it to the next-lower kernel-mode driver, which is layered immediately below the reflector. Processing then continues through the kernel-mode device stack, as described in the previous section.
When the new IRP is complete, the dispatcher receives status and completion information in the same way as any other user-mode application. It notifies the framework so that the framework can manage completion processing of the WDF request and associated user-mode IRP through the UMDF device stack.
The framework calls the I/O completion callbacks-if any-that UMDF drivers set as the user-mode IRP traveled down the stack, in reverse order.
After the last UMDF completion callback has returned, the framework passes the completion status and data to the reflector.
The reflector completes the original IRP back to the I/O manager, which continues completion back to the application as described in the previous section.
Figure 8-3 shows the path a request takes through a KMDF driver. In this example, the device stack contains a KMDF function driver layered beneath a filter driver. However, the processing is the same regardless of the driver's position in the device stack.
Figure 8-3: I/O request path through a KMDF driver
The numbers refer to the following major steps in Figure 8-3:
The top driver in the kernel-mode device stack receives the IRP from the I/O manager and processes it as described previously. In Figure 8-3, the top driver is a filter driver that passes the request down the stack to the next-lower driver, which is a KMDF function driver.
The KMDF function driver is represented in the device stack by a framework-created FDO. The framework intercepts the IRP, inspects the major function code, and determines whether the driver can handle the IRP. "I/O Request Flow within the Frameworks" later in this chapter explains how the framework makes this determination.
For a KMDF function or bus driver that does not handle requests of this type, the framework fails the request with STATUS_INVALID_DEVICE_REQUEST and processing continues with step 6.
For a KMDF filter driver that does not handle requests of this type, the framework sends the request to the next-lower driver and processing continues with step 4.
Otherwise, the framework creates a WDF request object and calls the KMDF driver's callback function or adds the request to a queue, as appropriate.
The KMDF driver processes the request. If the driver completes the request, it calls WdfRequestCompleteXxx. The framework then starts completion processing at step 7, and steps 4 through 6 do not take place.
If the driver cannot complete the request, the driver sends it to the default I/O target, which is the next-lower driver in the device stack. If the driver requires additional processing after lower drivers have completed the request, the driver can register a completion callback with KMDF. KMDF, in turn, registers a completion routine in the IRP.
The next-lower driver processes the request and so forth down the stack, as previously described in Figure 8-1.
After the request is complete, the I/O manager calls the completion callbacks that are registered in the IRP. If the KMDF driver set a completion callback, the framework regains ownership of the IRP when the I/O manager calls the framework's completion routine.
The framework calls the driver's completion callback with the WDF request object, so that the driver can post-process the request.
The framework ensures that the original IRP contains the status, completion information, and any requested data. It then deletes the WDF request object and returns control to the I/O manager, which continues completion back up through the stack and eventually to the requesting application.
When an I/O request is complete, WDF and Windows both perform completion processing. The frameworks clean up the WDF request object and return information to the I/O manager. The I/O manager, in turn, copies any requested data back to the user's buffer and returns completion information.
When a UMDF driver completes a WDF request, the framework proceeds in the following order:
Copies the returned data, the I/O status, and the completion information as required from the WDF request object to the user-mode IRP.
Calls the cleanup callback for the WDF request object, if the driver registered such a callback. The underlying buffers and WDM IRP are still valid.
Destroys the WDF request object itself, freeing any context storage that was associated with the object.
Notifies the reflector, which copies the returned data into the underlying WDM IRP and completes the WDM IRP.
When a KMDF driver completes a WDF request, the framework proceeds as follows:
Fills in the I/O status and completion information fields of the IRP with the information in the corresponding fields of the WDF request object.
Calls the cleanup callback for the WDF request object, if the driver registered such a callback. The underlying WDM IRP is still valid.
Completes the IRP associated with the WDF request after the cleanup callback returns.
Releases its reference on the WDF request object. When the reference count reaches zero, the framework calls the destroy callback for the request, if the driver registered such a callback.
Destroys the WDF request object itself, freeing any context storage that was associated with the object.
In short, the framework manages the cleanup and destruction of the WDF request object in the same way as for any other WDF object.
When an IRP is complete, Windows returns up to three data items to the thread that issued the request:
The status is expressed as:
An HRESULT in a UMDF driver.
An NTSTATUS value in a KMDF driver.
The I/O manager translates the result and returns a Windows error code to the application that issued the request.
This value indicates the number of bytes that were transferred as a result of a successful read, write, or device I/O control request.
This information is returned to a Windows application in the lpNumberOfBytesRead parameter for a ReadFile function call, in the lpNumberOfBytesWritten parameter for a WriteFile function call, or in the lpBytesReturned parameter for a DeviceIoControl call.
For read requests and certain device I/O control requests, the system returns data in the buffer that the caller provided.
In the framework, an internal IRP router inspects the major function code in each incoming IRP to determine which of the following internal components should handle the IRP:
The I/O request handler.
The I/O request handler dispatches I/O requests to the driver, manages I/O cancellation and completion, and works with the Plug and Play and power handler to ensure that the device state is compatible with performing device I/O.
The Plug and Play and power handler.
The Plug and Play and power handler uses its internal state machines and the driver-implemented event processing callbacks to manage the Plug and Play and power management process for the driver. This might involve internally processing and completing an arriving request or forwarding that request to other drivers in the system for additional processing and eventual completion.
Chapter 7, "Plug and Play and Power Management," has more information on the Plug and Play and power state machines.
KMDF The WMI handler. (KMDF only)
The WMI handler supports all types of WMI requests and provides a default WMI implementation so that KMDF drivers that do not provide WMI data are not required to register as WMI data providers.
Chapter 12, "WDF Support Objects," describes how KMDF drivers can support WMI requests.
Both frameworks process the request in a similar way through a request pipeline. Figure 8-4 shows a schematic view of the WDF request processing pipeline. The gray box on the right encloses features that are supported only in KMDF. The rest of the diagram applies to both UMDF and KMDF.
Figure 8-4: The WDF request processing pipeline
When an IRP enters the pipeline, the IRP router inspects the major function code to determine how to route the request.
KMDF KMDF drivers can register a preprocessing callback that handles IRP types that KMDF does not process. If a KMDF driver has registered such a callback for this IRP major function code, the IRP router invokes the callback. When preprocessing is complete, the IRP typically returns to the router. Chapter 14, "Beyond the Frameworks," describes how a KMDF driver can process IRPs that KMDF does not handle.
UMDF UMDF drivers cannot handle IRPs that the framework does not process. Therefore, no preprocessing occurs in UMDF drivers.
For both UMDF and KMDF, the IRP router then sends the IRP to one of the framework's internal request handlers:
IRPs with the IRP_MJ_PNP and IRP_MJ_POWER major function codes are sent to the Plug and Play and power handler.
IRPs with the following major function codes are sent to the I/O handler:
IRP_MJ_INTERNAL_DEVICE_CONTROL (KMDF only)
IRPs with the IRP_MJ_SYSTEM_CONTROL major function code are sent to the WMI handler. (KMDF only)
IRPs with other major function codes are sent to a default handler, which is not shown in Figure 8-4.
If the device object represents a filter driver, the default handler forwards these IRPs to the next-lower driver. Otherwise, it completes the IRP with STATUS_INVALID_DEVICE_REQUEST.
When the I/O request handler receives a new request, it first checks whether WDF supports and whether the driver handles the I/O request type. WDF supports the following I/O request types for Plug and Play drivers:
I/O request type
IRP major function code
Device I/O control
Internal device I/O control
IRP_MJ_INTERNAL_DEVICE_CONTROL (KMDF only)
If the framework does not support and the driver does not handle the I/O request, the I/O handler completes the request with STATUS_INVALID_DEVICE_REQUEST. The IRP then returns to the I/O manager, which returns the failure status to the requesting application.
If WDF supports and the driver handles I/O requests of this type, the I/O handler next checks the parameters in the IRP to ensure that they are appropriate for the request type. For example:
Read and write requests The I/O handler checks whether the IRP includes a data buffer that is zero bytes in length. Although the I/O manager considers zero-length buffers valid, drivers rarely handle such requests-and drivers that handle them typically process them incorrectly. Consequently, by default the I/O handler completes such read and write requests with success, indicating zero bytes actually transferred. However, a driver can configure its queues to receive requests that have zero-length buffers.
Device I/O control requests The I/O handler checks the input and output buffers for device I/O control requests that require direct I/O (that is, METHOD_IN_DIRECT and METHOD_OUT_DIRECT). If either the input or output buffer is invalid, the I/O handler completes the request with an appropriate error code and indicates that zero bytes of data were actually transferred.
Although the framework checks to make sure that the buffers are valid, it cannot determine whether a buffer is missing or whether a buffer is the correct size for this particular I/O control request because such requests are device-specific. The driver must validate the parameters based on the requirements of the individual request.
If the IRP passes these validation checks, the I/O handler determines what to do next based on the IRP type, the type of driver, and the driver's queue configuration. The I/O handler proceeds in the following order until it has completed the IRP, sent the IRP to an I/O target, or created and dispatched to the driver a WDF request that represents the IRP:
Handles the request on behalf of the driver, if possible. If the I/O handler can process and complete the IRP on behalf of the driver, it does so. WDF typically handles create, cleanup, and close requests on behalf of the driver, although a driver can supply callbacks to handle them directly. By default, WDF completes such requests with a success status for a function driver and forwards them to the default I/O target for a filter driver. A driver can change this default when it creates the device object.
Sends the request to the default I/O target, if appropriate. If the I/O handler cannot complete the IRP on behalf of the driver, the I/O handler determines whether to send the IRP to the default I/O target. If this is a filter driver, the I/O handler checks whether the driver has configured a queue to handle I/O requests of this type. If not, the I/O handler sends the IRP to the driver's default I/O target, as shown in Figures 8-1 and 8-2 earlier in this chapter.
Dispatches the request to the driver. If the IRP has not been completed or forwarded, the I/O handler creates a new WDF request object and dispatches the WDF request object to the driver.
The WDF request object describes and controls the processing of the request and conveys the data in the original IRP to the WDF driver.
After creating the WDF request object, the I/O handler dispatches the request to the driver by either calling the appropriate driver callback function or placing the request on one of the driver's queues. Placing the request on a queue might trigger a call to one of the driver's I/O event callback functions, depending on how the driver's queues dispatch I/O requests.