Newer
Older
AMI-Aptio-BIOS-Reversed / PcAtChipsetPkg / PcatRealTimeClockRuntimeDxe / PcRtc / PcRtc.c
@Ajax Dong Ajax Dong 2 days ago 24 KB Full restructure
/**
 * @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);
}