/** @file | |
Debug Agent library implementation for Dxe Core and Dxr modules. | |
Copyright (c) 2010 - 2018, Intel Corporation. All rights reserved.<BR> | |
SPDX-License-Identifier: BSD-2-Clause-Patent | |
**/ | |
#include "DxeDebugAgentLib.h" | |
DEBUG_AGENT_MAILBOX mMailbox; | |
DEBUG_AGENT_MAILBOX *mMailboxPointer = NULL; | |
IA32_IDT_GATE_DESCRIPTOR mIdtEntryTable[33]; | |
BOOLEAN mDxeCoreFlag = FALSE; | |
BOOLEAN mMultiProcessorDebugSupport = FALSE; | |
VOID *mSavedIdtTable = NULL; | |
UINTN mSaveIdtTableSize = 0; | |
BOOLEAN mDebugAgentInitialized = FALSE; | |
BOOLEAN mSkipBreakpoint = FALSE; | |
/** | |
Check if debug agent support multi-processor. | |
@retval TRUE Multi-processor is supported. | |
@retval FALSE Multi-processor is not supported. | |
**/ | |
BOOLEAN | |
MultiProcessorDebugSupport ( | |
VOID | |
) | |
{ | |
return mMultiProcessorDebugSupport; | |
} | |
/** | |
Internal constructor worker function. | |
It will register one callback function on EFI PCD Protocol. | |
It will allocate the NVS memory to store Mailbox and install configuration table | |
in system table to store its pointer. | |
**/ | |
VOID | |
InternalConstructorWorker ( | |
VOID | |
) | |
{ | |
EFI_STATUS Status; | |
EFI_PHYSICAL_ADDRESS Address; | |
BOOLEAN DebugTimerInterruptState; | |
DEBUG_AGENT_MAILBOX *Mailbox; | |
DEBUG_AGENT_MAILBOX *NewMailbox; | |
EFI_HOB_GUID_TYPE *GuidHob; | |
EFI_VECTOR_HANDOFF_INFO *VectorHandoffInfo; | |
// | |
// Check persisted vector handoff info | |
// | |
Status = EFI_SUCCESS; | |
GuidHob = GetFirstGuidHob (&gEfiVectorHandoffInfoPpiGuid); | |
if ((GuidHob != NULL) && !mDxeCoreFlag) { | |
// | |
// Check if configuration table is installed or not if GUIDed HOB existed, | |
// only when Debug Agent is not linked by DXE Core | |
// | |
Status = EfiGetSystemConfigurationTable (&gEfiVectorHandoffTableGuid, (VOID **)&VectorHandoffInfo); | |
} | |
if ((GuidHob == NULL) || (Status != EFI_SUCCESS)) { | |
// | |
// Install configuration table for persisted vector handoff info if GUIDed HOB cannot be found or | |
// configuration table does not exist | |
// | |
Status = gBS->InstallConfigurationTable (&gEfiVectorHandoffTableGuid, (VOID *)&mVectorHandoffInfoDebugAgent[0]); | |
if (EFI_ERROR (Status)) { | |
DEBUG ((DEBUG_ERROR, "DebugAgent: Cannot install configuration table for persisted vector handoff info!\n")); | |
CpuDeadLoop (); | |
} | |
} | |
// | |
// Install EFI Serial IO protocol on debug port | |
// | |
InstallSerialIo (); | |
Address = 0; | |
Status = gBS->AllocatePages ( | |
AllocateAnyPages, | |
EfiACPIMemoryNVS, | |
EFI_SIZE_TO_PAGES (sizeof (DEBUG_AGENT_MAILBOX) + PcdGet16 (PcdDebugPortHandleBufferSize)), | |
&Address | |
); | |
if (EFI_ERROR (Status)) { | |
DEBUG ((DEBUG_ERROR, "DebugAgent: Cannot install configuration table for mailbox!\n")); | |
CpuDeadLoop (); | |
} | |
DebugTimerInterruptState = SaveAndSetDebugTimerInterrupt (FALSE); | |
NewMailbox = (DEBUG_AGENT_MAILBOX *)(UINTN)Address; | |
// | |
// Copy Mailbox and Debug Port Handle buffer to new location in ACPI NVS memory, because original Mailbox | |
// and Debug Port Handle buffer may be free at runtime, SMM debug agent needs to access them | |
// | |
Mailbox = GetMailboxPointer (); | |
CopyMem (NewMailbox, Mailbox, sizeof (DEBUG_AGENT_MAILBOX)); | |
CopyMem (NewMailbox + 1, (VOID *)(UINTN)Mailbox->DebugPortHandle, PcdGet16 (PcdDebugPortHandleBufferSize)); | |
// | |
// Update Debug Port Handle in new Mailbox | |
// | |
UpdateMailboxContent (NewMailbox, DEBUG_MAILBOX_DEBUG_PORT_HANDLE_INDEX, (UINT64)(UINTN)(NewMailbox + 1)); | |
mMailboxPointer = NewMailbox; | |
DebugTimerInterruptState = SaveAndSetDebugTimerInterrupt (DebugTimerInterruptState); | |
Status = gBS->InstallConfigurationTable (&gEfiDebugAgentGuid, (VOID *)mMailboxPointer); | |
if (EFI_ERROR (Status)) { | |
DEBUG ((DEBUG_ERROR, "DebugAgent: Failed to install configuration for mailbox!\n")); | |
CpuDeadLoop (); | |
} | |
} | |
/** | |
Debug Agent constructor function. | |
@param[in] ImageHandle The firmware allocated handle for the EFI image. | |
@param[in] SystemTable A pointer to the EFI System Table. | |
@retval RETURN_SUCCESS When this function completed. | |
**/ | |
RETURN_STATUS | |
EFIAPI | |
DxeDebugAgentLibConstructor ( | |
IN EFI_HANDLE ImageHandle, | |
IN EFI_SYSTEM_TABLE *SystemTable | |
) | |
{ | |
if (mDxeCoreFlag) { | |
// | |
// Invoke internal constructor function only when DXE core links this library instance | |
// | |
InternalConstructorWorker (); | |
} | |
return RETURN_SUCCESS; | |
} | |
/** | |
Get the pointer to Mailbox from the configuration table. | |
@return Pointer to Mailbox. | |
**/ | |
DEBUG_AGENT_MAILBOX * | |
GetMailboxFromConfigurationTable ( | |
VOID | |
) | |
{ | |
EFI_STATUS Status; | |
DEBUG_AGENT_MAILBOX *Mailbox; | |
Status = EfiGetSystemConfigurationTable (&gEfiDebugAgentGuid, (VOID **)&Mailbox); | |
if ((Status == EFI_SUCCESS) && (Mailbox != NULL)) { | |
VerifyMailboxChecksum (Mailbox); | |
return Mailbox; | |
} else { | |
return NULL; | |
} | |
} | |
/** | |
Get the pointer to Mailbox from the GUIDed HOB. | |
@param[in] HobStart The starting HOB pointer to search from. | |
@return Pointer to Mailbox. | |
**/ | |
DEBUG_AGENT_MAILBOX * | |
GetMailboxFromHob ( | |
IN VOID *HobStart | |
) | |
{ | |
EFI_HOB_GUID_TYPE *GuidHob; | |
UINT64 *MailboxLocation; | |
DEBUG_AGENT_MAILBOX *Mailbox; | |
GuidHob = GetNextGuidHob (&gEfiDebugAgentGuid, HobStart); | |
if (GuidHob == NULL) { | |
return NULL; | |
} | |
MailboxLocation = (UINT64 *)(GET_GUID_HOB_DATA (GuidHob)); | |
Mailbox = (DEBUG_AGENT_MAILBOX *)(UINTN)(*MailboxLocation); | |
VerifyMailboxChecksum (Mailbox); | |
return Mailbox; | |
} | |
/** | |
Get Debug Agent Mailbox pointer. | |
@return Mailbox pointer. | |
**/ | |
DEBUG_AGENT_MAILBOX * | |
GetMailboxPointer ( | |
VOID | |
) | |
{ | |
AcquireMpSpinLock (&mDebugMpContext.MailboxSpinLock); | |
VerifyMailboxChecksum (mMailboxPointer); | |
ReleaseMpSpinLock (&mDebugMpContext.MailboxSpinLock); | |
return mMailboxPointer; | |
} | |
/** | |
Get debug port handle. | |
@return Debug port handle. | |
**/ | |
DEBUG_PORT_HANDLE | |
GetDebugPortHandle ( | |
VOID | |
) | |
{ | |
return (DEBUG_PORT_HANDLE)(UINTN)(GetMailboxPointer ()->DebugPortHandle); | |
} | |
/** | |
Worker function to set up Debug Agent environment. | |
This function will set up IDT table and initialize the IDT entries and | |
initialize CPU LOCAL APIC timer. | |
It also tries to connect HOST if Debug Agent was not initialized before. | |
@param[in] Mailbox Pointer to Mailbox. | |
**/ | |
VOID | |
SetupDebugAgentEnvironment ( | |
IN DEBUG_AGENT_MAILBOX *Mailbox | |
) | |
{ | |
IA32_DESCRIPTOR Idtr; | |
UINT16 IdtEntryCount; | |
UINT64 DebugPortHandle; | |
UINT32 DebugTimerFrequency; | |
if (mMultiProcessorDebugSupport) { | |
InitializeSpinLock (&mDebugMpContext.MpContextSpinLock); | |
InitializeSpinLock (&mDebugMpContext.DebugPortSpinLock); | |
InitializeSpinLock (&mDebugMpContext.MailboxSpinLock); | |
// | |
// Clear Break CPU index value | |
// | |
mDebugMpContext.BreakAtCpuIndex = (UINT32)-1; | |
} | |
// | |
// Get original IDT address and size. | |
// | |
AsmReadIdtr ((IA32_DESCRIPTOR *)&Idtr); | |
IdtEntryCount = (UINT16)((Idtr.Limit + 1) / sizeof (IA32_IDT_GATE_DESCRIPTOR)); | |
if (IdtEntryCount < 33) { | |
ZeroMem (&mIdtEntryTable, sizeof (IA32_IDT_GATE_DESCRIPTOR) * 33); | |
// | |
// Copy original IDT table into new one | |
// | |
CopyMem (&mIdtEntryTable, (VOID *)Idtr.Base, Idtr.Limit + 1); | |
// | |
// Load new IDT table | |
// | |
Idtr.Limit = (UINT16)(sizeof (IA32_IDT_GATE_DESCRIPTOR) * 33 - 1); | |
Idtr.Base = (UINTN)&mIdtEntryTable; | |
AsmWriteIdtr ((IA32_DESCRIPTOR *)&Idtr); | |
} | |
// | |
// Initialize the IDT table entries to support source level debug. | |
// | |
InitializeDebugIdt (); | |
// | |
// If mMailboxPointer is not set before, set it | |
// | |
if (mMailboxPointer == NULL) { | |
if (Mailbox != NULL) { | |
// | |
// If Mailbox exists, copy it into one global variable | |
// | |
CopyMem (&mMailbox, Mailbox, sizeof (DEBUG_AGENT_MAILBOX)); | |
} else { | |
ZeroMem (&mMailbox, sizeof (DEBUG_AGENT_MAILBOX)); | |
} | |
mMailboxPointer = &mMailbox; | |
} | |
// | |
// Initialize Debug Timer hardware and save its initial count and frequency | |
// | |
mDebugMpContext.DebugTimerInitCount = InitializeDebugTimer (&DebugTimerFrequency, TRUE); | |
UpdateMailboxContent (mMailboxPointer, DEBUG_MAILBOX_DEBUG_TIMER_FREQUENCY, DebugTimerFrequency); | |
// | |
// Initialize debug communication port | |
// | |
DebugPortHandle = (UINT64)(UINTN)DebugPortInitialize ((VOID *)(UINTN)mMailboxPointer->DebugPortHandle, NULL); | |
UpdateMailboxContent (mMailboxPointer, DEBUG_MAILBOX_DEBUG_PORT_HANDLE_INDEX, DebugPortHandle); | |
if (Mailbox == NULL) { | |
// | |
// Trigger one software interrupt to inform HOST | |
// | |
TriggerSoftInterrupt (SYSTEM_RESET_SIGNATURE); | |
SetDebugFlag (DEBUG_AGENT_FLAG_MEMORY_READY, 1); | |
// | |
// Memory has been ready | |
// | |
if (IsHostAttached ()) { | |
// | |
// Trigger one software interrupt to inform HOST | |
// | |
TriggerSoftInterrupt (MEMORY_READY_SIGNATURE); | |
} | |
} | |
} | |
/** | |
Initialize debug agent. | |
This function is used to set up debug environment for DXE phase. | |
If this function is called by DXE Core, Context must be the pointer | |
to HOB list which will be used to get GUIDed HOB. It will enable | |
interrupt to support break-in feature. | |
If this function is called by DXE module, Context must be NULL. It | |
will enable interrupt to support break-in feature. | |
@param[in] InitFlag Init flag is used to decide initialize process. | |
@param[in] Context Context needed according to InitFlag. | |
@param[in] Function Continue function called by debug agent library; it was | |
optional. | |
**/ | |
VOID | |
EFIAPI | |
InitializeDebugAgent ( | |
IN UINT32 InitFlag, | |
IN VOID *Context OPTIONAL, | |
IN DEBUG_AGENT_CONTINUE Function OPTIONAL | |
) | |
{ | |
UINT64 *MailboxLocation; | |
DEBUG_AGENT_MAILBOX *Mailbox; | |
BOOLEAN InterruptStatus; | |
VOID *HobList; | |
IA32_DESCRIPTOR IdtDescriptor; | |
IA32_DESCRIPTOR *Ia32Idtr; | |
IA32_IDT_ENTRY *Ia32IdtEntry; | |
BOOLEAN PeriodicMode; | |
UINTN TimerCycle; | |
if (InitFlag == DEBUG_AGENT_INIT_DXE_AP) { | |
// | |
// Check if CPU APIC Timer is working, otherwise initialize it. | |
// | |
InitializeLocalApicSoftwareEnable (TRUE); | |
GetApicTimerState (NULL, &PeriodicMode, NULL); | |
TimerCycle = GetApicTimerInitCount (); | |
if (!PeriodicMode || (TimerCycle == 0)) { | |
InitializeDebugTimer (NULL, FALSE); | |
} | |
// | |
// Invoked by AP, enable interrupt to let AP could receive IPI from other processors | |
// | |
EnableInterrupts (); | |
return; | |
} | |
// | |
// Disable Debug Timer interrupt | |
// | |
SaveAndSetDebugTimerInterrupt (FALSE); | |
// | |
// Save and disable original interrupt status | |
// | |
InterruptStatus = SaveAndDisableInterrupts (); | |
// | |
// Try to get mailbox firstly | |
// | |
HobList = NULL; | |
Mailbox = NULL; | |
MailboxLocation = NULL; | |
switch (InitFlag) { | |
case DEBUG_AGENT_INIT_DXE_LOAD: | |
// | |
// Check if Debug Agent has been initialized before | |
// | |
if (IsDebugAgentInitialzed ()) { | |
DEBUG ((DEBUG_INFO, "Debug Agent: The former agent will be overwritten by the new one!\n")); | |
} | |
mMultiProcessorDebugSupport = TRUE; | |
// | |
// Save original IDT table | |
// | |
AsmReadIdtr (&IdtDescriptor); | |
mSaveIdtTableSize = IdtDescriptor.Limit + 1; | |
mSavedIdtTable = AllocateCopyPool (mSaveIdtTableSize, (VOID *)IdtDescriptor.Base); | |
// | |
// Check if Debug Agent initialized in DXE phase | |
// | |
Mailbox = GetMailboxFromConfigurationTable (); | |
if (Mailbox == NULL) { | |
// | |
// Try to get mailbox from GUIDed HOB build in PEI | |
// | |
HobList = GetHobList (); | |
Mailbox = GetMailboxFromHob (HobList); | |
} | |
// | |
// Set up Debug Agent Environment and try to connect HOST if required | |
// | |
SetupDebugAgentEnvironment (Mailbox); | |
// | |
// For DEBUG_AGENT_INIT_S3, needn't to install configuration table and EFI Serial IO protocol | |
// For DEBUG_AGENT_INIT_DXE_CORE, InternalConstructorWorker() will invoked in Constructor() | |
// | |
InternalConstructorWorker (); | |
// | |
// Enable Debug Timer interrupt | |
// | |
SaveAndSetDebugTimerInterrupt (TRUE); | |
// | |
// Enable interrupt to receive Debug Timer interrupt | |
// | |
EnableInterrupts (); | |
mDebugAgentInitialized = TRUE; | |
FindAndReportModuleImageInfo (SIZE_4KB); | |
*(EFI_STATUS *)Context = EFI_SUCCESS; | |
break; | |
case DEBUG_AGENT_INIT_DXE_UNLOAD: | |
if (mDebugAgentInitialized) { | |
if (IsHostAttached ()) { | |
*(EFI_STATUS *)Context = EFI_ACCESS_DENIED; | |
// | |
// Enable Debug Timer interrupt again | |
// | |
SaveAndSetDebugTimerInterrupt (TRUE); | |
} else { | |
// | |
// Restore original IDT table | |
// | |
AsmReadIdtr (&IdtDescriptor); | |
IdtDescriptor.Limit = (UINT16)(mSaveIdtTableSize - 1); | |
CopyMem ((VOID *)IdtDescriptor.Base, mSavedIdtTable, mSaveIdtTableSize); | |
AsmWriteIdtr (&IdtDescriptor); | |
FreePool (mSavedIdtTable); | |
mDebugAgentInitialized = FALSE; | |
*(EFI_STATUS *)Context = EFI_SUCCESS; | |
} | |
} else { | |
*(EFI_STATUS *)Context = EFI_NOT_STARTED; | |
} | |
// | |
// Restore interrupt state. | |
// | |
SetInterruptState (InterruptStatus); | |
break; | |
case DEBUG_AGENT_INIT_DXE_CORE: | |
mDxeCoreFlag = TRUE; | |
mMultiProcessorDebugSupport = TRUE; | |
// | |
// Try to get mailbox from GUIDed HOB build in PEI | |
// | |
HobList = Context; | |
Mailbox = GetMailboxFromHob (HobList); | |
// | |
// Set up Debug Agent Environment and try to connect HOST if required | |
// | |
SetupDebugAgentEnvironment (Mailbox); | |
// | |
// Enable Debug Timer interrupt | |
// | |
SaveAndSetDebugTimerInterrupt (TRUE); | |
// | |
// Enable interrupt to receive Debug Timer interrupt | |
// | |
EnableInterrupts (); | |
break; | |
case DEBUG_AGENT_INIT_S3: | |
if (Context != NULL) { | |
Ia32Idtr = (IA32_DESCRIPTOR *)Context; | |
Ia32IdtEntry = (IA32_IDT_ENTRY *)(Ia32Idtr->Base); | |
MailboxLocation = (UINT64 *)((UINTN)Ia32IdtEntry[DEBUG_MAILBOX_VECTOR].Bits.OffsetLow + | |
((UINTN)Ia32IdtEntry[DEBUG_MAILBOX_VECTOR].Bits.OffsetHigh << 16)); | |
Mailbox = (DEBUG_AGENT_MAILBOX *)(UINTN)(*MailboxLocation); | |
VerifyMailboxChecksum (Mailbox); | |
} | |
// | |
// Save Mailbox pointer in global variable | |
// | |
mMailboxPointer = Mailbox; | |
// | |
// Set up Debug Agent Environment and try to connect HOST if required | |
// | |
SetupDebugAgentEnvironment (Mailbox); | |
// | |
// Disable interrupt | |
// | |
DisableInterrupts (); | |
FindAndReportModuleImageInfo (SIZE_4KB); | |
if (GetDebugFlag (DEBUG_AGENT_FLAG_BREAK_BOOT_SCRIPT) == 1) { | |
// | |
// If Boot Script entry break is set, code will be break at here. | |
// | |
CpuBreakpoint (); | |
} | |
break; | |
default: | |
// | |
// Only DEBUG_AGENT_INIT_PREMEM_SEC and DEBUG_AGENT_INIT_POSTMEM_SEC are allowed for this | |
// Debug Agent library instance. | |
// | |
DEBUG ((DEBUG_ERROR, "Debug Agent: The InitFlag value is not allowed!\n")); | |
CpuDeadLoop (); | |
break; | |
} | |
} |