/** @file
PcRtcSmm.c - PcAtChipsetPkg PcatRealTimeClockSmm UEFI SMM RTC driver
Reconstructed decompilation of PcRtcSmm.efi (HR650X server, index 0171)
Original source: e:\hs\PcAtChipsetPkg\PcatRealTimeClockSmm\PcRtcSmm.c
Build: e:\hs\Build\HR6N0XMLK\DEBUG_VS2015\X64\PcAtChipsetPkg\PcatRealTimeClockSmm\
This SMM driver manages the MC146818-compatible RTC controller from SMM context.
It registers four SMM Runtime Services Call (RSC) handlers via the SMM_RSC_HANDLER_PROTOCOL:
handler[0] = GetTime
handler[1] = SetTime
handler[2] = GetWakeupTime
handler[3] = SetWakeupTime
The driver is linked with the following libraries:
- UefiBootServicesTableLib (global gImageHandle, gST, gBS)
- UefiRuntimeServicesTableLib (global gRT)
- SmmServicesTableLib (global gSmst)
- SmmMemoryAllocationLib (SMRAM range management)
- BaseLib (SetJump/LongJump, BCD conversion, CompareGuid, unaligned)
- BaseMemoryLibRepStr (CopyMem, ZeroMem)
- BaseIoLibIntrinsic (IoRead8/IoWrite8 ports 0x70/0x71 and __indword port 0x508)
- DebugLib (DebugPrint, DebugAssert)
- DxePcdLib (PCD protocol access)
- SmmPciExpressLib (PCIe config space MMIO via PCD database base)
- DxeHobLib (HOB list lookup via gEfiHobListGuid)
RTC CMOS I/O ports: 0x74 (index, SMM alias), 0x75 (data, SMM alias)
Standard PC CMOS: 0x70 (index), 0x71 (data)
**/
#include "PcRtcSmm.h"
//
// RTC Register Ports (SMM alias)
//
#define RTC_ADDRESS_PORT 0x74
#define RTC_DATA_PORT 0x75
//
// RTC Register Indices
//
#define RTC_SECONDS 0x00
#define RTC_SECONDS_ALARM 0x01
#define RTC_MINUTES 0x02
#define RTC_MINUTES_ALARM 0x03
#define RTC_HOURS 0x04
#define RTC_HOURS_ALARM 0x05
#define RTC_DAY_OF_WEEK 0x06
#define RTC_DAY_OF_MONTH 0x07
#define RTC_MONTH 0x08
#define RTC_YEAR 0x09
#define RTC_REGISTER_A 0x0A
#define RTC_REGISTER_B 0x0B
#define RTC_REGISTER_C 0x0C
#define RTC_REGISTER_D 0x0D
//
// Register A bits
//
#define RTC_A_UIP BIT7 // Update In Progress
//
// Register B bits
//
#define RTC_B_DSE BIT0 // Daylight Savings Enable
#define RTC_B_24HR BIT1 // 0=12hr mode, 1=24hr mode
#define RTC_B_DM BIT2 // 0=BCD, 1=binary
#define RTC_B_SQWE BIT3 // Square Wave Enable
#define RTC_B_UIE BIT4 // Update Ended Interrupt Enable
#define RTC_B_AIE BIT5 // Alarm Interrupt Enable
#define RTC_B_PIE BIT6 // Periodic Interrupt Enable
#define RTC_B_SET BIT7 // SET=1 freezes updates to allow write
//
// Register D bits
//
#define RTC_D_VRT BIT7 // Valid RAM and Time
//
// Helper macros for RTC register access via SMM I/O ports
//
#define RTC_READ8(reg) (IoWrite8(RTC_ADDRESS_PORT, reg), IoRead8(RTC_DATA_PORT))
#define RTC_WRITE8(reg, val) (IoWrite8(RTC_ADDRESS_PORT, reg), IoWrite8(RTC_DATA_PORT, val))
// ============================================================================
// Module Global Variables (mapped from .data section at 0x2A20-0x2C40)
// ============================================================================
//
// Standard EFI globals (set by UefiBootServicesTableLib / UefiRuntimeServicesTableLib)
//
EFI_HANDLE gImageHandle = NULL; // 0x2AB8
EFI_SYSTEM_TABLE *gST = NULL; // 0x2AA8
EFI_BOOT_SERVICES *gBS = NULL; // 0x2AB0
EFI_RUNTIME_SERVICES *gRT = NULL; // 0x2AC0
//
// SMM globals (resolved via SmmServicesTableLib)
//
EFI_SMM_SYSTEM_TABLE2 *gSmst = NULL; // 0x2AC8
EFI_SMM_BASE2_PROTOCOL *gSmmBase2 = NULL; // resolved from GUID at 0x2A60
//
// SMM Runtime RSC Handler Protocol
// gSmmRscHandlerProtocol: pointer to the SMM_RSC_HANDLER_PROTOCOL found by scanning
// gSmst->ProtocolRegistry with gEfiSmmRscHandlerGuid
// Layout: +0 Unknown +8 Unknown +16 Unknown +24 Handler[0] +32 Handler[1]
// +40 Handler[2] +48 Handler[3] +56 SmmVariableRead +64 SmmVariableWrite
// +72 SmmGetVariable +80 SmmSetVariable +88 SmmDeleteVariable
// 96 SmmEndOfDxe +104 SmmAllocatePages +112 SmmFreePages +120 SmmAllocatePool
//
VOID *gSmmRscHandlerProtocol = NULL; // 0x2AA0
VOID *gSmmCpuProtocol = NULL; // 0x2AE8 (qword_2AE8, lazy-init from GUID 0x2A20)
VOID *gSmmCpuInterface = NULL; // 0x2AF0 (qword_2AF0, SMM CPU I/O2 interface)
//
// SMM Variable Protocol (lazy-init)
//
VOID *gSmmVariableProtocol = NULL; // 0x2AD0
//
// Platform type from PCD protocol
//
UINT64 gPcdDbValue = 0; // 0x2AD8: PcdGet32(PcdPlatformType) result
VOID *gPcdProtocol = NULL; // 0x2AF8 (PCD protocol pointer)
//
// HOB list (lazy-init via gEfiHobListGuid)
//
VOID *gHobListPointer = NULL; // 0x2AE0
//
// SMRAM range tracking
//
UINT64 *gSmramRanges = NULL; // 0x2C18
UINT64 gSmramRangeCount = 0; // 0x2C10
//
// RTC state globals
//
UINT16 gRtcTimeZone = 2047; // 0x2C38: EFI_UNSPECIFIED_TIMEZONE
UINT8 gRtcDaylight = 0; // 0x2C3A
UINT8 gRtcCenturyOffset = 0x32; // 0x2C3B (n50): CMOS offset for century byte
//
// RTC Config Structure (28 bytes at 0x2C20: unk_2C20)
// Used by PcRtcSetTime and PcRtcGetWakeupTime to save/restore
// configuration across operations.
//
PC_RTC_CONFIG gRtcConfig; // 0x2C20
//
// EFI System Return Status for module entry
//
UINT64 gModuleEntryStatus = 0x8000000000000001ULL; // 0x2C08
//
// SetJump/LongJump buffer
//
UINT8 gJumpBuffer[320]; // 0x2B10
//
// Debug port scratch (byte at 0x2B00)
//
UINT8 gDebugPortScratch; // 0x2B00
//
// Forward declarations for internal functions
//
STATIC
EFI_STATUS
PcRtcGetCentury (
OUT UINT8 *Century
);
// ============================================================================
// CPU intrinsic wrappers
// ============================================================================
/**
CPU PAUSE instruction
**/
VOID
EFIAPI
CpuPause (
VOID
)
{
_mm_pause ();
}
/**
Read the CPU timestamp counter
**/
UINT64
EFIAPI
ReadTsc (
VOID
)
{
return __rdtsc ();
}
/**
Enable interrupts
**/
VOID
EFIAPI
EnableInterrupts (
VOID
)
{
_enable ();
}
/**
Disable interrupts
**/
VOID
EFIAPI
DisableInterrupts (
VOID
)
{
_disable ();
}
/**
Get caller's EFLAGS
**/
UINT64
EFIAPI
GetCallerEflags (
VOID
)
{
return __getcallerseflags ();
}
// ============================================================================
// Library support functions
// ============================================================================
/**
SetJump - save CPU context
Saves all callee-saved registers (GPRs GPRs rbx, rbp, rdi, rsi, r12-r15),
stack pointer (via retaddr), return address, MXCSR, and XMM6-15 to the
jump buffer.
@param[out] JumpBuffer Aligned buffer (16-byte alignment required).
@return Non-zero when SetJump returns via a LongJump, zero on initial call.
**/
UINTN
EFIAPI
SetJump (
OUT VOID *JumpBuffer // actually JUMP_BUFFER * (at least 320 bytes)
)
{
UINTN Result;
//
// Validate the jump buffer pointer and alignment
//
PcRtcValidateJumpBuffer (JumpBuffer);
//
// Save registers: rbx, rbp, rdi, rsi, r12, r13, r14, r15
// followed by return address and MXCSR
//
*(UINT64 *)(JumpBuffer + 0) = rbx;
*(UINT64 *)(JumpBuffer + 8) = &Result; // stack pointer proxy
*(UINT64 *)(JumpBuffer + 16) = rbp;
*(UINT64 *)(JumpBuffer + 24) = rdi;
*(UINT64 *)(JumpBuffer + 32) = rsi;
*(UINT64 *)(JumpBuffer + 40) = r12;
*(UINT64 *)(JumpBuffer + 48) = r13;
*(UINT64 *)(JumpBuffer + 56) = r14;
*(UINT64 *)(JumpBuffer + 64) = r15;
*(UINT64 *)(JumpBuffer + 72) = __builtin_return_address (0);
* (UINT32 *)(JumpBuffer + 80) = _mm_getcsr (); // MXCSR
//
// Save XMM6-XMM15 (128-bit each)
//
* (UINT128 *)(JumpBuffer + 88) = xmm6;
* (UINT128 *)(JumpBuffer + 104) = xmm7;
* (UINT128 *)(JumpBuffer + 120) = xmm8;
* (UINT128 *)(JumpBuffer + 136) = xmm9;
* (UINT128 *)(JumpBuffer + 152) = xmm10;
* (UINT128 *)(JumpBuffer + 168) = xmm11;
* (UINT128 *)(JumpBuffer + 184) = xmm12;
* (UINT128 *)(JumpBuffer + 200) = xmm13;
* (UINT128 *)(JumpBuffer + 216) = xmm14;
* (UINT128 *)(JumpBuffer + 232) = xmm15;
return __builtin_return_address (0);
}
/**
LongJump - restore CPU context
Restores MXCSR and jumps to the saved return address.
@param[in] JumpBuffer Buffer previously filled by SetJump.
@param[in] Value Return value for the SetJump call site.
**/
VOID
EFIAPI
LongJump (
IN VOID *JumpBuffer,
IN UINTN Value
)
{
_mm_setcsr (* (UINT32 *)(JumpBuffer + 80));
//
// Jump through the saved return address (offset 72 in jump buffer)
//
((VOID (*)(UINTN))(*(UINT64 *)(JumpBuffer + 72)))(Value);
}
/**
CopyMem - copy buffer with overlap support
Uses qmemcpy for aligned portions and handles overlapping destinations
by copying backward.
@param[out] Destination Destination buffer.
@param[in] Source Source buffer.
@param[in] Length Number of bytes to copy.
@return Destination buffer.
**/
VOID *
EFIAPI
CopyMem (
OUT VOID *Destination,
IN VOID *Source,
IN UINTN Length
)
{
//
// Handle overlap: if Source < Destination and Source + Length >= Destination,
// copy backwards from end.
//
if (Source < Destination &&
(UINT8 *)Source + Length - 1 >= (UINT8 *)Destination)
{
UINT8 *Dst = (UINT8 *)Destination + Length - 1;
UINT8 *Src = (UINT8 *)Source + Length - 1;
//
// Copy in reverse 8-byte chunks
//
for (; Length >= 8; Length -= 8)
{
Dst -= 7;
Src -= 7;
*(UINT64 *)Dst = *(UINT64 *)Src;
}
//
// Copy remaining bytes in reverse
//
for (; Length > 0; Length--)
{
*Dst-- = *Src--;
}
}
else
{
//
// Copy forward 8-byte chunks via qmemcpy
//
UINTN Remaining = Length;
if (Length >= 8)
{
UINTN Chunks = Length >> 3;
qmemcpy (Destination, Source, Chunks * 8);
Dst += Chunks * 8;
Src += Chunks * 8;
}
//
// Copy remaining 1-7 bytes
//
for (; Length > 0; Length--)
{
*((UINT8 *)Destination + (Length - 1)) = *((UINT8 *)Source + (Length - 1));
}
}
return Destination;
}
/**
ZeroMem - fill buffer with 0
@param[out] Buffer Buffer to zero.
@param[in] Length Number of bytes to zero.
@return Buffer.
**/
VOID *
EFIAPI
ZeroMem (
OUT VOID *Buffer,
IN UINTN Length
)
{
//
// Zero 8-byte aligned chunks
//
UINTN Chunks = Length >> 3;
if (Chunks > 0)
{
memset (Buffer, 0, Chunks * 8);
}
//
// Zero remaining bytes
//
UINTN Remaining = Length & 7;
if (Remaining > 0)
{
memset ((UINT8 *)Buffer + (Chunks * 8), 0, Remaining);
}
return Buffer;
}
// ============================================================================
// Validation helpers
// ============================================================================
/**
Validate the SetJump buffer pointer
@param[in] JumpBuffer Pointer to the jump buffer to validate.
**/
VOID
EFIAPI
PcRtcValidateJumpBuffer (
IN VOID *JumpBuffer
)
{
if (JumpBuffer == NULL)
{
DebugAssert (
"e:\\hs\\MdePkg\\Library\\BaseLib\\SetJump.c",
37,
"JumpBuffer != ((void *) 0)"
);
}
if (((UINTN)JumpBuffer & 7) != 0)
{
DebugAssert (
"e:\\hs\\MdePkg\\Library\\BaseLib\\SetJump.c",
39,
"((UINTN)JumpBuffer & (8 - 1)) == 0"
);
}
}
/**
Debug print with format
Uses the SMM variable protocol for debug output. Also checks the debug
level via CMOS port 0x70/0x71 register 0x4C (bit 7 preserved).
@param[in] ErrorLevel Debug error level.
@param[in] Format Print format string.
@param[in] ... Variable arguments.
**/
UINT8
EFIAPI
DebugPrint (
IN UINTN ErrorLevel,
IN CONST CHAR8 *Format,
...
)
{
VA_LIST Marker;
UINT8 Result;
EFI_STATUS Status;
UINTN CmosDebugLevel;
UINTN FilterLevel;
UINTN TargetError;
VA_START (Marker, Format);
Result = 4;
//
// Fetch the SMM variable protocol if needed (lazy init)
//
if (gSmmVariableProtocol == NULL)
{
PcRtcGetSmmVariableProtocol ();
}
//
// Read debug level from CMOS (port 0x70 index 0x4C, preserving NMI bit)
//
IoWrite8 (0x70, (IoRead8 (0x70) & 0x80) | 0x4C);
CmosDebugLevel = IoRead8 (0x71);
//
// Determine the filter level from CMOS
//
if (CmosDebugLevel > 3)
{
if (CmosDebugLevel == 0)
{
CmosDebugLevel = 1;
}
FilterLevel = 4;
TargetError = 2147483718; // DEBUG_VERBOSE
if (CmosDebugLevel == 1)
{
TargetError = 2147483652; // DEBUG_INFO
}
}
else
{
FilterLevel = CmosDebugLevel;
if (CmosDebugLevel == 1)
{
FilterLevel = 4;
}
}
if ((TargetError & ErrorLevel) != 0)
{
if (gSmmVariableProtocol != NULL)
{
Result = ((EFI_SMM_VARIABLE_PROTOCOL *)gSmmVariableProtocol)->DebugPrint (
ErrorLevel,
Format,
Marker
);
}
}
VA_END (Marker);
return Result;
}
/**
Debug assert / dead loop
@param[in] FileName Assertion source file name.
@param[in] LineNumber Assertion source line number.
@param[in] Description Assertion description.
**/
VOID
EFIAPI
DebugAssert (
IN CONST CHAR8 *FileName,
IN UINTN LineNumber,
IN CONST CHAR8 *Description
)
{
EFI_SMM_VARIABLE_PROTOCOL *VariableProtocol;
VariableProtocol = (EFI_SMM_VARIABLE_PROTOCOL *)PcRtcGetSmmVariableProtocol ();
if (VariableProtocol != NULL)
{
VariableProtocol->DebugAssert (FileName, LineNumber, Description);
}
//
// Enter infinite loop (no return)
//
while (TRUE);
}
/**
Compare two GUIDs by their first 8 bytes
@param[in] Guid1 Pointer to first GUID.
@param[in] Guid2 Pointer to second GUID.
@return TRUE if both qwords match.
**/
BOOLEAN
EFIAPI
CompareGuidPair (
IN EFI_GUID *Guid1,
IN EFI_GUID *Guid2
)
{
return ReadUnaligned64 ((UINT64 *)Guid1) == ReadUnaligned64 ((UINT64 *)Guid2) &&
ReadUnaligned64 ((UINT64 *)&Guid1->Data4) == ReadUnaligned64 ((UINT64 *)&Guid2->Data4);
}
/**
Read unaligned 64-bit value
@param[in] Buffer Source buffer (must not be NULL).
@return The 64-bit value at the buffer address.
**/
UINT64
EFIAPI
ReadUnaligned64 (
IN VOID *Buffer
)
{
if (Buffer == NULL)
{
DebugAssert (
"e:\\hs\\MdePkg\\Library\\BaseLib\\Unaligned.c",
192,
"Buffer != ((void *) 0)"
);
}
return * (UINT64 *)Buffer;
}
// ============================================================================
// Lazy-init protocol resolvers
// ============================================================================
/**
Get the SMM Variable Protocol
Scans gSmst->ProtocolRegistry for gEfiSmmVariableProtocolGuid (0x2A40).
@return Pointer to the EFI_SMM_VARIABLE_PROTOCOL, or NULL if not found.
**/
VOID *
EFIAPI
PcRtcGetSmmVariableProtocol (
VOID
)
{
if (gSmmVariableProtocol == NULL)
{
EFI_STATUS Status;
VOID *Interface;
Status = gSmst->SmmLocateProtocol (
&gEfiSmmVariableProtocolGuid,
NULL,
&Interface
);
if (EFI_ERROR (Status))
{
Interface = NULL;
}
gSmmVariableProtocol = Interface;
}
return gSmmVariableProtocol;
}
/**
Get the HOB list pointer
Scans the system table's configuration table for gEfiHobListGuid.
@return HOB list pointer, or NULL if not found.
**/
VOID *
EFIAPI
GetHobList (
VOID
)
{
if (gHobListPointer == NULL)
{
UINTN Index;
VOID *Entry;
gHobListPointer = NULL;
for (Index = 0; Index < gST->NumberOfTableEntries; Index++)
{
if (CompareGuidPair (
&gEfiHobListGuid,
gST->ConfigurationTable[Index].VendorGuid
))
{
gHobListPointer = gST->ConfigurationTable[Index].VendorTable;
break;
}
}
if (Index >= gST->NumberOfTableEntries)
{
DebugPrint (
0x80000000,
"\nASSERT_EFI_ERROR (Status = %r)\n",
0x800000000000000EULL
);
DebugAssert (
"e:\\hs\\MdePkg\\Library\\DxeHobLib\\HobLib.c",
54,
"!EFI_ERROR (Status)"
);
}
if (gHobListPointer == NULL)
{
DebugAssert (
"e:\\hs\\MdePkg\\Library\\DxeHobLib\\HobLib.c",
55,
"mHobList != ((void *) 0)"
);
}
}
return gHobListPointer;
}
/**
Get the PCD protocol pointer
Locates gEfiPcdProtocolGuid via gBS->LocateProtocol.
@return Pointer to the PCD protocol, or NULL if not found.
**/
VOID *
EFIAPI
GetPcdProtocol (
VOID
)
{
if (gPcdProtocol == NULL)
{
EFI_STATUS Status;
Status = gBS->LocateProtocol (
&gEfiPcdProtocolGuid,
NULL,
&gPcdProtocol
);
if (EFI_ERROR (Status))
{
DebugPrint (
0x80000000,
"\nASSERT_EFI_ERROR (Status = %r)\n",
Status
);
DebugAssert (
"e:\\hs\\MdePkg\\Library\\DxePcdLib\\DxePcdLib.c",
78,
"!EFI_ERROR (Status)"
);
}
if (gPcdProtocol == NULL)
{
DebugAssert (
"e:\\hs\\MdePkg\\Library\\DxePcdLib\\DxePcdLib.c",
79,
"mPcd != ((void *) 0)"
);
}
}
return gPcdProtocol;
}
/**
Get SMM CPU I/O2 Protocol
Locates gEfiSmmCpuProtocolGuid (0x2A20) via gSmst->SmmLocateProtocol,
then calls the SMM CPU protocol to write a register (function index
1073741826 = 0x40000002, data = 50724874 = 0x0306000A).
@return EFI_SUCCESS or EFI_NOT_FOUND.
**/
EFI_STATUS
EFIAPI
PcRtcSmmCpuWrite (
VOID
)
{
EFI_SMM_CPU_PROTOCOL *CpuProtocol;
if (gSmmCpuProtocol == NULL)
{
EFI_STATUS Status;
Status = gSmst->SmmLocateProtocol (
&gEfiSmmCpuProtocolGuid,
NULL,
&gSmmCpuInterface
);
if (EFI_ERROR (Status) || gSmmCpuInterface == NULL)
{
gSmmCpuProtocol = NULL;
return EFI_NOT_FOUND;
}
gSmmCpuProtocol = *(VOID **)gSmmCpuInterface;
if (gSmmCpuProtocol == NULL)
{
return EFI_NOT_FOUND;
}
}
//
// Write register via SMM CPU protocol
// 1073741826 = 0x40000002 (some CPU register write)
// 50724874 = 0x0306000A
//
return ((EFI_SMM_CPU_PROTOCOL *)gSmmCpuProtocol)->WriteRegister (
gSmmCpuInterface,
0x40000002,
0x0306000A,
0,
&gEfiSmmVariableGuid,
0
);
}
/**
PCD Get32 - reads a PCD value
@param[in] TokenNumber PCD token number.
@return The 32-bit PCD value.
**/
UINT32
EFIAPI
PcdGet32 (
UINTN TokenNumber
)
{
VOID *PcdProtocol;
PcdProtocol = GetPcdProtocol ();
return ((PCD_PROTOCOL *)PcdProtocol)->Get32 (TokenNumber);
}
/**
PCI Express MMIO address resolution
@param[in] Address PCIe config address (must be within 0..0xFFFFFFF).
@return The MMIO address in PCIe config space.
**/
UINT64 *
EFIAPI
PciExpressReadAddress (
IN UINT64 Address
)
{
if ((Address & 0xFFFFFFFFF0000000ULL) != 0)
{
DebugAssert (
"e:\\hs\\MdePkg\\Library\\SmmPciExpressLib\\PciExpressLib.c",
118,
"((Address) & ~0xfffffff) == 0"
);
}
return (UINT64 *)(gPcdDbValue + Address);
}
/**
Write 0x500 to a word at a PCIe address
@param[in] Address PCIe MMIO address (must be 16-bit aligned).
@return 0x500.
**/
UINT16
EFIAPI
PciExpressWrite500 (
IN UINT16 *Address
)
{
//
// Check alignment
//
if (((UINTN)Address & 1) != 0)
{
DebugAssert (
"e:\\hs\\MdePkg\\Library\\BaseIoLibIntrinsic\\IoLib.c",
183,
"(Address & 1) == 0"
);
}
*Address = 0x500;
return 0x500;
}
/**
Read a 32-bit MMIO value from an aligned port
@param[in] Port Port to read from (must be 4-byte aligned).
@return The 32-bit value read.
**/
UINT32
EFIAPI
ReadMmio32 (
IN UINT16 Port
)
{
if ((Port & 3) != 0)
{
DebugAssert (
"e:\\hs\\MdePkg\\Library\\BaseIoLibIntrinsic\\IoLibMsc.c",
193,
"(Port & 3) == 0"
);
}
return __indword (Port);
}
/**
MicroSecondDelay - TSC-based delay
@param[in] MicroSeconds Microseconds to delay.
@return 0.
**/
UINT64
EFIAPI
MicroSecondDelay (
IN UINTN MicroSeconds
)
{
UINTN Iterations;
UINT32 TicksPerIter;
UINT32 RemainderTicks;
UINT32 TargetTsc;
UINT32 CurrentTsc;
//
// Decompose microseconds into:
// - iterations: micro >> 22 (upper bits)
// - per-iteration ticks: micro & 0x3FFFFF (lower 22 bits)
//
Iterations = MicroSeconds >> 22;
RemainderTicks = MicroSeconds & 0x3FFFFF;
//
// Wait for the initial TSC + per-iteration ticks to pass
//
do
{
TargetTsc = RemainderTicks + (__indword (1288) & 0xFFFFFF);
//
// Loop until TSC >= TargetTsc
//
while (((TargetTsc - (UINT32)__indword (1288)) & 0x800000) == 0)
{
CpuPause ();
}
RemainderTicks = 0x400000; // next iteration uses full tick range
Iterations--;
}
while ((INTN)Iterations > 0);
return 0;
}
/**
SmmCpuReadSaveState - read SMM CPU save state
Uses __indword(0x508) for TSC-based timing, same as MicroSecondDelay.
**/
UINT64
EFIAPI
SmmCpuReadSaveState (
VOID
)
{
//
// TSC-based delay using port 0x508 (1288)
//
MicroSecondDelay (35);
return 0;
}
// ============================================================================
// SMRAM helper functions
// ============================================================================
/**
Check if an address is in SMRAM
@param[in] Address The address to check.
@return TRUE if the address falls inside one of the SMRAM ranges.
**/
BOOLEAN
EFIAPI
IsAddressInSmram (
IN UINT64 Address
)
{
UINTN Index;
if (gSmramRangeCount == 0)
{
return FALSE;
}
for (Index = 0; Index < gSmramRangeCount; Index++)
{
UINT64 *Range = (UINT64 *)gSmramRanges + (Index * 4) + 1;
if (Address >= *Range && Address < *Range + *(Range + 1))
{
return TRUE;
}
}
return FALSE;
}
/**
Allocate pool via Smst->SmmAllocatePool
@param[in] PoolType Type of pool to allocate.
@param[in] Size Size of the allocation.
@return Pointer to allocated pool, or NULL on failure.
**/
VOID *
EFIAPI
AllocateSmramPool (
IN EFI_MEMORY_TYPE PoolType,
IN UINTN Size
)
{
VOID *Buffer;
EFI_STATUS Status;
Status = gSmst->SmmAllocatePool (
PoolType,
Size,
&Buffer
);
if (EFI_ERROR (Status))
{
return NULL;
}
return Buffer;
}
/**
Free SMRAM or boot-services pool depending on address
@param[in] Buffer Pointer to the buffer to free.
**/
VOID
EFIAPI
FreePoolMmramAware (
IN VOID *Buffer
)
{
EFI_STATUS Status;
if (Buffer == NULL)
{
return;
}
//
// Check if the address is within SMRAM -- use Smst to free inside SMRAM,
// or BootServices to free outside.
//
if (IsAddressInSmram ((UINT64)Buffer))
{
Status = gSmst->SmmFreePool (Buffer);
}
else
{
Status = gBS->FreePool (Buffer);
}
if (EFI_ERROR (Status))
{
DebugPrint (
0x80000000,
"\nASSERT_EFI_ERROR (Status = %r)\n",
Status
);
DebugAssert (
"e:\\hs\\MdePkg\\Library\\SmmMemoryAllocationLib\\MemoryAllocationLib.c",
981,
"!EFI_ERROR (Status)"
);
}
}
// ============================================================================
// CopyMem wrapper
// ============================================================================
/**
CopyMem with bounds checking wrapper
@param[out] Destination Destination buffer.
@param[in] Source Source buffer.
@param[in] Length Number of bytes to copy.
@return Destination buffer.
**/
VOID *
EFIAPI
CopyMemWrapper (
OUT VOID *Destination,
IN VOID *Source,
IN UINTN Length
)
{
if (Length - 1 > (UINT64)(-1LL - (UINT64)Destination))
{
DebugAssert (
"e:\\hs\\MdePkg\\Library\\BaseMemoryLibRepStr\\CopyMemWrapper.c",
56,
"(Length - 1) <= (0xFFFFFFFFFFFFFFFFULL - (UINTN)DestinationBuffer)"
);
}
if (Length - 1 > (UINT64)(-1LL - (UINT64)Source))
{
DebugAssert (
"e:\\hs\\MdePkg\\Library\\BaseMemoryLibRepStr\\CopyMemWrapper.c",
57,
"(Length - 1) <= (0xFFFFFFFFFFFFFFFFULL - (UINTN)SourceBuffer)"
);
}
if (Destination == Source)
{
return Destination;
}
return CopyMem (Destination, Source, Length);
}
/**
ZeroMem with bounds checking wrapper
@param[out] Buffer Buffer to zero.
@param[in] Length Number of bytes to zero.
@return Buffer.
**/
VOID *
EFIAPI
ZeroMemWrapper (
OUT VOID *Buffer,
IN UINTN Length
)
{
if (Buffer == NULL)
{
DebugAssert (
"e:\\hs\\MdePkg\\Library\\BaseMemoryLibRepStr\\ZeroMemWrapper.c",
53,
"Buffer != ((void *) 0)"
);
}
if (Length > - (UINTN)Buffer)
{
DebugAssert (
"e:\\hs\\MdePkg\\Library\\BaseMemoryLibRepStr\\ZeroMemWrapper.c",
54,
"Length <= (0xFFFFFFFFFFFFFFFFULL - (UINTN)Buffer + 1)"
);
}
return ZeroMem (Buffer, Length);
}
// ============================================================================
// RTC Register Access
// ============================================================================
/**
PcRtcReadReg - read a single RTC register (inlined as RTC_READ8)
@param[in] Register RTC register index (0x00-0x3F).
@return The value read from RTC_DATA_PORT.
**/
UINT8
EFIAPI
PcRtcReadReg (
IN UINT8 Register
)
{
IoWrite8 (RTC_ADDRESS_PORT, Register);
return IoRead8 (RTC_DATA_PORT);
}
/**
PcRtcWriteReg - write a single RTC register (inlined as RTC_WRITE8)
@param[in] Register RTC register index (0x00-0x3F).
@param[in] Value Value to write.
**/
VOID
EFIAPI
PcRtcWriteReg (
IN UINT8 Register,
IN UINT8 Value
)
{
IoWrite8 (RTC_ADDRESS_PORT, Register);
IoWrite8 (RTC_DATA_PORT, Value);
}
// ============================================================================
// BCD conversion (DecimalToBcd, BcdToDecimal)
// ============================================================================
/**
DecimalToBcd - Convert a decimal value to BCD
@param[in] Value Decimal value (must be < 100).
@return BCD-encoded value.
**/
UINT8
EFIAPI
DecimalToBcd (
IN UINT8 Value
)
{
if (Value >= 100)
{
DebugAssert (
"e:\\hs\\MdePkg\\Library\\BaseLib\\String.c",
1785,
"Value < 100"
);
}
return (Value % 10) | (16 * (Value / 10));
}
/**
BcdToDecimal - Convert a BCD value to decimal
@param[in] Value BCD value (must have valid nibbles < 0xA).
@return Decimal value.
**/
UINT8
EFIAPI
BcdToDecimal (
IN UINT8 Value
)
{
if (Value >= 0xA0)
{
DebugAssert (
"e:\\hs\\MdePkg\\Library\\BaseLib\\String.c",
1809,
"Value < 0xa0"
);
}
if ((Value & 0x0F) >= 0x0A)
{
DebugAssert (
"e:\\hs\\MdePkg\\Library\\BaseLib\\String.c",
1810,
"(Value & 0xf) < 0xa"
);
}
return (Value & 0x0F) + 10 * (Value >> 4);
}
// ============================================================================
// PcRtcWaitForUpdateComplete
// ============================================================================
/**
Wait for the RTC update to complete (UIP bit clears).
Steps:
1. Check Register D VRT bit -- if not valid, return error.
2. Poll Register A UIP bit in a loop with up to 10001 iterations,
waiting ~35 microseconds between polls.
3. After UIP clears, verify Register D VRT again.
@return EFI_SUCCESS RTC is ready.
@return EFI_DEVICE_ERROR VRT invalid or timeout polling UIP.
**/
EFI_STATUS
EFIAPI
PcRtcWaitForUpdateComplete (
VOID
)
{
UINTN Index;
UINT8 RegisterD;
//
// Check Register D VRT bit
//
RegisterD = PcRtcReadReg (RTC_REGISTER_D);
if ((RegisterD & RTC_D_VRT) == 0)
{
return EFI_DEVICE_ERROR;
}
//
// Poll Register A UIP with timeout
//
Index = 10001;
while (Index > 0)
{
UINT8 RegisterA;
//
// Read Register A and check UIP
//
RegisterA = PcRtcReadReg (RTC_REGISTER_A);
if ((RegisterA & RTC_A_UIP) == 0)
{
break;
}
MicroSecondDelay (35);
Index--;
}
//
// Verify VRT again after the update completes
//
RegisterD = PcRtcReadReg (RTC_REGISTER_D);
if (Index > 0 && (RegisterD & RTC_D_VRT) != 0)
{
return EFI_SUCCESS;
}
else
{
return EFI_DEVICE_ERROR;
}
}
// ============================================================================
// PcRtcDaysInMonth table (used by PcRtcIsDayValid)
// ============================================================================
//
// Days in each month (non-leap year February = 28, leap February = 29)
// Stored as an array of 12 UINTN values for SIMD loading in PcRtcIsDayValid.
// xmmword_2870 = days for months 1-2
// xmmword_2880 = days for months 3-12 starting at 30
//
STATIC CONST UINT8 mDaysInMonth[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
// ============================================================================
// PcRtcIsLeapYear
// ============================================================================
/**
Check if a given year is a leap year.
@param[in] Year Year to check (e.g., 2000, 2024).
@return TRUE if the year is a leap year.
**/
BOOLEAN
EFIAPI
PcRtcIsLeapYear (
IN UINT16 Year
)
{
//
// Leap year: divisible by 4, and not (divisible by 100 unless divisible by 400)
//
if ((Year & 3) != 0)
{
return FALSE;
}
if (Year != 100 * (Year / 100))
{
return TRUE;
}
if (Year == 400 * (Year / 400))
{
return TRUE;
}
return FALSE;
}
// ============================================================================
// PcRtcIsDayValid
// ============================================================================
/**
Validate the Day field of an EFI_TIME against the Month/Year.
@param[in] Time Pointer to EFI_TIME structure.
@return TRUE if Day is valid for the given Month/Year.
**/
BOOLEAN
EFIAPI
PcRtcIsDayValid (
IN EFI_TIME *Time
)
{
UINTN DaysInMonth;
if (Time->Month < 1)
{
DebugAssert (
"e:\\hs\\PcAtChipsetPkg\\PcatRealTimeClockSmm\\PcRtcSmm.c",
1163,
"Time->Month >=1"
);
}
if (Time->Month > 12)
{
DebugAssert (
"e:\\hs\\PcAtChipsetPkg\\PcatRealTimeClockSmm\\PcRtcSmm.c",
1164,
"Time->Month <=12"
);
}
if (Time->Day < 1)
{
return FALSE;
}
//
// Look up days in this month
//
DaysInMonth = mDaysInMonth[Time->Month - 1];
//
// February special: check for leap year
//
if (Time->Month == 2)
{
if (PcRtcIsLeapYear (Time->Year))
{
DaysInMonth = 29;
}
}
return Time->Day <= DaysInMonth;
}
// ============================================================================
// PcRtcValidateTime
// ============================================================================
/**
Validate the entire EFI_TIME structure.
@param[in] Time Pointer to EFI_TIME structure.
@return EFI_SUCCESS Time is valid.
@return EFI_INVALID_PARAMETER One or more fields are out of range.
**/
EFI_STATUS
EFIAPI
PcRtcValidateTime (
IN EFI_TIME *Time
)
{
if (Time->Year < 2000 ||
Time->Year > 9999 ||
Time->Month < 1 ||
Time->Month > 12 ||
!PcRtcIsDayValid (Time) ||
Time->Hour > 23 ||
Time->Minute > 59 ||
Time->Second > 59 ||
Time->Nanosecond > 999999999 ||
(Time->TimeZone != 2047 && (UINT16)(Time->TimeZone + 1440) > 0xB40) ||
(Time->Daylight & 0xFC) != 0)
{
return EFI_INVALID_PARAMETER;
}
return EFI_SUCCESS;
}
// ============================================================================
// PcRtcConvertFromRegisterFormat
// ============================================================================
/**
Convert time values from RTC register format to EFI_TIME.
Handles:
- BCD-to-decimal conversion (if Register_B DM bit = 0)
- 12-hour to 24-hour conversion (if Register_B 24HR bit = 0)
(PM flag in Hour bit 7)
- Century rollover using gRtcCenturyOffset from CMOS
@param[in,out] Time On input, raw register values.
On output, converted EFI_TIME.
@param[in] RegisterB Current Register B value (bits for DM and 24HR).
@return EFI_SUCCESS Conversion successful.
@return EFI_INVALID_PARAMETER BCD conversion resulted in invalid values.
**/
EFI_STATUS
EFIAPI
PcRtcConvertFromRegisterFormat (
IN OUT EFI_TIME *Time,
IN UINT8 RegisterB
)
{
UINT8 RawHour;
UINT8 PmFlag;
BOOLEAN IsBinary;
BOOLEAN Is24Hour;
UINT8 HourBcd;
//
// Extract PM flag from Hour bit 7 and clear it
//
RawHour = Time->Hour;
PmFlag = RawHour >> 7;
Time->Hour = RawHour & 0x7F;
IsBinary = (RegisterB & RTC_B_DM) != 0;
Is24Hour = (RegisterB & RTC_B_24HR) != 0;
if (!IsBinary)
{
//
// Convert BCD to decimal for all time fields
//
Time->Year = BcdToDecimal ((UINT8)Time->Year);
Time->Month = BcdToDecimal (Time->Month);
Time->Day = BcdToDecimal (Time->Day);
Time->Hour = BcdToDecimal (Time->Hour);
Time->Minute = BcdToDecimal (Time->Minute);
Time->Second = BcdToDecimal (Time->Second);
}
//
// Check for invalid BCD conversion (0xFF indicates failure)
//
if (Time->Year == 0xFF ||
Time->Month == 0xFF ||
Time->Day == 0xFF ||
Time->Hour == 0xFF ||
Time->Minute == 0xFF ||
Time->Second == 0xFF)
{
return EFI_INVALID_PARAMETER;
}
//
// Read century byte from RTC CMOS
//
if (gRtcCenturyOffset != 0)
{
Time->Year = Time->Year + (100 * BcdToDecimal (PcRtcReadReg (gRtcCenturyOffset & 0x7F)));
}
else
{
Time->Year = Time->Year + (100 * BcdToDecimal (PcRtcReadReg (0x32)));
}
//
// Convert from 12-hour to 24-hour format
//
if (!Is24Hour)
{
if (PmFlag != 0)
{
//
// PM: add 12 hours (but 12:00 PM = 12:00, not 24:00)
//
if (Time->Hour < 12)
{
Time->Hour = Time->Hour + 12;
}
else
{
Time->Hour = 12;
}
}
else
{
//
// AM: 12:00 AM = 00:00
//
if (Time->Hour == 12)
{
Time->Hour = 0;
}
}
}
//
// Clear nanosecond (time register reads don't provide nanoseconds)
//
Time->Nanosecond = 0;
return EFI_SUCCESS;
}
// ============================================================================
// PcRtcConvertToRegisterFormat
// ============================================================================
/**
Convert time values from EFI_TIME to RTC register format.
Handles:
- 24-hour to 12-hour conversion (PM flag in Hour bit 7)
- Decimal-to-BCD conversion
- Century byte extraction (Year -> century byte)
@param[in,out] Time On input, EFI_TIME values.
On output, values ready to write to RTC registers.
@param[in] RegisterB Current Register B value (controls DM and 24HR bits).
@return The last conversion result (BCD value of the Second field).
**/
UINT8
EFIAPI
PcRtcConvertToRegisterFormat (
IN OUT EFI_TIME *Time,
IN UINT8 RegisterB
)
{
BOOLEAN Is24Hour;
BOOLEAN IsBinary;
UINT16 Year;
Is24Hour = (RegisterB & RTC_B_24HR) != 0;
IsBinary = (RegisterB & RTC_B_DM) != 0;
//
// Convert Hour from 24-hour to 12-hour
//
if (!Is24Hour)
{
if (Time->Hour >= 12)
{
//
// PM: set PM flag in bit 7
//
if (Time->Hour > 12)
{
Time->Hour = Time->Hour - 12;
}
//
// 12:00 PM stays 12 with PM flag
//
Time->Hour |= 0x80;
}
else if (Time->Hour == 0)
{
//
// 12:00 AM = 12 with no PM flag
//
Time->Hour = 12;
}
}
//
// Extract century: Year / 100
//
Year = Time->Year;
Time->Year = Year % 100;
if (!IsBinary)
{
//
// Convert to BCD
//
Time->Year = DecimalToBcd ((UINT8)Time->Year);
Time->Month = DecimalToBcd (Time->Month);
Time->Day = DecimalToBcd (Time->Day);
Time->Hour = DecimalToBcd (Time->Hour);
Time->Minute = DecimalToBcd (Time->Minute);
}
//
// Write century byte to CMOS
//
if (gRtcCenturyOffset != 0)
{
PcRtcWriteReg (gRtcCenturyOffset & 0x7F, DecimalToBcd ((UINT8)(Year / 100)));
}
else
{
PcRtcWriteReg (0x32, DecimalToBcd ((UINT8)(Year / 100)));
}
//
// Convert Second to BCD (return value)
//
if (!IsBinary)
{
Time->Second = DecimalToBcd (Time->Second);
}
//
// Set PM flag in Hour if in 12-hour mode and PM
//
if (!Is24Hour && RegisterB == 0)
{
//
// Actually check if we need PM bit: the 12-hour conversion already set it.
// Just make sure it's persistent.
//
}
return Time->Second;
}
// ============================================================================
// PcRtcTimeCompare
// ============================================================================
/**
Compare two EFI_TIME values by Hour/Minute/Second only.
@param[in] Time1 First time to compare.
@param[in] Time2 Second time to compare.
@return 0 if Hour/Minute/Second match.
@return 1 if Time1 > Time2.
@return -1 if Time1 < Time2.
**/
INTN
EFIAPI
PcRtcTimeCompare (
IN EFI_TIME *Time1,
IN EFI_TIME *Time2
)
{
if (Time1->Hour > Time2->Hour)
{
return 1;
}
if (Time1->Hour < Time2->Hour)
{
return -1;
}
//
// Hours equal, check minutes
//
if (Time1->Minute > Time2->Minute)
{
return 1;
}
if (Time1->Minute < Time2->Minute)
{
return -1;
}
//
// Minutes equal, check seconds
//
if (Time1->Second > Time2->Second)
{
return 1;
}
if (Time1->Second < Time2->Second)
{
return -1;
}
return 0;
}
// ============================================================================
// PcRtcIsTimeNearby
// ============================================================================
/**
Check if "From" time is the same as or the day before "Target" time,
and that the target time is within the same day boundary.
This is used to validate that a SetWakeupTime alarm is within
1 day of the current RTC time.
@param[in] From Current RTC time.
@param[in] Target Proposed alarm time.
@return TRUE if Target is within the same day (or adjacent day)
and the alarm time is >= current time.
**/
BOOLEAN
EFIAPI
PcRtcIsTimeNearby (
IN EFI_TIME *From,
IN EFI_TIME *Target
)
{
if (From->Month < 1)
{
DebugAssert (
"e:\\hs\\PcAtChipsetPkg\\PcatRealTimeClockSmm\\PcRtcSmm.c",
1323,
"From->Month >=1"
);
}
if (From->Month > 12)
{
DebugAssert (
"e:\\hs\\PcAtChipsetPkg\\PcatRealTimeClockSmm\\PcRtcSmm.c",
1324,
"From->Month <=12"
);
}
if (From->Year != Target->Year)
{
//
// Allow Dec 31 -> Jan 1 cross-year
//
if ((UINT16)From->Year + 1 != (UINT16)Target->Year ||
From->Month != 12 ||
From->Day != 31 ||
Target->Month != 1 ||
Target->Day != 1)
{
return FALSE;
}
return PcRtcTimeCompare (From, Target) >= 0;
}
if (From->Month != Target->Month)
{
UINTN FromMonth = From->Month;
//
// Allow month rollover: From->Month + 1 == Target->Month and Target->Day == 1
//
if (FromMonth + 1 == (UINTN)Target->Month && Target->Day == 1)
{
BOOLEAN IsValidEndOfMonth;
if (From->Month == 2)
{
//
// February: check if it's 28th or 29th (leap year)
//
if (PcRtcIsLeapYear (From->Year))
{
IsValidEndOfMonth = (From->Day == 29);
}
else
{
IsValidEndOfMonth = (From->Day == 28);
}
}
else
{
//
// For other months, check days-in-month
//
IsValidEndOfMonth = (From->Day == mDaysInMonth[FromMonth - 1]);
}
if (IsValidEndOfMonth && PcRtcTimeCompare (From, Target) >= 0)
{
return TRUE;
}
}
return FALSE;
}
//
// Same year, same month -- check day rollover
//
if (From->Day + 1 == Target->Day)
{
return PcRtcTimeCompare (From, Target) >= 0;
}
//
// Same day -- alarm must be >= current time
//
if (From->Day == Target->Day && PcRtcTimeCompare (From, Target) <= 0)
{
return TRUE;
}
return FALSE;
}
// ============================================================================
// PcRtcGetTime - SMM GetTime handler
// ============================================================================
/**
Get the current RTC time from hardware.
Steps:
1. Wait for RTC update complete (UIP clear).
2. Read Register B to determine binary/BCD and 12hr/24hr mode.
3. Read all time registers (second, minute, hour, day, month, year).
4. Convert from register format.
5. Validate the time.
@param[out] Time Pointer to receive the current time.
@param[out] Capabilities Optional SMM capabilities descriptor.
@return EFI_SUCCESS Time read successfully.
@return EFI_INVALID_PARAMETER Time pointer is NULL.
@return EFI_DEVICE_ERROR RTC update failed, or converted time invalid.
**/
EFI_STATUS
EFIAPI
PcRtcGetTime (
OUT EFI_TIME *Time,
OUT EFI_TIME_CAPABILITIES *Capabilities OPTIONAL
)
{
EFI_STATUS Status;
UINT8 RegisterB;
UINT8 Second;
UINT8 Minute;
UINT8 Hour;
UINT8 Day;
UINT8 Month;
UINT8 Year;
if (Time == NULL)
{
return EFI_INVALID_PARAMETER;
}
Status = PcRtcWaitForUpdateComplete ();
if (EFI_ERROR (Status))
{
return Status;
}
//
// Read Register B to determine format
//
RegisterB = PcRtcReadReg (RTC_REGISTER_B);
//
// Read all time registers
//
Time->Second = PcRtcReadReg (RTC_SECONDS);
Time->Minute = PcRtcReadReg (RTC_MINUTES);
Time->Hour = PcRtcReadReg (RTC_HOURS);
Time->Day = PcRtcReadReg (RTC_DAY_OF_MONTH);
Time->Month = PcRtcReadReg (RTC_MONTH);
Time->Year = PcRtcReadReg (RTC_YEAR);
//
// Set TimeZone and Daylight from globals
//
Time->TimeZone = gRtcTimeZone;
Time->Daylight = gRtcDaylight;
//
// Convert from register format (BCD->decimal, 12hr->24hr, add century)
//
Status = PcRtcConvertFromRegisterFormat (Time, RegisterB);
if (EFI_ERROR (Status))
{
return EFI_DEVICE_ERROR;
}
//
// Validate the converted time
//
Status = PcRtcValidateTime (Time);
if (EFI_ERROR (Status))
{
return EFI_DEVICE_ERROR;
}
//
// Report capabilities
//
if (Capabilities != NULL)
{
Capabilities->Resolution = 1; // 1 Hz
Capabilities->Accuracy = 50000000; // 50,000,000 nanoseconds (50ms)
Capabilities->SetsToZero = FALSE;
}
return EFI_SUCCESS;
}
// ============================================================================
// PcRtcGetTime thunk
// ============================================================================
/**
Thunk function for PcRtcGetTime.
Called via the RSC handler array at gSmmRscHandlerProtocol+24.
**/
EFI_STATUS
EFIAPI
PcRtcSmmGetTimeHandler (
VOID
)
{
return PcRtcGetTime (NULL, NULL);
}
// ============================================================================
// PcRtcSetTime - SMM SetTime handler
// ============================================================================
/**
Set the RTC time on hardware.
Steps:
1. Validate the time.
2. Copy time to config structure.
3. Wait for RTC update complete.
4. Delete the "RTC" SMM variable (to invalidate DXE-side cache).
5. Set Register B SET bit to freeze updates.
6. Write century byte.
7. Convert time to register format (BCD, 12hr with PM flag).
8. Write all time registers.
9. Clear SET bit to resume updates.
10. Save TimeZone and Daylight to config structure.
@param[in,out] Time Time to set (may be modified during format conversion).
@param[in] Config RTC configuration structure containing century register.
@return EFI_SUCCESS Time set successfully.
@return EFI_INVALID_PARAMETER Time validation failed.
@return EFI_DEVICE_ERROR RTC update failed to complete.
**/
EFI_STATUS
EFIAPI
PcRtcSetTime (
IN OUT EFI_TIME *Time,
IN PC_RTC_CONFIG *Config OPTIONAL
)
{
EFI_STATUS Status;
UINT8 RegisterB;
EFI_TIME LocalTime;
//
// Validate the time
//
Status = PcRtcValidateTime (Time);
if (EFI_ERROR (Status))
{
return Status;
}
//
// Copy time for internal manipulation
//
CopyMemWrapper (&LocalTime, Time, sizeof (EFI_TIME));
//
// Wait for update complete before writing
//
Status = PcRtcWaitForUpdateComplete ();
if (EFI_ERROR (Status))
{
return Status;
}
//
// Delete the "RTC" SMM variable to invalidate DXE-side cache
//
if (gSmmRscHandlerProtocol != NULL)
{
//
// The RSC protocol has the SmmDeleteVariable at offset 88
//
((SMM_RSC_HANDLER_PROTOCOL *)gSmmRscHandlerProtocol)->SmmDeleteVariable (
L"RTC",
&gEfiSmmVariableGuid,
7
);
}
//
// Set SET bit in Register B to freeze RTC updates
//
RegisterB = PcRtcReadReg (RTC_REGISTER_B);
PcRtcWriteReg (RTC_REGISTER_B, RegisterB | RTC_B_SET);
//
// Write century byte to CMOS (offset from Config or default 0x32)
//
if (Config != NULL && Config->CenturyRegister != 0)
{
PcRtcWriteReg (Config->CenturyRegister & 0x7F,
DecimalToBcd ((UINT8)(LocalTime.Year / 100)));
}
else
{
PcRtcWriteReg (0x32,
DecimalToBcd ((UINT8)(LocalTime.Year / 100)));
}
//
// Convert time to register format
//
PcRtcConvertToRegisterFormat (&LocalTime, RegisterB);
//
// Write all time registers
//
PcRtcWriteReg (RTC_SECONDS, LocalTime.Second);
PcRtcWriteReg (RTC_MINUTES, LocalTime.Minute);
PcRtcWriteReg (RTC_HOURS, LocalTime.Hour);
PcRtcWriteReg (RTC_DAY_OF_MONTH, LocalTime.Day);
PcRtcWriteReg (RTC_MONTH, LocalTime.Month);
PcRtcWriteReg (RTC_YEAR, (UINT8)LocalTime.Year);
//
// Clear SET bit to resume RTC updates
//
PcRtcWriteReg (RTC_REGISTER_B, RegisterB & ~RTC_B_SET);
//
// Save TimeZone and Daylight to config structure
//
if (Config != NULL)
{
Config->TimeZone = Time->TimeZone;
Config->Daylight = Time->Daylight;
}
return EFI_SUCCESS;
}
// ============================================================================
// PcRtcSetTime thunk
// ============================================================================
/**
Thunk function for PcRtcSetTime.
Called via the RSC handler array at gSmmRscHandlerProtocol+32.
**/
EFI_STATUS
EFIAPI
PcRtcSmmSetTimeHandler (
IN PC_RTC_CONFIG *Config OPTIONAL
)
{
if (Config != NULL)
{
return PcRtcSetTime (NULL, &gRtcConfig);
}
return EFI_INVALID_PARAMETER;
}
// ============================================================================
// PcRtcGetWakeupTime - SMM GetWakeupTime handler
// ============================================================================
/**
Get the RTC wakeup alarm time.
Steps:
1. Wait for RTC update complete.
2. Read Register B for AIE status (alarm enabled).
3. Read Register C for AF bit (alarm fired).
4. Read alarm time registers (seconds, minutes, hours).
5. Read the "RTCALARM" SMM variable to get Year/Month (if stored).
6. Restore TimeZone/Daylight from config structure.
7. Convert/validate time.
@param[out] Enabled TRUE if alarm interrupt is enabled (AIE bit in Register B).
@param[out] Pending TRUE if alarm has fired (AF bit in Register C).
@param[out] Time Alarm time (from registers + RTCALARM variable).
@param[in] Config RTC configuration structure.
@return EFI_SUCCESS Alarm time read successfully.
@return EFI_INVALID_PARAMETER Any output pointer is NULL.
@return EFI_DEVICE_ERROR RTC update failed or time conversion invalid.
**/
EFI_STATUS
EFIAPI
PcRtcGetWakeupTime (
OUT BOOLEAN *Enabled,
OUT BOOLEAN *Pending,
OUT EFI_TIME *Time,
IN PC_RTC_CONFIG *Config
)
{
EFI_STATUS Status;
UINT8 RegisterB;
UINT8 RegisterC;
UINT8 Century;
UINTN DataSize;
if (Enabled == NULL || Pending == NULL || Time == NULL)
{
return EFI_INVALID_PARAMETER;
}
Status = PcRtcWaitForUpdateComplete ();
if (EFI_ERROR (Status))
{
return EFI_DEVICE_ERROR;
}
//
// Read Register B and C for alarm status
//
RegisterB = PcRtcReadReg (RTC_REGISTER_B);
RegisterC = PcRtcReadReg (RTC_REGISTER_C);
*Enabled = (RegisterB & RTC_B_AIE) != 0;
*Pending = (RegisterC & RTC_B_AIE) != 0;
//
// Read alarm registers (seconds, minutes, hours)
//
Time->Second = PcRtcReadReg (RTC_SECONDS_ALARM);
Time->Minute = PcRtcReadReg (RTC_MINUTES_ALARM);
Time->Hour = PcRtcReadReg (RTC_HOURS_ALARM);
//
// Read date registers (day, month, year)
//
Time->Day = PcRtcReadReg (RTC_DAY_OF_MONTH);
//
// Check Register D for century storage
//
UINT8 RegisterD;
RegisterD = PcRtcReadReg (RTC_REGISTER_D);
Century = RegisterD & 0x3F;
if (Century > 0 && Century - 1 <= 30)
{
Time->Day = DecimalToBcd (Century);
}
Time->Month = PcRtcReadReg (RTC_MONTH);
Time->Year = PcRtcReadReg (RTC_YEAR);
//
// Apply TimeZone/Daylight from config structure
//
Time->TimeZone = Config->TimeZone;
Time->Daylight = Config->Daylight;
//
// Try to read the "RTCALARM" SMM variable to get Year/Month
//
DataSize = sizeof (EFI_TIME);
Status = ((SMM_RSC_HANDLER_PROTOCOL *)gSmmRscHandlerProtocol)->SmmGetVariable (
L"RTCALARM",
&gEfiSmmVariableGuid,
0,
&DataSize,
Time
);
if (!EFI_ERROR (Status))
{
//
// Update Year/Month/Day from the stored alarm variable
//
Time->Day = Time->Day;
Time->Month = Time->Month;
Time->Year = Time->Year;
}
//
// Convert from register format
//
Status = PcRtcConvertFromRegisterFormat (Time, RegisterB);
if (EFI_ERROR (Status))
{
return EFI_DEVICE_ERROR;
}
//
// Validate the time
//
Status = PcRtcValidateTime (Time);
if (EFI_ERROR (Status))
{
return EFI_DEVICE_ERROR;
}
return EFI_SUCCESS;
}
// ============================================================================
// PcRtcGetWakeupTime thunk
// ============================================================================
/**
Thunk function for PcRtcGetWakeupTime.
Called via the RSC handler array at gSmmRscHandlerProtocol+40.
**/
EFI_STATUS
EFIAPI
PcRtcSmmGetAlarmHandler (
OUT BOOLEAN *Enabled,
OUT BOOLEAN *Pending,
OUT EFI_TIME *Time
)
{
return PcRtcGetWakeupTime (Enabled, Pending, Time, &gRtcConfig);
}
// ============================================================================
// PcRtcSetWakeupTime - SMM SetWakeupTime handler
// ============================================================================
/**
Set or clear the RTC wakeup alarm.
Steps:
1. Zero the local time buffer.
2. If Enabled:
a. Validate the alarm time.
b. Get current RTC time.
c. Validate current time.
d. Check alarm is within 24 hours of current time.
e. Copy alarm time.
3. Wait for RTC update complete.
4. Read Register B.
5. If Enabled:
a. Convert time to register format.
6. Else:
a. Save current alarm register values.
7. Write "RTCALARM" SMM variable.
8. Set Register B SET bit.
9. Write alarm registers (seconds, minutes, hours).
10. Update Register D century.
11. Write Month/Year.
12. Toggle AIE bit: set if Enabled, clear if not.
13. Clear SET bit.
@param[in] Enabled TRUE to set alarm, FALSE to clear.
@param[in] Time Alarm time (if Enabled is TRUE).
@return EFI_SUCCESS Alarm set/cleared successfully.
@return EFI_INVALID_PARAMETER Invalid time (if enabled).
@return EFI_DEVICE_ERROR RTC update failed.
**/
EFI_STATUS
EFIAPI
PcRtcSetWakeupTime (
IN BOOLEAN Enabled,
IN EFI_TIME *Time OPTIONAL
)
{
EFI_STATUS Status;
UINT8 RegisterB;
UINT8 RegisterBNew;
UINT8 RegisterD;
EFI_TIME AlarmTime;
EFI_TIME CurrentTime;
ZeroMemWrapper (&AlarmTime, sizeof (EFI_TIME));
if (Enabled)
{
if (Time == NULL)
{
return EFI_INVALID_PARAMETER;
}
Status = PcRtcValidateTime (Time);
if (EFI_ERROR (Status))
{
return EFI_INVALID_PARAMETER;
}
//
// Get current RTC time to validate proximity (alarm must be within 24 hours)
//
Status = PcRtcGetTime (&CurrentTime, NULL);
if (EFI_ERROR (Status))
{
return EFI_DEVICE_ERROR;
}
Status = PcRtcValidateTime (&CurrentTime);
if (EFI_ERROR (Status))
{
return Status;
}
//
// Check if alarm time is nearby (within 1 day)
//
if (!PcRtcIsTimeNearby (&CurrentTime, Time))
{
return EFI_INVALID_PARAMETER;
}
//
// Copy alarm time
//
CopyMemWrapper (&AlarmTime, Time, sizeof (EFI_TIME));
}
Status = PcRtcWaitForUpdateComplete ();
if (EFI_ERROR (Status))
{
return EFI_DEVICE_ERROR;
}
RegisterB = PcRtcReadReg (RTC_REGISTER_B);
if (Enabled)
{
//
// Convert alarm to register format
//
PcRtcConvertToRegisterFormat (&AlarmTime, RegisterB);
}
else
{
//
// Save current alarm register values (for restoring previous alarm)
//
AlarmTime.Second = PcRtcReadReg (RTC_SECONDS_ALARM);
AlarmTime.Minute = PcRtcReadReg (RTC_MINUTES_ALARM);
AlarmTime.Hour = PcRtcReadReg (RTC_HOURS_ALARM);
//
// Read Register D century
//
RegisterD = PcRtcReadReg (RTC_REGISTER_D) & 0x3F;
AlarmTime.Month = PcRtcReadReg (RTC_MONTH);
AlarmTime.Year = PcRtcReadReg (RTC_YEAR);
//
// Apply TimeZone/Daylight from globals
//
AlarmTime.TimeZone = gRtcTimeZone;
AlarmTime.Daylight = gRtcDaylight;
}
//
// Write "RTCALARM" SMM variable
//
Status = ((SMM_RSC_HANDLER_PROTOCOL *)gSmmRscHandlerProtocol)->SmmSetVariable (
L"RTCALARM",
&gEfiSmmVariableGuid,
7,
sizeof (EFI_TIME),
&AlarmTime
);
if (EFI_ERROR (Status))
{
return EFI_DEVICE_ERROR;
}
//
// Set Register B SET bit to freeze updates
//
RegisterBNew = RegisterB | RTC_B_SET;
PcRtcWriteReg (RTC_REGISTER_B, RegisterBNew);
if (Enabled)
{
//
// Write alarm registers
//
PcRtcWriteReg (RTC_SECONDS_ALARM, AlarmTime.Second);
PcRtcWriteReg (RTC_MINUTES_ALARM, AlarmTime.Minute);
PcRtcWriteReg (RTC_HOURS_ALARM, AlarmTime.Hour);
//
// Read and update Register D with the alarm day/century
//
PcRtcReadReg (RTC_REGISTER_C); // Clear pending AF
//
// Write Register D century
//
PcRtcWriteReg (RTC_REGISTER_D, AlarmTime.Day & 0x3F);
//
// Clear pending interrupt by reading Register C
//
PcRtcReadReg (RTC_REGISTER_C);
}
else
{
//
// Read existing alarm values and keep them (only clear AIE)
//
AlarmTime.Second = PcRtcReadReg (RTC_SECONDS_ALARM);
AlarmTime.Minute = PcRtcReadReg (RTC_MINUTES_ALARM);
AlarmTime.Hour = PcRtcReadReg (RTC_HOURS_ALARM);
//
// Get date
//
RegisterD = PcRtcReadReg (RTC_REGISTER_D);
AlarmTime.Day = PcRtcReadReg (RTC_DAY_OF_MONTH);
AlarmTime.Month = PcRtcReadReg (RTC_MONTH);
AlarmTime.Year = PcRtcReadReg (RTC_YEAR);
AlarmTime.TimeZone = gRtcTimeZone;
AlarmTime.Daylight = gRtcDaylight;
}
//
// Write alarm registers (redundant for Enabled, restores current for not)
//
PcRtcWriteReg (RTC_SECONDS_ALARM, AlarmTime.Second);
PcRtcWriteReg (RTC_MINUTES_ALARM, AlarmTime.Minute);
PcRtcWriteReg (RTC_HOURS_ALARM, AlarmTime.Hour);
//
// Write Register D century
//
if (!Enabled)
{
PcRtcReadReg (RTC_REGISTER_D); // Dummy read
}
PcRtcWriteReg (RTC_REGISTER_D, AlarmTime.Day & 0x3F);
//
// Write Month/Year
//
PcRtcWriteReg (RTC_MONTH, AlarmTime.Month);
PcRtcWriteReg (RTC_YEAR, (UINT8)AlarmTime.Year);
//
// Toggle AIE bit: set if enabled, clear if not
//
if (Enabled)
{
RegisterBNew = (RegisterBNew | RTC_B_AIE) & ~RTC_B_SET;
}
else
{
RegisterBNew = (RegisterBNew & ~RTC_B_AIE) & ~RTC_B_SET;
}
//
// Write final Register B value (clear SET, optionally set AIE)
//
PcRtcWriteReg (RTC_REGISTER_B, RegisterBNew & 0x7F);
return EFI_SUCCESS;
}
// ============================================================================
// PcRtcSetWakeupTime thunk
// ============================================================================
/**
Thunk function for PcRtcSetWakeupTime.
Called via the RSC handler array at gSmmRscHandlerProtocol+48.
**/
EFI_STATUS
EFIAPI
PcRtcSmmSetAlarmHandler (
VOID
)
{
return PcRtcSetWakeupTime (FALSE, NULL);
}
// ============================================================================
// PcRtcInitRtcHardware - RTC chip initialization
// ============================================================================
/**
Initialize the MC146818 RTC hardware.
Steps:
1. Write Register A = 0x26 (divider reset + 32768 Hz rate select).
2. Read Register B, save value.
3. Read Register C to clear pending interrupt.
4. Write Register D = 0 (clear VRT, will be set by hardware).
5. Wait for UIP to clear.
6. Read all time registers.
7. Configure Register B: preserve bits 5 (AIE) and 2 (DM),
force bit 1 (24HR) and clear bit 0 (DSE) -> value = (RegB & 0x24) | 0x02.
8. Try to read "RTC" SMM variable for TimeZone.
9. Convert/validate time.
10. If time invalid: software reset, set defaults (Year=2000, Month=1, Day=1,
Hour=0, Minute=0, Second=0, TimeZone=2047, Daylight=0).
11. Write current time via PcRtcSetTime.
12. Get wakeup alarm time.
13. If no alarm pending: set default alarm (2000-01-01, clear AIE).
@param[in] SmmRuntimeRsc Pointer to SMM_RSC_HANDLER_PROTOCOL.
@return EFI_SUCCESS RTC initialized successfully.
@return EFI_DEVICE_ERROR RTC hardware init failed.
**/
EFI_STATUS
EFIAPI
PcRtcInitRtcHardware (
IN VOID *SmmRuntimeRsc
)
{
EFI_STATUS Status;
UINT8 RegisterB;
UINT8 RegisterBNew;
UINT16 AlarmYear;
EFI_TIME CurrentTime;
EFI_TIME AlarmTime;
BOOLEAN AlarmEnabled;
BOOLEAN AlarmPending;
UINT8 Century;
UINTN DataSize;
UINTN TempTime;
gRtcCenturyOffset = 0x32;
//
// Step 1: Initialize RTC hardware registers
//
PcRtcWriteReg (RTC_REGISTER_A, 0x26); // 32768 Hz, divider reset
RegisterB = PcRtcReadReg (RTC_REGISTER_B);
PcRtcReadReg (RTC_REGISTER_C); // Clear pending interrupt
PcRtcWriteReg (RTC_REGISTER_D, 0); // Clear VRT (will be set by hardware)
//
// Wait for UIP to clear
//
Status = PcRtcWaitForUpdateComplete ();
if (EFI_ERROR (Status))
{
gRtcDaylight = 0;
return EFI_DEVICE_ERROR;
}
//
// Step 2: Read the current RTC time from hardware
//
CurrentTime.Second = PcRtcReadReg (RTC_SECONDS);
CurrentTime.Minute = PcRtcReadReg (RTC_MINUTES);
CurrentTime.Hour = PcRtcReadReg (RTC_HOURS);
CurrentTime.Day = PcRtcReadReg (RTC_DAY_OF_MONTH);
CurrentTime.Month = PcRtcReadReg (RTC_MONTH);
CurrentTime.Year = PcRtcReadReg (RTC_YEAR);
//
// Step 3: Configure Register B
// Preserve bits: AIE (5), DM (2) = 0x24
// Set: 24HR (1) = 0x02
// Clear: DSE (0), SQWE (3), UIE (4), PIE (6), SET (7)
//
RegisterBNew = (RegisterB & 0x24) | 0x02;
PcRtcWriteReg (RTC_REGISTER_B, RegisterBNew);
//
// Step 4: Try to read the "RTC" SMM variable for TimeZone
//
DataSize = sizeof (UINT64);
if (((SMM_RSC_HANDLER_PROTOCOL *)gSmmRscHandlerProtocol)->SmmGetVariable (
L"RTC",
&gEfiSmmVariableGuid,
0,
&DataSize,
&TempTime
) >= 0)
{
gRtcTimeZone = (INT16)(TempTime & 0xFFFF);
gRtcDaylight = (UINT8)((TempTime >> 16) & 0xFF);
}
else
{
gRtcTimeZone = 2047;
gRtcDaylight = 0;
}
//
// Step 5: Try to convert and validate the current time
//
Status = PcRtcConvertFromRegisterFormat (&CurrentTime, RegisterBNew);
if (EFI_ERROR (Status) || EFI_ERROR (PcRtcValidateTime (&CurrentTime)))
{
//
// Time invalid -- reset to factory defaults
//
//
// Software reset via SMM CPU protocol write
//
PcRtcSmmCpuWrite ();
CurrentTime.Year = 2000;
CurrentTime.Month = 1;
CurrentTime.Day = 1;
CurrentTime.Hour = 0;
CurrentTime.Minute = 0;
CurrentTime.Second = 0;
gRtcTimeZone = 2047;
gRtcDaylight = 0;
//
// Ensure SET (bit 7) is clear, AIE (bit 5) is set, 24HR (bit 1) is set
//
PcRtcWriteReg (RTC_REGISTER_B, RegisterBNew & 0xDF | 0x02);
}
//
// Step 6: Write back the SMM variable with the current time + config
//
Status = PcRtcSetTime (&CurrentTime, &gRtcConfig);
if (EFI_ERROR (Status))
{
return EFI_DEVICE_ERROR;
}
//
// Step 7: Get wakeup alarm time
//
Status = PcRtcGetWakeupTime (&AlarmEnabled, &AlarmPending, &AlarmTime, &gRtcConfig);
//
// Step 8: If no alarm pending, set default alarm (2000-01-01, clear AIE)
//
if (!AlarmEnabled && EFI_ERROR (Status))
{
//
// Read the current config timezone/daylight
//
gRtcTimeZone = gRtcConfig.TimeZone;
gRtcDaylight = gRtcConfig.Daylight;
//
// Set default alarm time: 2000-01-01 00:00:00
//
CurrentTime.Year = 2000;
CurrentTime.Month = 1;
CurrentTime.Day = 1;
CurrentTime.Hour = 0;
CurrentTime.Minute = 0;
CurrentTime.Second = 0;
Status = PcRtcWaitForUpdateComplete ();
if (EFI_ERROR (Status))
{
return EFI_DEVICE_ERROR;
}
RegisterB = PcRtcReadReg (RTC_REGISTER_B);
//
// Convert to register format
//
PcRtcConvertToRegisterFormat (&CurrentTime, RegisterB);
//
// Delete the "RTCALARM" variable
//
Status = ((SMM_RSC_HANDLER_PROTOCOL *)gSmmRscHandlerProtocol)->SmmSetVariable (
L"RTCALARM",
&gEfiSmmVariableGuid,
7,
sizeof (EFI_TIME),
&CurrentTime
);
if (EFI_ERROR (Status))
{
return EFI_DEVICE_ERROR;
}
//
// Set Register B SET bit, write alarm registers, clear SET bit
//
PcRtcWriteReg (RTC_REGISTER_B, RegisterB | RTC_B_SET);
PcRtcWriteReg (RTC_SECONDS_ALARM, CurrentTime.Second);
PcRtcWriteReg (RTC_MINUTES_ALARM, CurrentTime.Minute);
PcRtcWriteReg (RTC_HOURS_ALARM, CurrentTime.Hour);
PcRtcWriteReg (RTC_REGISTER_D, CurrentTime.Day & 0x3F);
PcRtcWriteReg (RTC_MONTH, CurrentTime.Month);
PcRtcWriteReg (RTC_YEAR, (UINT8)CurrentTime.Year);
PcRtcWriteReg (RTC_REGISTER_B, (RegisterB | RTC_B_AIE | 0x80) & 0x7F);
}
return EFI_SUCCESS;
}
// ============================================================================
// PcRtcInitialize - Protocol registration
// ============================================================================
/**
Initialize the SMM Runtime RSC handler.
Scans gSmst->SmmRegisterProtocol for registered protocols to find
gEfiSmmRscHandlerGuid. Once found, stores the protocol interface
and registers the four RTC handlers.
@param[in] Smst Pointer to EFI_SMM_SYSTEM_TABLE2 (gSmst).
@return EFI_SUCCESS RSC handler installed.
@return EFI_UNSUPPORTED SMM Runtime RSC protocol not found.
**/
EFI_STATUS
EFIAPI
PcRtcInitialize (
IN EFI_SMM_SYSTEM_TABLE2 *Smst
)
{
UINTN Index;
UINTN ProtocolCount;
VOID *Interface;
EFI_STATUS Status;
//
// Scan the protocol database for gEfiSmmRscHandlerGuid
//
ProtocolCount = Smst->NumberOfProtocols;
for (Index = 0; Index < ProtocolCount; Index++)
{
//
// Get protocol entry from Smst->ProtocolRegistry
// Each entry is 24 bytes:
// +0: Protocol GUID pointer
// +8: Protocol Interface (if matching)
// +16: Not used for matching
//
EFI_GUID *EntryGuid = (EFI_GUID *)(*(UINT64 *)(Smst + 160) + (Index * 24));
if (CompareGuidPair (EntryGuid, &gEfiSmmRscHandlerGuid))
{
gSmmRscHandlerProtocol = *(VOID **)(*(UINT64 *)(Smst + 160) + (Index * 24) + 16);
break;
}
}
if (gSmmRscHandlerProtocol == NULL)
{
//
// Could not find SMM Runtime Services Protocol
//
DebugPrint (4, "Couldn't find SMM Runtime Services\n");
return EFI_UNSUPPORTED;
}
//
// Initialize RTC hardware
//
Status = PcRtcInitRtcHardware (gSmmRscHandlerProtocol);
if (EFI_ERROR (Status))
{
DebugPrint (
0x80000000,
"\nASSERT_EFI_ERROR (Status = %r)\n",
Status
);
DebugAssert (
"e:\\hs\\PcAtChipsetPkg\\PcatRealTimeClockSmm\\PcRtcSmmEntry.c",
159,
"!EFI_ERROR (Status)"
);
}
//
// Register the four RTC handlers in the RSC protocol
//
((SMM_RSC_HANDLER_PROTOCOL *)gSmmRscHandlerProtocol)->Handler[0] = PcRtcSmmGetTimeHandler;
((SMM_RSC_HANDLER_PROTOCOL *)gSmmRscHandlerProtocol)->Handler[1] = PcRtcSmmSetTimeHandler;
((SMM_RSC_HANDLER_PROTOCOL *)gSmmRscHandlerProtocol)->Handler[2] = PcRtcSmmGetAlarmHandler;
((SMM_RSC_HANDLER_PROTOCOL *)gSmmRscHandlerProtocol)->Handler[3] = PcRtcSmmSetAlarmHandler;
return EFI_SUCCESS;
}
// ============================================================================
// PcRtcSmmDriverInit - Full driver entry
// ============================================================================
/**
Full SMM driver initialization.
Steps:
1. Save ImageHandle, SystemTable, BootServices, RuntimeServices to globals.
2. Locate SMM Base2 protocol (GUID at 0x2A60).
3. Call GetSmstLocation to get gSmst.
4. Locate SMM Access2 protocol (GUID at 0x2A90).
5. Get SMRAM capabilities (buffer size).
6. Allocate SmramRanges buffer via Smst->SmmAllocatePool.
7. Re-read SMRAM capabilities into gSmramRanges.
8. Set gSmramRangeCount = BufferSize >> 5 (divide by 32).
9. Get PCD protocol and read PcdPlatformType (token 5).
10. Initialize HOB list.
11. Check PCI express MMIO at config address 0x500 (if bit 31 clear):
Write 0x500 to the word at address (PcdDbValue + 1024064).
Then set bit 7 on the byte at (PcdDbValue + 1024068).
12. TSC-based delay: read EFLAGS, disable interrupts,
read TSC, wait ~357 TSC cycles, restore interrupts.
13. Return.
@param[in] ImageHandle EFI image handle for this driver.
@param[in] SystemTable EFI system table.
@return EFI_STATUS.
**/
EFI_STATUS
EFIAPI
PcRtcSmmDriverInit (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
EFI_STATUS Status;
UINTN SmramRangeBufferSize;
VOID *SmmBase2;
EFI_SMM_ACCESS2_PROTOCOL *SmmAccess2;
SMM_SERVICES_TABLE *Smst;
UINTN Index;
UINTN CpuIo2Handle;
UINT64 CmosDelayTsc;
UINT64 InitialTsc;
UINT64 TargetEflags;
BOOLEAN InterruptsEnabled;
//
// Step 1: Save EFI table pointers to globals
//
gImageHandle = ImageHandle;
gST = SystemTable;
gBS = SystemTable->BootServices;
gRT = SystemTable->RuntimeServices;
//
// Step 2: Locate SMM Base2 protocol
//
SmmBase2 = NULL;
Status = gBS->LocateProtocol (
&gEfiSmmBase2ProtocolGuid,
NULL,
&SmmBase2
);
if (EFI_ERROR (Status))
{
DebugPrint (0x80000000, "\nASSERT_EFI_ERROR (Status = %r)\n", Status);
DebugAssert (
"e:\\hs\\MdePkg\\Library\\SmmServicesTableLib\\SmmServicesTableLib.c",
52,
"!EFI_ERROR (Status)"
);
}
if (SmmBase2 == NULL)
{
DebugAssert (
"e:\\hs\\MdePkg\\Library\\SmmServicesTableLib\\SmmServicesTableLib.c",
53,
"InternalSmmBase2 != ((void *) 0)"
);
}
//
// Step 3: Get Smst location
//
Smst = NULL;
((EFI_SMM_BASE2_PROTOCOL *)SmmBase2)->GetSmstLocation (SmmBase2, &Smst);
if (Smst == NULL)
{
DebugAssert (
"e:\\hs\\MdePkg\\Library\\SmmServicesTableLib\\SmmServicesTableLib.c",
59,
"gSmst != ((void *) 0)"
);
}
gSmst = Smst;
//
// Step 4: Locate SMM Access2 protocol
//
CpuIo2Handle = 0;
Status = gBS->LocateProtocol (
&gEfiSmmAccess2ProtocolGuid,
NULL,
&CpuIo2Handle
);
if (EFI_ERROR (Status))
{
DebugPrint (0x80000000, "\nASSERT_EFI_ERROR (Status = %r)\n", Status);
DebugAssert (
"e:\\hs\\MdePkg\\Library\\SmmMemoryAllocationLib\\MemoryAllocationLib.c",
71,
"!EFI_ERROR (Status)"
);
}
//
// Step 5: Get SMRAM capabilities (query size first)
//
SmramRangeBufferSize = 0;
Status = ((EFI_SMM_ACCESS2_PROTOCOL *)(UINTN)CpuIo2Handle)->GetCapabilities (
(EFI_SMM_ACCESS2_PROTOCOL *)(UINTN)CpuIo2Handle,
&SmramRangeBufferSize,
NULL
);
if (Status != EFI_BUFFER_TOO_SMALL)
{
Status = EFI_BUFFER_TOO_SMALL;
DebugAssert (
"e:\\hs\\MdePkg\\Library\\SmmMemoryAllocationLib\\MemoryAllocationLib.c",
78,
"Status == ((RETURN_STATUS)(0x8000000000000000ULL | (5)))"
);
}
//
// Step 6: Allocate buffer for SMRAM ranges
//
gSmramRanges = AllocateSmramPool (EfiRuntimeServicesData, SmramRangeBufferSize);
if (gSmramRanges == NULL)
{
DebugAssert (
"e:\\hs\\MdePkg\\Library\\SmmMemoryAllocationLib\\MemoryAllocationLib.c",
81,
"mSmramRanges != ((void *) 0)"
);
}
//
// Step 7: Re-read SMRAM ranges into the allocated buffer
//
Status = ((EFI_SMM_ACCESS2_PROTOCOL *)(UINTN)CpuIo2Handle)->GetCapabilities (
(EFI_SMM_ACCESS2_PROTOCOL *)(UINTN)CpuIo2Handle,
&SmramRangeBufferSize,
gSmramRanges
);
if (EFI_ERROR (Status))
{
DebugPrint (0x80000000, "\nASSERT_EFI_ERROR (Status = %r)\n", Status);
DebugAssert (
"e:\\hs\\MdePkg\\Library\\SmmMemoryAllocationLib\\MemoryAllocationLib.c",
84,
"!EFI_ERROR (Status)"
);
}
//
// Step 8: Calculate SMRAM range count (each range is 32 bytes = 5 qwords >> 5)
//
gSmramRangeCount = SmramRangeBufferSize >> 5;
//
// Step 9: Get PCD protocol and read platform type
//
gPcdDbValue = ((PCD_PROTOCOL *)GetPcdProtocol ())->Get32 (5);
//
// Step 10: Initialize HOB list
//
GetHobList ();
//
// Step 11: Check PCIe config address for Express presence
//
if ((INT8)(*(UINT8 *)PciExpressReadAddress (1024068)) >= 0)
{
//
// Write 0x500 to the word at PCIe config address 0x100100 (1024064)
//
PciExpressWrite500 ((UINT16 *)PciExpressReadAddress (1024064));
//
// Set bit 7 on the byte at 0x100104 (1024068) -- enable memory space
//
*(UINT8 *)PciExpressReadAddress (1024068) |= 0x80;
}
//
// Step 12: TSC-based short delay (approx 357 TSC cycles)
//
TargetEflags = GetCallerEflags ();
InterruptsEnabled = (TargetEflags & 0x200) != 0;
DisableInterrupts ();
//
// Read initial TSC
//
CmosDelayTsc = ReadMmio32 (1288) & 0xFFFFFF;
//
// Calculate target TSC: initial + 357 cycles
//
InitialTsc = ReadTsc ();
while ((((CmosDelayTsc + 357 - (UINT32)ReadMmio32 (1288)) & 0x800000) == 0))
{
CpuPause ();
}
ReadTsc ();
//
// Restore previous interrupt state
//
if (InterruptsEnabled)
{
EnableInterrupts ();
}
else
{
DisableInterrupts ();
}
return EFI_SUCCESS;
}
// ============================================================================
// SmmCoreEntry
// ============================================================================
/**
SMM Core Entry - called from _ModuleEntryPoint.
Calls PcRtcSmmDriverInit to initialize all globals and protocol interfaces.
@param[in] ImageHandle EFI image handle for this driver.
@param[in] SystemTable EFI system table.
**/
VOID
EFIAPI
SmmCoreEntry (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
PcRtcSmmDriverInit (ImageHandle, SystemTable);
}
// ============================================================================
// _ModuleEntryPoint (0x4B4)
// ============================================================================
/**
Module entry point for the SMM driver.
This function:
1. Calls PcRtcSmmDriverInit for EFI library init.
2. Sets gModuleEntryStatus to 0x8000000000000001 (EfiNotStarted).
3. Sets up a SetJump buffer for recovery on LongJump.
4. On initial SetJump return (0):
a. Calls PcRtcInitialize to register RTC handlers.
b. If initialization fails or recovery needed: calls LongJump.
c. On recovery path: calls DebugAssert for AutoGen.c ASSERTs.
5. On LongJump (non-zero): stores the return status.
6. Returns the final status. If error: calls FreePoolMmramAware.
@param[in] ImageHandle EFI image handle for this driver.
@param[in] SystemTable EFI system table.
@return EFI_STATUS from driver initialization.
**/
EFI_STATUS
EFIAPI
_ModuleEntryPoint (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
UINT64 ReturnStatus;
EFI_STATUS Status;
//
// Initialize the driver
//
PcRtcSmmDriverInit (ImageHandle, SystemTable);
gModuleEntryStatus = 0x8000000000000001ULL;
//
// SetJump for error recovery
//
if (SetJump (&gJumpBuffer) == 0)
{
//
// First execution: initialize RTC and register handlers
//
Status = PcRtcInitialize (gSmst);
if (Status >= EFI_SUCCESS || gModuleEntryStatus < 0)
{
gModuleEntryStatus = Status;
}
//
// Reset the jump buffer and prepare for re-initialization
//
PcRtcValidateJumpBuffer (&gJumpBuffer);
LongJump (&gJumpBuffer, -1);
//
// These ASSERTs are reached only via AutoGen.c recovery:
//
DebugAssert (
"e:\\hs\\Build\\HR6N0XMLK\\DEBUG_VS2015\\X64\\PcAtChipsetPkg\\PcatRealTimeClockSmm\\PcatRealTimeClockSmm\\DEBUG\\AutoGen.c",
436,
"((BOOLEAN)(0==1))"
);
DebugAssert (
"e:\\hs\\Build\\HR6N0XMLK\\DEBUG_VS2015\\X64\\PcAtChipsetPkg\\PcatRealTimeClockSmm\\PcatRealTimeClockSmm\\DEBUG\\AutoGen.c",
451,
"((BOOLEAN)(0==1))"
);
}
ReturnStatus = gModuleEntryStatus;
if (gModuleEntryStatus < 0)
{
//
// Error path: free SMRAM pool
//
FreePoolMmramAware (gSmramRanges);
}
return ReturnStatus;
}
// ============================================================================
// SmmMain - backward compatibility entry (forwarded from entry point)
// ============================================================================
/**
SmmMain - main initialization (previously separate, now part of _ModuleEntryPoint).
@param[in] ImageHandle EFI image handle for this driver.
@param[in] Smst SMM system table.
@return EFI_SUCCESS.
**/
EFI_STATUS
EFIAPI
SmmMain (
IN EFI_HANDLE ImageHandle,
IN EFI_SMM_SYSTEM_TABLE2 *Smst
)
{
//
// Save the SMM system table
//
gSmst = Smst;
//
// All remaining init happens in _ModuleEntryPoint via PcRtcSmmDriverInit
// and PcRtcInitialize.
//
return EFI_SUCCESS;
}
/**
SmmInstallConfigurationTable - install SMST + config protocol (unused wrapper)
Original at 0x1158.
**/
VOID
SmmInstallConfigurationTable (
VOID
)
{
//
// This function is a no-op in the reconstructed driver; the original
// source allocated and populated the configuration table via
// gSmst->SmmInstallConfigurationTable.
//
}