mirror of https://github.com/acidanthera/audk.git
853 lines
27 KiB
C
853 lines
27 KiB
C
/** @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",
|
|
__FUNCTION__,
|
|
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;
|
|
}
|