/** @file
WatchdogTimer.c -- Implementation of the Watchdog Timer UEFI Driver
This module implements the EFI_WATCHDOG_TIMER_ARCH_PROTOCOL for the
HR650X platform. The watchdog timer is a hardware feature that resets
the system if firmware or software becomes unresponsive within a
configurable time period.
The driver performs the following:
- Installs the EFI_WATCHDOG_TIMER_ARCH_PROTOCOL with RegisterHandler,
SetTimerPeriod, and GetTimerPeriod services.
- On timer expiry, calls the registered notification handler, then
resets the system via gRT->ResetSystem().
- Coordinates with the SMM watchdog handler via the SMM Communication
Protocol to synchronize watchdog state between DXE and SMM phases.
Source: HR650X BIOS WatchdogTimer.efi (Index 0083)
Module: MdeModulePkg/Universal/WatchdogTimerDxe
Copyright (C) 2025 Your Company. All rights reserved.
**/
#include "WatchdogTimer.h"
//
// ============================================================================
// Protocol GUIDs
// ============================================================================
//
///
/// EFI_WATCHDOG_TIMER_ARCH_PROTOCOL_GUID
/// {665E3FF5-46CC-11D4-9A38-0090273FC14D}
///
EFI_GUID gEfiWatchdogTimerArchProtocolGuid =
{ 0x665E3FF5, 0x46CC, 0x11D4,
{ 0x9A, 0x38, 0x00, 0x90, 0x27, 0x3F, 0xC1, 0x4D } };
///
/// gEfiHobListGuid
/// {7739F24C-93D7-11D4-9A3A-0090273FC14D}
///
EFI_GUID gEfiHobListGuid =
{ 0x7739F24C, 0x93D7, 0x11D4,
{ 0x9A, 0x3A, 0x00, 0x90, 0x27, 0x3F, 0xC1, 0x4D } };
///
/// gEfiSmmCommunicationProtocolGuid
/// {D2B2B828-0826-48A7-B3DF-983C006024F0}
///
EFI_GUID gEfiSmmCommunicationProtocolGuid =
{ 0xD2B2B828, 0x0826, 0x48A7,
{ 0xB3, 0xDF, 0x98, 0x3C, 0x00, 0x60, 0x24, 0xF0 } };
///
/// gEfiGenericMemTestPatternGuid (HOB GUID for debug output device)
/// {36232936-0E76-31C8-A13A-3AF2FC1C3932}
///
EFI_GUID gEfiGenericMemTestPatternGuid =
{ 0x36232936, 0x0E76, 0x31C8,
{ 0xA1, 0x3A, 0x3A, 0xF2, 0xFC, 0x1C, 0x39, 0x32 } };
//
// ============================================================================
// Global Variables
// ============================================================================
//
///
/// The EFI_WATCHDOG_TIMER_ARCH_PROTOCOL instance installed on the handle.
/// Contains function pointers for RegisterHandler and SetTimerPeriod.
/// The third field (GetTimerPeriod) is overlapped with mWatchdogTimerPeriod.
///
EFI_WATCHDOG_TIMER_ARCH_PROTOCOL mWatchdogTimerProtocol = {
WatchdogTimerRegisterHandler,
WatchdogTimerSetTimerPeriod
// GetTimerPeriod is implicitly at &mWatchdogTimerPeriod
};
///
/// Stores the current watchdog timer period in 100ns units.
/// Initialized to WATCHDOG_TIMER_DEFAULT_PERIOD (1500 = 150us).
/// This also aliases with the third entry of the protocol structure,
/// allowing GetTimerPeriod to return the period value directly.
///
UINT64 mWatchdogTimerPeriod = WATCHDOG_TIMER_DEFAULT_PERIOD;
///
/// Pointer to the registered watchdog notification function, or NULL.
///
VOID *mWatchdogNotifyFunction = NULL;
///
/// Flag indicating whether a watchdog notification handler is registered.
///
VOID *mWatchdogNotifyRegistered = NULL;
///
/// Cached pointer to the HOB list, obtained from the system configuration table.
///
VOID *mHobList = NULL;
///
/// Cached pointer to the SMM Communication Protocol interface.
///
VOID *mSmmCommunicationProtocol = NULL;
///
/// NMI flag byte. Accessed via CMOS I/O ports 0x70/0x71.
/// Bit layout: bit 7 of CMOS index 0x4B stores an NMI status value.
///
UINT8 mNmiFlag = 0;
//
// ============================================================================
// Function Implementations
// ============================================================================
//
/**
Entry point for the Watchdog Timer DXE driver.
This function initializes the driver globals (ImageHandle, SystemTable,
BootServices, RuntimeServices), retrieves the HOB list, and installs
the EFI_WATCHDOG_TIMER_ARCH_PROTOCOL onto the image handle.
If the protocol is already installed, an ASSERT is generated.
Memory for the driver's protocol instance is allocated and installed
via InstallMultipleProtocolInterfaces().
@param[in] ImageHandle The firmware allocated handle for the EFI image.
@param[in] SystemTable A pointer to the EFI System Table.
@return EFI_SUCCESS The watchdog timer protocol was installed.
@return EFI_ALREADY_STARTED The protocol is already installed in the database.
@return others Error from AllocatePool or InstallMultipleProtocolInterfaces.
**/
EFI_STATUS
EFIAPI
WatchdogTimerDriverEntryPoint (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
EFI_STATUS Status;
EFI_WATCHDOG_TIMER_ARCH_PROTOCOL *Protocol;
//
// Initialize UEFI global variables (gImageHandle, gST, gBS, gRT)
//
gImageHandle = ImageHandle;
if (gImageHandle == NULL) {
DebugAssert (
__FILE__,
__LINE__,
"gImageHandle != ((void *) 0)"
);
}
gST = SystemTable;
if (gST == NULL) {
DebugAssert (
__FILE__,
__LINE__,
"gST != ((void *) 0)"
);
}
gBS = SystemTable->BootServices;
if (gBS == NULL) {
DebugAssert (
__FILE__,
__LINE__,
"gBS != ((void *) 0)"
);
}
gRT = SystemTable->RuntimeServices;
if (gRT == NULL) {
DebugAssert (
__FILE__,
__LINE__,
"gRT != ((void *) 0)"
);
}
//
// Initialize the HOB list
//
GetHobList ();
//
// Install the EFI_WATCHDOG_TIMER_ARCH_PROTOCOL
//
Status = gBS->LocateProtocol (
&gEfiWatchdogTimerArchProtocolGuid,
NULL,
(VOID **)&Protocol
);
if (!EFI_ERROR (Status)) {
//
// Protocol already installed -- this is unexpected
//
DebugAssert (
__FILE__,
__LINE__,
"&gEfiWatchdogTimerArchProtocolGuid already installed in database"
);
return Status;
}
//
// Allocate pool for the protocol interface structure
// EfiBootServicesData type, 16 bytes (2 function pointers)
//
Status = gBS->AllocatePool (
EfiBootServicesData,
sizeof (EFI_WATCHDOG_TIMER_ARCH_PROTOCOL),
(VOID **)&Protocol
);
if (EFI_ERROR (Status)) {
DebugAssertPrint (
0x80000000,
"\nASSERT_EFI_ERROR (Status = %r)\n",
Status
);
DebugAssert (
__FILE__,
__LINE__,
"!EFI_ERROR (Status)"
);
return Status;
}
//
// Initialize the protocol structure
//
Protocol->RegisterHandler = WatchdogTimerRegisterHandler;
Protocol->SetTimerPeriod = WatchdogTimerSetTimerPeriod;
//
// Note: GetTimerPeriod is implicitly available as mWatchdogTimerPeriod
// is aliased at the same offset as the third protocol function pointer.
//
//
// Install the protocol on the image handle
//
Status = gBS->InstallMultipleProtocolInterfaces (
&ImageHandle,
&gEfiWatchdogTimerArchProtocolGuid,
Protocol,
NULL
);
if (EFI_ERROR (Status)) {
DebugAssertPrint (
0x80000000,
"\nASSERT_EFI_ERROR (Status = %r)\n",
Status
);
DebugAssert (
__FILE__,
__LINE__,
"!EFI_ERROR (Status)"
);
return Status;
}
return EFI_SUCCESS;
}
/**
Notification handler called when the watchdog timer fires.
This handler is invoked by the timer hardware when the watchdog
period expires. It performs the following actions:
1. Calls the SMM communication handler to synchronize state.
2. Invokes any registered notification callback.
3. Logs the system reset event via debug output.
4. Resets the system via gRT->ResetSystem() with
EfiResetCold and the watchdog reset reason.
@param[in] TimePeriod The current timer period in 100ns units.
@param[in] WatchdogCode The watchdog code data.
@param[in] DataSize Size of the watchdog data buffer.
@param[in] WatchdogData Pointer to the watchdog data buffer.
**/
VOID
EFIAPI
WatchdogTimerNotifyHandler (
IN UINTN TimePeriod,
IN UINTN WatchdogCode,
IN UINTN DataSize,
IN VOID *WatchdogData
)
{
//
// Send SMM communication to update SMM watchdog handler state
//
SmmCommunicationSend ();
//
// Invoke registered notify function if present
//
if (mWatchdogNotifyRegistered != NULL) {
((EFI_WATCHDOG_TIMER_NOTIFY)mWatchdogNotifyRegistered) (
TimePeriod,
WatchdogCode,
DataSize,
WatchdogData
);
}
//
// Log the reset event
//
DebugAssertPrint (
0x80000000,
"Watchdog Timer reseting system\n"
);
//
// Reset the system
//
gRT->ResetSystem (
EfiResetCold,
EFI_HARDWARE_ERROR,
0,
NULL
);
}
/**
Registers or unregisters a notification handler for the watchdog timer.
If NotifyFunction is non-NULL and no handler is currently registered,
the function is registered. If NotifyFunction is NULL and a handler
is registered, it is unregistered. Attempting to register a handler
when one already exists returns EFI_ALREADY_STARTED.
@param[in] This Pointer to the EFI_WATCHDOG_TIMER_ARCH_PROTOCOL instance.
@param[in] NotifyFunction Pointer to the notification function to register,
or NULL to unregister the current handler.
@return EFI_SUCCESS The handler was registered or unregistered.
@return EFI_ALREADY_STARTED A handler is already registered and NotifyFunction is non-NULL.
@return EFI_INVALID_PARAMETER NotifyFunction is NULL but no handler is registered.
**/
EFI_STATUS
EFIAPI
WatchdogTimerRegisterHandler (
IN EFI_WATCHDOG_TIMER_ARCH_PROTOCOL *This,
IN EFI_WATCHDOG_TIMER_NOTIFY NotifyFunction
)
{
//
// Register: caller wants to set a notification handler
//
if (NotifyFunction != NULL) {
//
// Check if handler is already registered
//
if (mWatchdogNotifyRegistered != NULL) {
return EFI_ALREADY_STARTED;
}
} else {
//
// Unregister: caller wants to clear the handler
//
if (mWatchdogNotifyRegistered == NULL) {
return EFI_UNSUPPORTED;
}
}
//
// Store/clear the notify function pointer
//
mWatchdogNotifyRegistered = (VOID *)NotifyFunction;
return EFI_SUCCESS;
}
/**
Sets the watchdog timer period.
Sets the internal timer period storage and programs the hardware
timer via gBS->SetTimer(). A period of zero disables the watchdog
timer; any non-zero value arms the timer with a periodic signal.
In the periodic case, the timer is set with the EFI_TIMER_PERIODIC
type (value 2).
@param[in] This Pointer to the EFI_WATCHDOG_TIMER_ARCH_PROTOCOL instance.
@param[in] TimerPeriod The timer period in 100ns units.
Zero disables the watchdog timer.
@return EFI_SUCCESS The timer period was set and the hardware was programmed.
**/
EFI_STATUS
EFIAPI
WatchdogTimerSetTimerPeriod (
IN EFI_WATCHDOG_TIMER_ARCH_PROTOCOL *This,
IN UINT64 TimerPeriod
)
{
//
// Store the period value (aliased with the GetTimerPeriod function pointer)
//
mWatchdogTimerPeriod = TimerPeriod;
//
// Program the hardware timer
// - If TimerPeriod is non-zero, use periodic timer type (TimerPeriodic = 2)
// - If TimerPeriod is zero, use cancel timer type (TimerCancel = 0)
//
return gBS->SetTimer (
mWatchdogTimerPeriod, // Note: this references the global, not the period
(TimerPeriod != 0) ? TimerPeriodic : TimerCancel,
TimerPeriod
);
}
/**
Retrieves the current watchdog timer period.
Reads the internally stored timer period value and returns it to
the caller.
@param[in] This Pointer to the EFI_WATCHDOG_TIMER_ARCH_PROTOCOL instance.
@param[out] TimerPeriod Pointer to receive the timer period in 100ns units.
@return EFI_SUCCESS The period was returned successfully.
@return EFI_INVALID_PARAMETER TimerPeriod is NULL.
**/
EFI_STATUS
EFIAPI
WatchdogTimerGetTimerPeriod (
IN EFI_WATCHDOG_TIMER_ARCH_PROTOCOL *This,
OUT UINT64 *TimerPeriod
)
{
if (TimerPeriod == NULL) {
return EFI_INVALID_PARAMETER;
}
*TimerPeriod = mWatchdogTimerPeriod;
return EFI_SUCCESS;
}
/**
Gets the debug output device for logging.
Locates the gEfiGenericMemTestPatternGuid HOB to retrieve the
debug print error level configuration. If the HOB is not found
or its size exceeds the expected maximum, NULL is returned.
The first invocation caches the result in mDebugPrintErrorLevel
for subsequent accesses.
@return Pointer to the debug output device function table.
NULL if the HOB entry could not be located or is invalid.
**/
VOID *
GetDebugOutputDevice (
VOID
)
{
VOID *DebugDevice;
UINTN HobSize;
//
// Return cached value if already initialized
//
DebugDevice = mHobList;
if (DebugDevice != NULL) {
return DebugDevice;
}
//
// Allocate a buffer from EFI HOB space to read the HOB entry
//
HobSize = (UINTN)gBS->GetHobList ();
gBS->GetNextHob ();
if (HobSize <= sizeof (EFI_HOB_GUID_TYPE)) {
//
// HOB is too small or invalid; return NULL
//
return NULL;
}
//
// Locate the debug output device via protocol lookup
//
DebugDevice = NULL;
gBS->LocateProtocol (
&gEfiGenericMemTestPatternGuid,
NULL,
&DebugDevice
);
if (DebugDevice != NULL) {
//
// Cache the result
//
mHobList = DebugDevice;
}
return DebugDevice;
}
/**
Debug assertion handler that prints the assert message.
Writes the assert message using the debug output device found by
GetDebugOutputDevice(). The error level is determined by checking
the NMI flag via CMOS I/O ports 0x70/0x71.
The NMI check inspects CMOS index 0x4B. If the current NMI value is
greater than 3 or is zero, the effective error level is set to 4.
The error level is then AND-ed with the caller's ErrorLevel parameter.
If there is a match, the message is dispatched to the debug output
device.
@param[in] ErrorLevel Bitmask of enabled error levels from the caller.
@param[in] Format Format string for the assert message.
@param[in] ... Variable arguments for the format string.
**/
VOID
DebugAssertPrint (
IN UINTN ErrorLevel,
IN CONST CHAR8 *Format,
...
)
{
VA_LIST Marker;
VOID *DebugDevice;
UINTN EffectiveErrorLevel;
UINT8 NmiValue;
UINT8 SavedIndex;
UINT8 NmiStatus;
DEBUG_PRINT_ASSERT_FUNCTION PrintFunction;
VA_START (Marker, Format);
//
// Get the debug output device
//
DebugDevice = GetDebugOutputDevice ();
if (DebugDevice == NULL) {
VA_END (Marker);
return;
}
//
// Read CMOS NMI flag to determine assert severity
//
// Step 1: Save current CMOS index, mask to keep NMI enabled
//
SavedIndex = IoRead8 (0x70);
IoWrite8 (0x70, SavedIndex & 0x80 | 0x4B);
//
// Step 2: Read the NMI status value at CMOS index 0x4B
//
NmiValue = IoRead8 (0x71);
//
// Step 3: Determine effective error level from NMI value
//
NmiStatus = NmiValue;
if (NmiValue > 3) {
NmiStatus = NmiValue;
if (NmiValue == 0) {
//
// If NMI value is zero, read from fixed memory location
//
NmiStatus = (*(volatile UINT8 *)0xFDAF0490) & 2 | 1;
}
}
//
// Map NMI status to error level:
// NmiStatus == 1 -> EFI_ERROR_MAJOR (0x80000004)
// NmiStatus != 1 -> EFI_ERROR_MINOR (0x80000006)
//
if ((NmiStatus - 1) <= 0xFD) {
EffectiveErrorLevel = 4; // DEBUG_WARN / DEBUG_ERROR level
if (NmiStatus == 1) {
EffectiveErrorLevel = 0x80000004; // EFI_D_ERROR (major)
} else {
EffectiveErrorLevel = 0x80000006; // EFI_D_WARN (minor)
}
}
//
// Check if the determined error level matches the caller's mask
//
if ((EffectiveErrorLevel & ErrorLevel) != 0) {
//
// Dispatch to debug output device
//
PrintFunction = (DEBUG_PRINT_ASSERT_FUNCTION)((DEBUG_PRINT_PROTOCOL *)DebugDevice)->Print;
PrintFunction (ErrorLevel, Format, Marker);
}
VA_END (Marker);
}
/**
Standard UEFI assert handler with source file location.
Called when an ASSERT condition fails. Retrieves the debug output
device via GetDebugOutputDevice() and if available, calls the
device's assertion handler with the file name, line number, and
description of the failed assertion.
@param[in] FileName Source file name where the assert occurred.
@param[in] LineNumber Line number of the assert.
@param[in] Description Description string describing the assert condition.
**/
VOID
DebugAssert (
IN CONST CHAR8 *FileName,
IN UINTN LineNumber,
IN CONST CHAR8 *Description
)
{
VOID *DebugDevice;
DEBUG_ASSERT_FUNCTION AssertFunction;
//
// Get the debug output device
//
DebugDevice = GetDebugOutputDevice ();
if (DebugDevice == NULL) {
return;
}
//
// Call the device's assert function
//
AssertFunction = (DEBUG_ASSERT_FUNCTION)((DEBUG_PRINT_PROTOCOL *)DebugDevice)->Assert;
AssertFunction (FileName, LineNumber, Description);
}
/**
Retrieves the HOB list pointer from the system configuration table.
Iterates through the system table's configuration table array to
find the entry with gEfiHobListGuid. Once found, the HOB list pointer
is cached in mHobList for all subsequent calls.
If the HOB list GUID is not found in any configuration table entry,
an assert is generated.
@return Pointer to the start of the HOB list.
NULL if the HOB list could not be found.
**/
VOID *
GetHobList (
VOID
)
{
UINTN Index;
UINTN TableCount;
EFI_CONFIGURATION_TABLE *ConfigTable;
EFI_GUID *Guid;
VOID *HobEntry;
//
// Return cached value if already initialized
//
if (mHobList != NULL) {
return mHobList;
}
//
// Initialize to failure state
//
mHobList = NULL;
HobEntry = NULL;
//
// Get the number of configuration table entries
//
TableCount = gST->NumberOfTableEntries;
if (TableCount == 0) {
goto HOB_NOT_FOUND;
}
//
// Walk the configuration table looking for gEfiHobListGuid
//
ConfigTable = gST->ConfigurationTable;
for (Index = 0; Index < TableCount; Index++) {
Guid = &ConfigTable[Index].VendorGuid;
if (HobCompareGuidEntry (&gEfiHobListGuid, Guid)) {
//
// Found the HOB list
//
HobEntry = ConfigTable[Index].VendorTable;
mHobList = HobEntry;
return mHobList;
}
}
HOB_NOT_FOUND:
//
// HOB list not found -- generate assert
//
DebugAssertPrint (
0x80000000,
"\nASSERT_EFI_ERROR (Status = %r)\n",
EFI_NOT_FOUND
);
DebugAssert (
__FILE__,
__LINE__,
"!EFI_ERROR (Status)"
);
if (mHobList == NULL) {
DebugAssert (
__FILE__,
__LINE__,
"mHobList != ((void *) 0)"
);
}
return mHobList;
}
/**
Sends an SMM communication command to the watchdog timer SMI handler.
Locates (or uses the cached instance of) the SMM Communication Protocol
and sends a command to synchronize watchdog state with the SMM handler.
The communication buffer contains:
- Command: SMM_WATCHDOG_COMMUNICATION_COMMAND (0x40000002)
- Data: SMM_WATCHDOG_COMMUNICATION_DATA (69635)
The protocol is located and cached on first use; subsequent calls
use the cached pointer.
@return EFI_SUCCESS The SMM communication was sent successfully.
@return EFI_UNSUPPORTED The protocol could not be located or the function is unavailable.
@return EFI_NOT_FOUND The Boot Services table is not available.
**/
EFI_STATUS
SmmCommunicationSend (
VOID
)
{
EFI_SMM_COMMUNICATION_PROTOCOL *SmmComm;
EFI_STATUS Status;
UINTN CommSize;
//
// Use cached protocol if available
//
SmmComm = (EFI_SMM_COMMUNICATION_PROTOCOL *)mSmmCommunicationProtocol;
if (SmmComm != NULL) {
//
// Send the watchdog command via SMM communication
//
CommSize = sizeof (SMM_COMMUNICATION_BUFFER);
Status = SmmComm->Communicate (
SmmComm,
&mWatchdogTimerPeriod,
&CommSize
);
return Status;
}
//
// First call: validate Boot Services table
//
if (gBS == NULL) {
return EFI_UNSUPPORTED;
}
//
// Locate the SMM Communication Protocol
//
Status = gBS->LocateProtocol (
&gEfiSmmCommunicationProtocolGuid,
NULL,
(VOID **)&SmmComm
);
if (EFI_ERROR (Status)) {
return EFI_UNSUPPORTED;
}
//
// Cache the protocol pointer
//
mSmmCommunicationProtocol = (VOID *)SmmComm;
//
// Send the watchdog command via SMM communication
//
CommSize = sizeof (SMM_COMMUNICATION_BUFFER);
Status = SmmComm->Communicate (
SmmComm,
&mWatchdogTimerPeriod,
&CommSize
);
return Status;
}
/**
Reads a 64-bit value from an unaligned memory address.
This is a utility function that performs a direct 64-bit read
from the given Buffer address, even if the address is not
8-byte aligned.
@param[in] Buffer Pointer to the unaligned 64-bit value.
@return The 64-bit value read from Buffer.
**/
UINT64
EFIAPI
ReadUnaligned64 (
IN CONST VOID *Buffer
)
{
if (Buffer == NULL) {
DebugAssert (
__FILE__,
__LINE__,
"Buffer != ((void *) 0)"
);
}
return *(UINT64 *)Buffer;
}
/**
Compares two GUIDs to determine if a HOB entry matches the target GUID.
Used during HOB list traversal. Reads two GUIDs from memory and
compares them for equality. The first GUID is read from a fixed
address (the expected GUID), and the second is read from the
current HOB entry being examined.
@param[in] Entry Pointer to the current HOB entry data.
The GUID at offset 0 and offset 8 of the
entry are compared against the expected GUID.
@param[in] ExpectedGuid Pointer to the HOB entry whose GUID to match.
@return TRUE Both halves of the GUID match (GUIDs are equal).
@return FALSE The GUIDs differ.
**/
BOOLEAN
HobCompareGuidEntry (
IN VOID *Entry,
IN VOID *ExpectedGuid
)
{
UINT64 EntryGuidPart1;
UINT64 EntryGuidPart2;
UINT64 ExpectedGuidPart1;
UINT64 ExpectedGuidPart2;
//
// Read the first 8 bytes of each GUID
//
EntryGuidPart1 = ReadUnaligned64 (Entry);
ExpectedGuidPart1 = ReadUnaligned64 (ExpectedGuid);
//
// Read the second 8 bytes of each GUID
//
EntryGuidPart2 = ReadUnaligned64 ((UINT8 *)Entry + 8);
ExpectedGuidPart2 = ReadUnaligned64 ((UINT8 *)ExpectedGuid + 8);
//
// Compare both 64-bit halves
//
return (BOOLEAN)(EntryGuidPart1 == ExpectedGuidPart1 &&
EntryGuidPart2 == ExpectedGuidPart2);
}