Every KMDF driver that supports Plug and Play must have an EvtDriverDeviceAdd callback function. EvtDriverDeviceAdd is responsible for creating and initializing a device object and related resources. The framework calls the driver's EvtDriverDeviceAdd callback when the system discovers a device that the driver controls.
The EvtDriverDeviceAdd callback creates a WDFDEVICE object to represent the device and performs numerous initialization tasks to provide the framework with the information that it requires to set up its own internal structures.
The EvtDriverDeviceAdd function thus proceeds as follows:
Fills in a device initialization structure with information that is used to create the device object.
Sets up the device object's context area.
Creates the device object.
Performs additional initialization and startup tasks, such as creating I/O queues and a device interface.
A bus driver that enumerates child devices typically creates multiple device objects: an FDO for the bus itself and a PDO for each child device that is attached to the bus. After the bus driver creates the PDOs, the system loads the function drivers for the child devices and the framework calls their EvtDriverDeviceAdd functions, which in turn create the FDOs for the child devices.
Unlike most other KMDF objects, the device object has no configuration structure. Instead, a driver configures a device object by using a WDFDEVICE_INIT structure. The framework calls EvtDriverDeviceAdd with a pointer to the WDFDEVICE_INIT structure and the driver calls the WdfDeviceInitXxx methods to fill in the structure with information about the device and driver. The framework uses this information later when it creates the WDFDEVICE object.
A driver can call the WdfDeviceInitXxx methods to:
Set device characteristics.
Set I/O type.
Create a context area or set other attributes for I/O requests that the framework delivers to the driver.
Register Plug and Play and power management callbacks.
Register event callbacks for file create, close, and cleanup.
In addition, specific initialization tasks apply to FDOs, filter DOs, and PDOs. Initialization of FDOs and filter DOs is described in the following sections. PDOs have special requirements; see "Child Device Enumeration (KMDF PDOs Only)" later in this chapter.
By default, the FDO controls power policy for its device. If the device can idle in a low-power state or generate a wake signal, the driver typically calls WdfDeviceInitSetPowerPolicyEventCallbacks to register power policy callback functions for the FDO.
FDOs can add and remove resources from the resource requirements list as reported by the bus driver. A KMDF driver implements the EvtDeviceFilterRemoveResourceRequirements and EvtDeviceFilterAddResourceRequirements callbacks and registers them by calling WdfFdoInitSetEventCallbacks.
If the driver adds resources to the requirements list, the driver must remove the added resources from the final list of assigned resources before the device starts. A KMDF driver supplies an EvtDeviceRemoveAddedResources callback. Drivers for legacy devices sometimes add and remove resources, but drivers for modern Plug and Play devices rarely do.
Other WdfFdoInitXxx methods retrieve device properties, return a pointer to the WDM PDO for the device stack, provide access to the registry key for the device, and perform other FDO-specific tasks.
The driver must call WdfFdoInitSetFilter to notify the framework that the device object represents a filter. This method causes the framework to change its default for I/O requests that the driver does not handle. When an I/O request arrives that the filter DO does not handle, the framework passes the request down to the next lower driver instead of failing the request, as is the default for FDOs and PDOs. For a filter DO, the driver can call the same WdfDeviceInitXxx methods as for an FDO.
Chapter 5 describes the object context area Drivers typically store data that pertains to a device object with the object itself. A KMDF driver uses an object context area. The object context area is allocated from the nonpaged pool and has a driver-defined layout. When the KMDF driver creates the device object, it typically initializes the context area and specifies its size and type. When the framework deletes the object, it deletes the context area, too.
Listing 6-5 shows how the Osrusbfx2 driver defines the context area for its device object. This code appears in the Osrusbfx2.h header file.
Listing 6-5: Definition of a device object context area
typedef struct _DEVICE_CONTEXT { WDFUSBDEVICE UsbDevice; WDFUSBINTERFACE UsbInterface; WDFUSBPIPE BulkReadPipe; WDFUSBPIPE BulkWritePipe; WDFUSBPIPE InterruptPipe; UCHAR CurrentSwitchState; WDFQUEUE InterrputMsgQueue; } DEVICE_CONTEXT, *PDEVICE_CONTEXT; WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(DEVICE_CONTEXT, GetDeviceContext)
As the example shows, the header file defines a context area of type DEVICE_CONTEXT and then invokes the WDF_DECLARE_CONTEXT_TYPE_WITH_NAME macro. This macro creates an accessor method that is associated with a context type. Thus, when the Osrusbfx2 driver's EvtDriverDeviceAdd callback is called, the GetDeviceContext accessor method has already been created to read and write a context area of type DEVICE_CONTEXT.
To associate the named context area with an object, the driver must initialize the object's attributes structure with information about the context area by calling the macro WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE from the EvtDriverDeviceAdd callback.
After the KMDF driver calls the required WdfDeviceInitXxx methods to fill in the WDFDEVICE_INIT structure, it sets attributes for the device object in an attributes structure. For a device object, the attributes nearly always include the size and type of the context area and often include an object cleanup callback and possibly the synchronization scope as well.
The driver passes the attributes structure and the WDFDEVICE_INIT structure to WdfDeviceCreate to create the device object. WdfDeviceCreate creates a WDFDEVICE object, attaches the device object to the Plug and Play device stack, and returns a handle to the object.
Chapter 5, "WDF Object Model," describes the attributes structure.
After the EvtDriverDeviceAdd callback creates the device object, this callback should:
Set device idle policy and wake settings if the device object owns power policy.
Chapter 7, "Plug and Play and Power Management," discusses power policy.
Register I/O callbacks and create I/O queues for the device object.
Chapter 8, "I/O Flow and Dispatching," provides details.
Create a device interface, if required, by calling WdfDeviceCreateDeviceInterface.
Create an interrupt object if the hardware supports interrupts.
Chapter 16, "Hardware Resources and Interrupts," discusses interrupt handling.
Create WMI objects.
Chapter 12, "WDF Support Objects," provides implementation guidelines.
The framework starts the queues and connects the interrupt object at the appropriate time later, during start-device processing.
Listing 6-6 shows an abridged version of the Osrusbfx2 driver's EvtDriverDeviceAdd callback function. The numbered comments are explained following the example.
Listing 6-6: Sample EvtDriverDeviceAdd callback function
NTSTATUS OsrFxEvtDeviceAdd( IN WDFDRIVER Driver, IN PWDFDEVICE_INIT DeviceInit ) { WDF_PNPPOWER_EVENT_CALLBACKS pnpPowerCallbacks; WDF_OBJECT_ATTRIBUTES attributes; NTSTATUS status; WDFDEVICE device; WDF_DEVICE_PNP_CAPABILITIES pnpCaps; UNREFERENCED_PARAMETER(Driver); PAGED_CODE(); // Initialize the pnpPowerCallbacks structure. . . .// Code omitted. //[1] Register Plug and Play and power callbacks WdfDeviceInitSetPnpPowerEventCallbacks(DeviceInit, &pnpPowerCallbacks); //[2] Indicate what type of I/O this driver performs. WdfDeviceInitSetIoType(DeviceInit, WdfDeviceIoBuffered); //[3] Intialize the object attributes structure WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, DEVICE_CONTEXT); //[4] Call the framework to create the device object. status = WdfDeviceCreate(&DeviceInit, &attributes, &device); if (!NT_SUCCESS(status)) { return status; } // Set device PnP capabilities and configure and create I/O queues. . . .// Code omitted. //[5] Register a device interface. status = WdfDeviceCreateDeviceInterface(device, &GUID_DEVINTERFACE_OSRUSBFX2, NULL);// Reference String if (!NT_SUCCESS(status)) { return status; } return status; }
The EvtDriverDeviceAdd function in the listing proceeds as follows:
Registers Plug and Play and Power callbacks.
The driver initializes a structure-which is not shown-to contain information about the callback functions that handle Plug and Play and power management events and calls WdfDeviceInitSetPnpPowerEventCallbacks to record this information in the WDFDEVICE_INIT structure.
Sets the I/O type.
The driver indicates whether this device object performs buffered, direct, or neither buffered nor direct I/O for read and write requests. The driver sets WdfDeviceIoBuffered, which is the default.
Initializes the object attributes structure.
The driver initializes the object attributes structure with the type of the context area, so that the framework can create the context area for the device object.
Creates the device object.
The driver calls WdfDeviceCreate to create the device object. It passes the WDFDEVICE_INIT and object attributes structures that it has filled in and receives a handle to the device object in return.
Creates a device interface.
To create a device interface, the Osrusbfx2 driver calls WdfDeviceCreateInterface. The driver passes a handle to the device object, a pointer to a GUID, and a pointer to an optional reference string. The GUID is defined in the Public.h header file. A driver can use the reference string to distinguish two or more devices of the same interface class, that is, two or more devices that have identical GUIDs. The Osrusbfx2 driver passes NULL for the string.
By default, the framework enables the device's interfaces when the device enters the working state and disables them when the device leaves the working state. Therefore, in most circumstances, the driver is not required to enable or disable an interface.