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.
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_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.
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
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.
#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
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
EfiReleaseLock() functions hide the details of managing TPL levels.
#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
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