/** @file
File to contain all the hardware specific stuff for the Periodical Timer dispatch protocol.
Copyright (c) 2017, Intel Corporation. All rights reserved.
SPDX-License-Identifier: BSD-2-Clause-Patent
**/
#include "PchSmmHelpers.h"
#include
//
// There is only one instance for PeriodicTimerCommBuffer.
// It's safe in SMM since there is no re-entry for the function.
//
GLOBAL_REMOVE_IF_UNREFERENCED EFI_SMM_PERIODIC_TIMER_CONTEXT mPchPeriodicTimerCommBuffer;
typedef enum {
PERIODIC_TIMER= 0,
SWSMI_TIMER,
NUM_TIMERS
} SUPPORTED_TIMER;
typedef struct _TIMER_INTERVAL {
UINT64 Interval;
UINT8 AssociatedTimer;
} TIMER_INTERVAL;
#define NUM_INTERVALS 8
//
// Time constants, in 100 nano-second units
//
#define TIME_64s 640000000 ///< 64 s
#define TIME_32s 320000000 ///< 32 s
#define TIME_16s 160000000 ///< 16 s
#define TIME_8s 80000000 ///< 8 s
#define TIME_64ms 640000 ///< 64 ms
#define TIME_32ms 320000 ///< 32 ms
#define TIME_16ms 160000 ///< 16 ms
#define TIME_1_5ms 15000 ///< 1.5 ms
typedef enum {
INDEX_TIME_64s = 0,
INDEX_TIME_32s,
INDEX_TIME_16s,
INDEX_TIME_8s,
INDEX_TIME_64ms,
INDEX_TIME_32ms,
INDEX_TIME_16ms,
INDEX_TIME_1_5ms,
INDEX_TIME_MAX
} TIMER_INTERVAL_INDEX;
static TIMER_INTERVAL mSmmPeriodicTimerIntervals[NUM_INTERVALS] = {
{
TIME_64s,
PERIODIC_TIMER
},
{
TIME_32s,
PERIODIC_TIMER
},
{
TIME_16s,
PERIODIC_TIMER
},
{
TIME_8s,
PERIODIC_TIMER
},
{
TIME_64ms,
SWSMI_TIMER
},
{
TIME_32ms,
SWSMI_TIMER
},
{
TIME_16ms,
SWSMI_TIMER
},
{
TIME_1_5ms,
SWSMI_TIMER
},
};
typedef struct _TIMER_INFO {
UINTN NumChildren; ///< number of children using this timer
UINT64 MinReqInterval; ///< minimum interval required by children
UINTN CurrentSetting; ///< interval this timer is set at right now (index into interval table)
} TIMER_INFO;
GLOBAL_REMOVE_IF_UNREFERENCED TIMER_INFO mTimers[NUM_TIMERS];
GLOBAL_REMOVE_IF_UNREFERENCED PCH_SMM_SOURCE_DESC mTIMER_SOURCE_DESCS[NUM_TIMERS] = {
{
PCH_SMM_NO_FLAGS,
{
{
{
ACPI_ADDR_TYPE,
{R_PCH_SMI_EN}
},
S_PCH_SMI_EN,
N_PCH_SMI_EN_PERIODIC
},
NULL_BIT_DESC_INITIALIZER
},
{
{
{
ACPI_ADDR_TYPE,
{R_PCH_SMI_STS}
},
S_PCH_SMI_STS,
N_PCH_SMI_STS_PERIODIC
}
},
{
{
ACPI_ADDR_TYPE,
{R_PCH_SMI_STS}
},
S_PCH_SMI_STS,
N_PCH_SMI_STS_PERIODIC
}
},
{
PCH_SMM_NO_FLAGS,
{
{
{
ACPI_ADDR_TYPE,
{R_PCH_SMI_EN}
},
S_PCH_SMI_EN,
N_PCH_SMI_EN_SWSMI_TMR
},
NULL_BIT_DESC_INITIALIZER
},
{
{
{
ACPI_ADDR_TYPE,
{R_PCH_SMI_STS}
},
S_PCH_SMI_STS,
N_PCH_SMI_STS_SWSMI_TMR
}
},
{
{
ACPI_ADDR_TYPE,
{R_PCH_SMI_STS}
},
S_PCH_SMI_STS,
N_PCH_SMI_STS_SWSMI_TMR
}
}
};
/**
Program Smm Periodic Timer
@param[in] SrcDesc Pointer to the PCH_SMM_SOURCE_DESC instance.
**/
VOID
PchSmmPeriodicTimerProgramTimers (
IN PCH_SMM_SOURCE_DESC *SrcDesc
);
/**
Convert the dispatch context to the timer interval, this function will assert if then either:
(1) The context contains an invalid interval
(2) The timer interval table is corrupt
@param[in] DispatchContext The pointer to the Dispatch Context
@retval TIMER_INTERVAL The timer interval of input dispatch context
**/
TIMER_INTERVAL *
ContextToTimerInterval (
IN PCH_SMM_CONTEXT *DispatchContext
)
{
UINTN loopvar;
///
/// Determine which timer this child is using
///
for (loopvar = 0; loopvar < NUM_INTERVALS; loopvar++) {
if (((DispatchContext->PeriodicTimer.SmiTickInterval == 0) &&
(DispatchContext->PeriodicTimer.Period >= mSmmPeriodicTimerIntervals[loopvar].Interval)) ||
(DispatchContext->PeriodicTimer.SmiTickInterval == mSmmPeriodicTimerIntervals[loopvar].Interval)) {
return &mSmmPeriodicTimerIntervals[loopvar];
}
}
///
/// If this assertion fires, then either:
/// (1) the context contains an invalid interval
/// (2) the timer interval table is corrupt
///
ASSERT (FALSE);
return NULL;
}
/**
Figure out which timer the child is requesting and
send back the source description
@param[in] DispatchContext The pointer to the Dispatch Context instances
@param[out] SrcDesc The pointer to the source description
**/
VOID
MapPeriodicTimerToSrcDesc (
IN PCH_SMM_CONTEXT *DispatchContext,
OUT PCH_SMM_SOURCE_DESC *SrcDesc
)
{
TIMER_INTERVAL *TimerInterval;
///
/// Figure out which timer the child is requesting and
/// send back the source description
///
TimerInterval = ContextToTimerInterval (DispatchContext);
if (TimerInterval == NULL) {
return;
}
CopyMem (
(VOID *) SrcDesc,
(VOID *) (&mTIMER_SOURCE_DESCS[TimerInterval->AssociatedTimer]),
sizeof (PCH_SMM_SOURCE_DESC)
);
///
/// Program the value of the interval into hardware
///
PchSmmPeriodicTimerProgramTimers (SrcDesc);
}
/**
Update the elapsed time from the Interval data of DATABASE_RECORD
@param[in] Record The pointer to the DATABASE_RECORD.
@param[out] HwContext The Context to be updated.
**/
VOID
EFIAPI
PeriodicTimerGetContext (
IN DATABASE_RECORD *Record,
OUT PCH_SMM_CONTEXT *HwContext
)
{
TIMER_INTERVAL *TimerInterval;
ASSERT (Record->ProtocolType == PeriodicTimerType);
TimerInterval = ContextToTimerInterval (&Record->ChildContext);
if (TimerInterval == NULL) {
return;
}
///
/// Ignore the hardware context. It's not required for this protocol.
/// Instead, just increment the child's context.
/// Update the elapsed time w/ the data from our tables
///
Record->MiscData.ElapsedTime += mTimers[TimerInterval->AssociatedTimer].MinReqInterval;
*HwContext = Record->ChildContext;
}
/**
Check whether Periodic Timer of two contexts match
@param[in] Context1 Context 1 that includes Periodic Timer 1
@param[in] Context2 Context 2 that includes Periodic Timer 2
@retval FALSE Periodic Timer match
@retval TRUE Periodic Timer don't match
**/
BOOLEAN
EFIAPI
PeriodicTimerCmpContext (
IN PCH_SMM_CONTEXT *HwContext,
IN PCH_SMM_CONTEXT *ChildContext
)
{
DATABASE_RECORD *Record;
Record = DATABASE_RECORD_FROM_CHILDCONTEXT (ChildContext);
if (Record->MiscData.ElapsedTime >= ChildContext->PeriodicTimer.Period) {
///
/// For EDKII, the ElapsedTime is reset when PeriodicTimerGetCommBuffer
///
return TRUE;
} else {
return FALSE;
}
}
/**
Gather the CommBuffer information of SmmPeriodicTimerDispatch2.
@param[in] Record No use
@param[out] CommBuffer Point to the CommBuffer structure
@param[out] CommBufferSize Point to the Size of CommBuffer structure
**/
VOID
EFIAPI
PeriodicTimerGetCommBuffer (
IN DATABASE_RECORD *Record,
OUT VOID **CommBuffer,
OUT UINTN *CommBufferSize
)
{
ASSERT (Record->ProtocolType == PeriodicTimerType);
mPchPeriodicTimerCommBuffer.ElapsedTime = Record->MiscData.ElapsedTime;
///
/// For EDKII, the ElapsedTime is reset here
///
Record->MiscData.ElapsedTime = 0;
///
/// Return the CommBuffer
///
*CommBuffer = (VOID *) &mPchPeriodicTimerCommBuffer;
*CommBufferSize = sizeof (EFI_SMM_PERIODIC_TIMER_CONTEXT);
}
/**
Program Smm Periodic Timer
@param[in] SrcDesc Pointer to the PCH_SMM_SOURCE_DESC instance.
**/
VOID
PchSmmPeriodicTimerProgramTimers (
IN PCH_SMM_SOURCE_DESC *SrcDesc
)
{
SUPPORTED_TIMER Timer;
DATABASE_RECORD *RecordInDb;
LIST_ENTRY *LinkInDb;
TIMER_INTERVAL *TimerInterval;
UINT8 GenPmConA;
UINT8 GenPmConB;
UINTN PciPmcRegBase;
PciPmcRegBase = MmPciBase (
DEFAULT_PCI_BUS_NUMBER_PCH,
PCI_DEVICE_NUMBER_PCH_PMC,
PCI_FUNCTION_NUMBER_PCH_PMC
);
///
/// Find the minimum required interval for each timer
///
for (Timer = 0; Timer < NUM_TIMERS; Timer++) {
mTimers[Timer].MinReqInterval = ~ (UINT64) 0x0;
mTimers[Timer].NumChildren = 0;
}
LinkInDb = GetFirstNode (&mPrivateData.CallbackDataBase);
while (!IsNull (&mPrivateData.CallbackDataBase, LinkInDb)) {
RecordInDb = DATABASE_RECORD_FROM_LINK (LinkInDb);
if (RecordInDb->ProtocolType == PeriodicTimerType) {
///
/// This child is registerd with the PeriodicTimer protocol
///
TimerInterval = ContextToTimerInterval (&RecordInDb->ChildContext);
if (TimerInterval == NULL) {
return;
}
Timer = TimerInterval->AssociatedTimer;
if (Timer < 0 || Timer >= NUM_TIMERS) {
ASSERT (FALSE);
CpuDeadLoop ();
return;
}
if (mTimers[Timer].MinReqInterval > RecordInDb->ChildContext.PeriodicTimer.SmiTickInterval) {
mTimers[Timer].MinReqInterval = RecordInDb->ChildContext.PeriodicTimer.SmiTickInterval;
}
mTimers[Timer].NumChildren++;
}
LinkInDb = GetNextNode (&mPrivateData.CallbackDataBase, &RecordInDb->Link);
}
///
/// Program the hardware
///
if (mTimers[PERIODIC_TIMER].NumChildren > 0) {
GenPmConA = MmioRead8 (PciPmcRegBase + R_PCH_PMC_GEN_PMCON_A);
GenPmConA &= (UINT8) ~B_PCH_PMC_GEN_PMCON_A_PER_SMI_SEL;
switch (mTimers[PERIODIC_TIMER].MinReqInterval) {
case TIME_64s:
GenPmConA |= V_PCH_PMC_GEN_PMCON_A_PER_SMI_64S;
mTimers[PERIODIC_TIMER].CurrentSetting = INDEX_TIME_64s;
break;
case TIME_32s:
GenPmConA |= V_PCH_PMC_GEN_PMCON_A_PER_SMI_32S;
mTimers[PERIODIC_TIMER].CurrentSetting = INDEX_TIME_32s;
break;
case TIME_16s:
GenPmConA |= V_PCH_PMC_GEN_PMCON_A_PER_SMI_16S;
mTimers[PERIODIC_TIMER].CurrentSetting = INDEX_TIME_16s;
break;
case TIME_8s:
GenPmConA |= V_PCH_PMC_GEN_PMCON_A_PER_SMI_8S;
mTimers[PERIODIC_TIMER].CurrentSetting = INDEX_TIME_8s;
break;
default:
ASSERT (FALSE);
break;
}
MmioWrite8 (PciPmcRegBase + R_PCH_PMC_GEN_PMCON_A, GenPmConA);
///
/// Restart the timer here, just need to clear the SMI
///
if (SrcDesc->Sts[0].Bit == N_PCH_SMI_STS_PERIODIC) {
PchSmmClearSource (&mTIMER_SOURCE_DESCS[PERIODIC_TIMER]);
}
} else {
PchSmmDisableSource (&mTIMER_SOURCE_DESCS[PERIODIC_TIMER]);
}
if (mTimers[SWSMI_TIMER].NumChildren > 0) {
///
/// ICH9, ICH10 and PCH share the same bit positions for SW SMI Rate settings
///
GenPmConB = MmioRead8 (PciPmcRegBase + R_PCH_PMC_GEN_PMCON_B);
//
// Don't clear B_PCH_PMC_GEN_PMCON_B_PWR_FLR by accident since it's RW/1C.
//
GenPmConB &= ~B_PCH_PMC_GEN_PMCON_B_PWR_FLR;
GenPmConB &= ~B_PCH_PMC_GEN_PMCON_B_SWSMI_RTSL;
switch (mTimers[SWSMI_TIMER].MinReqInterval) {
case TIME_64ms:
GenPmConB |= V_PCH_PMC_GEN_PMCON_B_SWSMI_RTSL_64MS;
mTimers[SWSMI_TIMER].CurrentSetting = INDEX_TIME_64ms;
break;
case TIME_32ms:
GenPmConB |= V_PCH_PMC_GEN_PMCON_B_SWSMI_RTSL_32MS;
mTimers[SWSMI_TIMER].CurrentSetting = INDEX_TIME_32ms;
break;
case TIME_16ms:
GenPmConB |= V_PCH_PMC_GEN_PMCON_B_SWSMI_RTSL_16MS;
mTimers[SWSMI_TIMER].CurrentSetting = INDEX_TIME_16ms;
break;
case TIME_1_5ms:
GenPmConB |= V_PCH_PMC_GEN_PMCON_B_SWSMI_RTSL_1_5MS;
mTimers[SWSMI_TIMER].CurrentSetting = INDEX_TIME_1_5ms;
break;
default:
ASSERT (FALSE);
break;
}
///
/// SWSMI_RATE_SEL BIT (PMC PCI offset A4h[7:6]) bits are in RTC well
///
MmioWrite8 (PciPmcRegBase + R_PCH_PMC_GEN_PMCON_B, GenPmConB);
///
/// Restart the timer here, need to disable, clear, then enable to restart this timer
///
if (SrcDesc->Sts[0].Bit == N_PCH_SMI_STS_SWSMI_TMR) {
PchSmmDisableSource (&mTIMER_SOURCE_DESCS[SWSMI_TIMER]);
PchSmmClearSource (&mTIMER_SOURCE_DESCS[SWSMI_TIMER]);
PchSmmEnableSource (&mTIMER_SOURCE_DESCS[SWSMI_TIMER]);
}
} else {
PchSmmDisableSource (&mTIMER_SOURCE_DESCS[SWSMI_TIMER]);
}
}
/**
This services returns the next SMI tick period that is supported by the chipset.
The order returned is from longest to shortest interval period.
@param[in] This Pointer to the EFI_SMM_PERIODIC_TIMER_DISPATCH2_PROTOCOL instance.
@param[in, out] SmiTickInterval Pointer to pointer of the next shorter SMI interval period that is supported by the child.
@retval EFI_SUCCESS The service returned successfully.
@retval EFI_INVALID_PARAMETER The parameter SmiTickInterval is invalid.
**/
EFI_STATUS
PchSmmPeriodicTimerDispatchGetNextShorterInterval (
IN CONST EFI_SMM_PERIODIC_TIMER_DISPATCH2_PROTOCOL *This,
IN OUT UINT64 **SmiTickInterval
)
{
TIMER_INTERVAL *IntervalPointer;
ASSERT (SmiTickInterval != NULL);
IntervalPointer = (TIMER_INTERVAL *) *SmiTickInterval;
if (IntervalPointer == NULL) {
///
/// The first time child requesting an interval
///
IntervalPointer = &mSmmPeriodicTimerIntervals[0];
} else if (IntervalPointer == &mSmmPeriodicTimerIntervals[NUM_INTERVALS - 1]) {
///
/// At end of the list
///
IntervalPointer = NULL;
} else {
if ((IntervalPointer >= &mSmmPeriodicTimerIntervals[0]) &&
(IntervalPointer < &mSmmPeriodicTimerIntervals[NUM_INTERVALS - 1])
) {
///
/// Get the next interval in the list
///
IntervalPointer++;
} else {
///
/// Input is out of range
///
return EFI_INVALID_PARAMETER;
}
}
if (IntervalPointer != NULL) {
*SmiTickInterval = &IntervalPointer->Interval;
} else {
*SmiTickInterval = NULL;
}
return EFI_SUCCESS;
}
/**
This function is responsible for calculating and enabling any timers that are required
to dispatch messages to children. The SrcDesc argument isn't acutally used.
@param[in] SrcDesc Pointer to the PCH_SMM_SOURCE_DESC instance.
**/
VOID
EFIAPI
PchSmmPeriodicTimerClearSource (
IN PCH_SMM_SOURCE_DESC *SrcDesc
)
{
PchSmmPeriodicTimerProgramTimers (SrcDesc);
}
/**
Check if the handle is in type of PeriodicTimer
@retval TRUE The handle is in type of PeriodicTimer.
@retval FALSE The handle is not in type of PeriodicTimer.
**/
BOOLEAN
IsSmmPeriodicTimerHandle (
IN EFI_HANDLE DispatchHandle
)
{
DATABASE_RECORD *RecordInDb;
LIST_ENTRY *LinkInDb;
LinkInDb = GetFirstNode (&mPrivateData.CallbackDataBase);
while (!IsNull (&mPrivateData.CallbackDataBase, LinkInDb)) {
if (DispatchHandle == (EFI_HANDLE) LinkInDb) {
RecordInDb = DATABASE_RECORD_FROM_LINK (LinkInDb);
if (RecordInDb->ProtocolType == PeriodicTimerType) {
return TRUE;
}
}
LinkInDb = GetNextNode (&mPrivateData.CallbackDataBase, LinkInDb);
}
return FALSE;
}
/**
Pause SMM periodic timer callback function.
This function disable the SMI enable of SMI timer according to the DispatchHandle,
which is returned by SMM periodic timer callback registration.
@retval EFI_SUCCESS This operation is complete.
@retval EFI_INVALID_PARAMETER The DispatchHandle is invalid.
**/
EFI_STATUS
EFIAPI
PchSmmPeriodicTimerControlPause (
IN PCH_SMM_PERIODIC_TIMER_CONTROL_PROTOCOL *This,
IN EFI_HANDLE DispatchHandle
)
{
DATABASE_RECORD *RecordInDb;
TIMER_INTERVAL *TimerInterval;
if (IsSmmPeriodicTimerHandle (DispatchHandle) == FALSE) {
return EFI_INVALID_PARAMETER;
}
RecordInDb = DATABASE_RECORD_FROM_LINK (DispatchHandle);
TimerInterval = NULL;
TimerInterval = ContextToTimerInterval (&RecordInDb->ChildContext);
if (TimerInterval == NULL) {
return EFI_INVALID_PARAMETER;
}
PchSmmDisableSource (&mTIMER_SOURCE_DESCS[TimerInterval->AssociatedTimer]);
return EFI_SUCCESS;
}
/**
Resume SMM periodic timer callback function.
This function enable the SMI enable of SMI timer according to the DispatchHandle,
which is returned by SMM periodic timer callback registration.
@retval EFI_SUCCESS This operation is complete.
@retval EFI_INVALID_PARAMETER The DispatchHandle is invalid.
**/
EFI_STATUS
EFIAPI
PchSmmPeriodicTimerControlResume (
IN PCH_SMM_PERIODIC_TIMER_CONTROL_PROTOCOL *This,
IN EFI_HANDLE DispatchHandle
)
{
DATABASE_RECORD *RecordInDb;
TIMER_INTERVAL *TimerInterval;
if (IsSmmPeriodicTimerHandle (DispatchHandle) == FALSE) {
return EFI_INVALID_PARAMETER;
}
RecordInDb = DATABASE_RECORD_FROM_LINK (DispatchHandle);
TimerInterval = NULL;
TimerInterval = ContextToTimerInterval (&RecordInDb->ChildContext);
if (TimerInterval == NULL) {
return EFI_INVALID_PARAMETER;
}
PchSmmEnableSource (&mTIMER_SOURCE_DESCS[TimerInterval->AssociatedTimer]);
return EFI_SUCCESS;
}
GLOBAL_REMOVE_IF_UNREFERENCED PCH_SMM_PERIODIC_TIMER_CONTROL_PROTOCOL mPchSmmPeriodicTimerControlProtocol = {
PchSmmPeriodicTimerControlPause,
PchSmmPeriodicTimerControlResume
};
/**
Install PCH SMM periodic timer control protocol
@param[in] Handle handle for this driver
@retval EFI_SUCCESS Driver initialization completed successfully
**/
EFI_STATUS
EFIAPI
InstallPchSmmPeriodicTimerControlProtocol (
IN EFI_HANDLE Handle
)
{
EFI_STATUS Status;
//
// Install protocol interface
//
Status = gSmst->SmmInstallProtocolInterface (
&Handle,
&gPchSmmPeriodicTimerControlGuid,
EFI_NATIVE_INTERFACE,
&mPchSmmPeriodicTimerControlProtocol
);
ASSERT_EFI_ERROR (Status);
return EFI_SUCCESS;
}