5.1.4 Task Priority Level(TPL) Services
The Task Priority Level Services provide a mechanism for code to execute code at a raised priority for short periods of time. One use case is a UEFI Driver that is required to raise the priority because the implementation of a service of a specific protocol requires execution at a specific TPL to be UEFI conformant. Another use case is a UEFI Driver that needs to implement a simple lock, or critical section, on global data structures maintained by the UEFI Driver. Event notification functions, covered in the next section, always execute at raised priority levels.
The service RaiseTPL()
is used to raise the priority level from its current
level to a higher level and return the priority level before it was raised. The
service RestoreTPL()
is used to restore a the priority level to a priority
level returned by RaiseTPL()
. These two services are always used in pairs.
Note: There are no UEFI services provided to lower the TPL, and it is
illegal to use RaiseTPL()
to attempt to raise the priority level to a level
below the current priority level. If attempted, the behavior of the platform is
indeterminate.
The Event, Timer, and Task Priority Services section of the UEFI
Specification defines four TPL levels. These are TPL_APPLICATION
,
TPL_CALLBACK
, TPL_NOTIFY
, and TPL_HIGH_LEVEL
. UEFI Driver and UEFI Applications are started at TPL_APPLICATION
. UEFI Drivers should execute code at the lowest possible TPL level and minimize the time spent at raised TPL levels.
Note: Only TPL_APPLICATION
, TPL_CALLBACK
, TPL_NOTIFY
, and
TPL_HIGH_LEVEL
may be used by UEFI Drivers. All other values are reserved
for use by the firmware. Using them results in unpredictable behavior. Good
coding practice dictates that all code should execute at its lowest possible
TPL level, and the use of TPL levels above TPL_APPLICATION
must be
minimized. Executing at TPL levels above TPL_APPLICATION
for extended
periods of time may also result in unpredictable behavior.
UEFI firmware, applications, and drivers all run on one thread on one
processor. However, UEFI firmware does
support a single timer interrupt
.
Because UEFI code can run in interrupt context, it is possible that a global
data structure can be accessed from both normal context and interrupt context.
As a result, global data structures that are accessed from both normal context
and interrupt context must be protected by a lock.
The following code fragment shows how the RaiseTPL()
and RestoreTPL()
services can be used to implement a lock when the contents of a global variable
are modified. The timer interrupt is blocked at EFI_TPL_HIGH_LEVEL
, so most
locks raise to this level.
Example 45-Using TPL Services for a Global Lock
#include <Uefi.h>
#include <Library/UefiBootServicesTableLib.h>
UINT32 gCounter;
EFI_TPL OldTpl;
//
// Raise the Task Priority Level to TPL_HIGH_LEVEL to block timer
// interrupts
//
OldTpl = gBS->RaiseTPL (TPL_HIGH_LEVEL);
//
// Increment the global variable now that it is safe to do so.
// gCounter++;
//
// Restore the Task Priority Level to its original level
//
gBS->RestoreTPL (OldTpl);
The code fragment in Example 46, below, has the same functionality as Example
45, above, but uses the lock macros and functions from the EDK II Library
UefiLib
that use RaiseTPL()
and RestoreTPL()
to implement general purpose
locks. The global variable gLock
is an EFI_LOCK
structure that is
initialized using the EFI_INITIALIZE_LOCK_VARIABLE()
macro that specifies the use of
TPL_HIGH_LEVEL
when the lock is acquired. The EfiAcquireLock()
and
EfiReleaseLock()
functions hide the details of managing TPL levels.
Example 46-Using UEFI Library for a Global Lock
#include <Uefi.h>
#include <Library/UefiLib.h>
EFI_LOCK gLock = EFI_INITIALIZE_LOCK_VARIABLE (TPL_HIGH_LEVEL);
UINT32 gCounter;
//
// Acquire the lock to block timer interrupts
//
EfiAcquireLock (&gLock);
//
// Increment the global variable now that it is safe to do so.
// gCounter++;
//
// Release the lock
//
EfiReleaseLock (&gLock);
The algorithm shown in these two global lock examples also applies to a UEFI
Driver that is required to implement protocol services that execute at a
specific TPL level. For example, the services in the Block I/O Protocol must be
called at or below TPL_CALLBACK
. This means that the implementation of the ReadBlocks()
,
WriteBlocks()
, and FlushBlocks()
services should raise the priority level
to TPL_CALLBACK
. This would be identical to Example 46, above, but would use
TPL_CALLBACK
instead of TPL_HIGH_LEVEL
.