Managing Devices without Interrupts

< BACK  NEXT >
[oR]

Some devices don't generate interrupts with every significant state change. Even when the device does generate interrupts, a case can be made to drive an infrequently used, relatively slow device with a polled technique. Printer drivers are a common example of this technique. Since the printer device buffer is large in comparison to the physical print rate, a device driver has a considerable window in which it can occasionally check for the need to refill the buffer.

Working with Polled Devices

Once a device is started, it is generally unacceptable for driver code to hang in a tight loop waiting for the device to finish. In a multiprocessing and multitasking operating system such as Windows 2000, there are always useful chores that can or must occur in parallel with the device's operation.

By Microsoft edict, a driver is not allowed to stall in a tight polling loop for more than 50 microseconds. With today's extraordinary processor speeds, 50 microseconds is an eternity a period in which hundreds of instructions could have executed.

When polling is required, drivers can utilize one of four techniques based on the amount of required poll time and the context in which the stall occurs.

  • Driver code running at PASSIVE_LEVEL IRQL can call KeDelayExecutionThread, described in Table 11.3, to suspend a thread's execution for a specified interval. The thread is removed from the "ready to run" queue of threads and thus does not interfere with other "good to go" threads.

    This technique is available only to kernel-mode threads started by other driver code or during the device's initialization or cleanup code.

  • Driver code can "busy wait" the processor using KeStallExecutionProcessor, described in Table 11.4. The function call is equivalent to code hanging in a tight countdown loop, except that the time stalled is processor speed-independent. Threads should not stall the processor for more than 50 microseconds.

  • Synchonization objects, such as kernel events and mutexes, can be utilized. The "stalling" code then waits on the synchronization object to enter the signaled state an act for which another nonstalled code path must take responsibility.

  • A driver can utilize CustomTimerDpc routines to gain the benefit of I/O Timer functionality with controllable timing granularity.

If a device needs to be polled repeatedly, and the delay interval between each polling operation is more than 50 microseconds, the driver design should incorporate system threads (discussed in Chapter 14).

Table 11.3. Function Prototype for KeDelayExecution Thread
NSTATUS KeDelayExecutionThread IRQL == PASSIVE_LEVEL
Parameter Description
IN KPROCESSOR_MODE waitMode
  • KernelMode

  • UserMode

IN BOOLEAN bAlertable TRUE - if wait is canceled upon receipt of Async Procedure Call
FALSE - in kernel mode
IN PLARGE_INTEGER interval Wait interval in 100 nanosecond units
Return value Success or failure code

Table 11.4. Function Prototype for KeStallExecutionProcessor
VOID KeStallExecutionProcessor IRQL == Any Level
Parameter Description
IN ULONG interval Wait interval in microseconds
Return value - void -

How CustomTimerDpc Routines Work

A CustomTimerDpc routine is just a DPC routine that is associated with a kernel Timer object. The CustomTimerDpc routine runs after the timer's timeout value expires. The Kernel automatically queues the DPC routine for execution. When the Kernel's DPC dispatcher pulls the request from the queue, the CustomTimerDpc routine finally executes. Of course, there could be some delay between the moment the Timer object expires and the actual execution of the DPC routine.

Kernel Timer objects can be programmed to fire once or repeatedly. Thus, CustomTimerDpc routines can be scheduled to run at regular intervals. Like all other DPC routines, a CustomTimerDpc runs at DISPATCH_LEVEL IRQL. Table 11.5 shows the prototype for one of these routines. Notice the CustomTimerDpc routine always receives two reserved arguments from the system. The contents of these two system arguments are undefined. With CustomTimerDpc routines, there is a single context argument that is permanently associated with the DPC object.

CustomTimerDpc routines differ from I/O Timer routines in several ways.

  • Unlike I/O Timer routines, a CustomTimerDpc is not associated with any particular Device object. There can be one CustomTimerDpc for many device objects or many CustomTimerDpc's for one device object.

Table 11.5. Function Prototype for a CustomTimerDpc Routine
VOID CustomTimerDpc IRQL == Any Level
Parameter Description
IN PKDPC pDpc DPC object generating the request
IN PVOID pContext Context passed when DPC initialized
IN PVOID SystemArg1 Reserved
IN PVOID SystemArg2 Reserved
Return value - void -

  • The minimum resolution of an I/O Timer is one second; the expiration time of a CustomTimerDpc is specified in units of 100 nanoseconds. In reality, the resolution is limited to about 10 milliseconds.

  • The I/O Timer always uses a one-second interval. The expiration interval for a CustomTimerDpc can be specified differently with each firing.

  • The storage for an I/O Timer object is automatically part of the Device object. To use a CustomTimerDpc, both a KDPC and a KTIMER object must be manually declared in nonpaged storage.

How to Set Up a CustomTimerDpc Routine

Working with CustomTimerDpc routines is very straightforward. A driver simply needs to follow these steps.

  1. Allocate nonpaged storage (usually in a device or controller extension) for both a KDPC and a KTIMER object.

  2. AddDevice calls KeInitializeDpc (Table 11.6) to associate a DPC routine and a context item with the DPC object. This context item is passed to the CustomTimerDpc routine when it fires. The address of the device or controller extension is a good choice for the context item.

  3. AddDevice also calls KeInitializeTimer (Table 11.7) just once to set up the timer object.

  4. To start a one-shot timer, call KeSetTimer (Table 11.8); to set up a repeating timer, use KeSetTimerEx instead. If these functions are used on a timer object that is currently active, the previous request is canceled and the new expiration time replaces the old one.

