5.2. Additional Device Object Attributes
The Featured Toaster sample sets three more attributes for the device object than the Simple Toaster sample does:
5.2.1. Synchronization Scope
The
synchronization scope determines which of the callbacks for a device or
queue object KMDF calls concurrently and which it calls sequentially.
The following are the possible scopes:
Device scope (WdfSynchronizationScopeDevice)
means that KMDF does not make concurrent calls to certain I/O event
callbacks for the queue or file objects that are children of the device
object.
Queue scope (WdfSynchronizationScopeQueue)
means that KMDF does not call certain I/O event callbacks concurrently
for the individual queue objects that are children of the object.
No scope (WdfSynchronizationScopeNone)
means that KMDF does not acquire any locks and can call any event
callback concurrently with any other event callback. This is the
default value for the WDFDRIVER object, which is the root object.
Default scope (WdfSynchronizationScopeInheritFromParent) means that the object uses the same scope as its parent object. This is the default value for all objects other than the WDFDRIVER object.
By setting synchronization scope to device level (WdfSynchronizationScopeDevice), a driver tells KMDF to synchronize calls to certain I/O event callbacks (such as EvtIoRead, EvtIoWrite,
and so forth) for queue and file objects that are children of the
device object so that only one such callback executes at any given
time. In effect, the callbacks execute synchronously. Because only one
callback runs at a time, the driver is not required to hold locks to
protect data that the callbacks share, such as the device context area.
Drivers can use these KMDF synchronization
techniques (sometimes called frameworks locking) to synchronize access
to their own hardware and their internal data. However, drivers should
not use these techniques when calling externally, particularly to WDM
drivers, because KMDF might hold a lock when the WDM driver does not
expect it. This problem can occur because of IRQL restrictions or when
the WDM driver eventually calls back into the KMDF driver, which
results in a deadlock.
5.2.2. Execution Level
The execution level indicates the IRQL at which KMDF should call the event callback for the object. A driver can select PASSIVE_LEVEL (WdfExecutionLevelPassive), DISPATCH_LEVEL (WdfExecutionLevelDispatch), or the default level (WdfExecutionLevelDefault). If the driver sets the default execution level for an object, KMDF does not guarantee callbacks at a particular IRQL, but can call them at any IRQL <= Dispatch_Level. For example, KMDF would call the routines at DISPATCH_LEVEL if an upper-level driver or KMDF itself already holds a spin lock.
Setting dispatch execution level for an object does not force its callbacks to occur at DISPATCH_LEVEL. Instead, it indicates that the callbacks are designed to be called at DISPATCH_LEVEL
and so do not take any actions that might cause a page fault. If such a
callback requires synchronization, KMDF uses a spin lock, which raises IRQL to DISPATCH_LEVEL.
Selecting passive execution level means that KMDF calls the callbacks at PASSIVE_LEVEL, even if it must queue a work item to do so. This level is typically useful only for very basic drivers, such as the Toaster sample. The Featured Toaster driver specifies WdfExecutionLevelPassive, thus ensuring that certain callbacks are called at PASSIVE_LEVEL so it can use pageable data in all such callbacks.
Most drivers must coexist in a stack with WDM drivers and therefore should accept the default.
5.2.3. EvtCleanupCallback Function
KMDF calls the object’s EvtCleanupCallback
callback when the object is being deleted, so that the driver can
perform any cleanup related to the object. Device objects are typically
deleted during device removal processing, so the EvtCleanupCallback for a device object is not called unless the device is removed.
If the device explicitly takes out a reference on an object (by calling WdfObjectReference), it must implement EvtCleanupCallback
for that object so that it can release the reference. This callback
also typically deallocates memory buffers that the driver previously
allocated on a per-object basis and stored in the object context area.
In
many drivers, this callback is not required. Remember that KMDF
destroys the child objects of each parent object before deleting the
parent object itself. If you allocate a WDFMEMORY buffer for object-specific storage and ensure that the ParentObject
field in the attributes structure of the buffer points to the device
object, you can often avoid the necessity of deallocating the buffer
because KMDF deletes it automatically
In the Featured Toaster sample, this function is simply a stub.
5.3. Setting Additional Device Object Attributes
The following listing shows how the Featured Toaster driver sets these additional attributes and creates the WDFDEVICE_OBJECT:
//
// Set the Synchronization scope to device so that only one Evt
// callback for this device is executing at any time. This
// eliminates the need to hold any lock to synchronize access
// to the device context area.
//
// By specifying passive execution level, driver ensures
// that the framework will never call the I/O callbacks
// at DISPATCH_LEVEL.
//
fdoAttributes.SynchronizationScope =
WdfSynchronizationScopeDevice;
fdoAttributes.ExecutionLevel = WdfExecutionLevelPassive;
//
// Set a context cleanup routine to clean up any resources
// that are not defined in the parent to this device. This
// cleanup routine will be called during remove-device
// processing when the framework deletes the device object.
//
fdoAttributes.EvtCleanupCallback =
ToasterEvtDeviceContextCleanup;
//
// DeviceInit is completely initialized. Now call the
// framework to create the device object.
//
status = WdfDeviceCreate(&DeviceInit, &fdoAttributes,
&device);
if(!NT_SUCCESS(status)) {
KdPrint(("WdfDEviceCreate failed with Stats code 0x%x\n",
status));
return status;
}
The driver sets the attributes in fdoAttributes, and then creates the WDFDEVICE object.