Newer
Older
AMI-Aptio-BIOS-Reversed / PcRtcSmm / PcRtcSmm.md
@Ajax Dong Ajax Dong 2 days ago 20 KB Init

PcRtcSmm

Function Table

Address Name Description
PcRtcGetCentury
CpuPause
ReadTsc
EnableInterrupts
DisableInterrupts
GetCallerEflags
SetJump
LongJump
PcRtcValidateJumpBuffer
DebugPrint
DebugAssert
CompareGuidPair
ReadUnaligned64
PcRtcSmmCpuWrite
PcdGet32
PciExpressWrite500
ReadMmio32
MicroSecondDelay
SmmCpuReadSaveState
IsAddressInSmram
FreePoolMmramAware
PcRtcReadReg
PcRtcWriteReg
DecimalToBcd
BcdToDecimal
PcRtcWaitForUpdateComplete
PcRtcIsLeapYear
PcRtcIsDayValid
PcRtcValidateTime
PcRtcConvertFromRegisterFormat
PcRtcConvertToRegisterFormat
PcRtcIsTimeNearby
PcRtcGetTime
PcRtcSmmGetTimeHandler
PcRtcSetTime
PcRtcSmmSetTimeHandler
PcRtcGetWakeupTime
PcRtcSmmGetAlarmHandler
PcRtcSetWakeupTime
PcRtcSmmSetAlarmHandler
PcRtcInitRtcHardware
PcRtcInitialize
PcRtcSmmDriverInit
SmmCoreEntry
_ModuleEntryPoint
SmmMain
SmmInstallConfigurationTable
RTC Register Ports (SMM alias)
RTC Register Indices
Register A bits
Update In Progress
Register B bits
Daylight Savings Enable
Square Wave Enable
Update Ended Interrupt Enable
Alarm Interrupt Enable
Register D bits
Valid RAM and Time
Helper macros for RTC register access via SMM I/O ports
Module Global Variables (mapped from .data section at 0x2A20-0x2C40)
Standard EFI globals (set by UefiBootServicesTableLib / UefiRuntimeServicesTableLib)
EFI_HANDLE gImageHandle = NULL; // 0x2AB8
0x2AA8 EFI_BOOT_SERVICES *gBS = NULL; // 0x2AB0
0x2AC0 //
SMM globals (resolved via SmmServicesTableLib)
EFI_SMM_SYSTEM_TABLE2 *gSmst = NULL; // 0x2AC8
resolved from GUID at 0x2A60
SMM Runtime RSC Handler Protocol
96 SmmEndOfDxe +104 SmmAllocatePages +112 SmmFreePages +120 SmmAllocatePool
VOID *gSmmRuntimeRsc = NULL; // 0x2AA0 (qword_2AA0)
0x2AE8 (qword_2AE8, lazy-init from GUID 0x2A20)
0x2AF0 (qword_2AF0, SMM CPU I/O2 interface)
SMM Variable Protocol (lazy-init)
VOID *gSmmVariable = NULL; // 0x2AD0 (qword_2AD0)
Platform type from PCD protocol
UINT64 gPcdDbValue = 0; // 0x2AD8: PcdGet32(PcdPlatformType) result
0x2AF8 (PCD protocol pointer)
HOB list (lazy-init via gEfiHobListGuid)
VOID *gHobList = NULL; // 0x2AE0 (qword_2AE0)
SMRAM range tracking
UINT64 *gSmramRanges = NULL; // 0x2C18
RTC state globals
UINT16 gRtcTimeZone = 2047; // 0x2C38: EFI_UNSPECIFIED_TIMEZONE
0x2C3A UINT8 gRtcCenturyOffset = 0x32; // 0x2C3B (n50): CMOS offset for century byte
RTC Config Structure (28 bytes at 0x2C20: unk_2C20)
Used by sub_D20 (SetTime) and sub_EC8 (GetWakeupTime) to save/restore
configuration across operations.
PC_RTC_CONFIG gRtcConfig; // 0x2C20
EFI System Return Status for module entry
UINT64 gModuleEntryStatus = 0x8000000000000001ULL; // 0x2C08
UINT8 gJumpBuffer[320]; // 0x2B10 (unk_2B10)
Debug port scratch (byte at 0x2B00)
UINT8 gDebugPortScratch; // 0x2B00
Forward declarations for internal functions
STATIC EFI_STATUS
CPU intrinsic wrappers (sub_470, sub_480, sub_490, sub_4A0, sub_4B0)
Library support functions
actually JUMP_BUFFER * (at least 320 bytes)
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
stack pointer proxy
Save XMM6-XMM15 (128-bit each)
Jump through the saved return address (offset 72 in jump buffer)
Handle overlap: if Source < Destination and Source + Length >= Destination
copy backwards from end.
if (Source < Destination &&
Copy in reverse 8-byte chunks
for (; Length >= 8; Length -= 8)
Copy remaining bytes in reverse
for (; Length > 0; Length--)
Copy forward 8-byte chunks via qmemcpy
UINTN Remaining = Length;
Copy remaining 1-7 bytes
Zero 8-byte aligned chunks
UINTN Chunks = Length >> 3;
Zero remaining bytes
UINTN Remaining = Length & 7;
Validation helpers (sub_199C at 0x199C and sub_1ABC at 0x1ABC / sub_1A34 at 0x1A34)
Fetch the SMM variable protocol if needed (lazy init)
if (gSmmVariable == NULL)
Read debug level from CMOS (port 0x70 index 0x4C, preserving NMI bit)
IoWrite8 **(0x70, (IoRead8 (0x70) & 0x80) 0x4C);**
Determine the filter level from CMOS
if (CmosDebugLevel > 3)
DEBUG_VERBOSE if (CmosDebugLevel == 1)
DEBUG_INFO }
Enter infinite loop (no return)
while (TRUE);
Write register via SMM CPU protocol
1073741826 = 0x40000002 (some CPU register write)
50724874 = 0x0306000A
return ((EFI_SMM_CPU_PROTOCOL *)gSmmCpuProtocol)->WriteRegister (
Check alignment
if (((UINTN)Address & 1) != 0)
Decompose microseconds into:
Iterations = MicroSeconds >> 22;
Wait for the initial TSC + per-iteration ticks to pass
do {
Loop until TSC >= TargetTsc
while (((TargetTsc - (UINT32)__indword (1288)) & 0x800000) == 0)
next iteration uses full tick range
MicroSecondDelay (35);
SMRAM helper functions
Check if the address is within SMRAM use Smst to free inside SMRAM,
or BootServices to free outside.
if (IsAddressInSmram ((UINT64)Buffer))
CopyMem wrapper (sub_1B64 at 0x1B64)
RTC Register Access
BCD conversion (DecimalToBcd = sub_18F4 at 0x18F4
BcdToDecimal = sub_1940 at 0x1940)
PcRtcWaitForUpdateComplete (sub_1400 at 0x1400)
Check Register D VRT bit
RegisterD = PcRtcReadReg (RTC_REGISTER_D);
Poll Register A UIP with timeout
Index = 10001;
Read Register A and check UIP
RegisterA = PcRtcReadReg (RTC_REGISTER_A);
Verify VRT again after the update completes
PcRtcDaysInMonth table (used by IsDayValid at sub_1500)
Days in each month (non-leap year February = 28, leap February = 29)
Stored as an array of 12 UINTN values for SIMD loading in sub_1500.
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 // ============================================================================
Leap year: divisible by 4, and not (divisible by 100 unless divisible by 400)
if ((Year & 3) != 0)
PcRtcIsDayValid (sub_1500 at 0x1500)
Look up days in this month
DaysInMonth = mDaysInMonth[Time->Month - 1];
February special: check for leap year
if (Time->Month == 2)
PcRtcValidateTime (sub_1478 at 0x1478)
PcRtcConvertFromRegisterFormat (sub_1260 at 0x1260)
Extract PM flag from Hour bit 7 and clear it
RawHour = Time->Hour;
Convert BCD to decimal for all time fields
Check for invalid BCD conversion (0xFF indicates failure)
if **(Time->Year == 0xFF **
Read century byte from RTC CMOS
if (gRtcCenturyOffset != 0)
Convert from 12-hour to 24-hour format
if (!Is24Hour)
if (Time->Hour < 12)
if (Time->Hour == 12)
Clear nanosecond (time register reads don't provide nanoseconds)
PcRtcConvertToRegisterFormat (sub_1640 at 0x1640)
Convert Hour from 24-hour to 12-hour
if (Time->Hour > 12)
Extract century: Year / 100
Year = Time->Year;
Convert to BCD
Write century byte to CMOS
Convert Second to BCD (return value)
if (!IsBinary)
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.
PcRtcTimeCompare (sub_16F8 at 0x16F8)
Hours equal, check minutes
if (Time1->Minute > Time2->Minute)
Minutes equal, check seconds
if (Time1->Second > Time2->Second)
PcRtcIsTimeNearby (sub_1744 at 0x1744)
Allow Dec 31 -> Jan 1 cross-year
if **((UINT16)From->Year + 1 != (UINT16)Target->Year **
Allow month rollover: From->Month + 1 == Target->Month and Target->Day == 1
if (FromMonth + 1 == (UINTN)Target->Month && Target->Day == 1)
if (PcRtcIsLeapYear (From->Year))
For other months, check days-in-month
IsValidEndOfMonth = (From->Day == mDaysInMonth[FromMonth - 1]);
Same year, same month check day rollover
if (From->Day + 1 == Target->Day)
Same day alarm must be >= current time
if (From->Day == Target->Day && PcRtcTimeCompare (From, Target) <= 0)
PcRtcGetTime - SMM GetTime handler (sub_84C -> sub_C28 at 0xC28)
Read Register B to determine format
RegisterB = PcRtcReadReg (RTC_REGISTER_B);
Read all time registers
Set TimeZone and Daylight from globals
Convert from register format (BCD->decimal, 12hr->24hr, add century)
Status = PcRtcConvertFromRegisterFormat (Time, RegisterB);
Validate the converted time
Status = PcRtcValidateTime (Time);
Report capabilities
if (Capabilities != NULL)
1 Hz
PcRtcGetTime thunk (sub_84C at 0x84C)
PcRtcSetTime - SMM SetTime handler (sub_854 -> sub_D20 at 0xD20)
Validate the time
Copy time for internal manipulation
CopyMemWrapper (&LocalTime, Time, sizeof (EFI_TIME));
Wait for update complete before writing
Status = PcRtcWaitForUpdateComplete ();
Delete the "RTC" SMM variable to invalidate DXE-side cache
if (gSmmRuntimeRsc != NULL)
The RSC protocol has the SmmDeleteVariable at offset 88
Set SET bit in Register B to freeze RTC updates
Write century byte to CMOS (offset from Config or default 0x32)
if (Config != NULL && Config->CenturyRegister != 0)
Convert time to register format
PcRtcConvertToRegisterFormat (&LocalTime, RegisterB);
PcRtcWriteReg (RTC_SECONDS, LocalTime.Second);
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)
PcRtcSetTime thunk (sub_854 at 0x854)
PcRtcGetWakeupTime - SMM GetWakeupTime handler (sub_870 -> sub_EC8 at 0xEC8)
Read Register B and C for alarm status
Read alarm registers (seconds, minutes, hours)
Read date registers (day, month, year)
Check Register D for century storage
UINT8 RegisterD;
Apply TimeZone/Daylight from config structure
Try to read the "RTCALARM" SMM variable to get Year/Month
DataSize = sizeof (EFI_TIME);
Update Year/Month/Day from the stored alarm variable
Convert from register format
PcRtcGetWakeupTime thunk (sub_870 at 0x870)
PcRtcSetWakeupTime - SMM SetWakeupTime handler (sub_87C -> sub_1074 at 0x1074)
Get current RTC time to validate proximity (alarm must be within 24 hours)
Status = PcRtcGetTime (&CurrentTime, NULL);
Check if alarm time is nearby (within 1 day)
if (!PcRtcIsTimeNearby (&CurrentTime, Time))
Copy alarm time
CopyMemWrapper (&AlarmTime, Time, sizeof (EFI_TIME));
Convert alarm to register format
PcRtcConvertToRegisterFormat (&AlarmTime, RegisterB);
Save current alarm register values (for restoring previous alarm)
Read Register D century
RegisterD = PcRtcReadReg (RTC_REGISTER_D) & 0x3F;
Apply TimeZone/Daylight from globals
Write "RTCALARM" SMM variable
Status = ((SMM_RSC_HANDLER_PROTOCOL *)gSmmRuntimeRsc)->SmmSetVariable (
Set Register B SET bit to freeze updates
RegisterBNew **= RegisterB RTC_B_SET;**
Write alarm registers
PcRtcWriteReg (RTC_SECONDS_ALARM, AlarmTime.Second);
Read and update Register D with the alarm day/century
PcRtcReadReg (RTC_REGISTER_C); // Clear pending AF
PcRtcWriteReg (RTC_REGISTER_D, AlarmTime.Day & 0x3F);
Clear pending interrupt by reading Register C
PcRtcReadReg (RTC_REGISTER_C);
Read existing alarm values and keep them (only clear AIE)
Get date
Write alarm registers (redundant for Enabled, restores current for not)
if (!Enabled)
Dummy read
Write Month/Year
PcRtcWriteReg (RTC_MONTH, AlarmTime.Month);
Toggle AIE bit: set if enabled, clear if not
if (Enabled)
Write final Register B value (clear SET, optionally set AIE)
PcRtcWriteReg (RTC_REGISTER_B, RegisterBNew & 0x7F);
PcRtcSetWakeupTime thunk (sub_87C at 0x87C)
PcRtcInitRtcHardware - RTC chip initialization (sub_984 at 0x984)
Step 1: Initialize RTC hardware registers
PcRtcWriteReg (RTC_REGISTER_A, 0x26); // 32768 Hz, divider reset
Clear pending interrupt
Clear VRT (will be set by hardware)
Wait for UIP to clear
Step 2: Read the current RTC time from hardware
Step 3: Configure Register B
Preserve bits: AIE (5), DM (2) = 0x24
RegisterBNew **= (RegisterB & 0x24) 0x02;**
Step 4: Try to read the "RTC" SMM variable for TimeZone
DataSize = sizeof (UINT64);
Step 5: Try to convert and validate the current time
Status = PcRtcConvertFromRegisterFormat (&CurrentTime, RegisterBNew);
Time invalid reset to factory defaults
Software reset via SMM CPU protocol write
PcRtcSmmCpuWrite ();
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);
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;
Set default alarm time: 2000-01-01 00:00:00
Convert to register format
PcRtcConvertToRegisterFormat (&CurrentTime, RegisterB);
Delete the "RTCALARM" variable
Set Register B SET bit, write alarm registers, clear SET bit
PcRtcWriteReg **(RTC_REGISTER_B, RegisterB RTC_B_SET);**
PcRtcInitialize - Protocol registration (sub_884 at 0x884)
Scan the protocol database for gEfiSmmRscHandlerGuid
ProtocolCount = Smst->NumberOfProtocols;
Get protocol entry from Smst->ProtocolRegistry
Each entry is 24 bytes:
EFI_GUID EntryGuid = (EFI_GUID )((UINT64 )(Smst + 160) + (Index * 24));
Could not find SMM Runtime Services Protocol
DebugPrint (4, "Couldn't find SMM Runtime Services\n");
Initialize RTC hardware
Status = PcRtcInitRtcHardware (gSmmRuntimeRsc);
Register the four RTC handlers in the RSC protocol
PcRtcSmmDriverInit - Full driver entry (sub_560 at 0x560)
Step 1: Save EFI table pointers to globals
gImageHandle = ImageHandle;
Step 2: Locate SMM Base2 protocol
SmmBase2 = NULL;
Step 3: Get Smst location
Step 4: Locate SMM Access2 protocol
CpuIo2Handle = 0;
Step 5: Get SMRAM capabilities (query size first)
Step 6: Allocate buffer for SMRAM ranges
gSmramRanges = AllocateSmramPool (EfiRuntimeServicesData, SmramRangeBufferSize);
Step 7: Re-read SMRAM ranges into the allocated buffer
Status = ((EFI_SMM_ACCESS2_PROTOCOL *)(UINTN)CpuIo2Handle)->GetCapabilities (
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
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
Step 12: TSC-based short delay (approx 357 TSC cycles)
TargetEflags = GetCallerEflags ();
Read initial TSC
CmosDelayTsc = ReadMmio32 (1288) & 0xFFFFFF;
Calculate target TSC: initial + 357 cycles
InitialTsc = ReadTsc ();
Restore previous interrupt state
if (InterruptsEnabled)
SmmCoreEntry (sub_480 at 0x480 -> sub_560 at 0x560)
_ModuleEntryPoint (0x4B4)
Initialize the driver
PcRtcSmmDriverInit (ImageHandle, SystemTable);
SetJump for error recovery
if (SetJump (&gJumpBuffer) == 0)
First execution: initialize RTC and register handlers
Status = PcRtcInitialize (gSmst);
Reset the jump buffer and prepare for re-initialization
PcRtcValidateJumpBuffer (&gJumpBuffer);
These ASSERTs are reached only via AutoGen.c recovery:
DebugAssert (
Error path: free SMRAM pool
FreePoolMmramAware (gSmramRanges);
SmmMain - backward compatibility entry (forwarded from entry point)
Save the SMM system table
gSmst = Smst;
All remaining init happens in _ModuleEntryPoint via PcRtcSmmDriverInit
and PcRtcInitialize.
return EFI_SUCCESS;
This function is a no-op in the reconstructed driver; the original
source allocated and populated the configuration table via

Generated by HR650X BIOS Decompilation Project