Some drivers perform I/O activities that
are not related to queued I/O requests or must be synchronized with
activities of WDM drivers in the same device stack. For example, a
driver might maintain a timer that monitors
the status of its device. Similarly, a driver might be required to
communicate with its device or another driver at a particular point
during the device’s start-up or shut-down sequence. KMDF provides
self-managed I/O to accommodate such requirements. The self-managed I/O
callbacks correspond more closely to the underlying WDM Plug and Play
and power management IRPs than do other WDF Plug and Play and power management callbacks.
To use self-managed I/O, a driver implements the
self-managed I/O event callbacks. KMDF calls these callbacks during
Plug and Play and power state transitions when the device is added to
or removed from the system, when the device is stopped to rebalance
resources, when the idle device transitions to a low-power state, and
when the device returns to the working state from a low-power idle
state.
The following are the self-managed I/O callbacks:
EvtDeviceSelfManagedIoInit
EvtDeviceSelfManagedIoSuspend
EvtDeviceSelfManagedIoFlush
EvtDeviceSelfManagedIoCleanup
EvtDEviceSelfManagedIoRestart
1. Self-Managed I/O Device Startup and Restart
When the system is booted or the user plugs in the device, KMDF calls the driver’s EvtDeviceSelfManagedIoInit callback after the driver’s EvtDeviceDOEntry function has returned but before KMDF completes the underlying Plug and Play or power IRP.
KMDF calls this function only during the initial start-up sequence; it
does not call this function when the device returns to the working
state from a low-power state.
The EvtDeviceSelfManagedIoInit
callback should perform whatever tasks are required to initiate the I/O
that the framework doesn’t manage. For example, a driver that must
monitor the state of its device might initialize and start a timer.
When the device returns to the working state from a
low-power state, such as occurs when the device has been idle or has
been stopped to rebalance resources, KMDF calls the EvtDeviceSelfManagedIoRestart callback.
Like the self-managed I/O initialization callback,
this function is the last one that is called after the device returns
to the working state, but before WDF completes the underlying IRP. EvtDeviceSelfManagedIoRestart should resume any I/O activities that EvtDeviceSelfManagedIoInit
initialized and that were later suspended when the device exited from
the working state. Typically, this means that it reverses the actions
of EvtDeviceSelfManagedIoSuspend.
EvtDeviceSelfManagedIoRestart is called only after EvtDeviceSelfManagedIoSuspend
has previously been called. This function is called only when the
device has been in a low-power state or its resources have been
rebalanced; it is not called when the user initially plugs in the
device.
2. Self-Managed I/O During Device Power-Down and Removal
When the device is powered down or removed, KMDF
calls one or more of the self-managed I/O callbacks so that the driver
can stop and clean up after its self-managed I/O operations.
Every time the device goes through the power-down
sequence—whether because it is idle, it is being removed, or system
resources are being rebalanced—KMDF calls the EvtDeviceSelfManagedIoSuspend
callback. This function should stop any self-managed I/O activities
that are in progress and must be handled while the device is present.
During rebalance, power-down, and orderly removal, it is called while
the device is still operational, before EvtDeviceDOExit. During surprise removal, it is called before EvtDeviceSurpriseRemoval if the device was in a low-power state and afterward if the device was in the DO state.
If the device is being removed, KMDF calls EvtDeviceSelfManagedIoFlush
after the device has been stopped. This function should fail any I/O
requests that the driver did not complete before the device was
removed. It is called after the driver’s EvtDEviceSelfManagedIoSuspend and EvtDeviceDOExit functions have returned.
Finally, KMDF calls EvtDeviceSelfManagedIoCleanup
after device removal is complete. This function should ensure that all
self-managed I/O has stopped completely and should release any
resources that EvtDEviceSelfManagedIoInit allocated for self-managed I/O. The clean-up function is called only once.
3. Implementing a Watchdog Timer
The PCIDRV
sample uses self-managed I/O to implement a watchdog timer, which is
used during hardware link detection to check for hangs. Implementing
the watchdog timer involves the following driver tasks:
Setting callbacks for the self-managed I/O events.
Initializing the timer in EvtDeviceSelfManagedIoInit.
Stopping the timer in EvtDeviceSelfManagedIoSuspend.
Restarting the timer in EvtDeviceSelfManagedIoRestart.
Deleting the timer and resources in EvtDeviceSelfManagedIoCleanup.
The PCIDRV sample does not implement the EvtDeviceSelfManagedIoFlush callback because no I/O requests are involved in its self-managed I/O. The suspend and clean-up callbacks are sufficient.
A driver registers its self-managed I/O callbacks by setting their entry points in the WDF_PNP_POWER_CALLBACKS structure along with the other Plug and Play and power event callbacks (such as EvtDEviceDOEntry and EvtDeviceDOExit, among others). The driver sets these in the EvtDriverDeviceAdd callback, before it creates the WDFDEVICE object.
3.1. Code to Set Self-Managed I/O Callbacks
The PCIDRV sample registers these callbacks in the PciDrvEvtDeviceAdd function in pcidrv.c:
WDF_PNPPOWER_EVENT_CALLBACKS pnpPowerCallbacks;
//
// Initialize the PnpPowerCallbacks structure.
//
WDF_PNPPOWER_EVENT_CALLBACK_INIT (&pnpPowerCallbacks);
//
// Set entry points for self-managed I/O callbacks.
//
pnpPowerCallbacks.EvtDeviceSElfManagedIoInit =
PciDrvEvtDEvicesSelfManagedIoInit;
pnpPowerCallbacks.EvtDeviceSelfManagedIoCleanup =
PciDrvEvtDeviceSelfManagedIoCleanup;
pnpPowerCallbacks.EvtDeviceSelfManagedIoSuspend =
PciDrvEvtDeviceSelfManagedIoSuspend;
pnpPowerCallbacks.EvtDeviceSelfManageIoRestart =
PciDrvEvtDeviceSelfManagedIoRestart;
//
// Register the PnP and power callbacks.
//
WdfDeviceInitSetPnpPowerEventCallbacks (DeviceInit,
&pnpPowerCallbacks);
As the example shows, the PCIDRV sample sets callbacks for EvtDeviceSelfManagedIoInit, EvtDeviceSelfManagedIoCleanup, EvtDeviceSelfManagedIoSuspend, and EvtDeviceSelfManagedIoRestart.
3.2. Code to Create and Initialize the Timer
The PCIDRV sample creates, initializes, and starts the watchdog timer in its EvtDeviceSelfManagedIoInit callback. The watchdog timer is a WDF time object (WDFTIMER). When the timer expires, KMDF queues a DPC, which calls the driver’s EvtTimerFunc.
The following code is the sample’s EvtDeviceSelfManagedIoInit callback, which appears in the pcidrv.c source file:
NTSTATUS
PciDrvEvtDeviceSelfManagedIoInit (
IN WDFDEVICE Device
)
{
PFDO_DATA fdoData = NULL;
WDF_TIMER_CONFIG wdfTimerconfig;
NTSTATUS status;
WDF_OBJECT_ATTRIBUTES timerAttributes;
PAGED_CODE ();
TraceEvents (TRACE_LEVEL_INFORMATION, DBG_PNP,
"--> PciDrvEvtDeviceSelfManagedIoInit\n");
fdoData = FdoGetData (Device);
//
// To minimize init-time, create a timer DPC to do link
// detection. The DPC will also be used to check for hardware
// hang.
WDF_TIMER_CONFIG_INIT (&wdfTimerconfig,
NICWatchDogEvtTimerFunc);
WDF_OBJECT_ATTRIBUTES_INIT (&timerAttributes);
timerAttributes.ParentObject = fdoData->WdfDevice;
status = WdfTimerCreate (
&wdfTimerConfig,
&timerAttributes,
&fdoData->WatchDogTimer
);
if (!NT_SUCCESS (status))
{
TraceEvents (TRACE_LEVEL_ERROR, DBG_PNP,
"Error: WdfTimerCreate create failed 0x%x\n",
Status);
return status;
}
NICStartWatchDogTimer (fdoData);
TraceEvents (TRACE_LEVEL_INFORMATION, DBG_PNP,
"<-PciDrvEvtDeviceSelfManagedIoInit\n");
return status;
}
The driver declares two structures for use in creating the timer: a WDF_TIMER_CONFIG structure named wdfTimerConfig and a WDF_OBJECT_ATTRIBUTES structure named timerAttributes. To initialize the WdfTimerConfig structure, the driver uses the WDF_TIMER_CONFIG_INIT function, passing as parameters the WdfTimerConfig structure and a pointer to NICWatchdogEvtTimerFunc, which is the driver’s EvtTimerFunc callback.
Next, the driver initializes the attribute’s structure by using the WDF_OBJECT_ATTRIBUTES_INIT function. By default, a timer object has no parent, so the driver sets the ParentObject field to the device object (WdfDevice) so that KMDF deletes the timer when it deletes the device object.
Finally, the driver calls WdfTimerCreate
to create the timer, passing as parameters the configuration structure,
the attribute’s structure, and a location to receive the handle to the
timer. If KMDF successfully creates the timer, PCIDRV calls the internal function NICStartWatchDogTimer to start the timer.
3.3. Code to Start the Timer
The NICStartWatchDogTimer function appears in the sys/HW/isrdpc.c source file, as follows:
VOID
NICStartWatchDogTimer (
IN PFDO_DATA FdoData
)
{
LARGE_INTEGER dueTime;
if (!FdoData->CheckForHang)
{
//
// Set the link detection flag to indicate that
// NICWatchDogEvtTimerFunc
// is first doing link detection.
//
MP_SET_FLAG (FdoData,
fMP_ADAPTER_LINK_DETECTION);
FdoData->CheckforHang = FALSE;
FdoData->bLinkDetectionWait = FALSE;
FdoData->bLookForLink = FALSE;
dueTime.QuadPart = NIC_LINK_DETECTION_DELAY;
}
else
{
dueTime.QuadPart = NIC_CHECK_FOR_HANG_DELAY;
}
WdfTimerStart (FdoData->WatchDogTimer,
dueTime.QuadPart
);
return;
}
This function sets the expiration time for the timer
to a hardware-dependent value, depending on whether the driver is
attempting to detect a link or check for a device hang. It then starts
the timer by calling WdfTimerStart. When the timer expires, KMDF queues a DPC that invokes the driver’s timer function, NICWatchDogEvtTimerFunc. The timer function performs the required task (link detection or check for hang) and then restarts the timer by calling WdfTimerStart in the same way shown in the preceding example.
3.4. Code to Stop the Timer
When the device leaves the working state or is removed from the system, KMDF calls the EvtDeviceSelfManagedIoSuspend callback. In the PCIDRV sample, this callback stops the timer, as the following code from pcidrv.c shows:
NTSTATUS
PciDrvEvtDeviceSelfManagedIoSuspend (
IN WDFDEVICE Device
)
{
PFDO_DATA fdoData = NULL;
PAGED_CODE ();
fdoData = FdoGetData (Device);
//
// Stop the watchdog timer and wait for DPC to run
// to completion
// if it has already fired.
//
WdfTimerStop (fdoData->WatchDogTimer, TRUE);
return STATUS_SUCCESS;
}
To stop the timer, the driver simply calls WdfTimerStop, passing as parameters the handle to the timer and a Boolean value. The PCIDRV sample passes TRUE to specify that if the driver has any DPCs in the DPC queue (including the NICWatchDogEvtTimerFunc timer DPC function), KMDF should wait until all of those functions have returned before stopping the timer. Specifying FALSE means that KMDF should stop the timer immediately without waiting for any DPCs to complete.
WdfTimerStop is defined as a Boolean function, which returns TRUE if the timer object was in the system’s timer queue. However, the PCIDRV sample does not check the return value because it waits for all the driver’s DPCs to complete, so whether the timer was already set is not important.
3.5. Code to Restart the Timer
When the device returns to the working state after being in low-power state, KMDF calls the EvtDeviceSelfManagedIoRestart callback. In the PCIDRV driver, this callback restarts the timer as follows, from the pcidrv.c source file:
NTSTATUS
PciDrvEvtDeviceSelfManagedIoRestart (
IN WDFDEVICE Device
)
{
PFDO_DATA fdoData;
PAGED_CODE ();
fdoData = FdoGetData (Device);
//
// Restart the watchdog timer.
//
NICStartWatchDogTimer (fdoData);
return STATUS_SUCCESS;
}
Restarting the timer simply requires a call to the internal NICStartWatchDogTimer,
as previously discussed. Because the device object and the timer (a
child of the device object) were not deleted when the device
transitioned out of the working state, the driver is not required to
reinitialize or recreate the timer object.
3.6. Code to Delete the Timer
When the device is removed, the driver deletes the timer in its EvtDeviceSelfManagedIoCleanup function, as follows:
VOID
PciDrvEvtDeviceSelfManagedIoCleanup (
IN WDFDEVICE Device
)
{
PFDO_DATA fdoData = NULL;
PAGED_CODE ();
fdoData = FdoGetData (Device);
if (fdoData->WatchDogTimer)
{
WdfObjectDelete (fdoData->WatchDogTimer);
}
return;
}
To delete the timer, the driver simply calls WdfObjectDelete,
passing a handle to the timer object. If the driver had allocated any
additional resources related to the timer, it would release those
resources in this function.