Using a CustomTimerDpc Routine
To disable a previously set timer object, a driver calls KeCancelTimer. This routine removes the timer object from the system's timer queue. Generally, the timer object is not set to the signaled state and the CustomTimerDpc routine is not queued for execution. However, if the timer is about to expire when KeCancelTimer is called, expiration might occur before KeCancelTimer has a chance to access the time queue, in which case signaling and DPC queuing will occur.
The kernel removes the timer object from the timer queue, without setting the object to the signaled state or queuing the CustomTimerDpc routine.
The kernel reinserts the timer object in the timer queue, using the new DueTime value.
Using the same timer object for different purposes can cause race conditions or serious driver errors. For example, assume that a driver specifies a single timer object both to set up a call to a CustomTimerDpc routine and to set up waits in a driver-dedicated thread. Whenever the driver-dedicated thread calls KeSetTimer, KeSetTimerEx, or KeCancelTimer for the common timer object, the thread would cancel calls to the CustomTimerDpc routine, if the timer object was already queued for a CustomTimerDpc call.
If a driver has CustomTimerDpc routines, and also waits on timer objects in a nonarbitrary thread context, it should:
Never use a thread-context-sensitive timer object in a nonarbitrary thread context, or vice versa.
Allocate a separate timer object for each CustomTimerDpc routine. Each set of driver threads or driver routines that are called in a nonarbitrary thread context should have its own set of "waitable" timer objects.
If you use a CustomTimerDpc routine, choose carefully the interval the driver passes in calls to KeSetTimer or KeSetTimerEx. In addition, consider all possible effects of a call to KeCancelTimer with the same timer object from any driver routine that makes this call, particularly on SMP platforms.
Keep in mind the following fact about CustomTimerDpc routines:
Only one instantiation of a DPC object representing a particular DPC routine can be queued for execution at any given moment.
If a second driver routine calls KeSetTimer or KeSetTimerEx to run the same CustomTimerDpc routine before the interval specified by the first caller expires, the CustomTimerDpc routine is run only after the interval specified by the second caller expires. In these circumstances, the CustomTimerDpc does none of the work for which the first routine called KeSetTimer or KeSetTimerEx.
For drivers that have CustomTimerDpc routines and use periodic timers:
A driver cannot deallocate a periodic timer from a DPC routine. Drivers can deallocate only nonperiodic timers from a DPC routine.
Consider the following a design guideline for drivers that have both CustomDpc and CustomTimerDpc routines:
To prevent race conditions, never pass the same Dpc pointer to KeSetTimer or KeSetTimerEx and KeInsertQueueDpc.
In other words, suppose a driver's StartIo routine calls KeSetTimer or KeSetTimerEx to queue a CustomTimerDpc routine, and the driver's ISR calls KeInsertQueueDpc simultaneously from another processor with the same Dpc pointer. That DPC routine will be run when IRQL on a processor falls below DISPATCH_LEVEL or the timer interval expires, whichever comes first. Whichever does come first, some essential work for the StartIo or ISR would simply be dropped by the DPC routine.
In addition, a DPC used by two standard driver routines with very different functionality would have poorer performance characteristics than separate CustomTimerDpc and CustomDpc routines. The DPC would have to determine which operations to carry out, depending on the conditions that caused the StartIo routine or ISR to queue it. Testing for these conditions in the DPC would use additional CPU cycles.