KMDF defines an object-based programming model in
which object types represent common driver constructs. Each object
exports methods (functions) and properties (data) that drivers can
access and is associated with object-specific events, which drivers can
support by providing event callbacks. The objects themselves are opaque
to the driver.
KMDF creates some objects on
behalf of the driver, and the driver creates others depending on its
specific requirements. The driver also provides callbacks for the events
for which the KMDF defaults do not suit its device and calls methods on
the object to get and set properties and perform any additional
actions. Consequently, a KMDF driver is essentially a DriverEntry
routine, a set of callback functions that perform device-specific
tasks, and whatever utility functions the driver implementation
requires.
Framework-base drivers
never directly access instances of framework objects. Instead, they
reference object instances by handles, which the driver passes as
parameters to object methods and KMDF passes as parameters to event
callbacks. Framework objects are unique to the framework. They are not
managed by the Windows object manager and cannot be manipulated by using
the system’s ObXxx function. Only the framework (and its drivers) can create and operate on them.
1. Methods, Properties, and Events
Methods are functions that
perform an action on an object, such as creating or deleting the object.
KMDF methods are named according to the following pattern:
Object specifies the KMDF object on which the method operates, and Operation indicates what the method does. For example, the WdfDeviceCreate method creates a framework device object.
Properties are functions
that read and write data fields in an object, thus defining object
behavior and defaults. Properties are named according to the following
pattern:
WdfObject{Set|Get}Data
WdfObject{Assign|Retrieve}Data
Object specifies the KMDF object on which the function operates, and Data
specifies the field that the function reads or writes. Some properties
can be read and written without failure, but others can sometimes fail.
Functions with Set and Get in their names read and write fields without failure. The Set functions return VOID, and the Get functions typically return the value of the field. Functions with Assign and Retrieve in their names read and write fields but can fail. These functions return an NTSTATUS value.
For example, the WDFINTERRUPT
object represents the interrupt object for a device. Each interrupt
object is described by a set of characteristics that indicate the type
of interrupt (message signaled or IRQ based) and provide additional
information about the interrupt. The WdfInterruptGetInfo
method returns this information. A corresponding method to set the
value is not available because the driver initializes this information
when it creates the interrupt object and cannot change it during device
operation.
Events represent run-time
states to which a driver can respond or during which a driver can
participate. A driver registers callbacks only for the events that are
important to its operation. When the event occurs, the framework invokes
the callback, passing as a parameter a handle to the object for which
the callback is registered. For example, the ejection of a device is a
Plug and Play event. If a device can be ejected, its driver registers an
EvtDeviceEject
callback routine, which performs device-specific operations upon
ejection. KMDF calls this routine with a handle to the device object
when the Plug and Play manager sends an IRP_MN_EJECT request for the device. If the device cannot be ejected, the driver does not require such a callback.
For most events, a driver can
either provide a callback routine or allow KMDF to perform a default
action in response. For a few events, however, a driver-specific
callback is required. For example, adding a device is an event for which
every Plug and Play driver must include a callback. The driver’s EvtDriverDeviceAdd callback creates the device object and sets device attributes.
KMDF events are not related
to the kernel-dispatcher events that Windows provides as
synchronization mechanisms. A driver cannot create, manipulate,
or wait on a KMDF event. Instead, the driver registers a callback for
the event and KMDF calls the driver when the event occurs. (For
time-related waits, KMDF provides timer objects.)
2. Object Hierarchy
KMDF objects are organized hierarchically. WDFDRIVER
is the root object; all other objects are considered its children. For
most object types, a driver can specify the parent when it creates the
object. If the driver does not specify a parent at object creation, the
framework sets the default parent to the WDFDRIVERFigure 1 shows the default KMDF object hierarchy. object.
For each
object, the figure shows which other object(s) must be in its parent
chain. These objects are not necessarily the immediate parent but could
be the grandparent, great-grandparent, and so forth. For example, Figure 6.1 shows the WDFDEVICE object as parent of the WDFQUEUE object. However, a WDFQUEUE object could be the child of a WDFIOTARGET object, which in turn is the child of a WDFDEVICE object. Thus, the WDFDEVICE object is in the parent chain for the WDFQUEUE object.
The object hierarchy affects
the object’s lifetime. The parent holds a reference count for each child
object. When the parent object is deleted, the child objects are
deleted and their callbacks are invoked in a defined order. Table 1 lists all the KMDF object types.
Table 1. KMDF Object Types
Object | Type | Description |
---|
Child list | WDFCHILDLIST | Represents a list of the child devices for a device. |
Collection | WDFCOLLECTION | Describes a list of similar objects, such as resources or the devices for which a filter driver filters requests. |
Device | WDFDEVICE | Represents an instance of a device. A driver typically has one WDFDEVICE object for each device that it controls. |
DMA common buffer | WDFCOMMON BUFFER | Represents a buffer that can be accessed by both the device and the driver to perform DMA. |
DMA enabler | WDFDMAENABLER | Enables
a driver to use DMA. A driver that handles device I/O operations has
one WDFDMAENABLER object for each DMA channel within the device. |
DMA transaction | WDFDMATRANSACTION | Represents a single DMA transaction. |
Deferred Procedure Call (DPC) | WDFDPC | Represents a Deferred Procedure Call. |
Driver | WDFDRIVER | Represents
the driver itself and maintains information about the driver, such as
its entry points. Every driver has one WDFDRIVER object. |
File | WDFFILEOBJECT | Represents a file object through which external drivers or applications can access the device. |
Generic object | WDFOBJECT | Represents a generic object for use as the driver requires. |
I/O queue | WDFQUEUE | Represents an I/O queue. A driver can have any number of WDFIOQUEUE objects. |
I/O request | WDFREQUEST | Represents a request for device I/O. |
I/O target | WDFIOTARGET | Represents a device stack to which the driver is forwarding an I/O request. |
Interrupt | WDFINTERRUPT | Represents
a device’s interrupt object. Any driver that handles device interrupts
has one WDFINTERRUPT object for each IRQ or message-signaled interrupt
(MSI) that the device can trigger. |
Look-aside list | WDFLOOKASIDE | Represents
a dynamically sized list of identical buffers that are allocated from
the paged or nonpaged pool. Both the WDFLOOKASIDE object and its
component memory buffers can have attributes. |
Memory | WDFMEMORY | Represents memory that the driver uses, typically an input or output buffer that is associated with an I/O request. |
Registry key | WDFKEY | Represents a registry key. |
Resource list | WDFCMRESLIST | Represents the list of resources that have actually been assigned to the device. |
Resource range list | WDFIORESLIST | Represents a possible configuration for a device. |
Resource requirements list | WDFIORESREQLIST | Represents
a set of I/O resource lists, which comprises all possible
configurations for the device. Each element of the list is a
WDFIORESLIST object. |
String | WDFSTRING | Represents a counted Unicode string. |
Synchronization: spin lock | WDFSPINLOCK | Represents a spin lock, which synchronizes access to data DISPATCH_LEVEL. |
Synchronization: wait lock | WDFWAITLOCK | Represents a wait lock, which synchronizes access to data at PASSIVE_LEVEL. |
Timer | WDFTIMER | Represents a timer that fires either once or periodically and causes a callback routine run. |
USB device | WDFUSBDEVICE | Represents a USB device. |
USB interface | WDFUSBINTERFACE | Represents an interface on a USB device. |
USB pipe | WDFUSBPIPE | Represents a pipe in a USB interface. |
Windows Management Instrumentation (WMI) instance | WDFWMIINSTANCE | Represents an individual WMI data block that is associated with a particular provider |
WMI provider | WDFWMIPROVIDER | Represents the schema for WMI data blocks that the driver provides. |
Work item | WDFWORKITEM | Represents a work item, which runs in a system thread at PASSIVE_LEVEL. |
3. Object Attributes
Every
KMDF object is associated with a set of attributes. The attributes
define information that KMDF requires for objects, as listed in Table 2.
Table 2. KMDF Object Attributes
Field | Description |
---|
ContextSizeOverride | Size of the context area; overrides the value in ContextTypeInfo->ContextSize. |
ContextTypeInfo | Pointer to the type information for the object context area. |
EvtCleanupCallback | Pointer
to a callback routine that is invoked to clean up the object before it
is deleted; the object might still have references. |
EvtDestroyCallback | Pointer to a callback routine that is invoked when the reference count reaches zero for an object that is marked for deletion. |
ExecutionLevel | Maximum interrupt request level (IRQL) at which KMDF can invoke certain object callbacks. |
ParentObject | Handle to the object’s parent. |
Size | Size of the object |
SynchronizationScope | Level at which certain callbacks for this object are synchronized; applies only to driver, device, and file objects. |
The
framework supplies defaults for most attributes. A driver can override
these defaults when it creates the object by using the WDF_OBJECT_ATTRIBUTES_INIT function.
4. Object Context
Every instance of a KMDF
object can have one or more object context areas. This area is a
driver-defined storage area for data that is related to a specific
instance of an object, such as a driver-allocated lock or event for the
object. The size and layout of the object context area are determined by
the driver. When the driver creates the object, it initializes the
context area and specifies its size and type. The driver can create
additional context areas after the object has been created. For a KMDF
device object, the object context area is the equivalent of the WDM
device extension.
When KMDF creates the
object, it allocates memory for context areas from the nonpaged pool and
initializes them according to the driver’s specifications. When KMDF
deletes the object, it deletes the context areas, too. The framework
provides macros to associate a type and a name with the context area and
to create a named accessor function that returns a pointer to the
context area.
If you are familiar with
WDM, this design might seem unnecessarily complicated. However, it
provides flexibility in attaching information to I/O requests as they
flow through the driver. In addition, it enables different libraries to
have their own separate context for an object. For example, an IEEE 1394
library could track a WDFDEVICE object at the same time that
the device’s function driver tracks it, but with separate contexts.
Within a driver, the context area enables a design pattern that is
similar to inheritance. If the driver uses a request for several
different tasks, the request object can have a separate context area to
each task. Functions that are related to a specific task can access
their own contexts and do not require any information about the
existence or contents of any other contexts.
5. Object Creation and Deletion
To create an object, KMDF does the following:
Allocates memory from the nonpaged pool for the object and its context areas.
Initializes the object’s attributes with default values and the driver’s specifications (if any).
Zeroes the object’s context areas.
Configures the object by storing pointers to its event callbacks and setting other object-specific characteristics.
If object initialization fails, KMDF deletes the object and any children that have already been created.
To initialize object
attributes and configuration structures, a driver invokes KMDF
initialization functions before it calls the object-creation methods.
KMDF uses the initialized attributes and structures when it creates the
object.
KMDF maintains a reference
count for each object and ensures that the object persists until all
references to it have been released. If the driver explicitly deletes an
object (by calling a deletion method), KMDF marks the object for
deletion but does not physically delete it until its reference count
reaches zero.
Drivers do not typically
take out references on the objects that they create, but in some cases
(such as when escaping directly to WDM) such references are necessary to
ensure that the object’s handle remains valid. For example, a driver
that sends asynchronous I/O requests might take out a reference on the
request object to guard against race conditions during cancellation.
Before the request object can be deleted, the driver must release this
reference.
Object deletion starts
from the object farthest from the parent and works up the object
hierarchy toward the root. KMDF takes the following steps to delete an
object:
- Starting with the child object farthest from the parent, calls the object’s EvtCleanupCallback.
In this routine, drivers should perform any cleanup tasks that must be
done before the object’s parent is deleted. Such tasks might include
releasing explicit references on the object or a parent object. Note
that when the EvtCleanupCallback function runs, the object’s children still exist; even though their EvtCleanupCallback functions have already been invoked.
- When the object’s reference count reaches zero, calls the object’s EvtDestroyCallback, if the driver has registered one.
- Deallocates the memory that was allocated to the object and its context area.
KMDF always calls the EvtCleanupCallback
routines of child objects before calling those of their parent objects,
so drivers are guaranteed that the parent object still exists when a
child’s EvtCleanupCallback runs. This guarantee does not apply to EvtDestroyCallbacks, however; KMDF can call the EvtDestroyCallback routines in any order, so that the EvtDestroyCallback for a parent might be called before that of one of its children.
Drivers can change the parent of most KMDF objects by setting the ParentObject
attribute. By setting the parent/child relationships appropriately, a
driver can avoid taking out explicit references on related objects and
can instead use the hierarchy and the associated callbacks to manage the
object’s lifetime.