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:

Calling KeSetTimer or KeSetTimerEx with the same Timer and Dpc pointers before the previously specified interval expires has all of the following effects:

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:

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.