16.3  Polling a Device

An NT device driver should avoid polling its device unless it is absolutely necessary and should never use a whole timeslice polling. Polling a device is an expensive operation that makes any operating system compute-bound within the polling driver. If a device driver does much polling, it interferes with I/O operations on other devices and can make the system slow and unresponsive to users.

Recently developed devices, which are as technologically advanced as the processors on which Windows NT is designed to run, seldom require a driver that polls its device to be sure the device is ready to start an I/O operation or that an operation has been completed.

Nevertheless, some devices still in use were designed to work with old processors, which had narrow data buses, slow clock rates, and single-user, single-tasking operating systems that did synchronous I/O. Such devices might require polling or some other means of waiting for the device to update its registers, particularly for Windows NT, which is designed to do asynchronous I/O on new processors with wide data buses and fast clock rates.

An inexperienced driver programmer might think that an NT driver could solve such a slow-device problem by coding a simple loop that increments a counter, thereby “wasting” a minimum interval while its device updates registers. However, such an NT driver is unlikely to be portable across Windows NT platforms. The loop counter maximum would require customization for each Windows NT platform. Furthermore, when such a driver is recompiled with a good optimizing compiler, the compiler could remove the driver’s counter variable and the loop(s) where it is incremented.

Follow this implementation guideline if the device is such that its driver must stall while the hardware updates state:

An NT driver can call KeStallExecutionProcessor before it reads the device register(s). Such a driver should minimize the interval it stalls and should, in general, specify a stall interval no longer than 50 microseconds.

The granularity of a KeStallExecutionProcessor interval is 1 microsecond.

The designer of an NT driver for a device that frequently requires more than 50 microseconds to update state should consider setting up a device-dedicated thread.

Driver Threads

The driver of a slow device or a device that is seldom used like the floppy controller, can solve many “waiting” problems by creating a device-dedicated system thread or, for a file system driver, by using system worker threads and supplying worker-thread callback routines, as most NT FSDs do. A thread can call KeDelayExecutionThread to wait for an interval that could be a full timeslice or longer.

The granularity of a KeDelayExecutionThread interval is around 10 milliseconds. Because KeDelayExecutionThread is a timer-driven routine, the granularity of its interval is slightly faster or slower than 10 milliseconds, depending on the platform. However, such a call is portable because the delta time specified is constant.

If such an NT device driver has its own thread context or is running in a system-thread context, the device-dedicated thread or highest-level driver’s worker-thread callback routine can synchronize operations on a Kernel-defined dispatcher object such as an event or semaphore in a shared communication region of the driver’s device extension. While its device is not in use, a device-dedicated thread can wait on a shared dispatcher object, for example, by calling KeWaitForSingleObject with a semaphore. Until such a device driver is called to carry out an I/O operation and sets the semaphore to the Signaled state, its waiting thread uses no CPU time.

Setting the Base Priority of a Driver-Created Thread

An NT driver should set the base priority of any driver- or device-dedicated thread that it creates with PsCreateSystemThread to the lowest real-time priority level in order to give its thread(s) a relatively high priority while avoiding runtime priority inversions in SMP machines.