Table 11.6. Function Prototype for KeInitializeDpc
VOID KeInitializeDpc IRQL == PASSIVE_LEVEL
Parameter Description
IN PKDPC pDpc Pointer to a DPC object for which the caller provides the storage
IN PKDEFERRED_ROUTINE DeferredRoutine Specifies the entry point for a routine to be called when the DPC object is removed from the DPC queue
IN PVOID pContext Pointer to a caller-supplied context to be passed to the DeferredRoutine when it is called
Return value - void -

Table 11.7. Function Prototype for KeInitializeTimer
VOID KeInitializeTimer RQL == PASSIVE_LEVEL
Parameter Description
IN PKTIMER Timer Pointer to a timer object for which the caller provides the storage
Return value - void -

Table 11.8. Function Prototype for KeSetTimer
BOOLEAN KeSetTimer IRQL <= DISPATCH_LEVEL
Parameter Description
IN PKTIMER Timer Pointer to timer object to set
IN LARGE_INTEGER DueTime Specifies the absolute or relative time at which the timer is to expire
IN PKDPC Dpc Pointer to a DPC object that was initialized by KeInitializeDpc
Return value
  • TRUE - Timer was armed before call

  • FALSE - Timer is freshly armed

To keep a timer from firing, call KeCancelTimer (Table 11.9) before the Timer object expires. This also cancels a repeating Timer. To find out whether a Timer has already expired, call KeReadStateTimer (Table 11.10).

To initialize the DPC and timer objects, code must be executing at PASSIVE_LEVEL IRQL. To set, cancel, or read the state of the timer, code must be running at or below DISPATCH_LEVEL IRQL. In general, the function KeInsertQueueDpc should be avoided with the DPC object used for CustomTimerDpc routine. It can lead to race conditions within the driver.

How to Specify Expiration Times

Internally, Windows 2000 maintains the current system time by counting the number of 100-nanosecond intervals since January 1, 1601. This being a very big number, 64 bits are required to hold it in a structure tagged LARGE_ INTEGER. Table 11.11 lists the functions drivers can use to work with time values.

Table 11.9. Function Prototype for KeCancelTimer
BOOLEAN KeCancelTimer IRQL <= DISPATCH_LEVEL
Parameter Description
IN PKTIMER Timer Pointer to timer object to cancel
Return value
  • TRUE - Timer was armed before call

  • FALSE - Timer was not armed

Table 11.10. Function Prototype for KeReadStateTimer
BOOLEAN KeReadStateTimer IRQL <= DISPATCH_LEVEL
Parameter Description
IN PKTIMER Timer Pointer to the timer object to query
Return value
  • TRUE - Timer has expired

  • FALSE - Timer still armed

When using KeSetTimer to on a timer object, the expiration time can be specified in one of two ways.

  • A positive LARGE_INTEGER value represents an absolute system time at which the timer will expire. Absolute times correspond to some exact moment in the future, like December 28, 2020 at 6:42 PM.

  • A negative LARGE_INTEGER value represents the length of an interval measured from the current moment, like "10 seconds from now." Clearly, relative time intervals are more useful within driver work.

This fragment of code shows how to set a Timer object to expire after an interval of 75 microseconds. It assumes that pDevExt holds a pointer to a device extension, and that the Extension contains initialized Timer and DPC objects.

 LARGE_INTEGER timeDue; timeDue = RtlConvertLongToLargeInteger( -75 * 10 ); KeSetTimer( &pDevExt->Timer, timeDue, &pDevExt->DPC ); 

Table 11.11. Functions That Operate on System Time Values
Time Functions
Function Description
KeQuerySystemTime Return 64-bit absolute system time
RtlTimeToTimeFields Break 64-bit time into date and time fields
RtlTimeFieldsToTime Convert date and time into 64-bit system time
KeQueryTickCount Return number of clock interrupts since boot
KeQueryTimeIncrement Return number of 100-nanosecond units added to system time for each clock interrupt
RtlConvertLongToLargeInteger Create a signed LARGE_INTEGER
RtlConvertUlongToLargeInteger Create a positive LARGE_INTEGER
RtlLargeIntegerXxx Perform various arithmetic and logical operations on LARGE_INTEGERs

Since the value passed to KeSetTimer is negative, the system interprets it as a relative time value. Scaling the number by 10 is necessary because the basic unit of system time for the call is 100 nanoseconds, one-tenth of a microsecond.

Other Uses for CustomTimerDpc Routines

In the next section, an example of a driver that performs data transfers using the CustomTimerDpc is presented. Typically, this technique is used to manage devices that do not generate interrupts. However, a CustomTimerDpc can also be used in specialized device timeout situations.

< BACK  NEXT >


The Windows 2000 Device Driver Book(c) A Guide for Programmers
The Windows 2000 Device Driver Book: A Guide for Programmers (2nd Edition)
ISBN: 0130204315
EAN: 2147483647
Year: 2000
Pages: 156

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net