/**
* @file SecFlashUpdDXE.c
* @brief Secure Flash Update DXE Driver
*
* Handles authenticated BIOS capsule updates. This DXE driver registers
* a Flash_Ready_To_Lock notification callback that:
* 1. Queries UEFI Runtime Services for "CapsuleUpdateData" variables
* 2. Iterates through CapsuleUpdateData, CapsuleUpdateData0, CapsuleUpdateData1, ...
* 3. Finds the first valid capsule payload and triggers the secure update
*
* Source file: AmiModulePkg/SecureFlash/SecFlashUpd/SecFlashUpdDxe.c
* Builder: e:\hs\Build\HR6N0XMLK\DEBUG_VS2015\X64\...
* Debug file: SecFlashUpdDXE.pdb
*
* Module: SecFlashUpdDXE.efi (HR650X BIOS, index 0063)
* Size: 0x1e60 (7.5 KB)
* Arch: x86_64
* MD5: d2b8f923a93a1787c4a1a6efa64d2c50
* SHA256: 22b4ce86fed6df3a8e5c9ca587e09972686b78fd1205eb30de25ed088ca02675
*/
#include "SecFlashUpdDXE.h"
//=============================================================================
// Module Global Variables
//=============================================================================
EFI_HANDLE gImageHandle = NULL;
EFI_SYSTEM_TABLE *gST = NULL;
EFI_BOOT_SERVICES *gBS = NULL;
EFI_RUNTIME_SERVICES *gRT = NULL;
VOID *gHobList = NULL;
PCD_PROTOCOL *mPcd = NULL;
//=============================================================================
// GUID Definitions (Protocols, Events, Variables)
//=============================================================================
// AMI Flash Update Protocol GUID: {0x974231D5, 0xED4B, 0x44D1, {0x88, 0x70, 0xCE, 0x51, 0x5C, 0xC1, 0x4D, 0x68}}
STATIC CONST EFI_GUID mAmiFlashUpdProtocolGuid = AMI_FLASH_UPD_PROTOCOL_GUID;
// Capsule Update Data Variable GUID: {0x711C703F, 0xC285, 0x4B10, {0xA3, 0xB0, 0x36, 0xEC, 0xBD, 0x3C, 0x8B, 0xE2}}
STATIC CONST EFI_GUID mCapsuleUpdateDataVariableGuid = CAPSULE_UPDATE_DATA_VARIABLE_GUID;
// DXE SMM Ready To Lock Protocol GUID: {0x49D34AE7, 0x9454, 0x4551, {0x8F, 0x71, 0x46, 0x7D, 0x8C, 0x0E, 0x4E, 0xF5}}
STATIC CONST EFI_GUID mDxeSmmReadyToLockProtocolGuid = DXE_SMM_READY_TO_LOCK_PROTOCOL_GUID;
// Event Ready To Boot Group GUID: {0x60FF8964, 0xE906, 0x41D0, {0xAF, 0xED, 0xF2, 0x41, 0xE9, 0x74, 0xE0, 0x8E}}
STATIC CONST EFI_GUID mEventReadyToBootGuid = EVENT_READY_TO_BOOT_GUID;
//=============================================================================
// Forward Declarations
//=============================================================================
STATIC
EFI_STATUS
EFIAPI
FlashReadyToLockCallback (
IN EFI_EVENT Event,
IN VOID *Context
);
//=============================================================================
// Helper Functions
//=============================================================================
/**
* Retrieves the HOB list pointer.
*
* Searches through system configuration tables for the EFI_HOB_LIST_GUID
* entry. If not found, falls back to the PCD database pointer.
*
* @return Pointer to the HOB list, or NULL on failure
*/
STATIC
VOID *
GetHobList (
VOID
)
{
EFI_STATUS Status;
UINTN Index;
UINTN HobCount;
EFI_GUID **HobGuidArray;
VOID **HobTableArray;
//
// Check if HOB list has already been located
//
if (gHobList != NULL) {
return gHobList;
}
//
// Default to NULL
//
gHobList = NULL;
//
// Iterate through configuration table entries to find the HOB list GUID
//
HobCount = gST->NumberOfTableEntries;
HobGuidArray = (EFI_GUID **)(&gST->ConfigurationTable);
HobTableArray = (VOID **)((CHAR8 *)&gST->ConfigurationTable + sizeof(UINTN));
for (Index = 0; Index < HobCount; Index++) {
if (IsHobGuidMatch((VOID *)(HobTableArray + Index))) {
//
// Found the HOB list; extract its GUID and table pointer
//
break;
}
}
if (Index < HobCount) {
gHobList = *(VOID **)((UINTN)HobTableArray + 24 * Index + 16);
} else {
//
// HOB list not found in configuration table -- assertion
//
DEBUG ((EFI_D_ERROR, "\nASSERT_EFI_ERROR (Status = %r)\n", EFI_NOT_FOUND));
ASSERT (!EFI_ERROR (EFI_NOT_FOUND));
ASSERT (mHobList != NULL);
}
return gHobList;
}
/**
* Checks whether a HOB entry matches the HOB list GUID.
*
* Compares the 16-byte GUID at the HOB entry against the well-known
* EFI_HOB_LIST_GUID using an 8-byte-per-side comparison.
*
* @param[in] HobEntry Pointer to the HOB entry to check
* @return TRUE if the GUID matches, FALSE otherwise
*/
STATIC
BOOLEAN
IsHobGuidMatch (
IN CONST VOID *HobEntry
)
{
UINT64 GuidFirstHalf;
UINT64 GuidSecondHalf;
UINT64 EntryFirstHalf;
UINT64 EntrySecondHalf;
//
// Read the two 8-byte halves of the reference HOB GUID
//
GuidFirstHalf = ReadUnaligned64 (&HOB_LIST_GUID);
GuidSecondHalf = ReadUnaligned64 ((VOID *)((UINTN)&HOB_LIST_GUID + 8));
//
// Read the two 8-byte halves from the candidate HOB entry
//
EntryFirstHalf = ReadUnaligned64 (HobEntry);
EntrySecondHalf = ReadUnaligned64 ((UINTN)(HobEntry + 8));
//
// Match if both halves are equal
//
return (GuidFirstHalf == EntryFirstHalf) &&
(GuidSecondHalf == EntrySecondHalf);
}
/**
* Reads a 64-bit unaligned value from memory.
*
* @param[in] Buffer Pointer to the unaligned buffer (must not be NULL)
* @return 64-bit value
*/
STATIC
UINT64
ReadUnaligned64 (
IN CONST VOID *Buffer
)
{
ASSERT (Buffer != NULL);
return *(UINT64 *)Buffer;
}
//=============================================================================
// Debug / Formatting Helpers
//=============================================================================
/**
* Debug print helper.
*
* Wraps the UEFI debug print infrastructure with level checking.
* Retrieves the debug mask from PCD protocol and prints if the
* requested error level matches the current debug mask.
*
* @param[in] ErrorLevel Debug error level mask (e.g., EFI_D_ERROR, EFI_D_INFO)
* @param[in] Format Format string
* @param[in] ... Variable arguments
*/
STATIC
VOID
DebugPrint (
IN UINTN ErrorLevel,
IN CONST CHAR8 *Format,
...
)
{
VA_LIST Marker;
PCD_PROTOCOL *PcdProtocol;
UINT64 DebugMask;
BOOLEAN DebugPrintEnabled;
PcdProtocol = GetPcdProtocol ();
if (PcdProtocol == NULL) {
return;
}
DebugMask = DebugGetLevel ();
if ((DebugMask & ErrorLevel) == 0) {
return;
}
VA_START (Marker, Format);
//
// Forward to the UEFI debug print routine stored in the DebugLib protocol
//
PcdProtocol->DebugPrint (ErrorLevel, Format, Marker);
VA_END (Marker);
}
/**
* Assertion failure handler.
*
* Called when an ASSERT() expression evaluates to FALSE.
* Prints the source file name, line number, and the assertion text.
*
* @param[in] FileName Source file name
* @param[in] LineNumber Line number of the assertion
* @param[in] Description Assertion expression text
*/
STATIC
VOID
EFIAPI
AssertHandler (
IN CONST CHAR8 *FileName,
IN UINTN LineNumber,
IN CONST CHAR8 *Description
)
{
PCD_PROTOCOL *PcdProtocol;
PcdProtocol = GetPcdProtocol ();
if (PcdProtocol == NULL) {
return;
}
PcdProtocol->AssertHandler (FileName, LineNumber, Description);
}
/**
* Retrieves the PCD protocol pointer (singleton).
*
* Lazily initializes the protocol pointer via BootServices->LocateProtocol
* on first call. Subsequent calls return the cached pointer.
*
* @return Pointer to the PCD protocol, or NULL on failure
*/
STATIC
PCD_PROTOCOL *
GetPcdProtocol (
VOID
)
{
EFI_STATUS Status;
if (mPcd == NULL) {
//
// Allocate a small buffer to check heap availability
//
UINTN BufferSize = gBS->GetFreePoolSize ();
if (BufferSize <= 0x10) {
//
// Heap not yet available; return NULL
//
return NULL;
}
Status = gBS->LocateProtocol (
&gEfiPcdProtocolGuid,
NULL,
(VOID **)&mPcd
);
if (EFI_ERROR (Status)) {
mPcd = NULL;
}
}
return mPcd;
}
/**
* Returns the current debug print level from the PCD database.
*
* @return Current debug print error level mask
*/
STATIC
UINT64
DebugGetLevel (
VOID
)
{
UINT64 DebugMask;
EFI_STATUS Status;
UINTN BufferSize;
BufferSize = sizeof (DebugMask);
Status = gRT->GetVariable (
L"DebugLevel",
&mAmiFlashUpdProtocolGuid,
NULL,
&BufferSize,
&DebugMask
);
if (EFI_ERROR (Status)) {
DebugMask = EFI_D_INFO;
}
return DebugMask;
}
/**
* Converts an EFI_STATUS code to a human-readable string.
*
* Handles all standard UEFI error codes including warning and error
* classifications, as well as interrupt-pending status codes.
*
* @param[in] Status EFI status value
* @return Pointer to a static string
*/
STATIC
CONST CHAR8 *
StatusToString (
IN EFI_STATUS Status
)
{
UINT8 Index;
CONST CHAR8 *StatusText;
if (Status == EFI_SUCCESS) {
return "EFI_SUCCESS";
}
//
// Check for warning codes
//
if ((Status & 0x2000000000000000LL) != 0) {
Index = (UINT8)(Status & 0x1FFFFFFFFFFFFFFFLL);
if (Index >= 2) {
return NULL;
}
StatusText = (CONST CHAR8 *)((UINTN)_ImageBase + 35 * Index);
return StatusText;
}
//
// Check for error codes
//
if (Status >= 0) {
//
// Non-error codes (0 < Status < MAX_BIT)
//
if ((UINTN)Status > 4) {
return NULL;
}
StatusText = (CONST CHAR8 *)((UINTN)_ImageBase + 26 * Status + 6102);
return StatusText;
}
//
// Negative status values
//
Index = (UINT8)(Status & 0x1FFFFFFFFFFFFFFFLL);
if ((Status & 0xA000000000000000LL) == 0xA000000000000000LL) {
//
// Interrupt pending / high severity errors
//
if (Index >= 3) {
return NULL;
}
StatusText = (CONST CHAR8 *)((UINTN)_ImageBase + 25 * Index);
return StatusText;
}
if ((Status & 0xC000000000000000LL) == 0xC000000000000000LL) {
//
// Extended error codes
//
if (Index > 2) {
return NULL;
}
StatusText = (CONST CHAR8 *)((UINTN)_ImageBase + 25 * Index + 6391);
return StatusText;
}
//
// Standard error codes
//
if (Index > 0x1E) {
return NULL;
}
StatusText = (CONST CHAR8 *)((UINTN)_ImageBase + 25 * Index + 6455);
return StatusText;
}
/**
* Converts a numeric value to a string in the given base.
*
* Supports decimal (base 10) with negative sign handling and
* hexadecimal (base 16). Stores the result in reversed digit
* order in the buffer, then null-terminates.
*
* @param[in] Value Value to convert
* @param[out] Buffer Output character buffer (must be large enough)
* @param[in] Base Numeric base (10 or 16)
* @param[in] Signed TRUE for signed conversion (adds '-' for negatives
* when base is 10)
* @return Pointer to the last character written (pre- or post-reversal)
*/
STATIC
CHAR8 *
ValueToString (
IN INT64 Value,
OUT CHAR8 *Buffer,
IN UINTN Base,
IN BOOLEAN Signed
)
{
UINT64 Remainder;
UINT64 AbsValue;
CHAR8 Digit;
CHAR8 *WritePtr;
//
// Determine absolute value
//
if (Signed && Base == 10) {
AbsValue = (Value < 0) ? -(UINT64)Value : (UINT64)Value;
} else {
AbsValue = (UINT64)Value;
}
WritePtr = Buffer;
if (AbsValue == 0) {
*WritePtr++ = '0';
} else {
//
// Convert digits in reverse order
//
while (AbsValue > 0) {
Remainder = AbsValue % Base;
AbsValue /= Base;
if (Remainder >= 10) {
Digit = (CHAR8)(Remainder + 87); // 'a' - 10
} else {
Digit = (CHAR8)(Remainder + 48); // '0'
}
*WritePtr++ = Digit;
}
}
//
// Add negative sign for base-10 signed negative values
//
if (Base == 10 && Value < 0 && Signed) {
*WritePtr++ = '-';
}
*WritePtr = '\0';
return WritePtr - 1;
}
/**
* Parses a decimal or hexadecimal integer from a wide string.
*
* Skips leading spaces/tabs, handles optional '+'/'-' sign,
* and parses digits until a non-digit character is found.
* Limits value to 32-bit range (overflow clamped to INT_MAX/INT_MIN).
*
* @param[in] String Pointer to the wide string to parse
* @param[out] EndString On return, points to the first unparsed character
* @param[in] Base Numeric base (defaults to decimal if 0)
* @return Parsed integer value
*/
STATIC
UINTN
ParseInteger (
IN CONST CHAR16 *String,
OUT CONST CHAR16 **EndString,
IN INTN Base
)
{
INTN Sign;
UINTN Value;
BOOLEAN Overflow;
CHAR16 Char;
Sign = 1;
Value = 0;
Overflow = FALSE;
//
// Skip leading whitespace (space = 0x0020, tab = 0x0009)
//
while (*String == L' ' || *String == L'\t') {
String++;
}
if (*String == L'\0') {
*EndString = String;
return 0;
}
//
// Handle sign
//
if (*String == L'-') {
Sign = -1;
String++;
} else if (*String == L'+') {
String++;
}
//
// Parse digits
//
CONST CHAR16 *ParsePtr = String;
if (*String != L'+' && *String != L'-') {
ParsePtr = String;
}
while (*ParsePtr != L'\0') {
Char = *ParsePtr;
if (Char >= L'0' && Char <= L'9') {
Char = Char - L'0';
} else if ((Char | 0x20) >= L'a' && (Char | 0x20) <= L'f') {
//
// Handle hex digits (a-f or A-F) -- but for base=10 parsing
// we stop at non-decimal
//
if (Base != 10) {
Char = (Char | 0x20) - L'a' + 10;
} else {
break;
}
} else {
break;
}
//
// Base 10 only accepts digits 0-9
//
if (Char >= 10) {
break;
}
Value = Value * 10 + Char;
//
// Check for overflow (32-bit boundary)
//
if (Sign == 1) {
if (Value >= 0x80000000) {
Overflow = TRUE;
}
} else if (Value > 0x80000000) {
Overflow = TRUE;
}
ParsePtr++;
}
*EndString = ParsePtr;
if (Overflow) {
Value = (Sign == 1) ? 0x7FFFFFFF : 0x80000000;
}
return Value * Sign;
}
/**
* Unicode (wide) sprintf wrapper.
*
* Calls UnicodeVSPrint with va_list extracted from variable arguments.
*
* @param[out] Buffer Output wide string buffer
* @param[in] MaxSize Maximum number of wide characters to write
* @param[in] Format Format string
* @param[in] ... Variable arguments
* @return Number of wide characters written
*/
STATIC
UINTN
UnicodeSPrint (
OUT CHAR16 *Buffer,
IN UINTN MaxSize,
IN CONST CHAR16 *Format,
...
)
{
UINTN Count;
VA_LIST Marker;
VA_START (Marker, Format);
Count = UnicodeVSPrint (Buffer, MaxSize, Format, Marker);
VA_END (Marker);
return Count;
}
//=============================================================================
// Core Driver Functions
//=============================================================================
/**
* Entry point for SecFlashUpdDXE driver.
*
* @param[in] ImageHandle Handle of this EFI image
* @param[in] SystemTable Pointer to EFI System Table
* @return EFI_STATUS
*/
EFI_STATUS
EFIAPI
ModuleEntryPoint (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
EFI_STATUS Status;
//
// Initialize UEFI boot services table pointers from the system table
//
Status = SecFlashUpdDriverInit (ImageHandle);
if (EFI_ERROR (Status)) {
return Status;
}
//
// Create ReadyToBoot event and register Flash Ready To Lock callback
//
Status = SecFlashUpdRegisterCallbacks (ImageHandle, SystemTable);
return Status;
}
/**
* Initializes the driver globals and UEFI service table pointers.
*
* Sets up:
* - gImageHandle
* - gST, gBS, gRT
* - HOB list pointer
* - PCD protocol
* - Publishes the ReadyToBoot event group GUID
*
* @param[in] ImageHandle Handle of this EFI image
* @return 0 on success
*/
STATIC
EFI_STATUS
SecFlashUpdDriverInit (
IN EFI_HANDLE ImageHandle
)
{
EFI_STATUS Status;
EFI_EVENT ReadyToBootEvent;
EFI_BOOT_SERVICES *BootServices;
EFI_RUNTIME_SERVICES *RuntimeServices;
EFI_TPL OldTpl;
//
// Save the image handle and system table
//
gImageHandle = ImageHandle;
ASSERT (gImageHandle != NULL);
gST = SystemTable;
ASSERT (gST != NULL);
//
// Extract BootServices and RuntimeServices from the system table
//
BootServices = gST->BootServices;
gBS = BootServices;
ASSERT (gBS != NULL);
RuntimeServices = gST->RuntimeServices;
gRT = RuntimeServices;
ASSERT (gRT != NULL);
//
// Initialize the HOB list pointer
//
GetHobList ();
//
// Create event in the ReadyToBoot event group
//
Status = gBS->CreateEvent (
EVT_NOTIFY_SIGNAL | EVT_GROUP,
TPL_CALLBACK,
FlashReadyToLockCallback,
NULL,
&ReadyToBootEvent
);
if (EFI_ERROR (Status)) {
return Status;
}
//
// Install the DXE SMM Ready To Lock protocol
//
Status = gBS->InstallProtocolInterface (
&ImageHandle,
&mDxeSmmReadyToLockProtocolGuid,
EFI_NATIVE_INTERFACE,
NULL
);
if (EFI_ERROR (Status)) {
gBS->CloseEvent (ReadyToBootEvent);
return Status;
}
ASSERT (ReadyToBootEvent != NULL);
return EFI_SUCCESS;
}
/**
* Registers the Flash_Ready_To_Lock notification callback by creating
* an event in the EFI_EVENT_GROUP_READY_TO_BOOT group.
*
* @param[in] ImageHandle Handle of this EFI image
* @param[in] SystemTable Pointer to EFI System Table
* @return EFI_STATUS
*/
STATIC
EFI_STATUS
SecFlashUpdRegisterCallbacks (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
EFI_STATUS Status;
//
// Create the ReadyToBoot group event. The event will fire the
// FlashReadyToLockCallback at TPL_CALLBACK when the group is signaled.
//
Status = gBS->CreateEvent (
EVT_NOTIFY_SIGNAL | EVT_GROUP,
TPL_CALLBACK,
FlashReadyToLockCallback,
NULL,
&mReadyToBootEvent
);
return Status;
}
/**
* Flash_Ready_To_Lock notification callback.
*
* Invoked when the DXE Foundation signals the ReadyToBoot event group.
* This callback:
* 1. Prints a debug banner
* 2. Reads the "AmiFlashUpd" variable from RT services to check
* for update availability
* 3. Iterates through CapsuleUpdateData, CapsuleUpdateData0, ... variables
* 4. On finding a valid capsule payload, closes the variable and proceeds
* 5. Finally signals the DXE SMM Ready To Lock protocol installation
*
* @param[in] Event ReadyToBoot event that triggered this callback
* @param[in] Context Not used (may be NULL)
* @return EFI_STATUS
*/
STATIC
EFI_STATUS
EFIAPI
FlashReadyToLockCallback (
IN EFI_EVENT Event,
IN VOID *Context
)
{
EFI_STATUS Status;
EFI_RUNTIME_SERVICES *RtServices;
UINT32 Attributes;
UINTN DataSize;
UINT64 CapsuleBlockDesc;
UINTN CapsuleIndex;
CHAR16 VariableName[30];
VOID *ProtocolInterface;
EFI_HANDLE ProtocolHandle;
//
// Ensure the Runtime Services pointer is valid
//
RtServices = gRT;
ASSERT (RtServices != NULL);
//
// Debug trace
//
DEBUG ((EFI_D_INFO, "\nSecure Fl Upd:\nFlash_Ready_To_Lock callback\n"));
//
// Check if the AmiFlashUpd variable exists (signals that an update
// has been staged)
//
DataSize = sizeof (UINT64);
CapsuleBlockDesc = 0;
Status = RtServices->GetVariable (
AMI_FLASH_UPD_VARIABLE_NAME,
&mAmiFlashUpdProtocolGuid,
&Attributes,
&DataSize,
&CapsuleBlockDesc
);
if (!EFI_ERROR (Status)) {
//
// AmiFlashUpd variable found -- close it (we're done reading)
//
RtServices->SetVariable (
AMI_FLASH_UPD_VARIABLE_NAME,
&mAmiFlashUpdProtocolGuid,
Attributes,
0,
NULL
);
//
// Search for CapsuleUpdateData variables
// First try "CapsuleUpdateData", then "CapsuleUpdateData0", "CapsuleUpdateData1", ...
//
CapsuleIndex = 0;
do {
if (CapsuleIndex == 0) {
UnicodeSPrint (
VariableName,
30,
L"%s",
CAPSULE_UPDATE_DATA_NAME
);
} else {
UnicodeSPrint (
VariableName,
30,
L"%s%d",
CAPSULE_UPDATE_DATA_NAME,
CapsuleIndex
);
}
DataSize = sizeof (CapsuleBlockDesc);
Status = RtServices->GetVariable (
VariableName,
&mCapsuleUpdateDataVariableGuid,
&Attributes,
&DataSize,
&CapsuleBlockDesc
);
DEBUG ((
EFI_D_INFO,
"Find '%s' %r, %lX[=%lx]\n",
VariableName,
Status,
CapsuleBlockDesc,
CapsuleBlockDesc
));
if (!EFI_ERROR (Status)) {
//
// Found a valid capsule update data variable! Check if the
// descriptor size matches the expected size stored in
// CapsuleBlockDesc + 8.
//
UINT64 ExpectedSize = *(UINT64 *)((UINTN)&CapsuleBlockDesc + 8);
if (CapsuleBlockDesc == ExpectedSize) {
//
// Descriptor matches -- close the variable and proceed
//
RtServices->SetVariable (
VariableName,
&mCapsuleUpdateDataVariableGuid,
Attributes,
0,
NULL
);
break;
}
}
CapsuleIndex++;
} while (!EFI_ERROR (Status));
}
//
// Install the DXE SMM Ready To Lock protocol to signal that flash
// should now be locked for write protection
//
ProtocolInterface = NULL;
gBS->InstallProtocolInterface (
&ProtocolHandle,
&mDxeSmmReadyToLockProtocolGuid,
EFI_NATIVE_INTERFACE,
ProtocolInterface
);
//
// Close the event so this callback doesn't fire again
//
gBS->CloseEvent (Event);
return Status;
}
//=============================================================================
// Unicode Formatted Print (vsnprintf for CHAR16)
//=============================================================================
/**
* Unicode (wide) string formatting function - vsnprintf equivalent.
*
* Supports the following format specifiers:
* %s - CHAR16* (wide string argument)
* %S - CHAR8* (narrow string argument, up-casted)
* %a - CHAR8* (narrow string argument)
* %d, %i - decimal integer
* %x, %X - hexadecimal (lower/upper case)
* %p - pointer (hexadecimal with uppercase)
* %08x - zero-padded hex
* %r - EFI_STATUS to string
* %g - GUID to string (%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x)
* %ld, %li - long (64-bit) decimal
* %lx, %lX - long (64-bit) hex
*
* @param[out] Buffer Output wide string buffer
* @param[in] MaxSize Maximum number of wide characters to write
* @param[in] Format Format string
* @param[in] VaList Variable argument list
* @return Number of wide characters written (excluding null terminator)
*/
STATIC
UINTN
UnicodeVSPrint (
OUT CHAR16 *Buffer,
IN UINTN MaxSize,
IN CONST CHAR16 *Format,
IN VA_LIST VaList
)
{
CONST CHAR16 *Fmt;
CHAR16 *Dst;
UINTN Remaining;
CHAR16 Ch;
UINTN Width;
CHAR16 PadChar;
UINTN Value;
UINTN ArgIndex;
BOOLEAN LongArg;
CHAR8 NarrowBuf[256];
CHAR16 WideBuf[32];
CHAR16 *StrPtr;
UINTN StrLen;
CHAR8 *NarrowStr;
Fmt = Format;
Dst = Buffer;
Remaining = MaxSize;
if (Fmt == NULL) {
*Dst = L'\0';
return 0;
}
while (*Fmt != L'\0') {
if (*Fmt != L'%') {
//
// Ordinary character -- copy to output
//
if (Remaining <= 1) {
goto Overflow;
}
*Dst++ = *Fmt;
Remaining--;
Fmt++;
continue;
}
//
// Process format specifier
//
Fmt++;
//
// Check for "%%" (literal percent)
//
if (*Fmt == L'%') {
if (Remaining <= 1) break;
*Dst++ = L'%';
Remaining--;
Fmt++;
continue;
}
//
// Parse width and padding
//
Width = 0;
PadChar = L' ';
if (*Fmt == L'0') {
PadChar = L'0';
Fmt++;
}
//
// Parse '*' width from argument
//
if (*Fmt == L'*') {
Fmt++;
ArgIndex = VA_ARG (VaList, UINTN);
Width = (UINTN)ArgIndex;
} else {
//
// Parse numeric width
//
CONST CHAR16 *WidthStart = Fmt;
CONST CHAR16 *WidthEnd;
Width = ParseInteger (Fmt, &WidthEnd, 10);
if (WidthEnd != WidthStart) {
Fmt = WidthEnd;
}
}
//
// Check for 'l' prefix (long/64-bit)
//
LongArg = FALSE;
if (*Fmt == L'l') {
LongArg = TRUE;
Fmt++;
}
//
// Process the conversion specifier
//
switch (*Fmt) {
case L's':
//
// Wide string argument
//
StrPtr = VA_ARG (VaList, CHAR16 *);
if (StrPtr == NULL) {
StrPtr = L"(null)";
}
while (*StrPtr != L'\0') {
if (Remaining <= 1) goto Overflow;
*Dst++ = *StrPtr++;
Remaining--;
}
break;
case L'S':
case L'a':
//
// Narrow (CHAR8) string argument, zero-extended to CHAR16
//
NarrowStr = VA_ARG (VaList, CHAR8 *);
if (NarrowStr == NULL) {
NarrowStr = "(null)";
}
while (*NarrowStr != '\0') {
if (Remaining <= 1) goto Overflow;
*Dst++ = (CHAR16)(UINT8)(*NarrowStr);
NarrowStr++;
Remaining--;
}
break;
case L'c':
//
// Character argument
//
if (Remaining <= 1) goto Overflow;
*Dst++ = (CHAR16)(VA_ARG (VaList, UINTN));
Remaining--;
break;
case L'd':
case L'i':
//
// Signed decimal integer
//
if (LongArg) {
Value = (INT64)VA_ARG (VaList, INT64);
} else {
Value = (INTN)VA_ARG (VaList, INTN);
}
ValueToString ((INT64)Value, NarrowBuf, 10, TRUE);
//
// Apply padding
//
StrLen = AsciiStrLen (NarrowBuf);
while (Width > StrLen) {
if (Remaining <= 1) goto Overflow;
*Dst++ = PadChar;
Remaining--;
Width--;
}
//
// Output digits
//
{
CHAR8 *Np = NarrowBuf + StrLen;
while (Np >= NarrowBuf) {
if (Remaining <= 1) goto Overflow;
*Dst++ = (CHAR16)(UINT8)(*Np--);
Remaining--;
}
}
break;
case L'X':
case L'x':
case L'p':
//
// Hexadecimal (unsigned)
//
if (LongArg || *Fmt == L'p') {
Value = VA_ARG (VaList, UINT64);
} else {
Value = VA_ARG (VaList, UINTN);
}
ValueToString ((INT64)Value, NarrowBuf, 16, FALSE);
//
// Apply padding
//
StrLen = AsciiStrLen (NarrowBuf);
while (Width > StrLen) {
if (Remaining <= 1) goto Overflow;
*Dst++ = PadChar;
Remaining--;
Width--;
}
//
// Output digits, applying uppercase conversion for %X
//
{
CHAR8 *Np;
Np = NarrowBuf + StrLen;
while (Np >= NarrowBuf) {
CHAR8 Digit = *Np--;
if (*Fmt == L'X' || *Fmt == L'p') {
if (Digit >= 'a' && Digit <= 'f') {
Digit -= 32;
}
}
if (Remaining <= 1) goto Overflow;
*Dst++ = (CHAR16)(UINT8)Digit;
Remaining--;
}
}
break;
case L'r':
//
// EFI_STATUS to string
//
{
CONST CHAR8 *StatusStr;
UINT64 StatusValue;
StatusValue = VA_ARG (VaList, UINT64);
StatusStr = StatusToString ((EFI_STATUS)StatusValue);
if (StatusStr != NULL) {
while (*StatusStr != '\0') {
if (Remaining <= 1) goto Overflow;
*Dst++ = (CHAR16)(UINT8)(*StatusStr);
StatusStr++;
Remaining--;
}
} else {
//
// Fallback: print raw hex value
//
ValueToString ((INT64)StatusValue, NarrowBuf, 16, FALSE);
CHAR8 *Np = NarrowBuf;
while (*Np != '\0') {
if (Remaining <= 1) goto Overflow;
*Dst++ = (CHAR16)(UINT8)(*Np);
Np++;
Remaining--;
}
}
}
break;
case L'g':
case L'G':
//
// GUID to string (%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x)
//
{
UINT32 *GuidData;
UINTN GuidBytes[11];
GuidData = VA_ARG (VaList, UINT32 *);
GuidBytes[0] = GuidData[0]; // Data1
GuidBytes[1] = (GuidData[1] & 0xFFFF); // Data2
GuidBytes[2] = ((GuidData[1] >> 16) & 0xFFFF); // Data3
GuidBytes[3] = *((UINT8 *)GuidData + 8); // Data4[0]
GuidBytes[4] = *((UINT8 *)GuidData + 9); // Data4[1]
GuidBytes[5] = *((UINT8 *)GuidData + 10); // Data4[2]
GuidBytes[6] = *((UINT8 *)GuidData + 11); // Data4[3]
GuidBytes[7] = *((UINT8 *)GuidData + 12); // Data4[4]
GuidBytes[8] = *((UINT8 *)GuidData + 13); // Data4[5]
GuidBytes[9] = *((UINT8 *)GuidData + 14); // Data4[6]
GuidBytes[10] = *((UINT8 *)GuidData + 15); // Data4[7]
UnicodeSPrint (
Dst,
Remaining,
L"%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x",
GuidBytes[0],
GuidBytes[1],
GuidBytes[2],
GuidBytes[3],
GuidBytes[4],
GuidBytes[5],
GuidBytes[6],
GuidBytes[7],
GuidBytes[8],
GuidBytes[9],
GuidBytes[10]
);
}
break;
default:
//
// Unknown format specifier - copy literal
//
if (Remaining <= 1) goto Overflow;
*Dst++ = *Fmt;
Remaining--;
break;
}
Fmt++;
}
Overflow:
*Dst = L'\0';
return (UINTN)(Dst - Buffer);
}