Understanding the Windows I/O System
- 9/15/2012
- I/O System Components
- Device Drivers
- I/O Processing
- Kernel-Mode Driver Framework (KMDF)
- User-Mode Driver Framework (UMDF)
- The Plug and Play (PnP) Manager
- The Power Manager
- Conclusion
Kernel-Mode Driver Framework (KMDF)
We’ve already discussed some details about the Windows Driver Foundation (WDF) in Chapter 2, “System Architecture,” in Part 1. In this section, we’ll take a deeper look at the components and functionality provided by the kernel-mode part of the framework, KMDF. Note that this section will only briefly touch on some of the core architecture of KMDF. For a much more complete overview on the subject, please refer to http://msdn.microsoft.com/en-us/library/windows/hardware/gg463370.aspx.
Structure and Operation of a KMDF Driver
First, let’s take a look at which kinds of drivers or devices are supported by KMDF. In general, any WDM-conformant driver should be supported by KMDF, as long as it performs standard I/O processing and IRP manipulation. KMDF is not suitable for drivers that don’t use the Windows kernel API directly but instead perform library calls into existing port and class drivers. These types of drivers cannot use KMDF because they only provide callbacks for the actual WDM drivers that do the I/O processing. Additionally, if a driver provides its own dispatch functions instead of relying on a port or class driver, IEEE 1394 and ISA, PCI, PCMCIA, and SD Client (for Secure Digital storage devices) drivers can also make use of KMDF.
Although KMDF provides an abstraction on top of WDM, the basic driver structure shown earlier also generally applies to KMDF drivers. At their core, KMDF drivers must have the following functions:
An initialization routine Just like any other driver, a KMDF driver has a DriverEntry function that initializes the driver. KMDF drivers will initiate the framework at this point and perform any configuration and initialization steps that are part of the driver or part of describing the driver to the framework. For non–Plug and Play drivers, this is where the first device object should be created.
An add-device routine KMDF driver operation is based on events and callbacks (described shortly), and the EvtDriverDeviceAdd callback is the single most important one for PnP devices because it receives notifications when the PnP manager in the kernel enumerates one of the driver’s devices.
One or more EvtIo* routines Just like a WDM driver’s dispatch routines, these callback routines handle specific types of I/O requests from a particular device queue. A driver typically creates one or more queues in which KMDF places I/O requests for the driver’s devices. These queues can be configured by request type and dispatching type.
The simplest KMDF driver might need to have only an initialization and add-device routine because the framework will provide the default, generic functionality that’s required for most types of I/O processing, including power and Plug and Play events. In the KMDF model, events refer to run-time states to which a driver can respond or during which a driver can participate. These events are not related to the synchronization primitives (synchronization is discussed in Chapter 3 in Part 1), but are internal to the framework.
For events that are critical to a driver’s operation, or which need specialized processing, the driver registers a given callback routine to handle this event. In other cases, a driver can allow KMDF to perform a default, generic action instead. For example, during an eject event (EvtDeviceEject), a driver can choose to support ejection and supply a callback or to fall back to the default KMDF code that will tell the user that the device is not ejectable. Not all events have a default behavior, however, and callbacks must be provided by the driver. One notable example is the EvtDriverDeviceAdd event that is at the core of any Plug and Play driver.
KMDF Data Model
The KMDF data model is object-based, much like the model for the kernel, but it does not make use of the object manager. Instead, KMDF manages its own objects internally, exposing them as handles to drivers and keeping the actual data structures opaque. For each object type, the framework provides routines to perform operations on the object, such as WdfDeviceCreate, which creates a device. Additionally, objects can have specific data fields or members that can be accessed by Get/Set (used for modifications that should never fail) or Assign/Retrieve APIs (used for modifications that can fail). For example, the WdfInterruptGetInfo function returns information on a given interrupt object (WDFINTERRUPT).
Also unlike the implementation of kernel objects, which all refer to distinct and isolated object types, KMDF objects are all part of a hierarchy—most object types are bound to a parent. The root object is the WDFDRIVER structure, which describes the actual driver. The structure and meaning is analogous to the DRIVER_OBJECT structure provided by the I/O manager, and all other KMDF structures are children of it. The next most important object is WDFDEVICE, which refers to a given instance of a detected device on the system, which must have been created with WdfDeviceCreate. Again, this is analogous to the DEVICE_OBJECT structure that’s used in the WDM model and by the I/O manager. Table 8-5 lists the object types supported by KMDF.
Table 8-5 KMDF Object Types
Object |
Type |
Description |
Child List |
WDFCHILDLIST |
List of child WDFDEVICE objects associated with the device. Only used by bus drivers. |
Collection |
WDFCOLLECTION |
List of objects of a similar type, such as a group of WDFDEVICE objects being filtered. |
Deferred Procedure Call |
WDFDPC |
Instance of a DPC object (see Chapter 3 in Part 1 for more information on DPCs). |
Device |
WDFDEVICE |
Instance of a device. |
DMA Common Buffer |
WDFCOMMONBUFFER |
Region of memory that a device and driver can access for direct memory access (DMA). |
DMA Enabler |
WDFDMAENABLER |
Enables DMA on a given channel for a driver. |
DMA Transaction |
WDFDMATRANSACTION |
Instance of a DMA transaction. |
Driver |
WDFDRIVER |
Root object for the driver; represents the driver, its parameters, and its callbacks, among other items. |
File |
WDFFILEOBJECT |
Instance of a file object that can be used as a channel for communication between an application and the driver. |
Generic Object |
WDFOBJECT |
Allows driver-defined custom data to be wrapped inside the framework’s object data model as an object. |
Interrupt |
WDFINTERRUPT |
Instance of an interrupt that the driver must handle. |
I/O Queue |
WDFQUEUE |
Represents a given I/O queue. |
I/O Request |
WDFREQUEST |
Represents a given request on a WDFQUEUE. |
I/O Target |
WDFIOTARGET |
Represents the device stack being targeted by a given WDFREQUEST. |
Look-Aside List |
WDFLOOKASIDE |
Describes an executive look-aside list. |
Memory |
WDFMEMORY |
Describes a region of paged or nonpaged pool. |
Registry Key |
WDFKEY |
Describes a registry key. |
Resource List |
WDFCMRESLIST |
Identifies the hardware resources assigned to a WDFDEVICE. |
Resource Range List |
WDFIORESLIST |
Identifies a given possible hardware resource range for a WDFDEVICE. |
Resource Requirements List |
WDFIORESREQLIST |
Contains an array of WDFIORESLIST objects describing all possible resource ranges for a WDFDEVICE. |
Spinlock |
WDFSPINLOCK |
Describes a spinlock (see Chapter 3 in Part 1 for more information). |
String |
WDFSTRING |
Describes a Unicode string structure. |
Timer |
WDFTIMER |
Describes an executive timer (see Chapter 3 in Part 1 for more information). |
USB Device |
WDFUSBDEVICE |
Identifies the one instance of a USB device. |
USB Interface |
WDFUSBINTERFACE |
Identifies one interface on the given WDFUSBDEVICE. |
USB Pipe |
WDFUSBPIPE |
Identifies a pipe to an endpoint on a given WDFUSBINTERFACE. |
Wait Lock |
WDFWAITLOCK |
Represents a kernel dispatcher event object. |
WMI Instance |
WDFWMIINSTANCE |
Represents a WMI data block for a given WDFWMIPROVIDER. |
WMI Provider |
WDFWMIPROVIDER |
Describes the WMI schema for all the WDFWMIINSTANCE objects supported by the driver. |
Work Item |
WDFWORKITEM |
Describes an executive work item. |
For each of these objects, other KMDF objects can be attached as children—some objects have only one or two valid parents, while other objects can be attached to any parent. For example, a WDFINTERRUPT object must be associated with a given WDFDEVICE, but a WDFSPINLOCK or WDFSTRING can have any object as a parent, allowing fine-grained control over their validity and usage and reducing global state variables. Figure 8-30 shows the entire KMDF object hierarchy.
Note that the associations mentioned earlier and shown in the figure are not necessarily immediate. The parent must simply be on the hierarchy chain, meaning one of the ancestor nodes must be of this type. This relationship is useful to implement because object hierarchies affect not only the objects’ locality but also their lifetime. Each time a child object is created, a reference count is added to it by its link to its parent. Therefore, when a parent object is destroyed, all the child objects are also destroyed, which is why associating objects such as WDFSTRING or WDFMEMORY with a given object, instead of the default WDFDRIVER object, can automatically free up memory and state information when the parent object is destroyed.
Closely related to the concept hierarchy is KMDF’s notion of object context. Because KMDF objects are opaque, as discussed, and are associated with a parent object for locality, it becomes important to allow drivers to attach their own data to an object in order to track certain specific information outside the framework’s capabilities or support.
Figure 8-30 KMDF object hierarchy
Object contexts allow all KMDF objects to contain such information, and they additionally allow multiple object context areas, which permit multiple layers of code inside the same driver to interact with the same object in different ways. In the WDM model, the device extension data structure allows such information to be associated with a given device, but with KMDF even a spinlock or string can contain context areas. This extensibility allows each library or layer of code responsible for processing an I/O to interact independently of other code, based on the context area that it works with, and allows a mechanism similar to inheritance.
Finally, KMDF objects are also associated with a set of attributes that are shown in Table 8-6. These attributes are usually configured to their defaults, but the values can be overridden by the driver when creating the object by specifying a WDF_OBJECT_ATTRIBUTES structure (similar to the object manager’s OBJECT_ATTRIBUTES structure that’s used when creating a kernel object).
Table 8-6 KMDF Object Attributes
Attribute |
Description |
ContextSizeOverride |
Size of the object context area. |
ContextTypeInfo |
Type of the object context area. |
EvtCleanupCallback |
Callback to notify the driver of the object’s cleanup before deletion (references may still exist). |
EvtDestroyCallback |
Callback to notify the driver of the object’s imminent deletion (reference count will be 0). |
ExecutionLevel |
Describes the maximum IRQL at which the callbacks may be invoked by KMDF. |
ParentObject |
Identifies the parent of this object. |
Size |
Size of the object. |
SynchronizationScope |
Specifies whether callbacks should be synchronized with the parent, a queue or device, or nothing. |
KMDF I/O Model
The KMDF I/O model follows the WDM mechanisms discussed earlier in the chapter. In fact, one can even think of the framework itself as a WDM driver, since it uses kernel APIs and WDM behavior to abstract KMDF and make it functional. Under KMDF, the framework driver sets its own WDM-style IRP dispatch routines and takes control over all IRPs sent to the driver. After being handled by one of three KMDF I/O handlers (which we’ll describe shortly), it then packages these requests in the appropriate KMDF objects, inserts them in the appropriate queues if required, and performs driver callback if the driver is interested in those events. Figure 8-31 describes the flow of I/O in the framework.
Based on the IRP processing discussed for WDM drivers earlier, KMDF performs one of the following three actions:
Sends the IRP to the I/O handler, which processes standard device operations
Sends the IRP to the PnP and power handler that processes these kinds of events and notifies other drivers if the state has changed
Sends the IRP to the WMI handler, which handles tracing and logging.
These components will then notify the driver of any events it registered for, potentially forward the request to another handler for further processing, and then complete the request based on an internal handler action or as the result of a driver call. If KMDF has finished processing the IRP but the request itself has still not been fully processed, KMDF will take one of the following actions:
For bus drivers and function drivers, complete the IRP with STATUS_INVALID_DEVICE_REQUEST
For filter drivers, forward the request to the next lower driver
Figure 8-31 KMDF I/O flow and IRP processing
I/O processing by KMDF is based on the mechanism of queues (WDFQUEUE, not the KQUEUE object discussed in the earlier section on I/O completion and in Chapter 3 in Part 1). KMDF queues are highly scalable containers of I/O requests (packaged as WDFREQUEST objects) and provide a rich feature set beyond merely sorting the pending I/Os for a given device. For example, queues also track currently active requests and support I/O cancellation, I/O concurrency (the ability to perform and complete more than one I/O request at a time), and I/O synchronization (as noted in the list of object attributes in Table 8-6). A typical KMDF driver creates at least one queue (if not more) and associates one or more events with each queue, as well as some of the following options:
The callbacks registered with the events associated with this queue.
The power management state for the queue. KMDF supports both power-managed and nonpower-managed queues. For the former, the I/O handler will handle waking up the device when required (and when possible), arm the idle timer when the device has no I/Os queued up, and call the driver’s I/O cancellation routines when the system is switching away from a working state.
The dispatch method for the queue. KMDF can deliver I/Os from a queue either in a sequential, parallel, or manual mode. Sequential I/Os are delivered one at a time (KMDF waits for the driver to complete the previous request), while parallel I/Os are delivered to the driver as soon as possible. In manual mode, the driver must manually retrieve I/Os from the queue.
Whether or not the queue can accept zero-length buffers, such as incoming requests that don’t actually contain any data.
Based on the mechanism of queues, the KMDF I/O handler can perform several possible tasks upon receiving either a create, close, cleanup, write, read, or device control (IOCTL) request:
For create requests, the driver can request to be immediately notified through EvtDeviceFileCreate, or it can create a nonmanual queue to receive create requests. It must then register an EvtIoDefault callback to receive the notifications. Finally, if none of these methods are used, KMDF will simply complete the request with a success code, meaning that by default, applications will be able to open handles to KMDF drivers that don’t supply their own code.
For cleanup and close requests, the driver will be immediately notified through EvtFileCleanup and EvtFileClose callbacks, if registered. Otherwise, the framework will simply complete with a success code.
Finally, Figure 8-32 illustrates the flow of an I/O request to a KMDF driver for the most common driver operations (read, write, and I/O control codes).
Figure 8-32 Handling read, write, and IOCTL I/O requests by KMDF