Add target based unit tests for the DxeCpuExceptionHandlerLib. A DXE driver is created to test DxeCpuExceptionHandlerLib. 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 the test case, only no error code exception is triggered and tested by INTn instruction. b.Test if exception handler can be registered/unregistered for GP and PF. In the test case, GP exception is triggered and tested by setting CR4_RESERVED_BIT to 1. PF exception is triggered by writting to not-present or RO address. c.Test if CpuContext 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. Signed-off-by: Dun Tan <dun.tan@intel.com> Cc: Eric Dong <eric.dong@intel.com> Reviewed-by: Ray Ni <ray.ni@intel.com> Cc: Rahul Kumar <rahul1.kumar@intel.com>
		
			
				
	
	
		
			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;
 | 
						|
}
 |