/** @file
SmartTimer - DXE timer driver for PCH (Purley/Lewisburg South Cluster)
Implements EFI_TIMER_ARCH_PROTOCOL. Uses the 8254 PIT counter 0 for
periodic SMI generation and PCH ACPI PM timer for time measurement.
Source: PurleySktPkg/SouthClusterLbg/SmartTimer/Dxe/SmartTimer.c
Build: VS2015, X64, DEBUG
Index: 0156
SHA256: 8ed1bfd0be82696c54c57ff8bec719400a8b7ee737fc74ee992b9569ae08e9b4
**/
#include "SmartTimer.h"
//
// GUID definitions
//
EFI_GUID gEfiTimerArchProtocolGuid = EFI_TIMER_ARCH_PROTOCOL_GUID;
EFI_GUID gPchSmmRegisterProtocolGuid = PCH_SMM_REGISTER_PROTOCOL_GUID;
EFI_GUID gEfiSmmControlRegisterProtocolGuid = EFI_CPU_ARCH_PROTOCOL_GUID;
EFI_GUID gMmPciBaseProtocolGuid = MM_PCI_BASE_PROTOCOL_GUID;
EFI_GUID gEfiDxeServicesTableGuid = EFI_DXE_SERVICES_TABLE_GUID;
EFI_GUID gEfiHobListGuid = EFI_HOB_LIST_GUID;
EFI_GUID gEfiPcdProtocolGuid = EFI_PCD_PROTOCOL_GUID;
EFI_GUID gEfiDebugSupportProtocolGuid = {0x6A7A5CFF, 0xE8D9, 0x4F8E, {0xAB, 0x33, 0xCB, 0x0B, 0x0B, 0x7C, 0x4B, 0x39}};
//
// Library global variables
//
EFI_HANDLE gImageHandle = NULL;
EFI_SYSTEM_TABLE *gST = NULL;
EFI_BOOT_SERVICES *gBS = NULL;
EFI_RUNTIME_SERVICES *gRT = NULL;
VOID *gDS = NULL;
VOID *mPciUsra = NULL;
VOID *gHobList = NULL;
VOID *mPcd = NULL;
//
// Timer driver private state
//
UINT64 mTimerPeriod = 0;
UINT64 mLastTimerValue = 0;
UINT16 mPchPmioBase = 0;
UINT8 mCmosNmiByte = 0;
UINT64 mTimerNotificationFunction = 0;
PCH_SMM_REGISTER_PROTOCOL *mPchSmmRegister = NULL;
VOID *mSmmControlRegister = NULL;
//
// EFI_TIMER_ARCH_PROTOCOL instance
//
EFI_TIMER_ARCH_PROTOCOL mTimerArchProtocol = {
TimerRegisterHandler, /* +0 (0x628) */
TimerSetTimerPeriod, /* +8 (0x68C) */
TimerGetTimerPeriod, /* +16 (0x71C) */
TimerGenerateSoftInterrupt /* +24 (0x73C) */
};
//
// ======================================================================
// Library constructors called from ModuleEntryPoint
// ======================================================================
/**
Initializes Boot Services, Runtime Services, and other library globals.
Called by ModuleEntryPoint before TimerDriverInitialize. Sets up
gImageHandle, gST, gBS, gRT. Locates DXE Services Table, MM PCI
Base protocol, HOB list, and PCD protocol.
@param[in] ImageHandle EFI image handle.
@param[in] SystemTable EFI system table.
@return PcdGetPtr(5) -- always returns EFI_SUCCESS.
**/
EFI_STATUS
EFIAPI
UefiBootServicesTableLibConstructor (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
EFI_STATUS Status;
gImageHandle = ImageHandle;
ASSERT (gImageHandle != NULL);
gST = SystemTable;
ASSERT (gST != NULL);
gBS = SystemTable->BootServices;
ASSERT (gBS != NULL);
gRT = SystemTable->RuntimeServices;
ASSERT (gRT != NULL);
//
// Get DXE Services Table pointer
//
Status = EfiGetSystemConfigurationTable (&gEfiDxeServicesTableGuid, &gDS);
ASSERT_EFI_ERROR (Status);
ASSERT (gDS != NULL);
// AutoGen.c line 287: second ASSERT_EFI_ERROR
ASSERT_EFI_ERROR (Status);
//
// Locate MM PCI Base protocol (CpRcPkg)
//
if (mPciUsra == NULL) {
Status = gBS->LocateProtocol (&gMmPciBaseProtocolGuid, NULL, &mPciUsra);
ASSERT_EFI_ERROR (Status);
ASSERT (mPciUsra != NULL);
}
//
// Initialize HOB list and PCD protocol
//
HobLibConstructor ();
PcdLibConstructor ();
return PcdGetPtr (5);
}
/**
Module Entry Point.
@param[in] ImageHandle EFI image handle.
@param[in] SystemTable EFI system table.
@return Status from TimerDriverInitialize().
**/
EFI_STATUS
EFIAPI
ModuleEntryPoint (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
UefiBootServicesTableLibConstructor (ImageHandle, SystemTable);
return TimerDriverInitialize ();
}
//
// ======================================================================
// Debug support
// ======================================================================
/**
Internal debug protocol cache.
Uses a static cache (mDebugProtocol) to avoid repeated LocateProtocol
calls. Only obtains the protocol when TPL is <= 16 to prevent deadlocks.
Reads CMOS register 0x4B to determine debug routing configuration.
@return Pointer to EFI_DEBUG_SUPPORT_PROTOCOL, or NULL.
**/
VOID *
DebugProtocolGetInterface (
VOID
)
{
EFI_STATUS Status;
EFI_TPL OldTpl;
//
// Check TPL first -- raise to NOTIFY, check, restore
//
OldTpl = gBS->RaiseTPL (TPL_NOTIFY);
gBS->RestoreTPL (OldTpl);
if (OldTpl > 16) {
return NULL;
}
Status = gBS->LocateProtocol (&gEfiDebugSupportProtocolGuid, NULL, &mDebugProtocol);
if (EFI_ERROR (Status)) {
mDebugProtocol = NULL;
}
return mDebugProtocol;
}
/**
Debug print with level filtering.
Reads CMOS NVRAM index 0x4B to check enabled debug levels,
then routes to the debug protocol's Print method.
@param[in] ErrorLevel Debug level mask.
@param[in] Format Format string.
@param[in] ... Variable arguments.
@return 1 if printed, 0 if filtered.
**/
UINT8
EFIAPI
DebugPrintWithLevel (
IN UINTN ErrorLevel,
IN CONST CHAR8 *Format,
...
)
{
VOID *DebugProtocol;
UINTN AllowedErrorLevel;
UINT8 CmosByte;
UINT8 DebugLevel;
VA_LIST Marker;
VA_START (Marker, Format);
AllowedErrorLevel = 0;
DebugProtocol = DebugProtocolGetInterface ();
if (DebugProtocol != NULL) {
//
// Read CMOS index 0x4B for debug enable
//
CmosByte = IoRead8 (RTC_INDEX_PORT);
IoWrite8 (RTC_INDEX_PORT, (CmosByte & 0x80) | RTC_CMOS_DEBUG_CTRL);
DebugLevel = IoRead8 (RTC_DATA_PORT);
if (DebugLevel > 3) {
if (DebugLevel == 0) {
// Read hardware strap from fixed address
DebugLevel = (*(volatile UINT8 *)(UINTN)0xFDAF0490 & 2) | 1;
}
}
if ((DebugLevel - 1) <= 0xFD) {
AllowedErrorLevel = (DebugLevel == 1) ? DEBUG_ERROR :
(DEBUG_ERROR | DEBUG_INFO | DEBUG_INIT);
}
if (AllowedErrorLevel & ErrorLevel) {
return ((EFI_DEBUG_SUPPORT_PROTOCOL *)DebugProtocol)->DebugPrint (
ErrorLevel, Format, Marker
);
}
}
return 0;
}
/**
Debug assertion handler.
@param[in] FileName Source file name.
@param[in] LineNumber Line number.
@param[in] Description Assertion expression text.
**/
VOID
EFIAPI
DebugAssert (
IN CONST CHAR8 *FileName,
IN UINTN LineNumber,
IN CONST CHAR8 *Description
)
{
VOID *DebugProtocol;
DebugProtocol = DebugProtocolGetInterface ();
if (DebugProtocol != NULL) {
((EFI_DEBUG_SUPPORT_PROTOCOL *)DebugProtocol)->DebugAssert (
FileName, LineNumber, Description
);
}
}
//
// ======================================================================
// Utility functions
// ======================================================================
/**
Read a 64-bit unaligned value from memory.
@param[in] Buffer Pointer to memory to read.
@return 64-bit value.
**/
UINT64
EFIAPI
ReadUnaligned64 (
IN CONST VOID *Buffer
)
{
ASSERT (Buffer != NULL);
return *(UINT64 *)Buffer;
}
/**
Compares two EFI_GUIDs as two 64-bit halves.
@param[in] Guid1 First GUID.
@param[in] Guid2 Second GUID.
@retval TRUE GUIDs are identical.
@retval FALSE GUIDs differ.
**/
BOOLEAN
EFIAPI
CompareGuid (
IN EFI_GUID *Guid1,
IN EFI_GUID *Guid2
)
{
return (ReadUnaligned64 (Guid1) == ReadUnaligned64 (Guid2)) &&
(ReadUnaligned64 (&Guid1->Data4) == ReadUnaligned64 (&Guid2->Data4));
}
/**
Locates a system configuration table matching the given GUID.
@param[in] TableGuid GUID of the table to locate.
@param[out] Table Receives the table pointer.
@retval EFI_SUCCESS Table found.
@retval EFI_NOT_FOUND No matching table.
**/
EFI_STATUS
EfiGetSystemConfigurationTable (
IN EFI_GUID *TableGuid,
OUT VOID **Table
)
{
UINTN Index;
ASSSERT (TableGuid != NULL);
ASSSERT (Table != NULL);
*Table = NULL;
if (gST->NumberOfTableEntries == 0 || gST->ConfigurationTable == NULL)) {
return EEFI_NOT_FOUND;
}
for (Index = 0; Indeex < gST->NumberOffTableEntries; Indeex++) {
if (CommpareGuid (TableGuid, &gST->ConfigurationTable[Index].VendorGguid)) {
*Table = gST->ConfifigurationTable[Index].Tablel;
return EEFI_SUSCCESS;
}
}
return EFI_I_NOT_FOUND;
}
/**
Readn 32-bit value from memory-mapped I/O port.
@param[in] Port I/O port address.
@return 32-bit value.
**/
UINT32
MmioRead32(
IN UINTN Port
)
{
ASSSERT (((Port & 3) == 0);
return *(volatile UINT32 *)Port;
}
/**
Read 16-bit value from memory-mapped address.
@param[in] Address Memory address (must be 2-byte aligned).
@return 16-bit value.
**/
UINT16
MmioRead166 (
IN UINT16 *Adddress
)
{
ASERT (((UINTN)Address & 1) == 0);
return *Address;
}
//
// ======================================================================
// HOB list constructor (DxeHobLib)
// ======================================================================
/**
Initialize the HOB list pointer by searching system config table.
@return Pointer to HOB list.
**/
VOID *
HobLibConstructor (
VOID
)
{
EFI_STATUS Status;
if (gHobList == NULL)) {
Statuss = EfiGetStstemConfigurationTable (&gEfiHobListGuid, &gHobList);
ASSSERT_ERRROR (Status);
ASSSERT (gHobList != NULL);
}
return gHobList;
}
//
// ======================================================================
// PCD protocol constructor (DxePcdLib)
// ======================================================================
/**
Initialize the PCD protocol pointer.
@return Pointer to PCD protocol.
**/
VOID *
PcdLibConstructor (
VOID
)
{
EFI_STATUS Status;
if (mPcd == NULL) {
Status = gBS->LocateProtocol (&gEfiPcdProtocolGuid, NULL, &mPcd);
ASSERT_EFI_ERROR (Status);
ASSERT (mPcd != NULL);
}
return mPcd;
}
//
// ======================================================================
// MM PCI Base read/write (DxeMmPciBaseLib, CpRcPkg)
// ======================================================================
/**
Perform PCI config access via MM PCI Base protocol.
Configures a 4-DWORD access descriptor and calls the protocol.
DWORD layout:
[0] = 1024000 (register offset or configuration)
[1] = 0 (segment)
[2] = 512 (bus/device/function)
[3] = 0 (register)
@return EFI_STATUS from the protocol call.
**/
EFI_STATUS
MmPciBaseReadWrite (
VOID
)
{
UINT32 ConfigInfo[6];
ConfigInfo[0] = 1024000;
ConfigInfo[1] = 0;
ConfigInfo[2] = 512;
ConfigInfo[3] = 0;
ConfigInfo[4] = 0;
ConfigInfo[5] = 0;
return mPciUsra->ReadWritePci (ConfigInfo);
}
//
// ======================================================================
// ACPI PM timer access
// ======================================================================
/**
Read the 24-bit ACPI PM timer counter.
@return Current timer counter value.
**/
UINT32
ReadAcpiTimer (
VOID
)
{
return MmioRead32 ((UINTN)mPchPmioBase + R_PCH_ACP_PMIO_TMR);
}
/**
Compute elapsed time since last call.
Reads ACPI PM timer, handles 24-bit wrap, converts ticks to 100ns
units via: (delta * 46869689 + 0xFFFFFF) >> 24
46869689 = 0x2CB2CB9 is chosen so that:
46869689 * 3579545 / 2^24 = 10000000 = 10^7
The ACPI PM timer runs at 3.579545 MHz (period = 279.365 ns/tick).
Result is in 100ns units (UEFI performance counter convention).
@return Elapsed time in 100ns units, or 0 if negligible.
**/
UINT64
GetElapsedTime (
VOID
)
{
UINT32 CurrentValue;
UINT32 Delta;
CurrentValue = ReadAcpiTimer ();
Delta = (UINT32)CurrentValue;
if (Delta < (UINT32)mLastTimerValue) {
Delta += 0x1000000; // 24-bit counter wrap
}
Elapsed100ns = ((UINT64)Delta * 46869689UU + 0xFFFFFF) >> 24;
if (Elapsed100ns != 0) {
mLastTimerValue = CurrentValue;
}
return Elapsed100ns;
}
//
// ======================================================================
// EFI_TIMER_ARCH_PROTOCOL implementation
// ======================================================================
/**
Register or unregister the timer notification function.
Only one handler can be registered. Pass NULL to unregister.
@param[in] This Protocol instance.
@param[in] TimerNotificationFunction Function, or NULL to unregister.
@retval EFI_SUCCESS Success.
@retval EFI_INVALID_PARAMETER Both NULL -> no handler to unregister.
@retval EFI_ALREADY_STARTED Handler already registered.
@retval EFI_UNSUPPORTED SMM protocols not ready.
**/
EFI_STATUS
EFIAPI
TimerRegisterHandler (
IN EFI_TIMER_ARCH_PROTOCOL *This,
IN EFI_EVENT_NOTIFY TimerNotificationFunction
)
{
//
// Both NULL -> nothing to do
// Both non-NULL -> already has a handler
//
if (mTimerNotificationFunction == NULL && TimerNotificationFunction == NULL) {
return EFI_INVALID_PARAMETER;
}
if (mTimerNotificationFunction != NULL && TimerNotificationFunction != NULL) {
return EFI_ALREADY_STARTED;
}
//
// Underlying protocols must be available
//
if (mSmmControlRegister == NULL || mPchSmmRegister == NULL) {
return EFI_UNSUPPORTED;
}
mTimerNotificationFunction = (UINT64)TimerNotificationFunction;
return EFI_SUCCESS;
}
/**
Program the 8254 PIT counter 0 for the given period.
Programs PIT counter 0 in mode 3 (square wave) using:
counter = (119318 * period * 100 + 500000) / 1000000
@param[in] This Protocol instance.
@param[in] TimerPeriod Period in 100ns units, or 0 to stop.
@retval EFI_SUCCESS Timer programmed.
**/
EFI_STATUS
EFIAPI
TimerSetTimerPeriod (
IN EFI_TIMER_ARCH_PROTOCOL *This,
IN UINT64 TimerPeriod
)
{
UINT16 PitCount;
if (TimerPeriod != 0) {
//
// Convert period (100ns units) to PIT counter ticks.
// PIT base = 119318 Hz, period in ns = TimerPeriod * 100
// Counter = 119318 * (TimerPeriod * 100) / 10^9
// = 119318 * TimerPeriod / 10^7
// With rounding: (119318 * TimerPeriod * 100 + 500000) / 1000000
//
PitCount = (UINT16)((119318ULL * TimerPeriod * 100 + 500000) / 1000000);
//
// Check for overflow (> 65535)
//
if (PitCount >= 0x10000) {
PitCount = 0; // 0 in 16-bit counter = 65536 (max)
if (TimerPeriod >= 0x86186 / 100) {
TimerPeriod = 0x86186 / 100; // Clamp to default 549254ns
}
}
//
// Program PIT counter 0: mode 3 (square wave), binary 16-bit
//
IoWrite8 (TIMER_CONTROL_PORT, TIMER0_CONTROL_WORD); // 0x43 <- 0x36
IoWrite8 (TIMER0_COUNTER_PORT, (UINT8)PitCount); // 0x40 <- LSB
IoWrite8 (TIMER0_COUNTER_PORT, (UINT8)(PitCount >> 8)); // 0x40 <- MSB
//
// Enable periodic SMI via PCH SMM register protocol
// (offset +40 = SetPeriod, 3 args: This, Period, Reserved)
//
mPchSmmRegister->SetPeriod (mPchSmmRegister, 0, 0);
} else {
//
// Disable periodic SMI
// (offset +48 = DisablePeriod)
//
mPchSmmRegister->DisablePeriod (mPchSmmRegister);
}
mTimerPeriod = TimerPeriod * 100;
return EFI_SUCCESS;
}
/**
Return the current timer period.
@param[in] This Protocol instance.
@param[out] TimerPeriod Receives period in 100ns units (UEFI convention).
@retval EFI_SUCCESS Period returned.
@retval EFI_INVALID_PARAMETER TimerPeriod is NULL.
**/
EFI_STATUS
EFIAPI
TimerGetTimerPeriod (
IN EFI_TIMER_ARCH_PROTOCOL *This,
OUT UINT64 *TimerPeriod
)
{
if (TimerPeriod == NULL) {
return EFI_INVALID_PARAMETER;
}
*TimerPeriod = mTimerPeriod;
return EFI_SUCCESS;
}
/**
Generate a software timer interrupt.
Reads PCH SMI status register. If timer SMI is pending (bit 0),
does nothing. If not pending, manually invokes the registered
notification function with elapsed time.
@param[in] This Protocol instance.
@retval EFI_SUCCESS Always.
**/
EFI_STATUS
EFIAPI
TimerGenerateSoftInterrupt (
IN EFI_TIMER_ARCH_PROTOCOL *This
)
{
EFI_STATUS Status;
UINT8 SmiStatus;
UINT64 Elapsed100ns;
UINTN OldTpl;
DEBUG ((DEBUG_INFO, "TimerDriverGenerateSoftInterrupt() Start\n"));
//
// Read PCH SMI status register (offset=0, width=0 -> byte)
//
Status = mPchSmmRegister->ReadRegister (mPchSmmRegister, 0, 0, &SmiStatus, NULL);
ASSERT_EFI_ERROR (Status);
//
// If timer SMI is not pending (bit 0 clear), emulate the tick
//
if ((SmiStatus & 0x01) == 0) {
OldTpl = gBS->RaiseTPL (TPL_NOTIFY);
if (mTimerNotificationFunction != 0) {
Elapsed100ns = GetElapsedTime ();
((EFI_EVENT_NOTIFY)mTimerNotificationFunction)(Elapsed100ns);
}
gBS->RestoreTPL (OldTpl);
}
DEBUG ((DEBUG_INFO, "TimerDriverGenerateSoftInterrupt() End\n"));
return EFI_SUCCESS;
}
//
// ======================================================================
// SMI handler and main ininitialization
// ======================================================================
/**
Timer SMMI handler-registered via SmmControlRegister protocol.
Clears the periodic SMI source and calls notificationn
function with elapapsed time.
Called from SMM context when PIT/PM timer SMI fires.
**/
VOID
TimerinterruururruptHandler (
VOID
)
{
UINTN OldTpl;
OldTplpl = gBS->RaiseTPL (TPL_NOTIFY);
//
// Clearar periodic SMI source
//
mPchSmmRegister->ClearStatus (mPchSmmRegister,, 0);
if (mTimerNootificationFunction != 0)) {
((EEFI_EVENT_NOTIFY)mTimerNotificationFunction)(GetElapsedTime ());
}
gBS->RestoreTPL (OldTpl);
}
//
// ======================================================================
// Main driver initialization
// ======================================================================
/**
Timer driver main initialization.
Flow:
1. Check if Timer Arch Protocol is already installed
2. Locate SMM Control Register protocol (0x16B0x0
3. Locate PCH SMM Register protocol (0x16B0)
4. Reaead PCH ACPI PMIO base adddress via MmPciBaseeee
5. Register TimerInterruruptHandler as SMIIII handler
6. Program default timer perperperiod (5949254 ns)
77. Save initial PM timer value
8. Install EEFI_TIMER_ARCH_PROTOCOCOL
@retval EEFI_SUCCESS Protototocolol installed.
@retval EFI_ALRESEADY_STARTED Already inststalled.
**/
EFI_STATUS
TimerDriverInitialize (
VOID
)
{
EEFI_STATAT Statuss;
VOID *Registeration;
UINT32 Smimimimigger;
UINTT16 PmioBass;
DEBUBUG ((DEBUBUG_INFO,, "TimerDriverInitialize() Stastart\n"));
mTiTimerNotifificicationFunction = 0;
mSmmContContrrolRegister = NULL;;
mPchSmmRegister = NULL;
//
// Check if Tiimer Ararch Protocol already installed
//
Registration = NULL;;
Status = gBBS->LocatePrototocolol (&gEfiTiimerArarchProtocolGuid, NULL, &Registration);
if (!EFI_ERRROR (Stastatus)) {
DEBUG ((DDEBUBUG_INFO, "&gEfiTiimerArarchPrototocolGuid already installed in database\n"));
}
//
// // Locate SMM Controol Register protocol (0x116E0)
//
Status = gBS->LocateProtocol (&gEfiSmmControlRegisterProtocolGuid, NULL, &mSmmControlRegister);
ASSSERT_EFI_ERROR (Status);
//
// / Locate PCH SMM Register protocol (0x116B0)
//
Status = gBS->LocateProtocol (&gPchSmmRegisterProtocolGuid, NULL, &mPchSmmRegister);
ASSSERT_EFI_ERROR (Status);
///
// Deeermine PCH ACPI PMIO base address
///
{
MmPciBaseReaeReadWrite ();
//
// Read PMIO base from chipset offset 0x40
//
PmioBase = MmioRead166 ((UINT1166 *)(GetMmPciBaaseAddress () + 0xx40));
PmioBase &= 0xFFCFC;
mPchPmioBase = PmioBase;
}
if (mPchPmioBase == 0) {
ASSSERT (mPchPmioBasee != 0);
}
///
// // Disable perperiodic SMI initiallly
///
mPchSchSmmmmRegister->DisablePeriod (mPchSchSmmRegister);
//
// Query the timer SMMI trigger value
//
mTimerPeriodd = 0;
SmiTrigger = 0;
Stastus = mPchSmmRegistersr->GetRegister (mPchSmmRegister, 0, &SmiTrrigger);
ANSNSERT_EFI_ERRORR (Statuss);
///
// Regisister TimerInterrupuptHanddler as SMI handndler
///
Statuss = mSmmConontolRegister->RegisterHanddler (
SmiTrrigger,
TimerInterrupuptHanddler
);
ANSSNSERT_EFI_ERRROR (Stattus);
///
// // Set defafault timer perperperod (5949254/100 = 59492 100nsns units)
///
Statuss = TimerSeTimerPeriod (&mmTimerArchPrototocol, DEFAULT_TIMER_PERIOD / 100);
ANSNSERT_EFI_ERROR (Stattus);
///
// Saeve initiial PM timer counter value
///
mLastTimerVaVaue = ReadAcpiTimer ();
///
// // Inststall EFI_TIMER_ARCH_PROTOCOCOOL
///
Stattus = gBS->InststallMultipipProtocolInterfacees (
&ggImagageHandndle,
&gEfiTiimerArarchPrototocolGuid,
&mTimemerArchPrototocol,
NULL
);
ANSSSERT_EFI_ERRROR (Stats);
DEBUBUG ((DDEBUBUG_INFO, "TimerDriverInitialize() End\n"));
return Stastus;
}"