16.1 Managing Hardware Priorities
The IRQL at which a particular NT device or intermediate driver routine is run
determines which kernel-mode support routines it can call. For example, some
support routines require that the caller be running at DISPATCH_LEVEL IRQL.
Others cannot be called safely if the caller is running at raised IRQL:
that is, at any IRQL higher than PASSIVE_LEVEL.
Figure 16.1 illustrates the default IRQLs at
which the most commonly implemented standard NT driver routines are called and
the Kernel-defined ordering of low-to-high IRQL values.
Figure 16.1 Default IRQLs for NT Driver Routines
As Figure 16.1 shows, lowest-level NT drivers process IRPs while running at
one of three IRQLs:
-
PASSIVE_LEVEL, with no interrupts masked off on the processor, in the driver’s
Dispatch routine(s)
DriverEntry, Reinitialize, and Unload routines also are run at PASSIVE_LEVEL,
as are any driver-created system threads.
-
DISPATCH_LEVEL, with DISPATCH_LEVEL and APC_LEVEL interrupts masked off on the
processor, in the StartIo routine
AdapterControl, ControllerControl, IoTimer, Cancel (while it holds the cancel
spin lock), and CustomTimerDpc routines also are run at DISPATCH_LEVEL, as are
DpcForIsr and CustomDpc routines.
-
Device IRQL (DIRQL), with all interrupts at less than or equal to the SynchronizeIrql
of the driver’s interrupt object(s) masked off on the processor, in the ISR
and SynchCritSection routines
Most higher-level NT drivers process IRPs while running at either of two
IRQLs:
-
PASSIVE_LEVEL, with no interrupts masked off on the processor, in the driver’s
Dispatch routine(s)
DriverEntry, Reinitialize, and Unload routines also are run at PASSIVE_LEVEL,
as are any driver-created system threads or worker-thread callback routines or
file system drivers.
-
DISPATCH_LEVEL, with DISPATCH_LEVEL and APC_LEVEL interrupts masked off on the
processor, in the driver’s IoCompletion routine(s)
IoTimer, Cancel, and CustomTimerDpc routines also are run at DISPATCH_LEVEL.
In some circumstances, intermediate and lowest-level drivers of mass-storage
devices are called at IRQL APC_LEVEL. In particular, this can occur at a page
fault for which an NT file system driver sends an IRP_MJ_READ request to lower
drivers.
Most standard driver routines are run at an IRQL that allows them simply to
call the appropriate support routines. For example, a device driver must call IoAllocateAdapter
or IoAllocateController while running at DISPATCH_LEVEL IRQL. Since
most NT device drivers call these routines from a StartIo routine, usually
they are running at DISPATCH_LEVEL already.
Note that a device driver that has no StartIo routine because it sets up and
manages its own queue(s) of IRPs is not necessarily running at DISPATCH_LEVEL
IRQL when it should call IoAllocateAdapter (or IoAllocateController).
Such a driver must nest its call to IoAllocateAdapter between calls to KeRaiseIrql
and KeLowerIrql so that it runs at the required IRQL when it calls IoAllocateAdapter
and restores the original IRQL when the calling routine regains control.
Consider the following facts in order to call support routines at appropriate IRQLs and to manage hardware priorities successfully in NT drivers:
-
Calling KeRaiseIrql with an input NewIrql value that is less
than the current IRQL causes a fatal error. Calling KeLowerIrql except
to restore the original IRQL (that is, after a call to KeRaiseIrql)
also causes a fatal error.
-
While running at raised IRQL, calling KeWaitForSingleObject or KeWaitForMultipleObjects
with Kernel-defined dispatcher object(s) to wait for a nonzero interval causes
a fatal error. Only driver routines that run in a nonarbitrary thread context
at IRQL PASSIVE_LEVEL, such as driver-created threads, the DriverEntry and
Reinitialize routines, or Dispatch routines for inherently synchronous I/O
operations, such as most device I/O control requests, can safely wait on
events, semaphores, mutexes, or timers for a nonzero interval.
-
Even while running at IRQL PASSIVE_LEVEL, pageable driver code must not call KeSetEvent,
KeReleaseSemaphore, or KeReleaseMutex with the input Wait
parameter set to TRUE. Such a call can cause a fatal page fault.
-
Any routine that is running at greater than IRQL APC_LEVEL can neither
allocate memory from paged pool nor access memory in paged pool safely. If a
routine running at IRQL greater than APC_LEVEL causes a page fault, it is a
fatal error.
-
A driver must be running at IRQL DISPATCH_LEVEL when it calls KeAcquireSpinLockAtDpcLevel
and KeReleaseSpinLockFromDpcLevel. A driver can be running at IRQL
<= DISPATCH_LEVEL when it calls KeAcquireSpinLock but it must
release that spin lock by calling KeReleaseSpinLock. In other words, it
is a programming error to release a spin lock acquired with KeAcquireSpinLock
by calling KeReleaseSpinLockFromDpcLevel.
-
Calling a support routine, such as an ExInterlockedXxx, that
uses a spin lock raises IRQL on the current processor either to DISPATCH_LEVEL
or to DIRQL if the caller is not already running at raised IRQL.
-
Driver code that runs at raised IRQL should execute as quickly as possible.
The higher the IRQL at which a routine runs, the more important it is for good
overall performance to tune that routine to execute as quickly as possible.
For example, any driver that calls KeRaiseIrql should make the
reciprocal call to KeLowerIrql as soon as it can.
For routine-specific information about IRQL requirements for calling NT
support routines, see the Kernel-Mode Driver Reference. For more
information about using spin locks, see Section
16.2.