diff --git a/UefiCpuPkg/Library/CpuExceptionHandlerLib/UnitTest/CpuExceptionHandlerTest.h b/UefiCpuPkg/Library/CpuExceptionHandlerLib/UnitTest/CpuExceptionHandlerTest.h new file mode 100644 index 0000000000..936098fde8 --- /dev/null +++ b/UefiCpuPkg/Library/CpuExceptionHandlerLib/UnitTest/CpuExceptionHandlerTest.h @@ -0,0 +1,336 @@ +/** @file + + Copyright (c) 2022, Intel Corporation. All rights reserved.
+ SPDX-License-Identifier: BSD-2-Clause-Patent + + Four test cases are created in this Unit Test module. + a.Test if exception handler can be registered/unregistered for no error code exception + In this test case, only no error code exception is triggered and tested by INTn instruction. + The special hanlder for these exception will modify a global variable for check. + + b.Test if exception handler can be registered/unregistered for GP and PF. + In this test case, GP exception is triggered and tested by setting CR4_RESERVED_BIT to 1. + PF exception is triggered and tested by writting to not-present or RO addres. + The special hanlder for these exceptions will set a global vartiable for check and adjust Rip to return from fault exception. + + c.Test if Cpu Context is consistent before and after exception. + In this test case: + 1. Set Cpu register to mExpectedContextInHandler before exception. + 2. Trigger exception specified by ExceptionType. + 3. Store SystemContext in mActualContextInHandler and set SystemContext to mExpectedContextAfterException in handler. + 4. After return from exception, store Cpu registers in mActualContextAfterException. + The expectation is: + 1. Register values in mActualContextInHandler are the same with register values in mExpectedContextInHandler. + 2. Register values in mActualContextAfterException are the same with register values mActualContextAfterException. + + d.Test if stack overflow can be captured by CpuStackGuard in both Bsp and AP. + In this test case, stack overflow is triggered by a funtion which calls itself continuously. This test case triggers stack + overflow in both BSP and AP. All AP use same Idt with Bsp. The expectation is: + 1. PF exception is triggered (leading to a DF if sepereated stack is not prepared for PF) when Rsp <= StackBase + SIZE_4KB + since [StackBase, StackBase + SIZE_4KB] is marked as not present in page table when PcdCpuStackGuard is TRUE. + 2. Stack for PF/DF exception handler in both Bsp and AP is succussfully switched by InitializeSeparateExceptionStacks. + +**/ + +#ifndef CPU_EXCEPTION_HANDLER_TEST_H_ +#define CPU_EXCEPTION_HANDLER_TEST_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define UNIT_TEST_APP_NAME "Cpu Exception Handler Lib Unit Tests" +#define UNIT_TEST_APP_VERSION "1.0" + +#define CPU_INTERRUPT_NUM 256 +#define SPEC_MAX_EXCEPTION_NUM 22 +#define CR4_RESERVED_BIT BIT15 + +typedef struct { + IA32_DESCRIPTOR OriginalGdtr; + IA32_DESCRIPTOR OriginalIdtr; + UINT16 Tr; +} CPU_REGISTER_BUFFER; + +typedef union { + EDKII_PEI_MP_SERVICES2_PPI *Ppi; + EFI_MP_SERVICES_PROTOCOL *Protocol; +} MP_SERVICES; + +typedef struct { + VOID *Buffer; + UINTN BufferSize; + EFI_STATUS Status; +} EXCEPTION_STACK_SWITCH_CONTEXT; + +typedef struct { + UINT64 Rdi; + UINT64 Rsi; + UINT64 Rbx; + UINT64 Rdx; + UINT64 Rcx; + UINT64 Rax; + UINT64 R8; + UINT64 R9; + UINT64 R10; + UINT64 R11; + UINT64 R12; + UINT64 R13; + UINT64 R14; + UINT64 R15; +} GENERAL_REGISTER; + +extern UINTN mFaultInstructionLength; +extern EFI_EXCEPTION_TYPE mExceptionType; +extern UINTN mRspAddress[]; + +/** + Initialize Bsp Idt with a new Idt table and return the IA32_DESCRIPTOR buffer. + In PEIM, store original PeiServicePointer before new Idt table. + + @return Pointer to the allocated IA32_DESCRIPTOR buffer. +**/ +VOID * +InitializeBspIdt ( + VOID + ); + +/** + Trigger no error code exception by INT n instruction. + + @param[in] ExceptionType No error code exception type. +**/ +VOID +EFIAPI +TriggerINTnException ( + IN EFI_EXCEPTION_TYPE ExceptionType + ); + +/** + Trigger GP exception by setting CR4_RESERVED_BIT to 1. + + @param[in] Cr4ReservedBit Cr4 reserved bit. +**/ +VOID +EFIAPI +TriggerGPException ( + UINTN Cr4ReservedBit + ); + +/** + Trigger PF exception by write to not present or ReadOnly address. + + @param[in] PFAddress Not present or ReadOnly address in page table. +**/ +VOID +EFIAPI +TriggerPFException ( + UINTN PFAddress + ); + +/** + Special handler for fault exception. + This handler sets Rip/Eip in SystemContext to the instruction address after the exception instruction. + + @param ExceptionType Exception type. + @param SystemContext Pointer to EFI_SYSTEM_CONTEXT. +**/ +VOID +EFIAPI +AdjustRipForFaultHandler ( + IN EFI_EXCEPTION_TYPE ExceptionType, + IN EFI_SYSTEM_CONTEXT SystemContext + ); + +/** + Test consistency of Cpu context. Four steps: + 1. Set Cpu register to mExpectedContextInHandler before exception. + 2. Trigger exception specified by ExceptionType. + 3. Store SystemContext in mActualContextInHandler and set SystemContext to mExpectedContextAfterException in handler. + 4. After return from exception, store Cpu registers in mActualContextAfterException. + + Rcx/Ecx in mExpectedContextInHandler is decided by different exception type runtime since Rcx/Ecx is needed in assembly code. + For GP and PF, Rcx/Ecx is set to FaultParameter. For other exception triggered by INTn, Rcx/Ecx is set to ExceptionType. + + @param[in] ExceptionType Exception type. + @param[in] FaultParameter Parameter for GP and PF. OPTIONAL +**/ +VOID +EFIAPI +AsmTestConsistencyOfCpuContext ( + IN EFI_EXCEPTION_TYPE ExceptionType, + IN UINTN FaultParameter OPTIONAL + ); + +/** + Special handler for ConsistencyOfCpuContext test case. General register in SystemContext + is modified to mExpectedContextInHandler in this handler. + + @param ExceptionType Exception type. + @param SystemContext Pointer to EFI_SYSTEM_CONTEXT. +**/ +VOID +EFIAPI +AdjustCpuContextHandler ( + IN EFI_EXCEPTION_TYPE ExceptionType, + IN EFI_SYSTEM_CONTEXT SystemContext + ); + +/** + Compare cpu context in ConsistencyOfCpuContext test case. + 1.Compare mActualContextInHandler with mExpectedContextInHandler. + 2.Compare mActualContextAfterException with mActualContextAfterException. + + @retval UNIT_TEST_PASSED The Unit test has completed and it was successful. + @retval UNIT_TEST_ERROR_TEST_FAILED A test case assertion has failed. +**/ +UNIT_TEST_STATUS +CompareCpuContext ( + VOID + ); + +/** + Get EFI_MP_SERVICES_PROTOCOL/EDKII_PEI_MP_SERVICES2_PPI pointer. + + @param[out] MpServices Pointer to the MP_SERVICES buffer + + @retval EFI_SUCCESS EFI_MP_SERVICES_PROTOCOL/PPI interface is returned + @retval EFI_NOT_FOUND EFI_MP_SERVICES_PROTOCOL/PPI interface is not found +**/ +EFI_STATUS +GetMpServices ( + OUT MP_SERVICES *MpServices + ); + +/** + 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 + ); + +/** + Execute a caller provided function on all enabled APs. + + @param[in] MpServices MP_SERVICES structure. + @param[in] Procedure Pointer to the function to be run on enabled APs of the system. + @param[in] SingleThread If TRUE, then all the enabled APs execute the function specified by Procedure + one by one, in ascending order of processor handle number. + If FALSE, then all the enabled APs execute the function specified by Procedure + simultaneously. + @param[in] TimeoutInMicroseconds Indicates the time limit in microseconds for APs to return from Procedure, + for blocking mode only. Zero means infinity. + @param[in] ProcedureArgument The parameter passed into Procedure for all APs. + + @retval EFI_SUCCESS Execute a caller provided function on all enabled APs successfully + @retval Others Execute a caller provided function on all enabled APs unsuccessfully +**/ +EFI_STATUS +MpServicesUnitTestStartupAllAPs ( + IN MP_SERVICES MpServices, + IN EFI_AP_PROCEDURE Procedure, + IN BOOLEAN SingleThread, + IN UINTN TimeoutInMicroSeconds, + IN VOID *ProcedureArgument + ); + +/** + Caller gets one enabled AP to execute a caller-provided function. + + @param[in] MpServices MP_SERVICES structure. + @param[in] Procedure Pointer to the function to be run on enabled APs of the system. + @param[in] ProcessorNumber The handle number of the AP. + @param[in] TimeoutInMicroseconds Indicates the time limit in microseconds for APs to return from Procedure, + for blocking mode only. Zero means infinity. + @param[in] ProcedureArgument The parameter passed into Procedure for all APs. + + + @retval EFI_SUCCESS Caller gets one enabled AP to execute a caller-provided function successfully + @retval Others Caller gets one enabled AP to execute a caller-provided function unsuccessfully +**/ +EFI_STATUS +MpServicesUnitTestStartupThisAP ( + IN MP_SERVICES MpServices, + IN EFI_AP_PROCEDURE Procedure, + IN UINTN ProcessorNumber, + IN UINTN TimeoutInMicroSeconds, + IN VOID *ProcedureArgument + ); + +/** + Get the handle number for the calling processor. + + @param[in] MpServices MP_SERVICES structure. + @param[out] ProcessorNumber The handle number for the calling processor. + + @retval EFI_SUCCESS Get the handle number for the calling processor successfully. + @retval Others Get the handle number for the calling processor unsuccessfully. +**/ +EFI_STATUS +MpServicesUnitTestWhoAmI ( + IN MP_SERVICES MpServices, + OUT UINTN *ProcessorNumber + ); + +/** + Retrieve the number of logical processor in the platform and the number of those logical processors that + are enabled on this boot. + + @param[in] MpServices MP_SERVICES structure. + @param[out] NumberOfProcessors Pointer to the total number of logical processors in the system, including + the BSP and disabled APs. + @param[out] NumberOfEnabledProcessors Pointer to the number of processors in the system that are enabled. + + @retval EFI_SUCCESS Retrieve the number of logical processor successfully + @retval Others Retrieve the number of logical processor unsuccessfully +**/ +EFI_STATUS +MpServicesUnitTestGetNumberOfProcessors ( + IN MP_SERVICES MpServices, + OUT UINTN *NumberOfProcessors, + OUT UINTN *NumberOfEnabledProcessors + ); + +/** + Trigger stack overflow by calling itself continuously. +**/ +VOID +EFIAPI +TriggerStackOverflow ( + VOID + ); + +/** + Special handler for CpuStackGuard test case. + + @param ExceptionType Exception type. + @param SystemContext Pointer to EFI_SYSTEM_CONTEXT. +**/ +VOID +EFIAPI +CpuStackGuardExceptionHandler ( + IN EFI_EXCEPTION_TYPE ExceptionType, + IN EFI_SYSTEM_CONTEXT SystemContext + ); + +#endif diff --git a/UefiCpuPkg/Library/CpuExceptionHandlerLib/UnitTest/CpuExceptionHandlerTestCommon.c b/UefiCpuPkg/Library/CpuExceptionHandlerLib/UnitTest/CpuExceptionHandlerTestCommon.c new file mode 100644 index 0000000000..17afb592d3 --- /dev/null +++ b/UefiCpuPkg/Library/CpuExceptionHandlerLib/UnitTest/CpuExceptionHandlerTestCommon.c @@ -0,0 +1,852 @@ +/** @file + Unit tests of the CpuExceptionHandlerLib. + + Copyright (c) 2022, Intel Corporation. All rights reserved.
+ 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; +} diff --git a/UefiCpuPkg/Library/CpuExceptionHandlerLib/UnitTest/DxeCpuExceptionHandlerLibUnitTest.inf b/UefiCpuPkg/Library/CpuExceptionHandlerLib/UnitTest/DxeCpuExceptionHandlerLibUnitTest.inf new file mode 100644 index 0000000000..e3dbe7b9ab --- /dev/null +++ b/UefiCpuPkg/Library/CpuExceptionHandlerLib/UnitTest/DxeCpuExceptionHandlerLibUnitTest.inf @@ -0,0 +1,58 @@ +## @file +# Unit tests of the DxeCpuExceptionHandlerLib instance. +# +# Copyright (c) 2022, Intel Corporation. All rights reserved.
+# SPDX-License-Identifier: BSD-2-Clause-Patent +## + +[Defines] + INF_VERSION = 0x00010005 + BASE_NAME = CpuExceptionHandlerDxeTest + FILE_GUID = D76BFD9C-0B6D-46BD-AD66-2BBB6FA7031A + MODULE_TYPE = DXE_DRIVER + VERSION_STRING = 1.0 + ENTRY_POINT = CpuExceptionHandlerTestEntry + +# +# The following information is for reference only and not required by the build tools. +# +# VALID_ARCHITECTURES = X64 +# +[Sources.X64] + X64/ArchExceptionHandlerTestAsm.nasm + X64/ArchExceptionHandlerTest.c + +[Sources.common] + CpuExceptionHandlerTest.h + CpuExceptionHandlerTestCommon.c + DxeCpuExceptionHandlerUnitTest.c + +[Packages] + MdePkg/MdePkg.dec + MdeModulePkg/MdeModulePkg.dec + UefiCpuPkg/UefiCpuPkg.dec + +[LibraryClasses] + BaseLib + BaseMemoryLib + DebugLib + UnitTestLib + MemoryAllocationLib + CpuExceptionHandlerLib + UefiDriverEntryPoint + HobLib + UefiBootServicesTableLib + CpuPageTableLib + +[Guids] + gEfiHobMemoryAllocStackGuid + +[Pcd] + gEfiMdeModulePkgTokenSpaceGuid.PcdCpuStackGuard ## CONSUMES + gUefiCpuPkgTokenSpaceGuid.PcdCpuApStackSize ## CONSUMES + +[Protocols] + gEfiMpServiceProtocolGuid + +[Depex] + gEfiMpServiceProtocolGuid diff --git a/UefiCpuPkg/Library/CpuExceptionHandlerLib/UnitTest/DxeCpuExceptionHandlerUnitTest.c b/UefiCpuPkg/Library/CpuExceptionHandlerLib/UnitTest/DxeCpuExceptionHandlerUnitTest.c new file mode 100644 index 0000000000..917fc549bf --- /dev/null +++ b/UefiCpuPkg/Library/CpuExceptionHandlerLib/UnitTest/DxeCpuExceptionHandlerUnitTest.c @@ -0,0 +1,196 @@ +/** @file + Unit tests of the CpuExceptionHandlerLib. + + Copyright (c) 2022, Intel Corporation. All rights reserved.
+ SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#include "CpuExceptionHandlerTest.h" +#include + +/** + Initialize Bsp Idt with a new Idt table and return the IA32_DESCRIPTOR buffer. + In PEIM, store original PeiServicePointer before new Idt table. + + @return Pointer to the allocated IA32_DESCRIPTOR buffer. +**/ +VOID * +InitializeBspIdt ( + VOID + ) +{ + UINTN *NewIdtTable; + IA32_DESCRIPTOR *Idtr; + + Idtr = AllocateZeroPool (sizeof (IA32_DESCRIPTOR)); + ASSERT (Idtr != NULL); + NewIdtTable = AllocateZeroPool (sizeof (IA32_IDT_GATE_DESCRIPTOR) * CPU_INTERRUPT_NUM); + ASSERT (NewIdtTable != NULL); + Idtr->Base = (UINTN)NewIdtTable; + Idtr->Limit = (UINT16)(sizeof (IA32_IDT_GATE_DESCRIPTOR) * CPU_INTERRUPT_NUM - 1); + + AsmWriteIdtr (Idtr); + return Idtr; +} + +/** + Retrieve the number of logical processor in the platform and the number of those logical processors that + are enabled on this boot. + + @param[in] MpServices MP_SERVICES structure. + @param[out] NumberOfProcessors Pointer to the total number of logical processors in the system, including + the BSP and disabled APs. + @param[out] NumberOfEnabledProcessors Pointer to the number of processors in the system that are enabled. + + @retval EFI_SUCCESS Retrieve the number of logical processor successfully + @retval Others Retrieve the number of logical processor unsuccessfully +**/ +EFI_STATUS +MpServicesUnitTestGetNumberOfProcessors ( + IN MP_SERVICES MpServices, + OUT UINTN *NumberOfProcessors, + OUT UINTN *NumberOfEnabledProcessors + ) +{ + return MpServices.Protocol->GetNumberOfProcessors (MpServices.Protocol, NumberOfProcessors, NumberOfEnabledProcessors); +} + +/** + Get the handle number for the calling processor. + + @param[in] MpServices MP_SERVICES structure. + @param[out] ProcessorNumber The handle number for the calling processor. + + @retval EFI_SUCCESS Get the handle number for the calling processor successfully. + @retval Others Get the handle number for the calling processor unsuccessfully. +**/ +EFI_STATUS +MpServicesUnitTestWhoAmI ( + IN MP_SERVICES MpServices, + OUT UINTN *ProcessorNumber + ) +{ + return MpServices.Protocol->WhoAmI (MpServices.Protocol, ProcessorNumber); +} + +/** + Caller gets one enabled AP to execute a caller-provided function. + + @param[in] MpServices MP_SERVICES structure. + @param[in] Procedure Pointer to the function to be run on enabled APs of the system. + @param[in] ProcessorNumber The handle number of the AP. + @param[in] TimeoutInMicroSeconds Indicates the time limit in microseconds for APs to return from Procedure, + for blocking mode only. Zero means infinity. + @param[in] ProcedureArgument The parameter passed into Procedure for all APs. + + + @retval EFI_SUCCESS Caller gets one enabled AP to execute a caller-provided function successfully + @retval Others Caller gets one enabled AP to execute a caller-provided function unsuccessfully +**/ +EFI_STATUS +MpServicesUnitTestStartupThisAP ( + IN MP_SERVICES MpServices, + IN EFI_AP_PROCEDURE Procedure, + IN UINTN ProcessorNumber, + IN UINTN TimeoutInMicroSeconds, + IN VOID *ProcedureArgument + ) +{ + return MpServices.Protocol->StartupThisAP (MpServices.Protocol, Procedure, ProcessorNumber, NULL, TimeoutInMicroSeconds, ProcedureArgument, NULL); +} + +/** + Execute a caller provided function on all enabled APs. + + @param[in] MpServices MP_SERVICES structure. + @param[in] Procedure Pointer to the function to be run on enabled APs of the system. + @param[in] SingleThread If TRUE, then all the enabled APs execute the function specified by Procedure + one by one, in ascending order of processor handle number. + If FALSE, then all the enabled APs execute the function specified by Procedure + simultaneously. + @param[in] TimeoutInMicroSeconds Indicates the time limit in microseconds for APs to return from Procedure, + for blocking mode only. Zero means infinity. + @param[in] ProcedureArgument The parameter passed into Procedure for all APs. + + @retval EFI_SUCCESS Execute a caller provided function on all enabled APs successfully + @retval Others Execute a caller provided function on all enabled APs unsuccessfully +**/ +EFI_STATUS +MpServicesUnitTestStartupAllAPs ( + IN MP_SERVICES MpServices, + IN EFI_AP_PROCEDURE Procedure, + IN BOOLEAN SingleThread, + IN UINTN TimeoutInMicroSeconds, + IN VOID *ProcedureArgument + ) +{ + return MpServices.Protocol->StartupAllAPs (MpServices.Protocol, Procedure, SingleThread, NULL, TimeoutInMicroSeconds, ProcedureArgument, NULL); +} + +/** + Get EFI_MP_SERVICES_PROTOCOL pointer. + + @param[out] MpServices Pointer to the buffer where EFI_MP_SERVICES_PROTOCOL is stored + + @retval EFI_SUCCESS EFI_MP_SERVICES_PROTOCOL interface is returned + @retval EFI_NOT_FOUND EFI_MP_SERVICES_PROTOCOL interface is not found +**/ +EFI_STATUS +GetMpServices ( + OUT MP_SERVICES *MpServices + ) +{ + return gBS->LocateProtocol (&gEfiMpServiceProtocolGuid, NULL, (VOID **)&MpServices->Protocol); +} + +/** + Entry for CpuExceptionHandlerDxeTest driver. + + @param ImageHandle Image handle this driver. + @param SystemTable Pointer to the System Table. + + @retval EFI_SUCCESS The driver executed normally. + +**/ +EFI_STATUS +EFIAPI +CpuExceptionHandlerTestEntry ( + IN EFI_HANDLE ImageHandle, + IN EFI_SYSTEM_TABLE *SystemTable + ) +{ + EFI_STATUS Status; + UNIT_TEST_FRAMEWORK_HANDLE Framework; + + Framework = NULL; + + DEBUG ((DEBUG_INFO, "%a v%a\n", UNIT_TEST_APP_NAME, UNIT_TEST_APP_VERSION)); + + // + // Start setting up the test framework for running the tests. + // + Status = InitUnitTestFramework (&Framework, UNIT_TEST_APP_NAME, gEfiCallerBaseName, UNIT_TEST_APP_VERSION); + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_ERROR, "Failed in InitUnitTestFramework. Status = %r\n", Status)); + goto EXIT; + } + + Status = AddCommonTestCase (Framework); + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_ERROR, "Failed in AddCommonTestCase. Status = %r\n", Status)); + goto EXIT; + } + + // + // Execute the tests. + // + Status = RunAllTestSuites (Framework); + +EXIT: + if (Framework) { + FreeUnitTestFramework (Framework); + } + + return Status; +} diff --git a/UefiCpuPkg/Library/CpuExceptionHandlerLib/UnitTest/X64/ArchExceptionHandlerTest.c b/UefiCpuPkg/Library/CpuExceptionHandlerLib/UnitTest/X64/ArchExceptionHandlerTest.c new file mode 100644 index 0000000000..c0d962f26d --- /dev/null +++ b/UefiCpuPkg/Library/CpuExceptionHandlerLib/UnitTest/X64/ArchExceptionHandlerTest.c @@ -0,0 +1,166 @@ +/** @file + Unit tests of the CpuExceptionHandlerLib. + + Copyright (c) 2022, Intel Corporation. All rights reserved.
+ SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#include "CpuExceptionHandlerTest.h" + +GENERAL_REGISTER mActualContextInHandler; +GENERAL_REGISTER mActualContextAfterException; + +// +// In TestCpuContextConsistency, Cpu registers will be set to mExpectedContextInHandler/mExpectedContextAfterException. +// Rcx in mExpectedContextInHandler is set runtime since Rcx is needed in assembly code. +// For GP and PF, Rcx is set to FaultParameter. For other exception triggered by INTn, Rcx is set to ExceptionType. +// +GENERAL_REGISTER mExpectedContextInHandler = { 1, 2, 3, 4, 5, 0, 7, 8, 9, 0xa, 0xb, 0xc, 0xd, 0xe }; +GENERAL_REGISTER mExpectedContextAfterException = { 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e }; + +/** + Special handler for fault exception. + Rip/Eip in SystemContext will be modified to the instruction after the exception instruction. + + @param ExceptionType Exception type. + @param SystemContext Pointer to EFI_SYSTEM_CONTEXT. +**/ +VOID +EFIAPI +AdjustRipForFaultHandler ( + IN EFI_EXCEPTION_TYPE ExceptionType, + IN EFI_SYSTEM_CONTEXT SystemContext + ) +{ + mExceptionType = ExceptionType; + SystemContext.SystemContextX64->Rip += mFaultInstructionLength; +} + +/** + Special handler for ConsistencyOfCpuContext test case. + + @param ExceptionType Exception type. + @param SystemContext Pointer to EFI_SYSTEM_CONTEXT. +**/ +VOID +EFIAPI +AdjustCpuContextHandler ( + IN EFI_EXCEPTION_TYPE ExceptionType, + IN EFI_SYSTEM_CONTEXT SystemContext + ) +{ + // + // Store SystemContext in mActualContextInHandler. + // + mActualContextInHandler.Rdi = SystemContext.SystemContextX64->Rdi; + mActualContextInHandler.Rsi = SystemContext.SystemContextX64->Rsi; + mActualContextInHandler.Rbx = SystemContext.SystemContextX64->Rbx; + mActualContextInHandler.Rdx = SystemContext.SystemContextX64->Rdx; + mActualContextInHandler.Rcx = SystemContext.SystemContextX64->Rcx; + mActualContextInHandler.Rax = SystemContext.SystemContextX64->Rax; + mActualContextInHandler.R8 = SystemContext.SystemContextX64->R8; + mActualContextInHandler.R9 = SystemContext.SystemContextX64->R9; + mActualContextInHandler.R10 = SystemContext.SystemContextX64->R10; + mActualContextInHandler.R11 = SystemContext.SystemContextX64->R11; + mActualContextInHandler.R12 = SystemContext.SystemContextX64->R12; + mActualContextInHandler.R13 = SystemContext.SystemContextX64->R13; + mActualContextInHandler.R14 = SystemContext.SystemContextX64->R14; + mActualContextInHandler.R15 = SystemContext.SystemContextX64->R15; + + // + // Modify cpu context. These registers will be stored in mActualContextAfterException. + // Do not handle Rsp and Rbp. CpuExceptionHandlerLib doesn't set Rsp and Rbp register + // to the value in SystemContext. + // + SystemContext.SystemContextX64->Rdi = mExpectedContextAfterException.Rdi; + SystemContext.SystemContextX64->Rsi = mExpectedContextAfterException.Rsi; + SystemContext.SystemContextX64->Rbx = mExpectedContextAfterException.Rbx; + SystemContext.SystemContextX64->Rdx = mExpectedContextAfterException.Rdx; + SystemContext.SystemContextX64->Rcx = mExpectedContextAfterException.Rcx; + SystemContext.SystemContextX64->Rax = mExpectedContextAfterException.Rax; + SystemContext.SystemContextX64->R8 = mExpectedContextAfterException.R8; + SystemContext.SystemContextX64->R9 = mExpectedContextAfterException.R9; + SystemContext.SystemContextX64->R10 = mExpectedContextAfterException.R10; + SystemContext.SystemContextX64->R11 = mExpectedContextAfterException.R11; + SystemContext.SystemContextX64->R12 = mExpectedContextAfterException.R12; + SystemContext.SystemContextX64->R13 = mExpectedContextAfterException.R13; + SystemContext.SystemContextX64->R14 = mExpectedContextAfterException.R14; + SystemContext.SystemContextX64->R15 = mExpectedContextAfterException.R15; + + // + // When fault exception happens, eip/rip points to the faulting instruction. + // For now, olny GP and PF are tested in fault exception. + // + if ((ExceptionType == EXCEPT_IA32_PAGE_FAULT) || (ExceptionType == EXCEPT_IA32_GP_FAULT)) { + AdjustRipForFaultHandler (ExceptionType, SystemContext); + } +} + +/** + Compare cpu context in ConsistencyOfCpuContext test case. + 1.Compare mActualContextInHandler with mExpectedContextInHandler. + 2.Compare mActualContextAfterException with mActualContextAfterException. + + @retval UNIT_TEST_PASSED The Unit test has completed and it was successful. + @retval UNIT_TEST_ERROR_TEST_FAILED A test case assertion has failed. +**/ +UNIT_TEST_STATUS +CompareCpuContext ( + VOID + ) +{ + UT_ASSERT_EQUAL (mActualContextInHandler.Rdi, mExpectedContextInHandler.Rdi); + UT_ASSERT_EQUAL (mActualContextInHandler.Rsi, mExpectedContextInHandler.Rsi); + UT_ASSERT_EQUAL (mActualContextInHandler.Rbx, mExpectedContextInHandler.Rbx); + UT_ASSERT_EQUAL (mActualContextInHandler.Rdx, mExpectedContextInHandler.Rdx); + UT_ASSERT_EQUAL (mActualContextInHandler.Rcx, mExpectedContextInHandler.Rcx); + UT_ASSERT_EQUAL (mActualContextInHandler.Rax, mExpectedContextInHandler.Rax); + UT_ASSERT_EQUAL (mActualContextInHandler.R8, mExpectedContextInHandler.R8); + UT_ASSERT_EQUAL (mActualContextInHandler.R9, mExpectedContextInHandler.R9); + UT_ASSERT_EQUAL (mActualContextInHandler.R10, mExpectedContextInHandler.R10); + UT_ASSERT_EQUAL (mActualContextInHandler.R11, mExpectedContextInHandler.R11); + UT_ASSERT_EQUAL (mActualContextInHandler.R12, mExpectedContextInHandler.R12); + UT_ASSERT_EQUAL (mActualContextInHandler.R13, mExpectedContextInHandler.R13); + UT_ASSERT_EQUAL (mActualContextInHandler.R14, mExpectedContextInHandler.R14); + UT_ASSERT_EQUAL (mActualContextInHandler.R15, mExpectedContextInHandler.R15); + + UT_ASSERT_EQUAL (mActualContextAfterException.Rdi, mExpectedContextAfterException.Rdi); + UT_ASSERT_EQUAL (mActualContextAfterException.Rsi, mExpectedContextAfterException.Rsi); + UT_ASSERT_EQUAL (mActualContextAfterException.Rbx, mExpectedContextAfterException.Rbx); + UT_ASSERT_EQUAL (mActualContextAfterException.Rdx, mExpectedContextAfterException.Rdx); + UT_ASSERT_EQUAL (mActualContextAfterException.Rcx, mExpectedContextAfterException.Rcx); + UT_ASSERT_EQUAL (mActualContextAfterException.Rax, mExpectedContextAfterException.Rax); + UT_ASSERT_EQUAL (mActualContextAfterException.R8, mExpectedContextAfterException.R8); + UT_ASSERT_EQUAL (mActualContextAfterException.R9, mExpectedContextAfterException.R9); + UT_ASSERT_EQUAL (mActualContextAfterException.R10, mExpectedContextAfterException.R10); + UT_ASSERT_EQUAL (mActualContextAfterException.R11, mExpectedContextAfterException.R11); + UT_ASSERT_EQUAL (mActualContextAfterException.R12, mExpectedContextAfterException.R12); + UT_ASSERT_EQUAL (mActualContextAfterException.R13, mExpectedContextAfterException.R13); + UT_ASSERT_EQUAL (mActualContextAfterException.R14, mExpectedContextAfterException.R14); + UT_ASSERT_EQUAL (mActualContextAfterException.R15, mExpectedContextAfterException.R15); + return UNIT_TEST_PASSED; +} + +/** + Special handler for CpuStackGuard test case. + + @param ExceptionType Exception type. + @param SystemContext Pointer to EFI_SYSTEM_CONTEXT. + +**/ +VOID +EFIAPI +CpuStackGuardExceptionHandler ( + IN EFI_EXCEPTION_TYPE ExceptionType, + IN EFI_SYSTEM_CONTEXT SystemContext + ) +{ + UINTN LocalVariable; + + AdjustRipForFaultHandler (ExceptionType, SystemContext); + mRspAddress[0] = (UINTN)SystemContext.SystemContextX64->Rsp; + mRspAddress[1] = (UINTN)(&LocalVariable); + + return; +} diff --git a/UefiCpuPkg/Library/CpuExceptionHandlerLib/UnitTest/X64/ArchExceptionHandlerTestAsm.nasm b/UefiCpuPkg/Library/CpuExceptionHandlerLib/UnitTest/X64/ArchExceptionHandlerTestAsm.nasm new file mode 100644 index 0000000000..e229dbed00 --- /dev/null +++ b/UefiCpuPkg/Library/CpuExceptionHandlerLib/UnitTest/X64/ArchExceptionHandlerTestAsm.nasm @@ -0,0 +1,256 @@ +;------------------------------------------------------------------------------ +; +; Copyright (c) 2022, Intel Corporation. All rights reserved.
+; SPDX-License-Identifier: BSD-2-Clause-Patent +; +; Module Name: +; +; ArchExceptionHandlerTestAsm.nasm +; +; Abstract: +; +; x64 CPU Exception Handler Lib Unit test +; +;------------------------------------------------------------------------------ + + DEFAULT REL + SECTION .text + +struc GENERAL_REGISTER + .Rdi: resq 1 + .Rsi: resq 1 + .Rbx: resq 1 + .Rdx: resq 1 + .Rcx: resq 1 + .Rax: resq 1 + .R8: resq 1 + .R9: resq 1 + .R10: resq 1 + .R11: resq 1 + .R12: resq 1 + .R13: resq 1 + .R14: resq 1 + .R15: resq 1 + +endstruc + +extern ASM_PFX(mExpectedContextInHandler) +extern ASM_PFX(mActualContextAfterException) +extern ASM_PFX(mFaultInstructionLength) + +;------------------------------------------------------------------------------ +; VOID +; EFIAPI +; TriggerGPException ( +; UINTN Cr4ReservedBit +; ); +;------------------------------------------------------------------------------ +global ASM_PFX(TriggerGPException) +ASM_PFX(TriggerGPException): + ; + ; Set reserved bit 15 of cr4 to 1 + ; + push rcx + lea rcx, [ASM_PFX(mFaultInstructionLength)] + mov qword[rcx], TriggerGPExceptionAfter - TriggerGPExceptionBefore + pop rcx +TriggerGPExceptionBefore: + mov cr4, rcx +TriggerGPExceptionAfter: + ret + +;------------------------------------------------------------------------------ +; VOID +; EFIAPI +; TriggerPFException ( +; UINTN PFAddress +; ); +;------------------------------------------------------------------------------ +global ASM_PFX(TriggerPFException) +ASM_PFX(TriggerPFException): + push rcx + lea rcx, [ASM_PFX(mFaultInstructionLength)] + mov qword[rcx], TriggerPFExceptionAfter - TriggerPFExceptionBefore + pop rcx +TriggerPFExceptionBefore: + mov qword[rcx], 0x1 +TriggerPFExceptionAfter: + ret + +;------------------------------------------------------------------------------ +; ModifyRcxInGlobalBeforeException; +; This function is writed by assebly code because it's only called in this file. +; It's used to set Rcx in mExpectedContextInHandler for different exception. +;------------------------------------------------------------------------------ +global ASM_PFX(ModifyRcxInGlobalBeforeException) +ASM_PFX(ModifyRcxInGlobalBeforeException): + push rax + lea rax, [ASM_PFX(mExpectedContextInHandler)] + mov [rax + GENERAL_REGISTER.Rcx], rcx + pop rax + ret + +;------------------------------------------------------------------------------ +;VOID +;EFIAPI +;AsmTestConsistencyOfCpuContext ( +; IN EFI_EXCEPTION_TYPE ExceptionType +; IN UINTN FaultParameter OPTIONAL +; ); +;------------------------------------------------------------------------------ +global ASM_PFX(AsmTestConsistencyOfCpuContext) +ASM_PFX(AsmTestConsistencyOfCpuContext): + ; + ; Push original register + ; + push r15 + push r14 + push r13 + push r12 + push r11 + push r10 + push r9 + push r8 + push rax + push rcx + push rbx + push rsi + push rdi + push rdx + push rdx + + ; + ; Modify registers to mExpectedContextInHandler. Do not handle Rsp and Rbp. + ; CpuExceptionHandlerLib doesn't set Rsp and Rsp register to the value in SystemContext. + ; + lea r15, [ASM_PFX(mExpectedContextInHandler)] + mov rdi, [r15 + GENERAL_REGISTER.Rdi] + mov rsi, [r15 + GENERAL_REGISTER.Rsi] + mov rbx, [r15 + GENERAL_REGISTER.Rbx] + mov rdx, [r15 + GENERAL_REGISTER.Rdx] + mov rax, [r15 + GENERAL_REGISTER.Rax] + mov r8, [r15 + GENERAL_REGISTER.R8] + mov r9, [r15 + GENERAL_REGISTER.R9] + mov r10, [r15 + GENERAL_REGISTER.R10] + mov r11, [r15 + GENERAL_REGISTER.R11] + mov r12, [r15 + GENERAL_REGISTER.R12] + mov r13, [r15 + GENERAL_REGISTER.R13] + mov r14, [r15 + GENERAL_REGISTER.R14] + mov r15, [r15 + GENERAL_REGISTER.R15] + + cmp rcx, 0xd + jz GPException + cmp rcx, 0xe + jz PFException + jmp INTnException + +PFException: + pop rcx ; Pop rdx(PFAddress) to rcx. + call ASM_PFX(ModifyRcxInGlobalBeforeException) ; Set mExpectedContextInHandler.Rcx to PFAddress. + call ASM_PFX(TriggerPFException) + jmp AfterException + +GPException: + pop rcx ; Pop rdx(Cr4ReservedBit) to rcx. + call ASM_PFX(ModifyRcxInGlobalBeforeException) ; Set mExpectedContextInHandler.Rcx to Cr4ReservedBit. + call ASM_PFX(TriggerGPException) + jmp AfterException + +INTnException: + ; + ; Modify Rcx in mExpectedContextInHandler. + ; + add Rsp, 8 ; Discard the extra Rdx in stack. Rcx is ExceptionType now. + call ASM_PFX(ModifyRcxInGlobalBeforeException) ; Set mExpectedContextInHandler.Rcx to ExceptionType. + call ASM_PFX(TriggerINTnException) + +AfterException: + ; + ; Save registers in mActualContextAfterException + ; + push rax + lea rax, [ASM_PFX(mActualContextAfterException)] + mov [rax + GENERAL_REGISTER.Rdi], rdi + mov [rax + GENERAL_REGISTER.Rsi], rsi + mov [rax + GENERAL_REGISTER.Rbx], rbx + mov [rax + GENERAL_REGISTER.Rdx], rdx + mov [rax + GENERAL_REGISTER.Rcx], rcx + pop rcx + mov [rax + GENERAL_REGISTER.Rax], rcx + mov [rax + GENERAL_REGISTER.R8], r8 + mov [rax + GENERAL_REGISTER.R9], r9 + mov [rax + GENERAL_REGISTER.R10], r10 + mov [rax + GENERAL_REGISTER.R11], r11 + mov [rax + GENERAL_REGISTER.R12], r12 + mov [rax + GENERAL_REGISTER.R13], r13 + mov [rax + GENERAL_REGISTER.R14], r14 + mov [rax + GENERAL_REGISTER.R15], r15 + + ; + ; restore original register + ; + pop rdx + pop rdi + pop rsi + pop rbx + pop rcx + pop rax + pop r8 + pop r9 + pop r10 + pop r11 + pop r12 + pop r13 + pop r14 + pop r15 + + ret + +;------------------------------------------------------------------------------ +; VOID +; EFIAPI +; TriggerStackOverflow ( +; VOID +; ); +;------------------------------------------------------------------------------ +global ASM_PFX(TriggerStackOverflow) +ASM_PFX(TriggerStackOverflow): + push rcx + lea rcx, [ASM_PFX(mFaultInstructionLength)] + mov qword[rcx], TriggerCpuStackGuardAfter - TriggerCpuStackGuardBefore + pop rcx +TriggerCpuStackGuardBefore: + call TriggerCpuStackGuardBefore +TriggerCpuStackGuardAfter: + ret + +;------------------------------------------------------------------------------ +; VOID +; EFIAPI +; TriggerINTnException ( +; IN EFI_EXCEPTION_TYPE ExceptionType +; ); +;------------------------------------------------------------------------------ +global ASM_PFX(TriggerINTnException) +ASM_PFX(TriggerINTnException): + push rax + push rdx + push rcx + lea rax, [AsmTriggerException1 - AsmTriggerException0] + mul rcx + mov rcx, AsmTriggerException0 + add rax, rcx + pop rcx + pop rdx + jmp rax + ; + ; rax = AsmTriggerException0 + (AsmTriggerException1 - AsmTriggerException0) * rcx + ; +%assign Vector 0 +%rep 22 +AsmTriggerException %+ Vector: + pop rax + INT Vector + ret +%assign Vector Vector+1 +%endrep