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