/** @file | |
SMM Periodic SMI Library. | |
Copyright (c) 2011 - 2018, Intel Corporation. All rights reserved.<BR> | |
SPDX-License-Identifier: BSD-2-Clause-Patent | |
**/ | |
#include <PiSmm.h> | |
#include <Protocol/SmmPeriodicTimerDispatch2.h> | |
#include <Library/BaseLib.h> | |
#include <Library/BaseMemoryLib.h> | |
#include <Library/SynchronizationLib.h> | |
#include <Library/DebugLib.h> | |
#include <Library/TimerLib.h> | |
#include <Library/MemoryAllocationLib.h> | |
#include <Library/SmmServicesTableLib.h> | |
#include <Library/SmmPeriodicSmiLib.h> | |
/// | |
/// Define the number of periodic SMI handler entries that should be allocated to the list | |
/// of free periodic SMI handlers when the list of free periodic SMI handlers is empty. | |
/// | |
#define PERIODIC_SMI_LIBRARY_ALLOCATE_SIZE 0x08 | |
/// | |
/// Signature for a PERIODIC_SMI_LIBRARY_HANDLER_CONTEXT structure | |
/// | |
#define PERIODIC_SMI_LIBRARY_HANDLER_CONTEXT_SIGNATURE SIGNATURE_32 ('P', 'S', 'M', 'I') | |
/// | |
/// Structure that contains state information for an enabled periodic SMI handler | |
/// | |
typedef struct { | |
/// | |
/// Signature value that must be set to PERIODIC_SMI_LIBRARY_HANDLER_CONTEXT_SIGNATURE | |
/// | |
UINT32 Signature; | |
/// | |
/// The link entry to be inserted to the list of periodic SMI handlers. | |
/// | |
LIST_ENTRY Link; | |
/// | |
/// The dispatch function to called to invoke an enabled periodic SMI handler. | |
/// | |
PERIODIC_SMI_LIBRARY_HANDLER DispatchFunction; | |
/// | |
/// The context to pass into DispatchFunction | |
/// | |
VOID *Context; | |
/// | |
/// The tick period in 100 ns units that DispatchFunction should be called. | |
/// | |
UINT64 TickPeriod; | |
/// | |
/// The Cpu number that is required to execute DispatchFunction. If Cpu is | |
/// set to PERIODIC_SMI_LIBRARY_ANY_CPU, then DispatchFunction may be executed | |
/// on any CPU. | |
/// | |
UINTN Cpu; | |
/// | |
/// The size, in bytes, of the stack allocated for a periodic SMI handler. | |
/// This value must be a multiple of EFI_PAGE_SIZE. | |
/// | |
UINTN StackSize; | |
/// | |
/// A pointer to the stack allocated using AllocatePages(). This field will | |
/// be NULL if StackSize is 0. | |
/// | |
VOID *Stack; | |
/// | |
/// Spin lock used to wait for an AP to complete the execution of a periodic SMI handler | |
/// | |
SPIN_LOCK DispatchLock; | |
/// | |
/// The rate in Hz of the performance counter that is used to measure the | |
/// amount of time that a periodic SMI handler executes. | |
/// | |
UINT64 PerfomanceCounterRate; | |
/// | |
/// The start count value of the performance counter that is used to measure | |
/// the amount of time that a periodic SMI handler executes. | |
/// | |
UINT64 PerfomanceCounterStartValue; | |
/// | |
/// The end count value of the performance counter that is used to measure | |
/// the amount of time that a periodic SMI handler executes. | |
/// | |
UINT64 PerfomanceCounterEndValue; | |
/// | |
/// The context record passed into the Register() function of the SMM Periodic | |
/// Timer Dispatch Protocol when a periodic SMI handler is enabled. | |
/// | |
EFI_SMM_PERIODIC_TIMER_REGISTER_CONTEXT RegisterContext; | |
/// | |
/// The handle returned from the Register() function of the SMM Periodic | |
/// Timer Dispatch Protocol when a periodic SMI handler is enabled. | |
/// | |
EFI_HANDLE DispatchHandle; | |
/// | |
/// The total number of performance counter ticks that the periodic SMI handler | |
/// has been executing in its current invocation. | |
/// | |
UINT64 DispatchTotalTime; | |
/// | |
/// The performance counter value that was captured the last time that the | |
/// periodic SMI handler called PeriodicSmiExecutionTime(). This allows the | |
/// time value returned by PeriodicSmiExecutionTime() to be accurate even when | |
/// the performance counter rolls over. | |
/// | |
UINT64 DispatchCheckPointTime; | |
/// | |
/// Buffer used to save the context when control is transfer from this library | |
/// to an enabled periodic SMI handler. This saved context is used when the | |
/// periodic SMI handler exits or yields. | |
/// | |
BASE_LIBRARY_JUMP_BUFFER DispatchJumpBuffer; | |
/// | |
/// Flag that is set to TRUE when a periodic SMI handler requests to yield | |
/// using PeriodicSmiYield(). When this flag IS TRUE, YieldJumpBuffer is | |
/// valid. When this flag is FALSE, YieldJumpBuffer is not valid. | |
/// | |
BOOLEAN YieldFlag; | |
/// | |
/// Buffer used to save the context when a periodic SMI handler requests to | |
/// yield using PeriodicSmiYield(). This context is used to resume the | |
/// execution of a periodic SMI handler the next time control is transferred | |
/// to the periodic SMI handler that yielded. | |
/// | |
BASE_LIBRARY_JUMP_BUFFER YieldJumpBuffer; | |
/// | |
/// The amount of time, in 100 ns units, that have elapsed since the last | |
/// time the periodic SMI handler was invoked. | |
/// | |
UINT64 ElapsedTime; | |
} PERIODIC_SMI_LIBRARY_HANDLER_CONTEXT; | |
/** | |
Macro that returns a pointer to a PERIODIC_SMI_LIBRARY_HANDLER_CONTEXT | |
structure based on a pointer to a Link field. | |
**/ | |
#define PERIODIC_SMI_LIBRARY_HANDLER_CONTEXT_FROM_LINK(a) \ | |
CR ( \ | |
a, \ | |
PERIODIC_SMI_LIBRARY_HANDLER_CONTEXT, \ | |
Link, \ | |
PERIODIC_SMI_LIBRARY_HANDLER_CONTEXT_SIGNATURE \ | |
) | |
/// | |
/// Pointer to the SMM Periodic Timer Dispatch Protocol that was located in the constructor. | |
/// | |
EFI_SMM_PERIODIC_TIMER_DISPATCH2_PROTOCOL *gSmmPeriodicTimerDispatch2 = NULL; | |
/// | |
/// Pointer to a table of supported periodic SMI tick periods in 100 ns units | |
/// sorted from largest to smallest terminated by a tick period value of 0. | |
/// This table is allocated using AllocatePool() in the constructor and filled | |
/// in based on the values returned from the SMM Periodic Timer Dispatch 2 Protocol | |
/// function GetNextShorterInterval(). | |
/// | |
UINT64 *gSmiTickPeriodTable = NULL; | |
/// | |
/// Linked list of free periodic SMI handlers that this library can use. | |
/// | |
LIST_ENTRY gFreePeriodicSmiLibraryHandlers = | |
INITIALIZE_LIST_HEAD_VARIABLE (gFreePeriodicSmiLibraryHandlers); | |
/// | |
/// Linked list of periodic SMI handlers that this library is currently managing. | |
/// | |
LIST_ENTRY gPeriodicSmiLibraryHandlers = | |
INITIALIZE_LIST_HEAD_VARIABLE (gPeriodicSmiLibraryHandlers); | |
/// | |
/// Pointer to the periodic SMI handler that is currently being executed. | |
/// Is set to NULL if no periodic SMI handler is currently being executed. | |
/// | |
PERIODIC_SMI_LIBRARY_HANDLER_CONTEXT *gActivePeriodicSmiLibraryHandler = NULL; | |
/** | |
Internal worker function that returns a pointer to the | |
PERIODIC_SMI_LIBRARY_HANDLER_CONTEXT structure associated with the periodic | |
SMI handler that is currently being executed. If a periodic SMI handler is | |
not currently being executed, the NULL is returned. | |
@retval NULL A periodic SMI handler is not currently being executed. | |
@retval other Pointer to the PERIODIC_SMI_LIBRARY_HANDLER_CONTEXT | |
associated with the active periodic SMI handler. | |
**/ | |
PERIODIC_SMI_LIBRARY_HANDLER_CONTEXT * | |
GetActivePeriodicSmiLibraryHandler ( | |
VOID | |
) | |
{ | |
return gActivePeriodicSmiLibraryHandler; | |
} | |
/** | |
Internal worker function that returns a pointer to the | |
PERIODIC_SMI_LIBRARY_HANDLER_CONTEXT structure associated with the | |
DispatchHandle that was returned when the periodic SMI handler was enabled | |
with PeriodicSmiEnable(). If DispatchHandle is NULL, then the active | |
periodic SMI handler is returned. If DispatchHandle is NULL and there is | |
no active periodic SMI handler, then NULL is returned. | |
@param[in] DispatchHandle DispatchHandle that was returned when the periodic | |
SMI handler was enabled with PeriodicSmiEnable(). | |
This is an optional parameter that may be NULL. | |
If this parameter is NULL, then the active periodic | |
SMI handler is returned. | |
@retval NULL DispatchHandle is NULL and there is no active periodic SMI | |
handler. | |
@retval NULL DispatchHandle does not match any of the periodic SMI handlers | |
that have been enabled with PeriodicSmiEnable(). | |
@retval other Pointer to the PERIODIC_SMI_LIBRARY_HANDLER_CONTEXT | |
associated with the DispatchHandle. | |
**/ | |
PERIODIC_SMI_LIBRARY_HANDLER_CONTEXT * | |
LookupPeriodicSmiLibraryHandler ( | |
IN EFI_HANDLE DispatchHandle OPTIONAL | |
) | |
{ | |
LIST_ENTRY *Link; | |
PERIODIC_SMI_LIBRARY_HANDLER_CONTEXT *PeriodicSmiLibraryHandler; | |
// | |
// If DispatchHandle is NULL, then return the active periodic SMI handler | |
// | |
if (DispatchHandle == NULL) { | |
return GetActivePeriodicSmiLibraryHandler (); | |
} | |
// | |
// Search the periodic SMI handler entries for a a matching DispatchHandle | |
// | |
for ( Link = GetFirstNode (&gPeriodicSmiLibraryHandlers) | |
; !IsNull (&gPeriodicSmiLibraryHandlers, Link) | |
; Link = GetNextNode (&gPeriodicSmiLibraryHandlers, Link) | |
) | |
{ | |
PeriodicSmiLibraryHandler = PERIODIC_SMI_LIBRARY_HANDLER_CONTEXT_FROM_LINK (Link); | |
if (PeriodicSmiLibraryHandler->DispatchHandle == DispatchHandle) { | |
return PeriodicSmiLibraryHandler; | |
} | |
} | |
// | |
// No entries match DispatchHandle, so return NULL | |
// | |
return NULL; | |
} | |
/** | |
Internal worker function that sets that active periodic SMI handler based on | |
the DispatchHandle that was returned when the periodic SMI handler was enabled | |
with PeriodicSmiEnable(). If DispatchHandle is NULL, then the | |
state is updated to show that there is not active periodic SMI handler. | |
A pointer to the active PERIODIC_SMI_LIBRARY_HANDLER_CONTEXT structure | |
is returned. | |
@param [in] DispatchHandle DispatchHandle that was returned when the periodic | |
SMI handler was enabled with PeriodicSmiEnable(). | |
This is an optional parameter that may be NULL. | |
If this parameter is NULL, then the state is updated | |
to show that there is not active periodic SMI handler. | |
@retval NULL DispatchHandle is NULL. | |
@retval other Pointer to the PERIODIC_SMI_LIBRARY_HANDLER_CONTEXT | |
associated with DispatchHandle. | |
**/ | |
PERIODIC_SMI_LIBRARY_HANDLER_CONTEXT * | |
SetActivePeriodicSmiLibraryHandler ( | |
IN EFI_HANDLE DispatchHandle OPTIONAL | |
) | |
{ | |
if (DispatchHandle == NULL) { | |
gActivePeriodicSmiLibraryHandler = NULL; | |
} else { | |
gActivePeriodicSmiLibraryHandler = LookupPeriodicSmiLibraryHandler (DispatchHandle); | |
} | |
return gActivePeriodicSmiLibraryHandler; | |
} | |
/** | |
Internal worker function that moves the specified periodic SMI handler from the | |
list of managed periodic SMI handlers to the list of free periodic SMI handlers. | |
@param[in] PeriodicSmiLibraryHandler Pointer to the periodic SMI handler to be reclaimed. | |
**/ | |
VOID | |
ReclaimPeriodicSmiLibraryHandler ( | |
PERIODIC_SMI_LIBRARY_HANDLER_CONTEXT *PeriodicSmiLibraryHandler | |
) | |
{ | |
ASSERT (PeriodicSmiLibraryHandler->DispatchHandle == NULL); | |
if (PeriodicSmiLibraryHandler->Stack != NULL) { | |
FreePages ( | |
PeriodicSmiLibraryHandler->Stack, | |
EFI_SIZE_TO_PAGES (PeriodicSmiLibraryHandler->StackSize) | |
); | |
PeriodicSmiLibraryHandler->Stack = NULL; | |
} | |
RemoveEntryList (&PeriodicSmiLibraryHandler->Link); | |
InsertHeadList (&gFreePeriodicSmiLibraryHandlers, &PeriodicSmiLibraryHandler->Link); | |
} | |
/** | |
Add the additional entries to the list of free periodic SMI handlers. | |
The function is assumed to be called only when the list of free periodic SMI | |
handlers is empty. | |
@retval TRUE The additional entries were added. | |
@retval FALSE There was no available resource for the additional entries. | |
**/ | |
BOOLEAN | |
EnlargeFreePeriodicSmiLibraryHandlerList ( | |
VOID | |
) | |
{ | |
UINTN Index; | |
PERIODIC_SMI_LIBRARY_HANDLER_CONTEXT *PeriodicSmiLibraryHandler; | |
// | |
// Add the entries to the list | |
// | |
for (Index = 0; Index < PERIODIC_SMI_LIBRARY_ALLOCATE_SIZE; Index++) { | |
PeriodicSmiLibraryHandler = AllocatePool (sizeof (PERIODIC_SMI_LIBRARY_HANDLER_CONTEXT)); | |
if (PeriodicSmiLibraryHandler == NULL) { | |
break; | |
} | |
PeriodicSmiLibraryHandler->Signature = PERIODIC_SMI_LIBRARY_HANDLER_CONTEXT_SIGNATURE; | |
InsertHeadList (&gFreePeriodicSmiLibraryHandlers, &PeriodicSmiLibraryHandler->Link); | |
} | |
return (BOOLEAN)(Index > 0); | |
} | |
/** | |
Internal worker function that returns a free entry for a new periodic | |
SMI handler. If no free entries are available, then additional | |
entries are allocated. | |
@retval NULL There are not enough resources available to to allocate a free entry. | |
@retval other Pointer to a free PERIODIC_SMI_LIBRARY_HANDLER_CONTEXT structure. | |
**/ | |
PERIODIC_SMI_LIBRARY_HANDLER_CONTEXT * | |
FindFreePeriodicSmiLibraryHandler ( | |
VOID | |
) | |
{ | |
PERIODIC_SMI_LIBRARY_HANDLER_CONTEXT *PeriodicSmiLibraryHandler; | |
if (IsListEmpty (&gFreePeriodicSmiLibraryHandlers)) { | |
if (!EnlargeFreePeriodicSmiLibraryHandlerList ()) { | |
return NULL; | |
} | |
} | |
// | |
// Get one from the list of free periodic SMI handlers. | |
// | |
PeriodicSmiLibraryHandler = PERIODIC_SMI_LIBRARY_HANDLER_CONTEXT_FROM_LINK ( | |
GetFirstNode (&gFreePeriodicSmiLibraryHandlers) | |
); | |
RemoveEntryList (&PeriodicSmiLibraryHandler->Link); | |
InsertTailList (&gPeriodicSmiLibraryHandlers, &PeriodicSmiLibraryHandler->Link); | |
return PeriodicSmiLibraryHandler; | |
} | |
/** | |
This function returns a pointer to a table of supported periodic | |
SMI tick periods in 100 ns units sorted from largest to smallest. | |
The table contains a array of UINT64 values terminated by a tick | |
period value of 0. The returned table must be treated as read-only | |
data and must not be freed. | |
@return A pointer to a table of UINT64 tick period values in | |
100ns units sorted from largest to smallest terminated | |
by a tick period of 0. | |
**/ | |
UINT64 * | |
EFIAPI | |
PeriodicSmiSupportedTickPeriod ( | |
VOID | |
) | |
{ | |
// | |
// Return the table allocated and populated by SmmPeriodicSmiLibConstructor() | |
// | |
return gSmiTickPeriodTable; | |
} | |
/** | |
This function returns the time in 100ns units since the periodic SMI | |
handler function was called. If the periodic SMI handler was resumed | |
through PeriodicSmiYield(), then the time returned is the time in | |
100ns units since PeriodicSmiYield() returned. | |
@return The actual time in 100ns units that the periodic SMI handler | |
has been executing. If this function is not called from within | |
an enabled periodic SMI handler, then 0 is returned. | |
**/ | |
UINT64 | |
EFIAPI | |
PeriodicSmiExecutionTime ( | |
VOID | |
) | |
{ | |
PERIODIC_SMI_LIBRARY_HANDLER_CONTEXT *PeriodicSmiLibraryHandler; | |
UINT64 Current; | |
UINT64 Count; | |
// | |
// If there is no active periodic SMI handler, then return 0 | |
// | |
PeriodicSmiLibraryHandler = GetActivePeriodicSmiLibraryHandler (); | |
if (PeriodicSmiLibraryHandler == NULL) { | |
return 0; | |
} | |
// | |
// Get the current performance counter value | |
// | |
Current = GetPerformanceCounter (); | |
// | |
// Count the number of performance counter ticks since the periodic SMI handler | |
// was dispatched or the last time this function was called. | |
// | |
if (PeriodicSmiLibraryHandler->PerfomanceCounterEndValue > PeriodicSmiLibraryHandler->PerfomanceCounterStartValue) { | |
// | |
// The performance counter counts up. Check for roll over condition. | |
// | |
if (Current > PeriodicSmiLibraryHandler->DispatchCheckPointTime) { | |
Count = Current - PeriodicSmiLibraryHandler->DispatchCheckPointTime; | |
} else { | |
Count = (Current - PeriodicSmiLibraryHandler->PerfomanceCounterStartValue) + (PeriodicSmiLibraryHandler->PerfomanceCounterEndValue - PeriodicSmiLibraryHandler->DispatchCheckPointTime); | |
} | |
} else { | |
// | |
// The performance counter counts down. Check for roll over condition. | |
// | |
if (PeriodicSmiLibraryHandler->DispatchCheckPointTime > Current) { | |
Count = PeriodicSmiLibraryHandler->DispatchCheckPointTime - Current; | |
} else { | |
Count = (PeriodicSmiLibraryHandler->DispatchCheckPointTime - PeriodicSmiLibraryHandler->PerfomanceCounterEndValue) + (PeriodicSmiLibraryHandler->PerfomanceCounterStartValue - Current); | |
} | |
} | |
// | |
// Accumulate the total number of performance counter ticks since the periodic | |
// SMI handler was dispatched or resumed. | |
// | |
PeriodicSmiLibraryHandler->DispatchTotalTime += Count; | |
// | |
// Update the checkpoint value to the current performance counter value | |
// | |
PeriodicSmiLibraryHandler->DispatchCheckPointTime = Current; | |
// | |
// Convert the total number of performance counter ticks to 100 ns units | |
// | |
return DivU64x64Remainder ( | |
MultU64x32 (PeriodicSmiLibraryHandler->DispatchTotalTime, 10000000), | |
PeriodicSmiLibraryHandler->PerfomanceCounterRate, | |
NULL | |
); | |
} | |
/** | |
This function returns control back to the SMM Foundation. When the next | |
periodic SMI for the currently executing handler is triggered, the periodic | |
SMI handler will restarted from its registered DispatchFunction entry point. | |
If this function is not called from within an enabled periodic SMI handler, | |
then control is returned to the calling function. | |
**/ | |
VOID | |
EFIAPI | |
PeriodicSmiExit ( | |
VOID | |
) | |
{ | |
PERIODIC_SMI_LIBRARY_HANDLER_CONTEXT *PeriodicSmiLibraryHandler; | |
// | |
// If there is no active periodic SMI handler, then return | |
// | |
PeriodicSmiLibraryHandler = GetActivePeriodicSmiLibraryHandler (); | |
if (PeriodicSmiLibraryHandler == NULL) { | |
return; | |
} | |
// | |
// Perform a long jump back to the point when the currently executing dispatch | |
// function was dispatched. | |
// | |
LongJump (&PeriodicSmiLibraryHandler->DispatchJumpBuffer, 1); | |
// | |
// Must never return | |
// | |
ASSERT (FALSE); | |
CpuDeadLoop (); | |
} | |
/** | |
This function yields control back to the SMM Foundation. When the next | |
periodic SMI for the currently executing handler is triggered, the periodic | |
SMI handler will be resumed and this function will return. Use of this | |
function requires a separate stack for the periodic SMI handler. A non zero | |
stack size must be specified in PeriodicSmiEnable() for this function to be | |
used. | |
If the stack size passed into PeriodicSmiEnable() was zero, the 0 is returned. | |
If this function is not called from within an enabled periodic SMI handler, | |
then 0 is returned. | |
@return The actual time in 100ns units elapsed since this function was | |
called. A value of 0 indicates an unknown amount of time. | |
**/ | |
UINT64 | |
EFIAPI | |
PeriodicSmiYield ( | |
VOID | |
) | |
{ | |
PERIODIC_SMI_LIBRARY_HANDLER_CONTEXT *PeriodicSmiLibraryHandler; | |
UINTN SetJumpFlag; | |
// | |
// If there is no active periodic SMI handler, then return | |
// | |
PeriodicSmiLibraryHandler = GetActivePeriodicSmiLibraryHandler (); | |
if (PeriodicSmiLibraryHandler == NULL) { | |
return 0; | |
} | |
// | |
// If PeriodicSmiYield() is called without an allocated stack, then just return | |
// immediately with an elapsed time of 0. | |
// | |
if (PeriodicSmiLibraryHandler->Stack == NULL) { | |
return 0; | |
} | |
// | |
// Set a flag so the next periodic SMI event will resume at where SetJump() | |
// is called below. | |
// | |
PeriodicSmiLibraryHandler->YieldFlag = TRUE; | |
// | |
// Save context in YieldJumpBuffer | |
// | |
SetJumpFlag = SetJump (&PeriodicSmiLibraryHandler->YieldJumpBuffer); | |
if (SetJumpFlag == 0) { | |
// | |
// The initial call to SetJump() always returns 0. | |
// If this is the initial call, then exit the current periodic SMI handler | |
// | |
PeriodicSmiExit (); | |
} | |
// | |
// We get here when a LongJump is performed from PeriodicSmiDispatchFunctionOnCpu() | |
// to resume a periodic SMI handler that called PeriodicSmiYield() on the | |
// previous time this periodic SMI handler was dispatched. | |
// | |
// Clear the flag so the next periodic SMI dispatch will not resume. | |
// | |
PeriodicSmiLibraryHandler->YieldFlag = FALSE; | |
// | |
// Return the amount elapsed time that occurred while yielded | |
// | |
return PeriodicSmiLibraryHandler->ElapsedTime; | |
} | |
/** | |
Internal worker function that transfers control to an enabled periodic SMI | |
handler. If the enabled periodic SMI handler was allocated its own stack, | |
then this function is called on that allocated stack through the BaseLin | |
function SwitchStack(). | |
@param[in] Context1 Context1 parameter passed into SwitchStack(). | |
@param[in] Context2 Context2 parameter passed into SwitchStack(). | |
**/ | |
VOID | |
EFIAPI | |
PeriodicSmiDispatchFunctionSwitchStack ( | |
IN VOID *Context1 OPTIONAL, | |
IN VOID *Context2 OPTIONAL | |
) | |
{ | |
PERIODIC_SMI_LIBRARY_HANDLER_CONTEXT *PeriodicSmiLibraryHandler; | |
// | |
// Convert Context1 to PERIODIC_SMI_LIBRARY_HANDLER_CONTEXT * | |
// | |
PeriodicSmiLibraryHandler = (PERIODIC_SMI_LIBRARY_HANDLER_CONTEXT *)Context1; | |
// | |
// Dispatch the registered handler passing in the context that was registered | |
// and the amount of time that has elapsed since the previous time this | |
// periodic SMI handler was dispatched. | |
// | |
PeriodicSmiLibraryHandler->DispatchFunction ( | |
PeriodicSmiLibraryHandler->Context, | |
PeriodicSmiLibraryHandler->ElapsedTime | |
); | |
// | |
// If this DispatchFunction() returns, then unconditionally call PeriodicSmiExit() | |
// to perform a LongJump() back to PeriodicSmiDispatchFunctionOnCpu(). The | |
// LongJump() will resume execution on the original stack. | |
// | |
PeriodicSmiExit (); | |
} | |
/** | |
Internal worker function that transfers control to an enabled periodic SMI | |
handler on the specified logical CPU. This function determines if the periodic | |
SMI handler yielded and needs to be resumed. It also and switches to an | |
allocated stack if one was allocated in PeriodicSmiEnable(). | |
@param[in] PeriodicSmiLibraryHandler A pointer to the context for the periodic | |
SMI handler to execute. | |
**/ | |
VOID | |
EFIAPI | |
PeriodicSmiDispatchFunctionOnCpu ( | |
PERIODIC_SMI_LIBRARY_HANDLER_CONTEXT *PeriodicSmiLibraryHandler | |
) | |
{ | |
// | |
// Save context in DispatchJumpBuffer. The initial call to SetJump() always | |
// returns 0. If this is the initial call, then either resume from a prior | |
// call to PeriodicSmiYield() or call the DispatchFunction registered in | |
// PeriodicSmiEnable() using an allocated stack if one was specified. | |
// | |
if (SetJump (&PeriodicSmiLibraryHandler->DispatchJumpBuffer) != 0) { | |
return; | |
} | |
// | |
// Capture the performance counter value just before the periodic SMI handler | |
// is resumed so the amount of time the periodic SMI handler executes can be | |
// calculated. | |
// | |
PeriodicSmiLibraryHandler->DispatchTotalTime = 0; | |
PeriodicSmiLibraryHandler->DispatchCheckPointTime = GetPerformanceCounter (); | |
if (PeriodicSmiLibraryHandler->YieldFlag) { | |
// | |
// Perform a long jump back to the point where the previously dispatched | |
// function called PeriodicSmiYield(). | |
// | |
LongJump (&PeriodicSmiLibraryHandler->YieldJumpBuffer, 1); | |
} else if (PeriodicSmiLibraryHandler->Stack == NULL) { | |
// | |
// If Stack is NULL then call DispatchFunction using current stack passing | |
// in the context that was registered and the amount of time that has | |
// elapsed since the previous time this periodic SMI handler was dispatched. | |
// | |
PeriodicSmiLibraryHandler->DispatchFunction ( | |
PeriodicSmiLibraryHandler->Context, | |
PeriodicSmiLibraryHandler->ElapsedTime | |
); | |
// | |
// If this DispatchFunction() returns, then unconditionally call PeriodicSmiExit() | |
// to perform a LongJump() back to this function. | |
// | |
PeriodicSmiExit (); | |
} else { | |
// | |
// If Stack is not NULL then call DispatchFunction switching to the allocated stack | |
// | |
SwitchStack ( | |
PeriodicSmiDispatchFunctionSwitchStack, | |
PeriodicSmiLibraryHandler, | |
NULL, | |
(UINT8 *)PeriodicSmiLibraryHandler->Stack + PeriodicSmiLibraryHandler->StackSize | |
); | |
} | |
// | |
// Must never return | |
// | |
ASSERT (FALSE); | |
CpuDeadLoop (); | |
} | |
/** | |
Internal worker function that transfers control to an enabled periodic SMI | |
handler on the specified logical CPU. This worker function is only called | |
using the SMM Services Table function SmmStartupThisAp() to execute the | |
periodic SMI handler on a logical CPU that is different than the one that is | |
running the SMM Foundation. When the periodic SMI handler returns, a lock is | |
released to notify the CPU that is running the SMM Foundation that the periodic | |
SMI handler execution has finished its execution. | |
@param[in, out] Buffer A pointer to the context for the periodic SMI handler. | |
**/ | |
VOID | |
EFIAPI | |
PeriodicSmiDispatchFunctionWithLock ( | |
IN OUT VOID *Buffer | |
) | |
{ | |
PERIODIC_SMI_LIBRARY_HANDLER_CONTEXT *PeriodicSmiLibraryHandler; | |
// | |
// Get context | |
// | |
PeriodicSmiLibraryHandler = (PERIODIC_SMI_LIBRARY_HANDLER_CONTEXT *)Buffer; | |
// | |
// Execute dispatch function on the currently executing logical CPU | |
// | |
PeriodicSmiDispatchFunctionOnCpu (PeriodicSmiLibraryHandler); | |
// | |
// Release the dispatch spin lock | |
// | |
ReleaseSpinLock (&PeriodicSmiLibraryHandler->DispatchLock); | |
} | |
/** | |
Internal worker function that transfers control to a periodic SMI handler that | |
was enabled using PeriodicSmiEnable(). | |
@param[in] DispatchHandle The unique handle assigned to this handler by | |
SmiHandlerRegister(). | |
@param[in] Context Points to an optional handler context which was | |
specified when the handler was registered. | |
@param[in, out] CommBuffer A pointer to a collection of data in memory that | |
will be conveyed from a non-SMM environment into | |
an SMM environment. | |
@param[in, out] CommBufferSize The size of the CommBuffer. | |
@retval EFI_SUCCESS The interrupt was handled and quiesced. | |
No other handlers should still be called. | |
@retval EFI_WARN_INTERRUPT_SOURCE_QUIESCED The interrupt has been quiesced but other | |
handlers should still be called. | |
@retval EFI_WARN_INTERRUPT_SOURCE_PENDING The interrupt is still pending and other | |
handlers should still be called. | |
@retval EFI_INTERRUPT_PENDING The interrupt could not be quiesced. | |
**/ | |
EFI_STATUS | |
EFIAPI | |
PeriodicSmiDispatchFunction ( | |
IN EFI_HANDLE DispatchHandle, | |
IN CONST VOID *Context OPTIONAL, | |
IN OUT VOID *CommBuffer OPTIONAL, | |
IN OUT UINTN *CommBufferSize OPTIONAL | |
) | |
{ | |
PERIODIC_SMI_LIBRARY_HANDLER_CONTEXT *PeriodicSmiLibraryHandler; | |
EFI_SMM_PERIODIC_TIMER_CONTEXT *TimerContext; | |
EFI_STATUS Status; | |
// | |
// Set the active periodic SMI handler | |
// | |
PeriodicSmiLibraryHandler = SetActivePeriodicSmiLibraryHandler (DispatchHandle); | |
if (PeriodicSmiLibraryHandler == NULL) { | |
return EFI_NOT_FOUND; | |
} | |
// | |
// Retrieve the elapsed time since the last time this periodic SMI handler was called | |
// | |
PeriodicSmiLibraryHandler->ElapsedTime = 0; | |
if (CommBuffer != NULL) { | |
TimerContext = (EFI_SMM_PERIODIC_TIMER_CONTEXT *)CommBuffer; | |
PeriodicSmiLibraryHandler->ElapsedTime = TimerContext->ElapsedTime; | |
} | |
// | |
// Dispatch the periodic SMI handler | |
// | |
if ((PeriodicSmiLibraryHandler->Cpu == PERIODIC_SMI_LIBRARY_ANY_CPU) || | |
(PeriodicSmiLibraryHandler->Cpu == gSmst->CurrentlyExecutingCpu)) | |
{ | |
// | |
// Dispatch on the currently execution CPU if the CPU specified in PeriodicSmiEnable() | |
// was PERIODIC_SMI_LIBRARY_ANY_CPU or the currently executing CPU matches the CPU | |
// that was specified in PeriodicSmiEnable(). | |
// | |
PeriodicSmiDispatchFunctionOnCpu (PeriodicSmiLibraryHandler); | |
} else { | |
// | |
// Acquire spin lock for ths periodic SMI handler. The AP will release the | |
// spin lock when it is done executing the periodic SMI handler. | |
// | |
AcquireSpinLock (&PeriodicSmiLibraryHandler->DispatchLock); | |
// | |
// Execute the periodic SMI handler on the CPU that was specified in | |
// PeriodicSmiEnable(). | |
// | |
Status = gSmst->SmmStartupThisAp ( | |
PeriodicSmiDispatchFunctionWithLock, | |
PeriodicSmiLibraryHandler->Cpu, | |
PeriodicSmiLibraryHandler | |
); | |
if (!EFI_ERROR (Status)) { | |
// | |
// Wait for the AP to release the spin lock. | |
// | |
while (!AcquireSpinLockOrFail (&PeriodicSmiLibraryHandler->DispatchLock)) { | |
CpuPause (); | |
} | |
} | |
// | |
// Release the spin lock for the periodic SMI handler. | |
// | |
ReleaseSpinLock (&PeriodicSmiLibraryHandler->DispatchLock); | |
} | |
// | |
// Reclaim the active periodic SMI handler if it was disabled during the current dispatch. | |
// | |
if (PeriodicSmiLibraryHandler->DispatchHandle == NULL) { | |
ReclaimPeriodicSmiLibraryHandler (PeriodicSmiLibraryHandler); | |
} | |
// | |
// Update state to show that there is no active periodic SMI handler | |
// | |
SetActivePeriodicSmiLibraryHandler (NULL); | |
return EFI_SUCCESS; | |
} | |
/** | |
This function enables a periodic SMI handler. | |
@param[in, out] DispatchHandle A pointer to the handle associated with the | |
enabled periodic SMI handler. This is an | |
optional parameter that may be NULL. If it is | |
NULL, then the handle will not be returned, | |
which means that the periodic SMI handler can | |
never be disabled. | |
@param[in] DispatchFunction A pointer to a periodic SMI handler function. | |
@param[in] Context Optional content to pass into DispatchFunction. | |
@param[in] TickPeriod The requested tick period in 100ns units that | |
control should be given to the periodic SMI | |
handler. Must be one of the supported values | |
returned by PeriodicSmiSupportedPickPeriod(). | |
@param[in] Cpu Specifies the CPU that is required to execute | |
the periodic SMI handler. If Cpu is | |
PERIODIC_SMI_LIBRARY_ANY_CPU, then the periodic | |
SMI handler will always be executed on the SMST | |
CurrentlyExecutingCpu, which may vary across | |
periodic SMIs. If Cpu is between 0 and the SMST | |
NumberOfCpus, then the periodic SMI will always | |
be executed on the requested CPU. | |
@param[in] StackSize The size, in bytes, of the stack to allocate for | |
use by the periodic SMI handler. If 0, then the | |
default stack will be used. | |
@retval EFI_INVALID_PARAMETER DispatchFunction is NULL. | |
@retval EFI_UNSUPPORTED TickPeriod is not a supported tick period. The | |
supported tick periods can be retrieved using | |
PeriodicSmiSupportedTickPeriod(). | |
@retval EFI_INVALID_PARAMETER Cpu is not PERIODIC_SMI_LIBRARY_ANY_CPU or in | |
the range 0 to SMST NumberOfCpus. | |
@retval EFI_OUT_OF_RESOURCES There are not enough resources to enable the | |
periodic SMI handler. | |
@retval EFI_OUT_OF_RESOURCES There are not enough resources to allocate the | |
stack specified by StackSize. | |
@retval EFI_SUCCESS The periodic SMI handler was enabled. | |
**/ | |
EFI_STATUS | |
EFIAPI | |
PeriodicSmiEnable ( | |
IN OUT EFI_HANDLE *DispatchHandle OPTIONAL, | |
IN PERIODIC_SMI_LIBRARY_HANDLER DispatchFunction, | |
IN CONST VOID *Context OPTIONAL, | |
IN UINT64 TickPeriod, | |
IN UINTN Cpu, | |
IN UINTN StackSize | |
) | |
{ | |
EFI_STATUS Status; | |
UINTN Index; | |
PERIODIC_SMI_LIBRARY_HANDLER_CONTEXT *PeriodicSmiLibraryHandler; | |
// | |
// Make sure all the input parameters are valid | |
// | |
if (DispatchFunction == NULL) { | |
return EFI_INVALID_PARAMETER; | |
} | |
for (Index = 0; gSmiTickPeriodTable[Index] != 0; Index++) { | |
if (gSmiTickPeriodTable[Index] == TickPeriod) { | |
break; | |
} | |
} | |
if (gSmiTickPeriodTable[Index] == 0) { | |
return EFI_UNSUPPORTED; | |
} | |
if ((Cpu != PERIODIC_SMI_LIBRARY_ANY_CPU) && (Cpu >= gSmst->NumberOfCpus)) { | |
return EFI_INVALID_PARAMETER; | |
} | |
// | |
// Find a free periodic SMI handler entry | |
// | |
PeriodicSmiLibraryHandler = FindFreePeriodicSmiLibraryHandler (); | |
if (PeriodicSmiLibraryHandler == NULL) { | |
return EFI_OUT_OF_RESOURCES; | |
} | |
// | |
// Initialize a new periodic SMI handler entry | |
// | |
PeriodicSmiLibraryHandler->YieldFlag = FALSE; | |
PeriodicSmiLibraryHandler->DispatchHandle = NULL; | |
PeriodicSmiLibraryHandler->DispatchFunction = DispatchFunction; | |
PeriodicSmiLibraryHandler->Context = (VOID *)Context; | |
PeriodicSmiLibraryHandler->Cpu = Cpu; | |
PeriodicSmiLibraryHandler->StackSize = ALIGN_VALUE (StackSize, EFI_PAGE_SIZE); | |
if (PeriodicSmiLibraryHandler->StackSize > 0) { | |
PeriodicSmiLibraryHandler->Stack = AllocatePages (EFI_SIZE_TO_PAGES (PeriodicSmiLibraryHandler->StackSize)); | |
if (PeriodicSmiLibraryHandler->Stack == NULL) { | |
return EFI_OUT_OF_RESOURCES; | |
} | |
ZeroMem (PeriodicSmiLibraryHandler->Stack, PeriodicSmiLibraryHandler->StackSize); | |
} else { | |
PeriodicSmiLibraryHandler->Stack = NULL; | |
} | |
InitializeSpinLock (&PeriodicSmiLibraryHandler->DispatchLock); | |
PeriodicSmiLibraryHandler->PerfomanceCounterRate = GetPerformanceCounterProperties ( | |
&PeriodicSmiLibraryHandler->PerfomanceCounterStartValue, | |
&PeriodicSmiLibraryHandler->PerfomanceCounterEndValue | |
); | |
PeriodicSmiLibraryHandler->RegisterContext.Period = TickPeriod; | |
PeriodicSmiLibraryHandler->RegisterContext.SmiTickInterval = TickPeriod; | |
Status = gSmmPeriodicTimerDispatch2->Register ( | |
gSmmPeriodicTimerDispatch2, | |
PeriodicSmiDispatchFunction, | |
&PeriodicSmiLibraryHandler->RegisterContext, | |
&PeriodicSmiLibraryHandler->DispatchHandle | |
); | |
if (EFI_ERROR (Status)) { | |
PeriodicSmiLibraryHandler->DispatchHandle = NULL; | |
ReclaimPeriodicSmiLibraryHandler (PeriodicSmiLibraryHandler); | |
return EFI_OUT_OF_RESOURCES; | |
} | |
// | |
// Return the registered handle if the optional DispatchHandle parameter is not NULL | |
// | |
if (DispatchHandle != NULL) { | |
*DispatchHandle = PeriodicSmiLibraryHandler->DispatchHandle; | |
} | |
return EFI_SUCCESS; | |
} | |
/** | |
This function disables a periodic SMI handler that has been previously | |
enabled with PeriodicSmiEnable(). | |
@param[in] DispatchHandle A handle associated with a previously enabled periodic | |
SMI handler. This is an optional parameter that may | |
be NULL. If it is NULL, then the active periodic SMI | |
handlers is disabled. | |
@retval FALSE DispatchHandle is NULL and there is no active periodic SMI handler. | |
@retval FALSE The periodic SMI handler specified by DispatchHandle has | |
not been enabled with PeriodicSmiEnable(). | |
@retval TRUE The periodic SMI handler specified by DispatchHandle has | |
been disabled. If DispatchHandle is NULL, then the active | |
periodic SMI handler has been disabled. | |
**/ | |
BOOLEAN | |
EFIAPI | |
PeriodicSmiDisable ( | |
IN EFI_HANDLE DispatchHandle OPTIONAL | |
) | |
{ | |
PERIODIC_SMI_LIBRARY_HANDLER_CONTEXT *PeriodicSmiLibraryHandler; | |
EFI_STATUS Status; | |
// | |
// Lookup the periodic SMI handler specified by DispatchHandle | |
// | |
PeriodicSmiLibraryHandler = LookupPeriodicSmiLibraryHandler (DispatchHandle); | |
if (PeriodicSmiLibraryHandler == NULL) { | |
return FALSE; | |
} | |
// | |
// Unregister the periodic SMI handler from the SMM Periodic Timer Dispatch 2 Protocol | |
// | |
Status = gSmmPeriodicTimerDispatch2->UnRegister ( | |
gSmmPeriodicTimerDispatch2, | |
PeriodicSmiLibraryHandler->DispatchHandle | |
); | |
if (EFI_ERROR (Status)) { | |
return FALSE; | |
} | |
// | |
// Mark the entry for the disabled periodic SMI handler as free, and | |
// call ReclaimPeriodicSmiLibraryHandler to move it to the list of free | |
// periodic SMI handlers. | |
// | |
PeriodicSmiLibraryHandler->DispatchHandle = NULL; | |
if (PeriodicSmiLibraryHandler != GetActivePeriodicSmiLibraryHandler ()) { | |
ReclaimPeriodicSmiLibraryHandler (PeriodicSmiLibraryHandler); | |
} | |
return TRUE; | |
} | |
/** | |
This constructor function caches the pointer to the SMM Periodic Timer | |
Dispatch 2 Protocol and collects the list SMI tick rates that the hardware | |
supports. | |
@param[in] ImageHandle The firmware allocated handle for the EFI image. | |
@param[in] SystemTable A pointer to the EFI System Table. | |
@retval EFI_SUCCESS The constructor always returns EFI_SUCCESS. | |
**/ | |
EFI_STATUS | |
EFIAPI | |
SmmPeriodicSmiLibConstructor ( | |
IN EFI_HANDLE ImageHandle, | |
IN EFI_SYSTEM_TABLE *SystemTable | |
) | |
{ | |
EFI_STATUS Status; | |
UINT64 *SmiTickInterval; | |
UINTN Count; | |
// | |
// Locate the SMM Periodic Timer Dispatch 2 Protocol | |
// | |
Status = gSmst->SmmLocateProtocol ( | |
&gEfiSmmPeriodicTimerDispatch2ProtocolGuid, | |
NULL, | |
(VOID **)&gSmmPeriodicTimerDispatch2 | |
); | |
ASSERT_EFI_ERROR (Status); | |
ASSERT (gSmmPeriodicTimerDispatch2 != NULL); | |
// | |
// Count the number of periodic SMI tick intervals that the SMM Periodic Timer | |
// Dispatch 2 Protocol supports. | |
// | |
SmiTickInterval = NULL; | |
Count = 0; | |
do { | |
Status = gSmmPeriodicTimerDispatch2->GetNextShorterInterval ( | |
gSmmPeriodicTimerDispatch2, | |
&SmiTickInterval | |
); | |
Count++; | |
} while (SmiTickInterval != NULL); | |
// | |
// Allocate a buffer for the table of supported periodic SMI tick periods. | |
// | |
gSmiTickPeriodTable = AllocateZeroPool (Count * sizeof (UINT64)); | |
ASSERT (gSmiTickPeriodTable != NULL); | |
// | |
// Fill in the table of supported periodic SMI tick periods. | |
// | |
SmiTickInterval = NULL; | |
Count = 0; | |
do { | |
gSmiTickPeriodTable[Count] = 0; | |
Status = gSmmPeriodicTimerDispatch2->GetNextShorterInterval ( | |
gSmmPeriodicTimerDispatch2, | |
&SmiTickInterval | |
); | |
if (SmiTickInterval != NULL) { | |
gSmiTickPeriodTable[Count] = *SmiTickInterval; | |
} | |
Count++; | |
} while (SmiTickInterval != NULL); | |
// | |
// Allocate buffer for initial set of periodic SMI handlers | |
// | |
EnlargeFreePeriodicSmiLibraryHandlerList (); | |
return EFI_SUCCESS; | |
} | |
/** | |
The constructor function caches the pointer to the SMM Periodic Timer Dispatch 2 | |
Protocol and collects the list SMI tick rates that the hardware supports. | |
@param[in] ImageHandle The firmware allocated handle for the EFI image. | |
@param[in] SystemTable A pointer to the EFI System Table. | |
@retval EFI_SUCCESS The constructor always returns EFI_SUCCESS. | |
**/ | |
EFI_STATUS | |
EFIAPI | |
SmmPeriodicSmiLibDestructor ( | |
IN EFI_HANDLE ImageHandle, | |
IN EFI_SYSTEM_TABLE *SystemTable | |
) | |
{ | |
LIST_ENTRY *Link; | |
PERIODIC_SMI_LIBRARY_HANDLER_CONTEXT *PeriodicSmiLibraryHandler; | |
// | |
// Free the table of supported periodic SMI tick rates | |
// | |
if (gSmiTickPeriodTable != NULL) { | |
FreePool (gSmiTickPeriodTable); | |
} | |
// | |
// Disable all periodic SMI handlers | |
// | |
for (Link = GetFirstNode (&gPeriodicSmiLibraryHandlers); !IsNull (&gPeriodicSmiLibraryHandlers, Link);) { | |
PeriodicSmiLibraryHandler = PERIODIC_SMI_LIBRARY_HANDLER_CONTEXT_FROM_LINK (Link); | |
Link = GetNextNode (&gPeriodicSmiLibraryHandlers, Link); | |
PeriodicSmiDisable (PeriodicSmiLibraryHandler->DispatchHandle); | |
} | |
// | |
// Free all the periodic SMI handler entries | |
// | |
for (Link = GetFirstNode (&gFreePeriodicSmiLibraryHandlers); !IsNull (&gFreePeriodicSmiLibraryHandlers, Link);) { | |
PeriodicSmiLibraryHandler = PERIODIC_SMI_LIBRARY_HANDLER_CONTEXT_FROM_LINK (Link); | |
Link = RemoveEntryList (Link); | |
FreePool (PeriodicSmiLibraryHandler); | |
} | |
return EFI_SUCCESS; | |
} |