| /** @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; | |
| } |