/** @file | |
Unit tests of the CpuExceptionHandlerLib. | |
Copyright (c) 2022, Intel Corporation. All rights reserved.<BR> | |
SPDX-License-Identifier: BSD-2-Clause-Patent | |
**/ | |
#include "CpuExceptionHandlerTest.h" | |
// | |
// Length of the assembly falut instruction. | |
// | |
UINTN mFaultInstructionLength = 0; | |
EFI_EXCEPTION_TYPE mExceptionType = 256; | |
UINTN mNumberOfProcessors = 1; | |
UINTN mRspAddress[2] = { 0 }; | |
// | |
// Error code flag indicating whether or not an error code will be | |
// pushed on the stack if an exception occurs. | |
// | |
// 1 means an error code will be pushed, otherwise 0 | |
// | |
CONST UINT32 mErrorCodeExceptionFlag = 0x20227d00; | |
/** | |
Special handler for exception triggered by INTn instruction. | |
This hanlder only modifies a global variable for check. | |
@param ExceptionType Exception type. | |
@param SystemContext Pointer to EFI_SYSTEM_CONTEXT. | |
**/ | |
VOID | |
EFIAPI | |
INTnExceptionHandler ( | |
IN EFI_EXCEPTION_TYPE ExceptionType, | |
IN EFI_SYSTEM_CONTEXT SystemContext | |
) | |
{ | |
mExceptionType = ExceptionType; | |
} | |
/** | |
Restore cpu original registers before exit test case. | |
@param[in] Buffer Argument of the procedure. | |
**/ | |
VOID | |
EFIAPI | |
RestoreRegistersPerCpu ( | |
IN VOID *Buffer | |
) | |
{ | |
CPU_REGISTER_BUFFER *CpuOriginalRegisterBuffer; | |
UINT16 Tr; | |
IA32_TSS_DESCRIPTOR *Tss; | |
CpuOriginalRegisterBuffer = (CPU_REGISTER_BUFFER *)Buffer; | |
AsmWriteGdtr (&(CpuOriginalRegisterBuffer->OriginalGdtr)); | |
AsmWriteIdtr (&(CpuOriginalRegisterBuffer->OriginalIdtr)); | |
Tr = CpuOriginalRegisterBuffer->Tr; | |
if ((Tr != 0) && (Tr < CpuOriginalRegisterBuffer->OriginalGdtr.Limit)) { | |
Tss = (IA32_TSS_DESCRIPTOR *)(CpuOriginalRegisterBuffer->OriginalGdtr.Base + Tr); | |
if (Tss->Bits.P == 1) { | |
// | |
// Clear busy bit of TSS before write Tr | |
// | |
Tss->Bits.Type &= 0xD; | |
AsmWriteTr (Tr); | |
} | |
} | |
} | |
/** | |
Restore cpu original registers before exit test case. | |
@param[in] MpServices MpServices. | |
@param[in] CpuOriginalRegisterBuffer Address of CpuOriginalRegisterBuffer. | |
@param[in] BspProcessorNum Bsp processor number. | |
**/ | |
VOID | |
RestoreAllCpuRegisters ( | |
MP_SERVICES *MpServices, OPTIONAL | |
CPU_REGISTER_BUFFER *CpuOriginalRegisterBuffer, | |
UINTN BspProcessorNum | |
) | |
{ | |
UINTN Index; | |
EFI_STATUS Status; | |
for (Index = 0; Index < mNumberOfProcessors; ++Index) { | |
if (Index == BspProcessorNum) { | |
RestoreRegistersPerCpu ((VOID *)&CpuOriginalRegisterBuffer[Index]); | |
continue; | |
} | |
ASSERT (MpServices != NULL); | |
Status = MpServicesUnitTestStartupThisAP ( | |
*MpServices, | |
(EFI_AP_PROCEDURE)RestoreRegistersPerCpu, | |
Index, | |
0, | |
(VOID *)&CpuOriginalRegisterBuffer[Index] | |
); | |
ASSERT_EFI_ERROR (Status); | |
} | |
} | |
/** | |
Store cpu registers before the test case starts. | |
@param[in] Buffer Argument of the procedure. | |
**/ | |
VOID | |
EFIAPI | |
SaveRegisterPerCpu ( | |
IN VOID *Buffer | |
) | |
{ | |
CPU_REGISTER_BUFFER *CpuOriginalRegisterBuffer; | |
IA32_DESCRIPTOR Gdtr; | |
IA32_DESCRIPTOR Idtr; | |
CpuOriginalRegisterBuffer = (CPU_REGISTER_BUFFER *)Buffer; | |
AsmReadGdtr (&Gdtr); | |
AsmReadIdtr (&Idtr); | |
CpuOriginalRegisterBuffer->OriginalGdtr.Base = Gdtr.Base; | |
CpuOriginalRegisterBuffer->OriginalGdtr.Limit = Gdtr.Limit; | |
CpuOriginalRegisterBuffer->OriginalIdtr.Base = Idtr.Base; | |
CpuOriginalRegisterBuffer->OriginalIdtr.Limit = Idtr.Limit; | |
CpuOriginalRegisterBuffer->Tr = AsmReadTr (); | |
} | |
/** | |
Store cpu registers before the test case starts. | |
@param[in] MpServices MpServices. | |
@param[in] BspProcessorNum Bsp processor number. | |
@return Pointer to the allocated CPU_REGISTER_BUFFER. | |
**/ | |
CPU_REGISTER_BUFFER * | |
SaveAllCpuRegisters ( | |
MP_SERVICES *MpServices, OPTIONAL | |
UINTN BspProcessorNum | |
) | |
{ | |
CPU_REGISTER_BUFFER *CpuOriginalRegisterBuffer; | |
EFI_STATUS Status; | |
UINTN Index; | |
CpuOriginalRegisterBuffer = AllocateZeroPool (mNumberOfProcessors * sizeof (CPU_REGISTER_BUFFER)); | |
ASSERT (CpuOriginalRegisterBuffer != NULL); | |
for (Index = 0; Index < mNumberOfProcessors; ++Index) { | |
if (Index == BspProcessorNum) { | |
SaveRegisterPerCpu ((VOID *)&CpuOriginalRegisterBuffer[Index]); | |
continue; | |
} | |
ASSERT (MpServices != NULL); | |
Status = MpServicesUnitTestStartupThisAP ( | |
*MpServices, | |
(EFI_AP_PROCEDURE)SaveRegisterPerCpu, | |
Index, | |
0, | |
(VOID *)&CpuOriginalRegisterBuffer[Index] | |
); | |
ASSERT_EFI_ERROR (Status); | |
} | |
return CpuOriginalRegisterBuffer; | |
} | |
/** | |
Initialize Ap Idt Procedure. | |
@param[in] Buffer Argument of the procedure. | |
**/ | |
VOID | |
EFIAPI | |
InitializeIdtPerAp ( | |
IN VOID *Buffer | |
) | |
{ | |
AsmWriteIdtr (Buffer); | |
} | |
/** | |
Initialize all Ap Idt. | |
@param[in] MpServices MpServices. | |
@param[in] BspIdtr Pointer to IA32_DESCRIPTOR allocated by Bsp. | |
**/ | |
VOID | |
InitializeApIdt ( | |
MP_SERVICES MpServices, | |
VOID *BspIdtr | |
) | |
{ | |
EFI_STATUS Status; | |
Status = MpServicesUnitTestStartupAllAPs ( | |
MpServices, | |
(EFI_AP_PROCEDURE)InitializeIdtPerAp, | |
FALSE, | |
0, | |
BspIdtr | |
); | |
ASSERT_EFI_ERROR (Status); | |
} | |
/** | |
Check if exception handler can registered/unregistered for no error code exception. | |
@param[in] Context [Optional] An optional parameter that enables: | |
1) test-case reuse with varied parameters and | |
2) test-case re-entry for Target tests that need a | |
reboot. This parameter is a VOID* and it is the | |
responsibility of the test author to ensure that the | |
contents are well understood by all test cases that may | |
consume it. | |
@retval UNIT_TEST_PASSED The Unit test has completed and the test | |
case was successful. | |
@retval UNIT_TEST_ERROR_TEST_FAILED A test case assertion has failed. | |
**/ | |
UNIT_TEST_STATUS | |
EFIAPI | |
TestRegisterHandlerForNoErrorCodeException ( | |
IN UNIT_TEST_CONTEXT Context | |
) | |
{ | |
EFI_STATUS Status; | |
UINTN Index; | |
CPU_REGISTER_BUFFER *CpuOriginalRegisterBuffer; | |
VOID *NewIdtr; | |
CpuOriginalRegisterBuffer = SaveAllCpuRegisters (NULL, 0); | |
NewIdtr = InitializeBspIdt (); | |
Status = InitializeCpuExceptionHandlers (NULL); | |
UT_ASSERT_EQUAL (Status, EFI_SUCCESS); | |
for (Index = 0; Index < SPEC_MAX_EXCEPTION_NUM; Index++) { | |
// | |
// Only test no error code exception by INT n instruction. | |
// | |
if ((mErrorCodeExceptionFlag & (1 << Index)) != 0) { | |
continue; | |
} | |
DEBUG ((DEBUG_INFO, "TestCase1: ExceptionType is %d\n", Index)); | |
Status = RegisterCpuInterruptHandler (Index, INTnExceptionHandler); | |
UT_ASSERT_EQUAL (Status, EFI_SUCCESS); | |
TriggerINTnException (Index); | |
UT_ASSERT_EQUAL (mExceptionType, Index); | |
Status = RegisterCpuInterruptHandler (Index, NULL); | |
UT_ASSERT_EQUAL (Status, EFI_SUCCESS); | |
} | |
RestoreAllCpuRegisters (NULL, CpuOriginalRegisterBuffer, 0); | |
FreePool (CpuOriginalRegisterBuffer); | |
FreePool (NewIdtr); | |
return UNIT_TEST_PASSED; | |
} | |
/** | |
Get Bsp stack base. | |
@param[out] StackBase Pointer to stack base of BSP. | |
**/ | |
VOID | |
GetBspStackBase ( | |
OUT UINTN *StackBase | |
) | |
{ | |
EFI_PEI_HOB_POINTERS Hob; | |
EFI_HOB_MEMORY_ALLOCATION *MemoryHob; | |
// | |
// Get the base of stack from Hob. | |
// | |
ASSERT (StackBase != NULL); | |
Hob.Raw = GetHobList (); | |
while ((Hob.Raw = GetNextHob (EFI_HOB_TYPE_MEMORY_ALLOCATION, Hob.Raw)) != NULL) { | |
MemoryHob = Hob.MemoryAllocation; | |
if (CompareGuid (&gEfiHobMemoryAllocStackGuid, &MemoryHob->AllocDescriptor.Name)) { | |
DEBUG (( | |
DEBUG_INFO, | |
"%a: Bsp StackBase = 0x%016lx StackSize = 0x%016lx\n", | |
__func__, | |
MemoryHob->AllocDescriptor.MemoryBaseAddress, | |
MemoryHob->AllocDescriptor.MemoryLength | |
)); | |
*StackBase = (UINTN)MemoryHob->AllocDescriptor.MemoryBaseAddress; | |
// | |
// Ensure the base of the stack is page-size aligned. | |
// | |
ASSERT ((*StackBase & EFI_PAGE_MASK) == 0); | |
break; | |
} | |
Hob.Raw = GET_NEXT_HOB (Hob); | |
} | |
ASSERT (*StackBase != 0); | |
} | |
/** | |
Get Ap stack base procedure. | |
@param[out] ApStackBase Pointer to Ap stack base. | |
**/ | |
VOID | |
EFIAPI | |
GetStackBasePerAp ( | |
OUT VOID *ApStackBase | |
) | |
{ | |
UINTN ApTopOfStack; | |
ApTopOfStack = ALIGN_VALUE ((UINTN)&ApTopOfStack, (UINTN)PcdGet32 (PcdCpuApStackSize)); | |
*(UINTN *)ApStackBase = ApTopOfStack - (UINTN)PcdGet32 (PcdCpuApStackSize); | |
} | |
/** | |
Get all Cpu stack base. | |
@param[in] MpServices MpServices. | |
@param[in] BspProcessorNum Bsp processor number. | |
@return Pointer to the allocated CpuStackBaseBuffer. | |
**/ | |
UINTN * | |
GetAllCpuStackBase ( | |
MP_SERVICES *MpServices, | |
UINTN BspProcessorNum | |
) | |
{ | |
UINTN *CpuStackBaseBuffer; | |
EFI_STATUS Status; | |
UINTN Index; | |
CpuStackBaseBuffer = AllocateZeroPool (mNumberOfProcessors * sizeof (UINTN)); | |
ASSERT (CpuStackBaseBuffer != NULL); | |
for (Index = 0; Index < mNumberOfProcessors; ++Index) { | |
if (Index == BspProcessorNum) { | |
GetBspStackBase (&CpuStackBaseBuffer[Index]); | |
continue; | |
} | |
ASSERT (MpServices != NULL); | |
Status = MpServicesUnitTestStartupThisAP ( | |
*MpServices, | |
(EFI_AP_PROCEDURE)GetStackBasePerAp, | |
Index, | |
0, | |
(VOID *)&CpuStackBaseBuffer[Index] | |
); | |
ASSERT_EFI_ERROR (Status); | |
DEBUG ((DEBUG_INFO, "AP[%d] StackBase = 0x%x\n", Index, CpuStackBaseBuffer[Index])); | |
} | |
return CpuStackBaseBuffer; | |
} | |
/** | |
Find not present or ReadOnly address in page table. | |
@param[out] PFAddress Access to the address which is not permitted will trigger PF exceptions. | |
@retval TRUE Found not present or ReadOnly address in page table. | |
@retval FALSE Failed to found PFAddress in page table. | |
**/ | |
BOOLEAN | |
FindPFAddressInPageTable ( | |
OUT UINTN *PFAddress | |
) | |
{ | |
IA32_CR0 Cr0; | |
IA32_CR4 Cr4; | |
UINTN PageTable; | |
PAGING_MODE PagingMode; | |
BOOLEAN Enable5LevelPaging; | |
RETURN_STATUS Status; | |
IA32_MAP_ENTRY *Map; | |
UINTN MapCount; | |
UINTN Index; | |
UINTN PreviousAddress; | |
ASSERT (PFAddress != NULL); | |
Cr0.UintN = AsmReadCr0 (); | |
if (Cr0.Bits.PG == 0) { | |
return FALSE; | |
} | |
PageTable = AsmReadCr3 (); | |
Cr4.UintN = AsmReadCr4 (); | |
if (sizeof (UINTN) == sizeof (UINT32)) { | |
ASSERT (Cr4.Bits.PAE == 1); | |
PagingMode = PagingPae; | |
} else { | |
Enable5LevelPaging = (BOOLEAN)(Cr4.Bits.LA57 == 1); | |
PagingMode = Enable5LevelPaging ? Paging5Level : Paging4Level; | |
} | |
MapCount = 0; | |
Status = PageTableParse (PageTable, PagingMode, NULL, &MapCount); | |
ASSERT (Status == RETURN_BUFFER_TOO_SMALL); | |
Map = AllocatePages (EFI_SIZE_TO_PAGES (MapCount * sizeof (IA32_MAP_ENTRY))); | |
Status = PageTableParse (PageTable, PagingMode, Map, &MapCount); | |
ASSERT (Status == RETURN_SUCCESS); | |
PreviousAddress = 0; | |
for (Index = 0; Index < MapCount; Index++) { | |
DEBUG (( | |
DEBUG_ERROR, | |
"%02d: %016lx - %016lx, %016lx\n", | |
Index, | |
Map[Index].LinearAddress, | |
Map[Index].LinearAddress + Map[Index].Length, | |
Map[Index].Attribute.Uint64 | |
)); | |
// | |
// Not present address in page table. | |
// | |
if (Map[Index].LinearAddress > PreviousAddress) { | |
*PFAddress = PreviousAddress; | |
return TRUE; | |
} | |
PreviousAddress = (UINTN)(Map[Index].LinearAddress + Map[Index].Length); | |
// | |
// ReadOnly address in page table. | |
// | |
if ((Cr0.Bits.WP != 0) && (Map[Index].Attribute.Bits.ReadWrite == 0)) { | |
*PFAddress = (UINTN)Map[Index].LinearAddress; | |
return TRUE; | |
} | |
} | |
return FALSE; | |
} | |
/** | |
Test if exception handler can registered/unregistered for GP and PF. | |
@param[in] Context [Optional] An optional parameter that enables: | |
1) test-case reuse with varied parameters and | |
2) test-case re-entry for Target tests that need a | |
reboot. This parameter is a VOID* and it is the | |
responsibility of the test author to ensure that the | |
contents are well understood by all test cases that may | |
consume it. | |
@retval UNIT_TEST_PASSED The Unit test has completed and the test | |
case was successful. | |
@retval UNIT_TEST_ERROR_TEST_FAILED A test case assertion has failed. | |
**/ | |
UNIT_TEST_STATUS | |
EFIAPI | |
TestRegisterHandlerForGPAndPF ( | |
IN UNIT_TEST_CONTEXT Context | |
) | |
{ | |
EFI_STATUS Status; | |
CPU_REGISTER_BUFFER *CpuOriginalRegisterBuffer; | |
UINTN PFAddress; | |
VOID *NewIdtr; | |
PFAddress = 0; | |
CpuOriginalRegisterBuffer = SaveAllCpuRegisters (NULL, 0); | |
NewIdtr = InitializeBspIdt (); | |
Status = InitializeCpuExceptionHandlers (NULL); | |
UT_ASSERT_EQUAL (Status, EFI_SUCCESS); | |
// | |
// GP exception. | |
// | |
DEBUG ((DEBUG_INFO, "TestCase2: ExceptionType is %d\n", EXCEPT_IA32_GP_FAULT)); | |
Status = RegisterCpuInterruptHandler (EXCEPT_IA32_GP_FAULT, AdjustRipForFaultHandler); | |
UT_ASSERT_EQUAL (Status, EFI_SUCCESS); | |
TriggerGPException (CR4_RESERVED_BIT); | |
UT_ASSERT_EQUAL (mExceptionType, EXCEPT_IA32_GP_FAULT); | |
Status = RegisterCpuInterruptHandler (EXCEPT_IA32_GP_FAULT, NULL); | |
UT_ASSERT_EQUAL (Status, EFI_SUCCESS); | |
// | |
// PF exception. | |
// | |
if (FindPFAddressInPageTable (&PFAddress)) { | |
DEBUG ((DEBUG_INFO, "TestCase2: ExceptionType is %d\n", EXCEPT_IA32_PAGE_FAULT)); | |
Status = RegisterCpuInterruptHandler (EXCEPT_IA32_PAGE_FAULT, AdjustRipForFaultHandler); | |
UT_ASSERT_EQUAL (Status, EFI_SUCCESS); | |
TriggerPFException (PFAddress); | |
UT_ASSERT_EQUAL (mExceptionType, EXCEPT_IA32_PAGE_FAULT); | |
Status = RegisterCpuInterruptHandler (EXCEPT_IA32_PAGE_FAULT, NULL); | |
UT_ASSERT_EQUAL (Status, EFI_SUCCESS); | |
} | |
RestoreAllCpuRegisters (NULL, CpuOriginalRegisterBuffer, 0); | |
FreePool (CpuOriginalRegisterBuffer); | |
FreePool (NewIdtr); | |
return UNIT_TEST_PASSED; | |
} | |
/** | |
Test if Cpu Context is consistent before and after exception. | |
@param[in] Context [Optional] An optional parameter that enables: | |
1) test-case reuse with varied parameters and | |
2) test-case re-entry for Target tests that need a | |
reboot. This parameter is a VOID* and it is the | |
responsibility of the test author to ensure that the | |
contents are well understood by all test cases that may | |
consume it. | |
@retval UNIT_TEST_PASSED The Unit test has completed and the test | |
case was successful. | |
@retval UNIT_TEST_ERROR_TEST_FAILED A test case assertion has failed. | |
**/ | |
UNIT_TEST_STATUS | |
EFIAPI | |
TestCpuContextConsistency ( | |
IN UNIT_TEST_CONTEXT Context | |
) | |
{ | |
EFI_STATUS Status; | |
UINTN Index; | |
CPU_REGISTER_BUFFER *CpuOriginalRegisterBuffer; | |
UINTN FaultParameter; | |
VOID *NewIdtr; | |
FaultParameter = 0; | |
CpuOriginalRegisterBuffer = SaveAllCpuRegisters (NULL, 0); | |
NewIdtr = InitializeBspIdt (); | |
Status = InitializeCpuExceptionHandlers (NULL); | |
UT_ASSERT_EQUAL (Status, EFI_SUCCESS); | |
for (Index = 0; Index < 22; Index++) { | |
if (Index == EXCEPT_IA32_PAGE_FAULT) { | |
if (!FindPFAddressInPageTable (&FaultParameter)) { | |
continue; | |
} | |
} else if (Index == EXCEPT_IA32_GP_FAULT) { | |
FaultParameter = CR4_RESERVED_BIT; | |
} else { | |
if ((mErrorCodeExceptionFlag & (1 << Index)) != 0) { | |
continue; | |
} | |
} | |
DEBUG ((DEBUG_INFO, "TestCase3: ExceptionType is %d\n", Index)); | |
Status = RegisterCpuInterruptHandler (Index, AdjustCpuContextHandler); | |
UT_ASSERT_EQUAL (Status, EFI_SUCCESS); | |
// | |
// Trigger different type exception and compare different stage cpu context. | |
// | |
AsmTestConsistencyOfCpuContext (Index, FaultParameter); | |
CompareCpuContext (); | |
Status = RegisterCpuInterruptHandler (Index, NULL); | |
UT_ASSERT_EQUAL (Status, EFI_SUCCESS); | |
} | |
RestoreAllCpuRegisters (NULL, CpuOriginalRegisterBuffer, 0); | |
FreePool (CpuOriginalRegisterBuffer); | |
FreePool (NewIdtr); | |
return UNIT_TEST_PASSED; | |
} | |
/** | |
Initializes CPU exceptions handlers for the sake of stack switch requirement. | |
This function is a wrapper of InitializeSeparateExceptionStacks. It's mainly | |
for the sake of AP's init because of EFI_AP_PROCEDURE API requirement. | |
@param[in,out] Buffer The pointer to private data buffer. | |
**/ | |
VOID | |
EFIAPI | |
InitializeExceptionStackSwitchHandlersPerAp ( | |
IN OUT VOID *Buffer | |
) | |
{ | |
EXCEPTION_STACK_SWITCH_CONTEXT *CpuSwitchStackData; | |
CpuSwitchStackData = (EXCEPTION_STACK_SWITCH_CONTEXT *)Buffer; | |
// | |
// This may be called twice for each Cpu. Only run InitializeSeparateExceptionStacks | |
// if this is the first call or the first call failed because of size too small. | |
// | |
if ((CpuSwitchStackData->Status == EFI_NOT_STARTED) || (CpuSwitchStackData->Status == EFI_BUFFER_TOO_SMALL)) { | |
CpuSwitchStackData->Status = InitializeSeparateExceptionStacks (CpuSwitchStackData->Buffer, &CpuSwitchStackData->BufferSize); | |
} | |
} | |
/** | |
Initializes MP exceptions handlers for the sake of stack switch requirement. | |
This function will allocate required resources required to setup stack switch | |
and pass them through SwitchStackData to each logic processor. | |
@param[in, out] MpServices MpServices. | |
@param[in, out] BspProcessorNum Bsp processor number. | |
@return Pointer to the allocated SwitchStackData. | |
**/ | |
EXCEPTION_STACK_SWITCH_CONTEXT * | |
InitializeMpExceptionStackSwitchHandlers ( | |
MP_SERVICES MpServices, | |
UINTN BspProcessorNum | |
) | |
{ | |
UINTN Index; | |
EXCEPTION_STACK_SWITCH_CONTEXT *SwitchStackData; | |
UINTN BufferSize; | |
EFI_STATUS Status; | |
UINT8 *Buffer; | |
SwitchStackData = AllocateZeroPool (mNumberOfProcessors * sizeof (EXCEPTION_STACK_SWITCH_CONTEXT)); | |
ASSERT (SwitchStackData != NULL); | |
for (Index = 0; Index < mNumberOfProcessors; ++Index) { | |
// | |
// Because the procedure may runs multiple times, use the status EFI_NOT_STARTED | |
// to indicate the procedure haven't been run yet. | |
// | |
SwitchStackData[Index].Status = EFI_NOT_STARTED; | |
if (Index == BspProcessorNum) { | |
InitializeExceptionStackSwitchHandlersPerAp ((VOID *)&SwitchStackData[Index]); | |
continue; | |
} | |
Status = MpServicesUnitTestStartupThisAP ( | |
MpServices, | |
InitializeExceptionStackSwitchHandlersPerAp, | |
Index, | |
0, | |
(VOID *)&SwitchStackData[Index] | |
); | |
ASSERT_EFI_ERROR (Status); | |
} | |
BufferSize = 0; | |
for (Index = 0; Index < mNumberOfProcessors; ++Index) { | |
if (SwitchStackData[Index].Status == EFI_BUFFER_TOO_SMALL) { | |
ASSERT (SwitchStackData[Index].BufferSize != 0); | |
BufferSize += SwitchStackData[Index].BufferSize; | |
} else { | |
ASSERT (SwitchStackData[Index].Status == EFI_SUCCESS); | |
ASSERT (SwitchStackData[Index].BufferSize == 0); | |
} | |
} | |
if (BufferSize != 0) { | |
Buffer = AllocateZeroPool (BufferSize); | |
ASSERT (Buffer != NULL); | |
BufferSize = 0; | |
for (Index = 0; Index < mNumberOfProcessors; ++Index) { | |
if (SwitchStackData[Index].Status == EFI_BUFFER_TOO_SMALL) { | |
SwitchStackData[Index].Buffer = (VOID *)(&Buffer[BufferSize]); | |
BufferSize += SwitchStackData[Index].BufferSize; | |
DEBUG (( | |
DEBUG_INFO, | |
"Buffer[cpu%lu] for InitializeExceptionStackSwitchHandlersPerAp: 0x%lX with size 0x%lX\n", | |
(UINT64)(UINTN)Index, | |
(UINT64)(UINTN)SwitchStackData[Index].Buffer, | |
(UINT64)(UINTN)SwitchStackData[Index].BufferSize | |
)); | |
} | |
} | |
for (Index = 0; Index < mNumberOfProcessors; ++Index) { | |
if (Index == BspProcessorNum) { | |
InitializeExceptionStackSwitchHandlersPerAp ((VOID *)&SwitchStackData[Index]); | |
continue; | |
} | |
Status = MpServicesUnitTestStartupThisAP ( | |
MpServices, | |
InitializeExceptionStackSwitchHandlersPerAp, | |
Index, | |
0, | |
(VOID *)&SwitchStackData[Index] | |
); | |
ASSERT_EFI_ERROR (Status); | |
} | |
for (Index = 0; Index < mNumberOfProcessors; ++Index) { | |
ASSERT (SwitchStackData[Index].Status == EFI_SUCCESS); | |
} | |
} | |
return SwitchStackData; | |
} | |
/** | |
Test if stack overflow is captured by CpuStackGuard in both Bsp and AP. | |
@param[in] Context [Optional] An optional parameter that enables: | |
1) test-case reuse with varied parameters and | |
2) test-case re-entry for Target tests that need a | |
reboot. This parameter is a VOID* and it is the | |
responsibility of the test author to ensure that the | |
contents are well understood by all test cases that may | |
consume it. | |
@retval UNIT_TEST_PASSED The Unit test has completed and the test | |
case was successful. | |
@retval UNIT_TEST_ERROR_TEST_FAILED A test case assertion has failed. | |
**/ | |
UNIT_TEST_STATUS | |
EFIAPI | |
TestCpuStackGuardInBspAndAp ( | |
IN UNIT_TEST_CONTEXT Context | |
) | |
{ | |
EFI_STATUS Status; | |
UINTN OriginalStackBase; | |
UINTN NewStackTop; | |
UINTN NewStackBase; | |
EXCEPTION_STACK_SWITCH_CONTEXT *SwitchStackData; | |
MP_SERVICES MpServices; | |
UINTN ProcessorNumber; | |
UINTN EnabledProcessorNum; | |
CPU_REGISTER_BUFFER *CpuOriginalRegisterBuffer; | |
UINTN Index; | |
UINTN BspProcessorNum; | |
VOID *NewIdtr; | |
UINTN *CpuStackBaseBuffer; | |
if (!PcdGetBool (PcdCpuStackGuard)) { | |
return UNIT_TEST_PASSED; | |
} | |
// | |
// Get MP Service Protocol | |
// | |
Status = GetMpServices (&MpServices); | |
Status = MpServicesUnitTestGetNumberOfProcessors (MpServices, &ProcessorNumber, &EnabledProcessorNum); | |
UT_ASSERT_EQUAL (Status, EFI_SUCCESS); | |
Status = MpServicesUnitTestWhoAmI (MpServices, &BspProcessorNum); | |
UT_ASSERT_EQUAL (Status, EFI_SUCCESS); | |
mNumberOfProcessors = ProcessorNumber; | |
CpuOriginalRegisterBuffer = SaveAllCpuRegisters (&MpServices, BspProcessorNum); | |
// | |
// Initialize Bsp and AP Idt. | |
// Idt buffer should not be empty or it will hang in MP API. | |
// | |
NewIdtr = InitializeBspIdt (); | |
Status = InitializeCpuExceptionHandlers (NULL); | |
UT_ASSERT_EQUAL (Status, EFI_SUCCESS); | |
InitializeApIdt (MpServices, NewIdtr); | |
// | |
// Get BSP and AP original stack base. | |
// | |
CpuStackBaseBuffer = GetAllCpuStackBase (&MpServices, BspProcessorNum); | |
// | |
// InitializeMpExceptionStackSwitchHandlers and register exception handler. | |
// | |
SwitchStackData = InitializeMpExceptionStackSwitchHandlers (MpServices, BspProcessorNum); | |
Status = RegisterCpuInterruptHandler (EXCEPT_IA32_PAGE_FAULT, CpuStackGuardExceptionHandler); | |
UT_ASSERT_EQUAL (Status, EFI_SUCCESS); | |
Status = RegisterCpuInterruptHandler (EXCEPT_IA32_DOUBLE_FAULT, AdjustRipForFaultHandler); | |
UT_ASSERT_EQUAL (Status, EFI_SUCCESS); | |
for (Index = 0; Index < mNumberOfProcessors; Index++) { | |
OriginalStackBase = CpuStackBaseBuffer[Index]; | |
NewStackTop = (UINTN)(SwitchStackData[Index].Buffer) + SwitchStackData[Index].BufferSize; | |
NewStackBase = (UINTN)(SwitchStackData[Index].Buffer); | |
if (Index == BspProcessorNum) { | |
TriggerStackOverflow (); | |
} else { | |
MpServicesUnitTestStartupThisAP ( | |
MpServices, | |
(EFI_AP_PROCEDURE)TriggerStackOverflow, | |
Index, | |
0, | |
NULL | |
); | |
} | |
DEBUG ((DEBUG_INFO, "TestCase4: mRspAddress[0] is 0x%x, mRspAddress[1] is 0x%x\n", mRspAddress[0], mRspAddress[1])); | |
UT_ASSERT_TRUE ((mRspAddress[0] >= OriginalStackBase) && (mRspAddress[0] <= (OriginalStackBase + SIZE_4KB))); | |
UT_ASSERT_TRUE ((mRspAddress[1] >= NewStackBase) && (mRspAddress[1] < NewStackTop)); | |
} | |
Status = RegisterCpuInterruptHandler (EXCEPT_IA32_PAGE_FAULT, NULL); | |
UT_ASSERT_EQUAL (Status, EFI_SUCCESS); | |
Status = RegisterCpuInterruptHandler (EXCEPT_IA32_DOUBLE_FAULT, NULL); | |
UT_ASSERT_EQUAL (Status, EFI_SUCCESS); | |
RestoreAllCpuRegisters (&MpServices, CpuOriginalRegisterBuffer, BspProcessorNum); | |
FreePool (SwitchStackData); | |
FreePool (CpuOriginalRegisterBuffer); | |
FreePool (NewIdtr); | |
return UNIT_TEST_PASSED; | |
} | |
/** | |
Create CpuExceptionLibUnitTestSuite and add test case. | |
@param[in] FrameworkHandle Unit test framework. | |
@return EFI_SUCCESS The unit test suite was created. | |
@retval EFI_OUT_OF_RESOURCES There are not enough resources available to | |
initialize the unit test suite. | |
**/ | |
EFI_STATUS | |
AddCommonTestCase ( | |
IN UNIT_TEST_FRAMEWORK_HANDLE Framework | |
) | |
{ | |
EFI_STATUS Status; | |
UNIT_TEST_SUITE_HANDLE CpuExceptionLibUnitTestSuite; | |
// | |
// Populate the Manual Test Cases. | |
// | |
Status = CreateUnitTestSuite (&CpuExceptionLibUnitTestSuite, Framework, "Test CpuExceptionHandlerLib", "CpuExceptionHandlerLib.Manual", NULL, NULL); | |
if (EFI_ERROR (Status)) { | |
DEBUG ((DEBUG_ERROR, "Failed in CreateUnitTestSuite for CpuExceptionHandlerLib Test Cases\n")); | |
Status = EFI_OUT_OF_RESOURCES; | |
return Status; | |
} | |
AddTestCase (CpuExceptionLibUnitTestSuite, "Check if exception handler can be registered/unregistered for no error code exception", "TestRegisterHandlerForNoErrorCodeException", TestRegisterHandlerForNoErrorCodeException, NULL, NULL, NULL); | |
AddTestCase (CpuExceptionLibUnitTestSuite, "Check if exception handler can be registered/unregistered for GP and PF", "TestRegisterHandlerForGPAndPF", TestRegisterHandlerForGPAndPF, NULL, NULL, NULL); | |
AddTestCase (CpuExceptionLibUnitTestSuite, "Check if Cpu Context is consistent before and after exception.", "TestCpuContextConsistency", TestCpuContextConsistency, NULL, NULL, NULL); | |
AddTestCase (CpuExceptionLibUnitTestSuite, "Check if stack overflow is captured by CpuStackGuard in Bsp and AP", "TestCpuStackGuardInBspAndAp", TestCpuStackGuardInBspAndAp, NULL, NULL, NULL); | |
return EFI_SUCCESS; | |
} |