/** @file
DpcDxe.c -- Deferred Procedure Call (DPC) Driver implementation
This DXE driver implements EFI_DPC_PROTOCOL for the UEFI network stack
(AmiNetworkPkg). Deferred Procedure Calls allow drivers to schedule
callback functions to be executed at TPL_CALLBACK level, useful for
deferring work from interrupt handlers or high-TPL code.
Architecture:
- 32 priority levels (0-31), each with its own doubly-linked list
- A free list of DPC_ENTRY structures is maintained
- Entries are allocated from the free list (or from pool) and queued
- DpcDispatchDpc() drains one priority level at a time, executing
each entry's callback and returning it to the free list
- DpcQueueDepth and DpcMaxQueueDepth track statistics
Module: DpcDxe.efi
Source: AmiNetworkPkg/UefiNetworkStack/Common/DpcDxe/Dpc.c
Image: Lenovo ThinkSystem HR650X BIOS (SHA256: a77c4daf...)
**/
#include <Uefi.h>
#include <Protocol/Dpc.h>
#include <Library/DebugLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/BaseLib.h>
#include <Library/MemoryAllocationLib.h>
#include "DpcDxe.h"
// ==========================================================================
// Global data
// ==========================================================================
//
// Protocol interface instance -- EFI_DPC_PROTOCOL
// +0x00: DpcQueueDpc (at 0x518)
// +0x08: DpcDispatchDpc (at 0x698)
//
EFI_DPC_PROTOCOL gDpcProtocolInterface;
//
// Head of the free DPC_ENTRY pool (at 0x1140)
//
LIST_ENTRY gDpcFreeListHead;
//
// DPC queue heads -- one LIST_ENTRY per priority level (0..31)
// Array at 0x11C0 .. 0x13AF (32 entries * 16 bytes each)
//
LIST_ENTRY gDpcQueue[DPC_NUM_QUEUES];
//
// Statistics (at 0x1160, 0x1170)
//
UINT64 gDpcQueueDepth = 0;
UINT64 gDpcMaxQueueDepth = 0;
//
// Protocol registration handle (at 0x1168)
//
EFI_HANDLE gDpcProtocolHandle = NULL;
//
// Debug output protocol (at 0x1198)
//
EFI_DEBUG_OUTPUT_PROTOCOL *gDpcDebugProtocol = NULL;
//
// HOB list pointer (at 0x11A0)
//
VOID *gDpcHobList = NULL;
// ==========================================================================
// Linked-list helper functions
// ==========================================================================
/**
Validates a LIST_ENTRY pointer, ASSERT-ing that ForwardLink and BackLink
are non-NULL.
@param[in] List Pointer to the LIST_ENTRY to validate.
@retval TRUE List is valid.
**/
BOOLEAN
EFIAPI
DpcInternalIsListValid (
IN LIST_ENTRY *List
)
{
ASSERT (List != NULL);
ASSERT (List->ForwardLink != NULL);
ASSERT (List->BackLink != NULL);
return TRUE;
}
/**
Inserts a new entry at the tail of a doubly-linked list.
@param[in] ListHead Pointer to the list head.
@param[in] Entry Pointer to the entry to insert.
@return ListHead.
**/
LIST_ENTRY *
EFIAPI
DpcInternalInsertTailList (
IN LIST_ENTRY *ListHead,
IN LIST_ENTRY *Entry
)
{
DpcInternalIsListValid (ListHead);
Entry->ForwardLink = ListHead;
Entry->BackLink = ListHead->BackLink;
ListHead->BackLink->ForwardLink = Entry;
ListHead->BackLink = Entry;
return ListHead;
}
/**
Checks whether a doubly-linked list is empty.
@param[in] ListHead Pointer to the list head.
@retval TRUE The list is empty (head points to itself).
@retval FALSE The list is not empty.
**/
BOOLEAN
EFIAPI
DpcInternalIsListEmpty (
IN LIST_ENTRY *ListHead
)
{
DpcInternalIsListValid (ListHead);
return (ListHead->ForwardLink == ListHead);
}
/**
Removes an entry from a doubly-linked list.
@param[in] Entry Pointer to the entry to remove.
@return The ForwardLink of the removed entry.
**/
LIST_ENTRY *
EFIAPI
DpcInternalRemoveEntryList (
IN LIST_ENTRY *Entry
)
{
ASSERT (!DpcInternalIsListEmpty (Entry));
Entry->ForwardLink->BackLink = Entry->BackLink;
Entry->BackLink->ForwardLink = Entry->ForwardLink;
return Entry->ForwardLink;
}
// ==========================================================================
// Memory management
// ==========================================================================
/**
Allocates a single DPC_ENTRY structure from pool (32 bytes).
@return Pointer to the allocated DPC_ENTRY, or NULL on failure.
**/
DPC_ENTRY *
EFIAPI
DpcAllocatePool (
VOID
)
{
return (DPC_ENTRY *)AllocatePool (sizeof (DPC_ENTRY));
}
// ==========================================================================
// Debug output support
// ==========================================================================
/**
Locates the Debug Output Protocol.
@return EFI_STATUS of the LocateProtocol call.
**/
EFI_STATUS
EFIAPI
DpcGetDebugOutputProtocol (
VOID
)
{
EFI_STATUS Status;
if (gDpcDebugProtocol != NULL) {
return EFI_SUCCESS;
}
Status = gBS->LocateProtocol (
&gEfiDebugPortProtocolGuid,
NULL,
(VOID **)&gDpcDebugProtocol
);
if (EFI_ERROR (Status)) {
gDpcDebugProtocol = NULL;
}
return Status;
}
/**
Debug assertion handler -- called by ASSERT() macros to report failures
with source file and line information.
@param[in] FileName Source file name (ASCII).
@param[in] LineNumber Line number in source file.
@param[in] Description Assertion condition description.
**/
VOID
EFIAPI
DpcDebugAssert (
IN CHAR8 *FileName,
IN UINTN LineNumber,
IN CHAR8 *Description
)
{
if (gDpcDebugProtocol != NULL) {
gDpcDebugProtocol->Write (
FileName,
LineNumber,
Description
);
}
}
/**
Reports an ASSERT_EFI_ERROR condition with formatted status output.
Determines the appropriate CMOS-based error severity level and reports
the error if it matches the given StatusCode filter.
@param[in] StatusCode Error code/tag to filter on.
@param[in] Format ASCII format string (e.g. "\nASSERT_EFI_ERROR (Status = %r)\n").
@param[in] ... Variable arguments (the EFI_STATUS code).
**/
VOID
EFIAPI
DpcReportAssertError (
IN UINTN StatusCode,
IN CONST CHAR8 *Format,
IN ...
)
{
VA_LIST Marker;
EFI_STATUS Status;
UINT8 CmosIndex;
UINT8 CmosValue;
DpcGetDebugOutputProtocol ();
if (gDpcDebugProtocol != NULL) {
//
// Read CMOS status -- check error reporting level.
// Port 0x70 index, port 0x71 data.
//
CmosIndex = __inbyte (0x70);
__outbyte (0x70, CmosIndex & 0x80 | 0x4B);
CmosValue = __inbyte (0x71);
if (CmosValue > 3) {
if (CmosValue == 0) {
CmosValue = (UINT8)((*(UINT8 *)(UINTN)0xFDAF0490 & 2) | 1);
}
}
//
// Map CMOS value to EFI error severity.
//
if (CmosValue == 1) {
Status = EFI_ERROR_WARNING; // 0x80000004
} else {
Status = EFI_ERROR_ERROR; // 0x80000006
}
if ((Status & StatusCode) != 0) {
VA_START (Marker, Format);
gDpcDebugProtocol->Write (StatusCode, Format, Marker);
VA_END (Marker);
}
}
}
// ==========================================================================
// GUID comparison and HOB list initialization
// ==========================================================================
/**
Read an unaligned 64-bit value from a buffer.
@param[in] Buffer Pointer to the buffer.
@return The 64-bit value.
**/
UINT64
EFIAPI
DpcReadUnaligned64 (
IN UINT64 *Buffer
)
{
ASSERT (Buffer != NULL);
return *Buffer;
}
/**
Compares two EFI_GUIDs by comparing their first and second 64-bit
halves (little-endian GUID layout).
@param[in] Guid1 First GUID to compare.
@param[in] Guid2 Second GUID to compare.
@retval TRUE Both halves match -- GUIDs are equal.
@retval FALSE GUIDs differ.
**/
BOOLEAN
EFIAPI
DpcCompareGuid (
IN EFI_GUID *Guid1,
IN EFI_GUID *Guid2
)
{
return (DpcReadUnaligned64 ((UINT64 *)Guid1) == DpcReadUnaligned64 ((UINT64 *)Guid2) &&
DpcReadUnaligned64 ((UINT64 *)Guid1 + 1) == DpcReadUnaligned64 ((UINT64 *)Guid2 + 1));
}
/**
Initializes the gDpcHobList pointer by scanning the DXE Configuration
Table for the DXE Services Table GUID.
Must be called before any HOB services are accessed.
@return VOID.
**/
VOID
EFIAPI
DpcHobInit (
VOID
)
{
UINTN Index;
EFI_GUID DxeServicesGuid = EFI_DXE_SERVICES_TABLE_GUID;
if (gDpcHobList != NULL) {
return;
}
gDpcHobList = NULL;
if (gST->NumberOfTableEntries > 0) {
for (Index = 0; Index < gST->NumberOfTableEntries; Index++) {
if (DpcCompareGuid (
&DxeServicesGuid,
&gST->ConfigurationTable[Index].VendorGuid
)) {
gDpcHobList = gST->ConfigurationTable[Index].VendorTable;
break;
}
}
}
if (gDpcHobList == NULL) {
DpcReportAssertError (
0x80000000LL,
"\nASSERT_EFI_ERROR (Status = %r)\n",
0x800000000000000EuLL
);
ASSERT (FALSE); // "!EFI_ERROR (Status)"
}
}
// ==========================================================================
// EFI_DPC_PROTOCOL: DpcQueueDpc
// ==========================================================================
/**
Enqueues a Deferred Procedure Call.
The caller specifies a DpcToken (priority level 4..31), a procedure
callback, and an optional context argument. The procedure executes
when DpcDispatchDpc() runs for that priority level.
@param[in] This The EFI_DPC_PROTOCOL instance.
@param[in] DpcToken Priority level (must be >= 4 and <= 0x1F).
@param[in] DpcProcedure Function pointer to execute at DPC dispatch.
@param[in] DpcContext Optional context pointer passed to the function.
@retval EFI_SUCCESS DPC was queued successfully.
@retval EFI_INVALID_PARAMETER DpcToken out of range, or DpcProcedure is NULL.
@retval EFI_OUT_OF_RESOURCES No DPC entries available (free list empty
and pool allocation failed).
**/
EFI_STATUS
EFIAPI
DpcQueueDpc (
IN EFI_DPC_PROTOCOL *This,
IN UINT32 DpcToken,
IN EFI_DPC_PROCEDURE DpcProcedure,
IN VOID *DpcContext OPTIONAL
)
{
EFI_STATUS Status;
DPC_ENTRY *Entry;
UINTN Tpl;
UINTN RetryCount;
//
// Validate DpcToken (must be in range 4..0x1F).
//
if ((DpcToken < 4) || (DpcToken > 0x1F)) {
return EFI_INVALID_PARAMETER;
}
if (DpcProcedure == NULL) {
return EFI_INVALID_PARAMETER;
}
//
// Raise TPL to serialize access to the DPC free list and queues.
//
Tpl = gBS->RaiseTPL (TPL_DPC_PROCESSING);
Status = EFI_SUCCESS;
//
// Check if the free list has an entry.
//
if (DpcInternalIsListEmpty (&gDpcFreeListHead)) {
//
// Free list is empty -- try to allocate more entries.
//
if (Tpl <= 16) {
//
// TPL is high but still low enough for AllocatePool.
// Loop up to DPC_ALLOCATE_BATCH (64) times, dropping and
// raising TPL each iteration.
//
for (RetryCount = 0; RetryCount < DPC_ALLOCATE_BATCH; RetryCount++) {
//
// Restore TPL so AllocatePool can work.
//
gBS->RestoreTPL (Tpl);
//
// Allocate a new DPC entry.
//
Entry = DpcAllocatePool ();
//
// Raise TPL back.
//
gBS->RaiseTPL (TPL_DPC_PROCESSING);
if (Entry != NULL) {
//
// Check again if free list is still empty (another thread
// may have freed entries while TPL was lowered).
//
if (DpcInternalIsListEmpty (&gDpcFreeListHead)) {
//
// Free list is still empty -- add this entry to it.
//
DpcInternalInsertTailList (&gDpcFreeListHead, &Entry->List);
}
}
}
//
// After the loop, check if we now have an entry.
//
if (DpcInternalIsListEmpty (&gDpcFreeListHead)) {
Status = EFI_OUT_OF_RESOURCES;
goto Done;
}
} else {
//
// TPL is too high for pool allocation.
//
Status = EFI_OUT_OF_RESOURCES;
goto Done;
}
}
//
// Take an entry from the free list.
//
Entry = BASE_CR (gDpcFreeListHead.ForwardLink, DPC_ENTRY, List);
DpcInternalRemoveEntryList (&Entry->List);
//
// Populate the DPC entry.
//
Entry->DpcProcedure = (VOID *)DpcProcedure;
Entry->DpcContext = DpcContext;
//
// Queue to the correct priority-level list.
//
DpcInternalInsertTailList (&gDpcQueue[DpcToken], &Entry->List);
//
// Update statistics.
//
gDpcQueueDepth++;
if (gDpcQueueDepth > gDpcMaxQueueDepth) {
gDpcMaxQueueDepth = gDpcQueueDepth;
}
Status = EFI_SUCCESS;
Done:
gBS->RestoreTPL (Tpl);
return Status;
}
// ==========================================================================
// EFI_DPC_PROTOCOL: DpcDispatchDpc
// ==========================================================================
/**
Dispatches all pending DPC entries.
Scans priority levels from highest (31) down to the current TPL,
executing the callback of each queued entry and returning it to
the free list.
@param[in] This The EFI_DPC_PROTOCOL instance.
@retval EFI_SUCCESS At least one DPC was dispatched.
@retval EFI_NOT_FOUND No DPCs were queued.
**/
EFI_STATUS
EFIAPI
DpcDispatchDpc (
IN EFI_DPC_PROTOCOL *This
)
{
EFI_STATUS Status;
UINTN Priority;
DPC_ENTRY *Entry;
Status = EFI_NOT_FOUND;
//
// Raise TPL to DPC_PROCESSING to serialize.
//
Priority = gBS->RaiseTPL (TPL_DPC_PROCESSING);
if (gDpcQueueDepth == 0) {
goto Done;
}
//
// Process priorities from the raised TPL (31) downwards.
//
if (Priority <= 31) {
for ( ; ; ) {
//
// If this priority queue has entries, drain it.
//
if (!DpcInternalIsListEmpty (&gDpcQueue[Priority])) {
Status = EFI_SUCCESS;
do {
DpcInternalIsListValid (&gDpcQueue[Priority]);
//
// Take the first entry from the queue.
//
Entry = BASE_CR (gDpcQueue[Priority].ForwardLink,
DPC_ENTRY,
List);
DpcInternalRemoveEntryList (&Entry->List);
//
// Decrement the queue depth counter.
//
gDpcQueueDepth--;
//
// Drop TPL to the priority level so the callback
// can execute at its intended TPL.
//
gBS->RestoreTPL (Priority);
//
// Execute the deferred procedure.
//
((EFI_DPC_PROCEDURE)Entry->DpcProcedure)(Entry->DpcContext);
//
// Raise TPL back, and return the entry to the free list.
//
gBS->RaiseTPL (TPL_DPC_PROCESSING);
DpcInternalInsertTailList (&gDpcFreeListHead, &Entry->List);
} while (!DpcInternalIsListEmpty (&gDpcQueue[Priority]));
}
//
// Move to the next lower priority level.
//
if (Priority == 0) {
break;
}
Priority--;
}
}
Done:
gBS->RestoreTPL (Priority);
return Status;
}
// ==========================================================================
// Driver initialization
// ==========================================================================
/**
Main driver initialization entry installed via EFI_DPC_PROTOCOL.
Initializes all DPC queue heads (32 entries), checks for
duplicate protocol installation, then installs the
EFI_DPC_PROTOCOL onto a protocol handle.
@param[in] ImageHandle Module's EFI image handle.
@param[in] SystemTable UEFI System Table.
@retval EFI_SUCCESS Protocol installed.
@retval EFI_ALREADY_STARTED Protocol already installed.
@retval Others InstallProtocolInterface failure.
**/
EFI_STATUS
EFIAPI
DpcDriverEntry (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
EFI_STATUS Status;
UINTN QueueIndex;
//
// Check if DPC protocol is already installed.
//
Status = gBS->LocateProtocol (
&gEfiDpcProtocolGuid,
NULL,
(VOID **)&gDpcProtocolHandle
);
if (!EFI_ERROR (Status)) {
//
// Protocol already registered -- this is a bug in the calling driver.
//
DpcDebugAssert (
"e:\\hs\\AmiNetworkPkg\\UefiNetworkStack\\Common\\DpcDxe\\Dpc.c",
326,
"&gEfiDpcProtocolGuid already installed in database"
);
}
//
// Initialize all 32 DPC queue heads.
//
for (QueueIndex = 0; QueueIndex < DPC_NUM_QUEUES; QueueIndex++) {
DpcInternalIsListValid (&gDpcQueue[QueueIndex]);
gDpcQueue[QueueIndex].ForwardLink = &gDpcQueue[QueueIndex];
gDpcQueue[QueueIndex].BackLink = &gDpcQueue[QueueIndex];
}
//
// Install the DPC protocol.
//
Status = gBS->InstallProtocolInterface (
&gDpcProtocolHandle,
&gEfiDpcProtocolGuid,
EFI_NATIVE_INTERFACE,
&gDpcProtocolInterface
);
if (EFI_ERROR (Status)) {
DpcReportAssertError (
0x80000000LL,
"\nASSERT_EFI_ERROR (Status = %r)\n",
Status
);
DpcDebugAssert (
"e:\\hs\\AmiNetworkPkg\\UefiNetworkStack\\Common\\DpcDxe\\Dpc.c",
344,
"!EFI_ERROR (Status)"
);
}
return Status;
}
/**
Standard UEFI DXE driver entry point.
Initializes the UEFI Boot Services, Runtime Services, System Table,
and Image Handle globals. Then initializes the HOB list and installs
the DPC protocol.
@param[in] ImageHandle Module's EFI image handle.
@param[in] SystemTable UEFI System Table.
@return DpcDriverEntry() return value.
**/
EFI_STATUS
EFIAPI
ModuleEntryPoint (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
//
// Initialize UEFI globals.
//
gImageHandle = ImageHandle;
ASSERT (ImageHandle != NULL);
gST = SystemTable;
ASSERT (gST != NULL);
gBS = SystemTable->BootServices;
ASSERT (gBS != NULL);
gRT = SystemTable->RuntimeServices;
ASSERT (gRT != NULL);
//
// Initialize HOB list for DXE phase services.
//
DpcHobInit ();
//
// Install the DPC protocol.
//
return DpcDriverEntry (ImageHandle, SystemTable);
}