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.