2022-10-17 08:35:39 +02:00
/** @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 " ,
2023-04-06 21:49:10 +02:00
__func__ ,
2022-10-17 08:35:39 +02:00
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 ;
}