Every driver, whether it runs in user mode or kernel mode, must implement certain functions and use certain objects, as follows:
An entry point at which the driver is called when it is loaded.
A driver object, which represents the driver.
One or more device objects, which represent the devices that the driver controls.
Additional objects that the driver uses with the device objects to control the device and manage the flow of I/O requests to the device.
Event callbacks to handle events of importance to the driver.
Every driver has a driver object, which represents the driver in the framework. The driver supplies the framework with information about the callbacks that it supports for the driver object.
When the system enumerates a device that the driver controls, the driver creates a device object to represent that device and supplies information about the event callbacks for the device objects. The driver also creates I/O queues to handle incoming requests for the device object. Many drivers also create additional support objects, including I/O target objects that represent the driver's targets for I/O requests.
After the driver creates the driver object, device objects, queues, and additional support objects, the working structure of the driver-its internal infrastructure-is in place and initialization is essentially complete.
Although both UMDF drivers and KMDF drivers require the same kinds of objects and structures, the implementations differ in many details, as described in this chapter.
Every UMDF driver must do the following:
Implement DllMain as the driver's entry point.
Implement and export by name the DllGetClassObject function.
Implement the IClassFactory interface to create a driver object.
Implement a driver callback object that exposes the IDriverEntry interface.
Implement a device callback object that exposes callback interfaces for the device object.
In addition, every UMDF driver creates one or more I/O queues. Each queue has a corresponding callback object that exposes callback interfaces for the I/O events that the driver handles. Drivers can also create additional objects to support any other requirements.
Chapter 18, "An Introduction COM," provides more information about DllMain, DllGetClassObject, and IClassFactory.
The IDriverEntry interface includes methods that initialize and uninitialize the driver and create a device object when the device is added to the system. The framework calls these methods when the driver is loaded or unloaded and when the PnP manager enumerates one of the driver's devices.
Figure 6-1 shows how control flows through UMDF driver loading, initialization, and operation.
Figure 6-1: Flow of control for a UMDF driver
When Windows starts, it loads the UMDF driver manager and the following actions occur:
The driver manager creates a new driver host process, creates a framework driver object, and then calls the system's DLL loader, which loads the driver DLL by calling the DllMain entry point. DllMain performs some types of global initialization for the driver, such as starting tracing. The system imposes some restrictions on what DllMain can do-most importantly, it must not take any actions that cause another library to be loaded. Most driver initialization should instead occur in the OnInitialize method.
The framework calls the driver's DllGetClassObject function to get a pointer to an IClassFactory interface that can create a driver callback object in the driver. DllGetClassObject returns a pointer to the driver's IClassFactory interface.
The framework calls the IClassFactory::CreateInstance method to create an instance of the driver callback object.
The driver callback object exposes the IDriverEntry interface, which includes methods to initialize the driver, to notify it that one of its devices has been enumerated and to prepare it for unloading.
The framework calls IDriverEntry::OnInitialize to initialize the driver. OnInitialize initializes driver-wide data and performs any additional tasks that DllMain cannot perform.
When the driver's device is enumerated, the framework calls IDriverEntry:: OnDeviceAdd.
OnDeviceAdd performs any required configuration, creates a device callback object to represent the device, creates any required device interfaces, and creates and configures the queues into which the framework will place I/O requests that are targeted at the driver.
Note A device interface describes a set of features provided by the device as a whole, which a driver exposes to applications or other system components, whereas a COM interface is a related group of functions that comprise the capabilities of a specific internal object.
As events occur, the framework invokes methods on the driver's callback objects to handle them. The framework gets pointers to the callback interfaces by calling QueryInterface on the callback objects.
When the device is removed, the framework calls the relevant driver callbacks and then releases the device callback object.
The framework calls the IDriverEntry::OnDeinitialize method to clean up after device removal and then the framework releases the driver object.
The framework calls DllMain, unloads the DLL, and deletes the driver host process.
Tip See "DllMain" on MSDN for more information about DllMain-online at http://go.microsoft.com/fwlink/?LinkId=80069.
Currently, UMDF loads the drivers for a single device stack into a host process. Therefore, the DllGetClassObject function and IDriverEntry::Xxx methods are called only once for each device. Another device that uses the same driver loads in a different host process and these initialization functions are invoked again.
For this reason, IDriverEntry::OnInitialize might seem superfluous. The driver is only loaded once, so why not do all of the driver initialization in DllMain or in the driver class constructor?
The system places certain restrictions on what a DLL can safely do in DllMain, so it's best to do as little as possible in that function to avoid the risk of deadlocking the loader. Use the driver class constructor to create a clean driver callback object, and use IDriverEntry::OnInitialize to perform any driver-wide initialization that OnDeinitialize later cleans up.
-Peter Wieland, Windows Driver Foundation Team, Microsoft
A KMDF driver consists of a DriverEntry function that identifies the driver as based on KMDF, a set of callback functions that the framework calls so that the driver can respond to events that affect its device, and other driver-specific utility functions. Every KMDF driver must have the following:
A DriverEntry function, which is the driver's primary entry point and creates a WDF driver object.
An EvtDriverDeviceAdd callback, which is called when the PnP manager enumerates one of the driver's devices.
This callback creates and initializes the device object, the queue objects, and other support objects that the driver requires for operation.
Drivers that support non-Plug and Play devices do not require an EvtDriverDeviceAdd callback.
One or more EvtXxx callbacks, which handle events that occur during driver operation.
A minimal KMDF driver for a simple device might have these functions and nothing more. The framework implements default power management and Plug and Play operations, so drivers that do not manipulate physical hardware can omit most Plug and Play and power management code. If a driver can use the defaults, it does not require code for many common tasks. The more device-specific features a device supports and the more functionality the driver provides, the more code the driver requires.
To see just how simple a loadable, working KMDF driver can be, look at the Simple Toaster sample's Toaster.c file. This file contains all of the source code-except for the header files-for the Simple Toaster. It creates a device interface and an I/O queue and can handle read, write, and device I/O control requests-all in fewer than 400 lines of code.
Driver unload functions KMDF drivers for Plug and Play devices do not require a driver unload function because the framework provides one by default. However, if the driver creates or allocates driver-wide resources in the DriverEntry function and uses them until the driver is unloaded, the driver should include an EvtDriverUnload callback to free those resources.
A non-Plug and Play driver can optionally register an EvtDriverUnload callback. The driver cannot be unloaded if it does not supply the callback.
Figure 6-2 shows how control flows through KMDF driver loading, initialization, and operation.
Figure 6-2: Flow of control for a KMDF driver
When Windows loads a KMDF driver, the driver is dynamically bound to a compatible version of the KMDF runtime library. The driver image contains information about the KMDF version against which it was built:
The WDF loader determines whether the required major version of the framework library is already loaded.
If not, it loads the required version of the library. If the driver requires a minor version of the runtime library that is newer than the one already loaded, the loader fails and then logs the failure in the system event log and none of the subsequent steps occur.
If the required version of the library has been loaded, the loader adds the driver as a client of the service and returns the relevant information to the framework. The framework calls the driver's DriverEntry function.
The DriverEntry function initializes driver-wide data, initiates tracing, and calls WdfDriverCreate to create the framework driver object.
The framework calls the EvtDriverDeviceAdd callback that the driver registered when it created the driver object.
The EvtDriverDeviceAdd callback creates and initializes a device object, creates and configures the I/O queues for the device object, creates any additional support object's that it requires for the device object and the queue objects, registers event callbacks for the objects it created, and registers any required device interfaces.
The framework calls the driver's EvtXxx functions to handle events. If additional devices are added that the driver controls, the EvtDriverDeviceAdd function is called again for each such device.
For a Plug and Play device, removal is an event for which the driver can register a callback. When the device is removed, the framework calls the appropriate event callbacks for the removal and then calls any cleanup event callbacks that the driver registered.