14.2.2 Using CustomTimerDpc Routines
As already mentioned in Section 14.1.2, an NT driver must call KeSetTimer or KeSetTimerEx with pointers to its timer and DPC objects and with an expiration time to have the driver's CustomTimerDpc routine queued for execution when the given DueTime arrives.
The expiration time is specified in units of 100 nanoseconds, with negative values indicating an interval relative to the current system time. When DueTime arrives, the CustomTimerDpc routine can be called.
The elapsed time until a driver's CustomTimerDpc routine begins execution can be somewhat longer than the DueTime interval specified when the driver called KeSetTimer or KeSetTimerEx. The NT Kernel queues the given Dpc at the expiration of the timer interval.
If the system time changes before a timer expires, relative timers are not affected but the system adjusts absolute timers. A relative timer always expires after the specified number of time units elapses, regardless of the absolute system time. An absolute timer expires at a specific system time, so a change in the system time changes the wait duration of an absolute timer.
A driver can associate a CustomTimerDpc routine with a timer in the call to KeSetTimer and the routine will be called after the timer expires. To set a recurring timer use KeSetTimerEx which accepts the same parameters as KeSetTimer plus an additional Period parameter.
If a driver routine calls KeSetTimer or KeSetTimerEx with a Dpc pointer for its CustomTimerDpc routine, a subsequent call to KeCancelTimer with the same Timer pointer has one of the following effects:
·If the most recently specified interval passed to KeSetTimer or KeSetTimerEx has not yet expired, the timer object and the DPC object representing the CustomTimerDpc routine are dequeued and disabled. In other words, the call to KeCancelTimer cancels the call to the driver's CustomTimerDpc routine.
·If the most recently specified interval passed to KeSetTimer or KeSetTimerEx is just about to expire when KeCancelTimer is called, the CustomTimerDpc might or might not be called, depending on whether a system clock interrupt causes the timer object to be removed from the timer queue before KeCancelTimer executes.
Calling KeSetTimer or KeSetTimerEx with the same Timer and Dpc pointers before the previously specified interval expires has all of the following effects:
·The new call to KeSetTimer or KeSetTimerEx implicitly cancels the previously specified DueTime for the already queued timer object.
·The new DueTime is set in the timer object.
·For KeSetTimer, the CustomTimerDpc routine is called only once, after the most recently specified DueTime has expired. For a periodic timer set with KeSetTimerEx, one instance of the timer (and thus one execution of the CustomTimerDpc routine) is deleted.
Note that using the same timer object for different purposes can cause race conditions or serious driver errors. For example, if the same Timer pointer is used both to set up a call to a CustomTimerDpc routine and to set up waits in a driver-dedicated thread, such a thread would cancel calls to the CustomTimerDpc routine at random whenever that thread called KeSetTimer (or KeSetTimerEx or KeCancelTimer) with the common Timer pointer and the timer object was already queued for a CustomTimerDpc call.
For drivers that have CustomTimerDpc routines and wait on timer objects in a nonarbitrary thread context:
·Never use a thread-context-sensitive timer object in a nonarbitrary thread context and vice versa.
·Each CustomTimerDpc should have its own associated timer object. Each set of driver threads and/or driver routines that are called in a nonarbitrary thread context should have its own set of "waitable" timer objects.
A driver writer who uses a CustomTimerDpc routine should exercise care in choosing the interval(s) the driver passes in calls to KeSetTimer or KeSetTimerEx. In addition, the driver writer must consider all possible effects of a call to KeCancelTimer with the same timer object from any driver routine that makes this call, particularly in Windows NTŪ SMP platforms.
NT driver writers should always keep in mind the following fact about their CustomTimerDpc and/or CustomDpc routines
Only one instantiation of a DPC object representing a particular DPC routine can be queued for execution at any given moment.
If two driver routines call KeSetTimer or KeSetTimerEx to run the same CustomTimerDpc routine and the interval specified by the first caller has not expired, that 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 nonperiodic timers from a DPC routine.
Consider the following a design guideline for NT 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, if the StartIo routine calls KeSetTimer or KeSetTimerEx to run a CustomTimerDpc and the 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 such a for-common-use DPC routine.
In addition, such a DPC used by two standard driver routines with very different functionality would necessarily have poorer performance characteristics than separate CustomTimerDpc and CustomDpc routines. Such a DPC would have to determine which operations to carry out to suit the reason(s) it was queued from the StartIo routine or ISR, and testing conditions in the DPC would use additional CPU cycles.