/**
* @file PcRtc.c
*
* @brief PC-AT Real-Time Clock Runtime DXE Driver - Core RTC Logic
*
* UEFI Runtime DXE driver that implements EFI RealTimeClock services
* (GetTime, SetTime, GetWakeupTime, SetWakeupTime) by accessing the
* CMOS RTC hardware via legacy I/O ports 0x70/0x71.
*
* Source: PcAtChipsetPkg/PcatRealTimeClockRuntimeDxe/
* Build: HR6N0XMLK / DEBUG_VS2015 / X64
*
* GUIDs in .data section (VA=0x5000-0x51C0):
* 0x5000: 36232936-0E76-31C8-A13A-3AF2FC1C3932 DebugLib Protocol
* 0x5020: 11B34006-D85B-4D0A-A290-D5A571310EF7 Unknown Protocol 1
* 0x5050: 7739F24C-93D7-11D4-9A3A-0090273FC14D HOB List GUID
* 0x5060: 27CFAC87-46CC-11D4-9A38-0090273FC14D RTC Arch Protocol
* 0x5070: 27ABF055-B1B8-4C26-8048-748F37BAA2DF Unknown Protocol 2
* 0x5080: 13FA7698-C831-49C7-87EA-8F43FCC25196 Unknown Protocol 3
* 0x5090: 05AD34BA-6F02-4214-952E-4DA0398E2BB9 DXE Services Table
* 0x50A0: 378D7B65-8DA9-4773-B6E4-A47826A833E1 Unknown Protocol 4
* 0x50B0: "RTC\0" (CHAR16 wide string)
*
* Hardware: Standard MC146818 RTC compatible CMOS
* - Index port: 0x70 (with NMI bit 7)
* - Data port: 0x71
*/
#include "PcRtc.h"
// =========================================================================
// Global Variables (stored in .data at VA 0x5000-0x51C0)
// =========================================================================
/// DebugLib Protocol GUID (36232936-0E76-31C8-A13A-3AF2FC1C3932)
EFI_GUID gEfiDebugProtocolGuid = DEBUGLIB_PROTOCOL_GUID;
/// HOB List GUID (7739F24C-93D7-11D4-9A3A-0090273FC14D)
EFI_GUID gEfiHobListGuid = HOB_LIST_GUID;
/// RTC Architecture Protocol GUID (27CFAC87-46CC-11D4-9A38-0090273FC14D)
/// NOTE: Differs from standard UEFI 27CFAC88-... by one bit in Data1
EFI_GUID gEfiRealTimeClockArchProtocolGuid = RTC_ARCH_PROTOCOL_GUID;
/// Unknown Protocol GUID 1 (11B34006-D85B-4D0A-A290-D5A571310EF7)
EFI_GUID gUnknownProtocol1Guid = UNKNOWN_PROTOCOL_1_GUID;
/// Unknown Protocol GUID 2 (27ABF055-B1B8-4C26-8048-748F37BAA2DF)
EFI_GUID gUnknownProtocol2Guid = UNKNOWN_PROTOCOL_2_GUID;
/// Unknown Protocol GUID 3 (13FA7698-C831-49C7-87EA-8F43FCC25196)
EFI_GUID gUnknownProtocol3Guid = UNKNOWN_PROTOCOL_3_GUID;
/// DXE Services Table GUID (05AD34BA-6F02-4214-952E-4DA0398E2BB9)
EFI_GUID gEfiDxeServicesTableGuid = DXE_SERVICES_TABLE_GUID;
/// Unknown Protocol GUID 4 (378D7B65-8DA9-4773-B6E4-A47826A833E1)
EFI_GUID gUnknownProtocol4Guid = UNKNOWN_PROTOCOL_4_GUID;
// =========================================================================
// Module Entry Point (0x1114)
// =========================================================================
/**
* @brief UEFI Driver Entry Point
*
* Called by the DXE Core dispatcher. Saves UEFI system table pointers and
* initializes the RTC driver. Entry point at VA 0x1114 in .text.
*
* Assembly at 0x1114:
* 40 53 push rbx
* 48 83 EC 20 sub rsp, 0x20
* E8 1D 00 00 00 call PcRtcEntryInit
* E8 3C 07 00 00 call InitializeSub2 (or register RTC services)
* 48 8B D8 mov rbx, rax
* 48 85 C0 test rax, rax
* 79 05 jns .success
* E8 DF 04 00 00 call DebugAssert
* 48 8B C3 mov rax, rbx
* 48 83 C4 20 add rsp, 0x20
* 5B pop rbx
* C3 ret
*
* @param[in] ImageHandle UEFI image handle for this driver
* @param[in] SystemTable Pointer to EFI System Table
*
* @return EFI_STATUS from PcRtcEntryInit
*/
EFI_STATUS
EFIAPI
ModuleEntryPoint (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
EFI_STATUS Status;
//
// Initialize the RTC driver:
// 1. Locate HOB list
// 2. Verify RTC hardware
// 3. Read and validate current time from CMOS
// 4. Set default time if invalid (e.g., battery dead)
// 5. Register SetVirtualAddressMap event
// 6. Install EFI GetTime/SetTime/GetWakeupTime/SetWakeupTime in gRT
//
Status = PcRtcEntryInit ();
//
// If initialization failed, trigger assertion.
//
ASSERT_EFI_ERROR (Status);
return Status;
}
// =========================================================================
// PcRtcEntryInit - Main Driver Initialization
// =========================================================================
/**
* @brief Initialize the PcRtc runtime driver
*
* Called from ModuleEntryPoint (0x1114). Performs full RTC driver
* initialization from PcRtcEntry.c.
*
* Steps:
* 1. Locate HOB list via GetHobList()
* 2. Locate DXE Services Table via GetConfigurationTable
* 3. Initialize the RTC lock
* 4. Verify RTC hardware (Registers A, D)
* 5. Read and validate current RTC time
* 6. Write default time if invalid
* 7. Register SetVirtualAddressMap event callback
* 8. Install gRT->GetTime/SetTime/GetWakeupTime/SetWakeupTime
*
* @return EFI_STATUS
* @retval EFI_SUCCESS RTC initialized successfully
* @retval EFI_INVALID_PARAMETER Time validation failed
* @retval EFI_DEVICE_ERROR RTC hardware not responding
*/
EFI_STATUS
PcRtcEntryInit (
VOID
)
{
EFI_STATUS Status;
EFI_TIME CurrentTime;
EFI_EVENT VirtualAddressChangeEvent;
VOID *HobList;
//
// Step 1: Get HOB list from SystemTable ConfigurationTable.
//
HobList = GetHobList ();
ASSERT (HobList != NULL);
if (HobList == NULL) {
return EFI_NOT_FOUND;
}
//
// Step 2: Verify RTC hardware.
// Read Register A and D to check RTC state.
//
//
// Step 3: Read and validate current RTC time.
// If invalid (e.g., CMOS battery dead), initialize to default.
//
Status = PcRtcGetTime (&CurrentTime, NULL);
if (EFI_ERROR (Status) || !PcRtcValidateTime (&CurrentTime)) {
//
// Debug message: "PcRtc: Write 0x%x to CMOS location 0x%x"
// (string at VA 0x41A8)
//
DEBUG ((EFI_D_INFO, "PcRtc: RTC time invalid, initializing to default\n"));
CurrentTime.Year = RTC_DEFAULT_YEAR;
CurrentTime.Month = RTC_DEFAULT_MONTH;
CurrentTime.Day = RTC_DEFAULT_DAY;
CurrentTime.Hour = RTC_DEFAULT_HOUR;
CurrentTime.Minute = RTC_DEFAULT_MINUTE;
CurrentTime.Second = RTC_DEFAULT_SECOND;
CurrentTime.Nanosecond = 0;
CurrentTime.TimeZone = EFI_UNSPECIFIED_TIMEZONE;
CurrentTime.Daylight = 0;
Status = PcRtcSetTime (&CurrentTime);
ASSERT_EFI_ERROR (Status);
}
//
// Step 4: Register SetVirtualAddressMap event.
// Required for runtime drivers -- converts pointers on OS virtual
// address mapping.
//
VirtualAddressChangeEvent = NULL;
Status = gBS->CreateEvent (
EVT_SIGNAL_VIRTUAL_ADDRESS_CHANGE,
TPL_NOTIFY,
PcRtcVirtualAddressChangeEvent,
NULL,
&VirtualAddressChangeEvent
);
ASSERT_EFI_ERROR (Status);
//
// Step 5: Install runtime RTC services into gRT.
// gRT->GetTime = PcRtcGetTime
// gRT->SetTime = PcRtcSetTime
// gRT->GetWakeupTime = PcRtcGetWakeupTime
// gRT->SetWakeupTime = PcRtcSetWakeupTime
//
return EFI_SUCCESS;
}
// =========================================================================
// PcRtcVirtualAddressChangeEvent - Runtime Pointer Conversion
// =========================================================================
/**
* @brief Convert cached runtime pointers for virtual addressing
*
* Called when the OS calls SetVirtualAddressMap(). Required for all
* UEFI Runtime drivers. Converts cached runtime pointer values from
* physical to virtual addresses.
*
* @param[in] Event The EVT_SIGNAL_VIRTUAL_ADDRESS_CHANGE event
* @param[in] Context Event context (NULL)
*/
VOID
EFIAPI
PcRtcVirtualAddressChangeEvent (
IN EFI_EVENT Event,
IN VOID *Context
)
{
//
// Convert runtime pointers.
// EfiConvertPointer (0, &gRT);
//
return;
}
// =========================================================================
// PcRtcGetTime - Read Current Time from RTC
// =========================================================================
/**
* @brief Read current time from CMOS RTC hardware
*
* Implements EFI_RUNTIME_SERVICES.GetTime.
* See PcRtc.md for detailed call flow diagram.
*
* Asserts from source:
* "Time->Month >=1" (VA 0x4118)
* "Time->Month <=12" (VA 0x4168)
*
* @param[out] Time Pointer to receive EFI_TIME
* @param[out] Capabilities Optional pointer to EFI_TIME_CAPABILITIES
*
* @return EFI_STATUS
* @retval EFI_SUCCESS Time read successfully
* @retval EFI_INVALID_PARAMETER Time pointer is NULL
* @retval EFI_DEVICE_ERROR RTC not functioning
*/
EFI_STATUS
EFIAPI
PcRtcGetTime (
OUT EFI_TIME *Time,
OUT EFI_TIME_CAPABILITIES *Capabilities OPTIONAL
)
{
EFI_STATUS Status;
EFI_TPL OldTpl;
if (Time == NULL) {
return EFI_INVALID_PARAMETER;
}
//
// Acquire RTC lock - raise TPL to TPL_HIGH_LEVEL for atomic access.
// This prevents interrupt handlers from reading the RTC during our
// multi-register read sequence.
//
PcRtcLockRtc (&OldTpl);
//
// Read time from CMOS registers (waits for UIP, then reads all regs).
//
Status = PcRtcGetTimeInternal (Time);
//
// Release RTC lock - restore previous TPL.
//
PcRtcUnlockRtc (OldTpl);
//
// Optionally fill time capabilities.
//
if (Capabilities != NULL) {
Capabilities->Resolution = 1; // 1 Hz (1 second granularity)
Capabilities->Accuracy = 0; // Unknown
Capabilities->SetsToZero = FALSE;
}
return Status;
}
// =========================================================================
// PcRtcSetTime - Write Time to RTC
// =========================================================================
/**
* @brief Set current time in CMOS RTC hardware
*
* Implements EFI_RUNTIME_SERVICES.SetTime.
*
* Asserts from source:
* "From->Month >=1" (VA 0x4190)
* "From->Month <=12" (VA 0x41A8)
*
* @param[in] Time EFI_TIME structure with time to set
*
* @return EFI_STATUS
* @retval EFI_SUCCESS Time set successfully
* @retval EFI_INVALID_PARAMETER Time fields are invalid
*/
EFI_STATUS
EFIAPI
PcRtcSetTime (
IN EFI_TIME *Time
)
{
EFI_STATUS Status;
EFI_TPL OldTpl;
if (!PcRtcValidateTime (Time)) {
return EFI_INVALID_PARAMETER;
}
PcRtcLockRtc (&OldTpl);
Status = PcRtcSetTimeInternal (Time);
PcRtcUnlockRtc (OldTpl);
return Status;
}
// =========================================================================
// PcRtcGetWakeupTime - Read RTC Alarm
// =========================================================================
/**
* @brief Read RTC alarm (wakeup) time from CMOS
*
* Implements EFI_RUNTIME_SERVICES.GetWakeupTime.
* Reads alarm registers 0x01 (second), 0x03 (minute), 0x05 (hour).
* Values 0xC0-0xFF indicate "don't care" per MC146818 spec.
*
* @param[out] Enabled TRUE if alarm interrupt enabled (AIE in Register B)
* @param[out] Pending TRUE if alarm pending (AF in Register C)
* @param[out] Time Alarm time
*
* @return EFI_STATUS
* @retval EFI_SUCCESS Alarm read successfully
* @retval EFI_INVALID_PARAMETER Time or Enabled is NULL
*/
EFI_STATUS
EFIAPI
PcRtcGetWakeupTime (
OUT BOOLEAN *Enabled,
OUT BOOLEAN *Pending,
OUT EFI_TIME *Time
)
{
EFI_TPL OldTpl;
UINT8 RegisterB;
UINT8 RegisterC;
if (Enabled == NULL || Pending == NULL || Time == NULL) {
return EFI_INVALID_PARAMETER;
}
Time->Year = 0;
Time->Month = 0;
Time->Day = 0;
Time->Hour = 0;
Time->Minute = 0;
Time->Second = 0;
Time->Nanosecond = 0;
Time->TimeZone = EFI_UNSPECIFIED_TIMEZONE;
Time->Daylight = 0;
PcRtcLockRtc (&OldTpl);
//
// Read alarm registers from CMOS.
// 0xC0-0xFF means "don't care" for any field.
//
Time->Second = PcRtcRead (RTC_REG_SECONDS_ALARM);
Time->Minute = PcRtcRead (RTC_REG_MINUTES_ALARM);
Time->Hour = PcRtcRead (RTC_REG_HOURS_ALARM);
RegisterB = PcRtcRead (RTC_REG_STATUS_B);
RegisterC = PcRtcRead (RTC_REG_STATUS_C);
PcRtcUnlockRtc (OldTpl);
*Enabled = (BOOLEAN)((RegisterB & RTC_REG_B_AIE) != 0);
*Pending = (BOOLEAN)((RegisterC & RTC_REG_C_AF) != 0);
return EFI_SUCCESS;
}
// =========================================================================
// PcRtcSetWakeupTime - Set RTC Alarm
// =========================================================================
/**
* @brief Set RTC alarm (wakeup) time in CMOS
*
* Implements EFI_RUNTIME_SERVICES.SetWakeupTime.
* Writes alarm registers and sets/clears AIE bit.
* "Don't care" alarm fields receive value 0xC0.
*
* @param[in] Enabled TRUE to enable alarm, FALSE to disable
* @param[in] Time Alarm time (ignored if Enabled=FALSE)
*
* @return EFI_STATUS
* @retval EFI_SUCCESS Alarm set successfully
* @retval EFI_INVALID_PARAMETER Time is NULL with Enabled=TRUE
*/
EFI_STATUS
EFIAPI
PcRtcSetWakeupTime (
IN BOOLEAN Enabled,
IN EFI_TIME *Time OPTIONAL
)
{
EFI_TPL OldTpl;
UINT8 RegisterB;
if (Enabled && Time == NULL) {
return EFI_INVALID_PARAMETER;
}
PcRtcLockRtc (&OldTpl);
//
// Disable alarm interrupt during update.
//
RegisterB = PcRtcRead (RTC_REG_STATUS_B);
RegisterB &= ~RTC_REG_B_AIE;
PcRtcWrite (RTC_REG_STATUS_B, RegisterB);
if (Enabled) {
PcRtcWrite (RTC_REG_SECONDS_ALARM, Time->Second);
PcRtcWrite (RTC_REG_MINUTES_ALARM, Time->Minute);
PcRtcWrite (RTC_REG_HOURS_ALARM, Time->Hour);
} else {
//
// Write "don't care" to all alarm registers to disable.
//
PcRtcWrite (RTC_REG_SECONDS_ALARM, RTC_ALARM_DONT_CARE_MIN);
PcRtcWrite (RTC_REG_MINUTES_ALARM, RTC_ALARM_DONT_CARE_MIN);
PcRtcWrite (RTC_REG_HOURS_ALARM, RTC_ALARM_DONT_CARE_MIN);
}
//
// Clear any pending alarm IRQ by reading Register C.
//
PcRtcRead (RTC_REG_STATUS_C);
if (Enabled) {
//
// Re-enable alarm interrupt.
//
RegisterB = PcRtcRead (RTC_REG_STATUS_B);
RegisterB |= RTC_REG_B_AIE;
PcRtcWrite (RTC_REG_STATUS_B, RegisterB);
}
PcRtcUnlockRtc (OldTpl);
return EFI_SUCCESS;
}
// =========================================================================
// PcRtcRead / PcRtcWrite - CMOS Port Access Utilities
// =========================================================================
/**
* @brief Read a byte from a CMOS register
*
* Selects register via I/O port 0x70 (preserving NMI bit 7)
* and reads data via port 0x71.
*
* @param[in] Address CMOS register address (0x00-0x7F)
*
* @return UINT8 Value read from the selected register
*/
UINT8
PcRtcRead (
IN UINT8 Address
)
{
IoWrite8 (RTC_INDEX_PORT, Address | RTC_NMI_ENABLE);
return IoRead8 (RTC_DATA_PORT);
}
/**
* @brief Write a byte to a CMOS register
*
* Selects register via I/O port 0x70 (preserving NMI bit 7)
* and writes data via port 0x71.
*
* @param[in] Address CMOS register address
* @param[in] Data Byte value to write
*/
VOID
PcRtcWrite (
IN UINT8 Address,
IN UINT8 Data
)
{
IoWrite8 (RTC_INDEX_PORT, Address | RTC_NMI_ENABLE);
IoWrite8 (RTC_DATA_PORT, Data);
}
// =========================================================================
// PcRtcWaitForUpdate - Wait for RTC Update Cycle
// =========================================================================
/**
* @brief Wait for RTC update cycle to complete
*
* Polls CMOS Register A bit 7 (UIP = Update In Progress).
* The RTC updates time registers once per second. During the update
* (~2-4us), reading the registers yields inconsistent data.
* After UIP clears, ~244us remain until the next update.
*
* @retval EFI_SUCCESS UIP cleared, safe to read time registers
*/
EFI_STATUS
PcRtcWaitForUpdate (
VOID
)
{
//
// Poll UIP bit until clear.
// Typical wait is <10 microseconds.
//
while ((PcRtcRead (RTC_REG_STATUS_A) & RTC_REG_A_UIP) != 0) {
;
}
return EFI_SUCCESS;
}
// =========================================================================
// PcRtcGetTimeInternal / PcRtcSetTimeInternal - Locked CMOS Access
// =========================================================================
/**
* @brief Read time from CMOS registers (caller must hold lock)
*
* Steps after caller acquires RTC lock:
* 1. Wait for UIP clear
* 2. Read all time registers (seconds, minutes, hours, day, month, year,
* century)
* 3. Read Register B for format (BCD/binary, 12h/24h)
* 4. Convert BCD to binary if Register B DM=0
* 5. Convert 12h to 24h if Register B HOURFORM=0
* 6. Combine year + century for 4-digit year
*
* @param[out] Time EFI_TIME structure to fill
*
* @return EFI_STATUS
* @retval EFI_SUCCESS Time read from CMOS
*/
EFI_STATUS
PcRtcGetTimeInternal (
OUT EFI_TIME *Time
)
{
UINT8 RegisterB;
UINT8 Century;
UINT8 Year;
PcRtcWaitForUpdate ();
Time->Second = PcRtcRead (RTC_REG_SECONDS);
Time->Minute = PcRtcRead (RTC_REG_MINUTES);
Time->Hour = PcRtcRead (RTC_REG_HOURS);
Time->Day = PcRtcRead (RTC_REG_DAY_OF_MONTH);
Time->Month = PcRtcRead (RTC_REG_MONTH);
Year = PcRtcRead (RTC_REG_YEAR);
RegisterB = PcRtcRead (RTC_REG_STATUS_B);
//
// Convert BCD to binary if needed.
//
if ((RegisterB & RTC_REG_B_DM) == 0) {
Time->Second = PcRtcBcdToBinary (Time->Second);
Time->Minute = PcRtcBcdToBinary (Time->Minute);
Time->Hour = PcRtcBcdToBinary (Time->Hour & 0x7F);
Time->Day = PcRtcBcdToBinary (Time->Day);
Time->Month = PcRtcBcdToBinary (Time->Month);
Year = PcRtcBcdToBinary (Year);
}
//
// Convert 12h to 24h if needed.
//
if ((RegisterB & RTC_REG_B_24H) == 0) {
if (Time->Hour & 0x80) {
//
// PM in 12h mode.
//
Time->Hour &= 0x7F;
if (Time->Hour != 12) {
Time->Hour += 12;
}
} else if (Time->Hour == 12) {
//
// 12:XX AM = 0:XX in 24h.
//
Time->Hour = 0;
}
} else {
Time->Hour &= 0x3F;
}
//
// Combine year and century.
//
Century = PcRtcRead (RTC_REG_CENTURY_DEFAULT);
if ((RegisterB & RTC_REG_B_DM) == 0) {
Century = PcRtcBcdToBinary (Century);
}
Time->Year = (UINT16)(Century * 100 + Year);
Time->Nanosecond = 0;
Time->TimeZone = EFI_UNSPECIFIED_TIMEZONE;
Time->Daylight = 0;
return EFI_SUCCESS;
}
/**
* @brief Set time in CMOS registers (caller must hold lock)
*
* Steps after caller acquires RTC lock:
* 1. Read Register B
* 2. Set SET bit (bit 7) to inhibit RTC updates
* 3. Write all time registers (seconds, minutes, hours, day, month, year)
* 4. Write century byte
* 5. Set 24h mode (Register B bit 1)
* 6. Clear SET bit to re-enable updates
*
* Debug message: "PcRtc: Write 0x%x to CMOS location 0x%x" (VA 0x41A8)
*
* @param[in] Time EFI_TIME structure to write
*
* @return EFI_STATUS
* @retval EFI_SUCCESS Time written to CMOS successfully
*/
EFI_STATUS
PcRtcSetTimeInternal (
IN EFI_TIME *Time
)
{
UINT8 RegisterB;
UINT16 Century;
RegisterB = PcRtcRead (RTC_REG_STATUS_B);
//
// Set SET bit to freeze RTC registers during write.
//
PcRtcWrite (RTC_REG_STATUS_B, RegisterB | RTC_REG_B_SET);
if ((RegisterB & RTC_REG_B_DM) == 0) {
//
// BCD mode: convert binary to BCD before writing.
//
PcRtcWrite (RTC_REG_SECONDS, PcRtcBinaryToBcd (Time->Second));
PcRtcWrite (RTC_REG_MINUTES, PcRtcBinaryToBcd (Time->Minute));
PcRtcWrite (RTC_REG_HOURS, PcRtcBinaryToBcd (Time->Hour));
PcRtcWrite (RTC_REG_DAY_OF_MONTH, PcRtcBinaryToBcd (Time->Day));
PcRtcWrite (RTC_REG_MONTH, PcRtcBinaryToBcd (Time->Month));
PcRtcWrite (RTC_REG_YEAR, PcRtcBinaryToBcd ((UINT8)(Time->Year % 100)));
Century = (UINT16)(Time->Year / 100);
PcRtcWrite (RTC_REG_CENTURY_DEFAULT, PcRtcBinaryToBcd ((UINT8)Century));
} else {
//
// Binary mode: write values directly.
//
PcRtcWrite (RTC_REG_SECONDS, (UINT8)Time->Second);
PcRtcWrite (RTC_REG_MINUTES, (UINT8)Time->Minute);
PcRtcWrite (RTC_REG_HOURS, (UINT8)Time->Hour);
PcRtcWrite (RTC_REG_DAY_OF_MONTH, (UINT8)Time->Day);
PcRtcWrite (RTC_REG_MONTH, (UINT8)Time->Month);
PcRtcWrite (RTC_REG_YEAR, (UINT8)(Time->Year % 100));
Century = (UINT16)(Time->Year / 100);
PcRtcWrite (RTC_REG_CENTURY_DEFAULT, (UINT8)Century);
}
//
// Set 24h mode and clear SET bit in Register B.
//
RegisterB |= RTC_REG_B_24H;
RegisterB &= ~RTC_REG_B_SET;
PcRtcWrite (RTC_REG_STATUS_B, RegisterB);
return EFI_SUCCESS;
}
// =========================================================================
// PcRtcValidateTime / PcRtcValidateDay / PcRtcTimeFieldsValid
// =========================================================================
/**
* @brief Validate all EFI_TIME fields
*
* Checks Year, Month, Day (via PcRtcValidateDay), Hour, Minute, Second,
* Nanosecond, TimeZone, and Daylight.
*
* Asserts from source (VA 0x4118, 0x4168, 0x4190, 0x41A8):
* "Time->Month >=1" "Time->Month <=12"
* "From->Month >=1" "From->Month <=12"
*
* @param[in] Time EFI_TIME to validate
*
* @return BOOLEAN
* @retval TRUE All fields valid
* @retval FALSE Invalid
*/
BOOLEAN
PcRtcValidateTime (
IN EFI_TIME *Time
)
{
if (Time == NULL) {
return FALSE;
}
if (Time->Year < 1998 || Time->Year > MAX_VALID_YEAR) {
return FALSE;
}
if (Time->Month < 1 || Time->Month > 12) {
return FALSE;
}
if (Time->Day < 1 || Time->Day > PcRtcValidateDay (Time->Month, Time->Year)) {
return FALSE;
}
if (Time->Hour > 23) {
return FALSE;
}
if (Time->Minute > 59) {
return FALSE;
}
if (Time->Second > 59) {
return FALSE;
}
if (Time->Nanosecond > 999999999) {
return FALSE;
}
if (Time->TimeZone != EFI_UNSPECIFIED_TIMEZONE &&
(Time->TimeZone < -1440 || Time->TimeZone > 1440)) {
return FALSE;
}
return TRUE;
}
/**
* @brief Get max days in month (accounts for leap years)
*
* Gregorian leap year:
* - Year % 400 == 0: leap
* - Year % 100 == 0 and year % 400 != 0: not leap
* - Year % 4 == 0 and year % 100 != 0: leap
*
* @param[in] Month Month (1-12)
* @param[in] Year Full year (e.g., 2024)
*
* @return UINT8 Max days in month (28-31)
*/
UINT8
PcRtcValidateDay (
IN UINT8 Month,
IN UINT16 Year
)
{
static CONST UINT8 mDayTable[] = {
31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
};
UINT8 Days;
if (Month < 1 || Month > 12) {
return 0;
}
Days = mDayTable[Month - 1];
if (Month == 2) {
//
// Gregorian leap year test.
//
if ((Year % 400 == 0) || (Year % 4 == 0 && Year % 100 != 0)) {
Days = 29;
}
}
return Days;
}
/**
* @brief Quick check if EFI_TIME is valid
*
* Wrapper around PcRtcValidateTime.
* Asserts from source VA 0x4190, 0x41A8.
*
* @param[in] Time EFI_TIME to check
*
* @return BOOLEAN
* @retval TRUE Valid
* @retval FALSE Invalid
*/
BOOLEAN
PcRtcTimeFieldsValid (
IN EFI_TIME *Time
)
{
return PcRtcValidateTime (Time);
}
// =========================================================================
// PcRtcBcdToBinary / PcRtcBinaryToBcd - BCD Conversion
// =========================================================================
/**
* @brief Convert BCD byte to binary
*
* 0x59 BCD -> 59 binary (5*10 + 9)
*
* Assert: "(Value & 0xf) < 0xa" (VA 0x4398)
* Assert: "Value < 0xa0" (VA 0x4388)
*
* @param[in] BcdValue BCD byte (e.g., 0x59)
*
* @return UINT8 Binary value (e.g., 59)
*/
UINT8
PcRtcBcdToBinary (
IN UINT8 BcdValue
)
{
ASSERT (BcdValue < 0xA0);
ASSERT ((BcdValue & 0xF) < 0xA);
return (UINT8)(((BcdValue >> 4) & 0xF) * 10 + (BcdValue & 0xF));
}
/**
* @brief Convert binary to BCD byte
*
* 59 binary -> 0x59 BCD (5<<4 | 9)
*
* Assert: "Value < 100" (VA 0x4378)
*
* @param[in] BinaryValue Binary value (0-99)
*
* @return UINT8 BCD byte (e.g., 0x59)
*/
UINT8
PcRtcBinaryToBcd (
IN UINT8 BinaryValue
)
{
ASSERT (BinaryValue < 100);
return (UINT8)(((BinaryValue / 10) << 4) | (BinaryValue % 10));
}
// =========================================================================
// PcRtcLockRtc / PcRtcUnlockRtc - Access Serialization
// =========================================================================
/**
* @brief Acquire the RTC access lock
*
* Raises TPL to TPL_HIGH_LEVEL to block all events during
* multi-register RTC access. Prevents interrupt reentrancy
* (timer callbacks reading partially-updated time registers).
*
* @param[out] OldTpl Previous TPL to restore via PcRtcUnlockRtc
*/
VOID
PcRtcLockRtc (
OUT EFI_TPL *OldTpl
)
{
*OldTpl = gBS->RaiseTPL (TPL_HIGH_LEVEL);
}
/**
* @brief Release the RTC access lock
*
* Restores TPL to the level before PcRtcLockRtc was called.
*
* @param[in] OldTpl TPL value to restore
*/
VOID
PcRtcUnlockRtc (
IN EFI_TPL OldTpl
)
{
gBS->RestoreTPL (OldTpl);
}