/** @file DXE H2O check point library. It provides functionalities to register, unregister and trigger check point and also has function to get check point information from handle. ;****************************************************************************** ;* Copyright (c) 2017 - 2020, Insyde Software Corporation. All Rights Reserved. ;* ;* You may not reproduce, distribute, publish, display, perform, modify, adapt, ;* transmit, broadcast, present, recite, release, license or otherwise exploit ;* any part of this publication in any form, by any means, without the prior ;* written permission of Insyde Software Corporation. ;* ;****************************************************************************** */ #include #include #include #include #include #include #include #include #include #include STATIC H2O_CP_PRIORITY mTpls[] = {H2O_CP_HIGH, H2O_CP_MEDIUM_HIGH, H2O_CP_MEDIUM, H2O_CP_MEDIUM_LOW, H2O_CP_LOW }; #define NUMBER_OF_TPL (sizeof(mTpls)/sizeof(H2O_CP_PRIORITY)) typedef struct { UINT32 Signature; LIST_ENTRY RegisterLink; LIST_ENTRY TriggerLink; EFI_GUID Guid; H2O_CP_PRIORITY Tpl; H2O_CP_HANDLER Handler; CONST CHAR8 *HandlerName; CONST CHAR8 *FunctionName; CONST CHAR8 *FileName; } INTERNAL_H2O_CP_HANDLE; typedef struct { UINT32 Signature; LIST_ENTRY HandleLink; LIST_ENTRY RegisterLink[NUMBER_OF_TPL]; LIST_ENTRY TriggerLink[NUMBER_OF_TPL]; UINTN TriggerTimes; H2O_CP_PRIORITY CurrentTriggerTpl; EFI_GUID Guid; VOID *CpData; } INTERNAL_H2O_CP_HANDLE_HEADER; #define H2O_PRIVATE_CP_HEADER_SIGNATURE SIGNATURE_32 ('H', 'P', 'C', 'H') #define H2O_PRIVATE_CP_SIGNATURE SIGNATURE_32 ('H', 'P', 'C', 'P') #define H2O_CP_HEADER_NODE_FROM_LINK(a) CR (a, INTERNAL_H2O_CP_HANDLE_HEADER, HandleLink, H2O_PRIVATE_CP_HEADER_SIGNATURE) #define H2O_CP_NODE_FROM_REGISTER_LINK(a) CR (a, INTERNAL_H2O_CP_HANDLE, RegisterLink, H2O_PRIVATE_CP_SIGNATURE) #define H2O_CP_NODE_FROM_TRIGGER_LINK(a) CR (a, INTERNAL_H2O_CP_HANDLE, TriggerLink, H2O_PRIVATE_CP_SIGNATURE) STATIC LIST_ENTRY *mCpList = NULL; STATIC EFI_GUID mInternalH2OCpListHeadGuid = {0xf4540d21, 0x18c6, 0x4e74, 0xbc, 0x2d, 0x3c, 0x6f, 0xa0, 0x8d, 0x8b, 0xc6}; STATIC INTERNAL_H2O_CP_HANDLE mCurrentCpHandle; #ifndef MDEPKG_NDEBUG /** Internal function to get H2O checkpoint priority string according to input priority. @param[in] H2O_CP_PRIORITY Input H2O checkpoint priority. @return Pointer to ASCII string of H2O checkpoint priority. **/ STATIC CHAR8 * TplToStr ( H2O_CP_PRIORITY Tpl ) { if (Tpl == H2O_CP_HIGH) { return "H2O_CP_HIGH"; } else if (Tpl == H2O_CP_MEDIUM_HIGH) { return "H2O_CP_MEDIUM_HIGH"; } else if (Tpl == H2O_CP_MEDIUM) { return "H2O_CP_MEDIUM"; } else if (Tpl == H2O_CP_MEDIUM_LOW) { return "H2O_CP_MEDIUM_LOW"; } else if (Tpl == H2O_CP_LOW) { return "H2O_CP_LOW"; } return "UNKNOWN_CP_PRIORITY"; } typedef struct { UINT32 Status; CHAR8 *String; } CP_STATUS_TO_STRING; CP_STATUS_TO_STRING mCpStatusToStr[] = { {H2O_CP_TASK_NORMAL, "H2O_CP_TASK_NORMAL"}, {H2O_CP_TASK_SKIP, "H2O_CP_TASK_SKIP"}, {H2O_CP_TASK_UPDATE, "H2O_CP_TASK_UPDATE"}, {H2O_CP_TASK_BOOT_FAIL, "H2O_CP_TASK_BOOT_FAIL"}, {H2O_CP_TASK_BREAK_NORMAL, "H2O_CP_TASK_BREAK_NORMAL"}, {H2O_CP_TASK_BREAK_SKIP, "H2O_CP_TASK_BREAK_SKIP"}, {H2O_CP_TASK_BREAK_UPDATE, "H2O_CP_TASK_BREAK_UPDATE"}, {H2O_CP_TASK_BREAK_BOOT_FAIL, "H2O_CP_TASK_BREAK_BOOT_FAIL"} }; /** Internal function to get H2O checkpoint status string according to input status. @param[in] Status Input H2O checkpoint Status. @return Pointer to ASCII string of H2O checkpoint status. **/ STATIC CHAR8 * CpStatusToStr ( IN UINT32 Status ) { UINTN Count; UINTN Index; Count = sizeof (mCpStatusToStr) / sizeof (CP_STATUS_TO_STRING); for (Index = 0; Index < Count; Index++) { if (Status == mCpStatusToStr[Index].Status) { return mCpStatusToStr[Index].String; } } return "UNKNOWN_CP_STATUS"; } #endif /** Internal function to get INTERNAL_H2O_CP_HANDLE_HEADER instance according to input checkpoint. @param[in] H2OCheckpoint Pointer to a GUID that specifies the checkpoint for which the handler is being registered. @return Pointer to the INTERNAL_H2O_CP_HANDLE_HEADER instance or NULL if not found **/ STATIC INTERNAL_H2O_CP_HANDLE_HEADER * FindCpHeader ( IN CONST EFI_GUID *H2OCheckpoint ) { LIST_ENTRY *List; INTERNAL_H2O_CP_HANDLE_HEADER *CurrentCp; for (List = GetFirstNode (mCpList); List != mCpList; List = GetNextNode (mCpList, List)) { CurrentCp = H2O_CP_HEADER_NODE_FROM_LINK(List); if (CompareGuid (H2OCheckpoint, &CurrentCp->Guid)){ return CurrentCp; } } return NULL; } /** Internal function to get array index according to input TPL. @param[in] Tpl Checkpoint priority level. @return The array index of the input TPL in mTpls array. **/ STATIC UINTN GetIndexFromTpl ( H2O_CP_PRIORITY Tpl ) { UINTN Index; for (Index = 0; Index < NUMBER_OF_TPL; Index++) { if (Tpl == mTpls[Index]) { return Index; } } ASSERT (FALSE); return 0; } /** This function registers a handler for the specified checkpoint with the specified priority. @param[in] H2OCheckpoint Pointer to a GUID that specifies the checkpoint for which the handler is being registered. @param[in] Handler Pointer to the handler function. @param[in] Priority Enumerated value that specifies the priority with which the function will be associated. @param[out] Handle Pointer to the returned handle that is associated with the newly registered checkpoint handler. @param[in] HandlerName Ascii string to handler function name. @param[in] FunctionName Ascii string to function name which we register handler function. @param[in] FileName Ascii string to file name which we register handler function. @retval EFI_SUCCESS Register check point handle successfully. @retval EFI_INVALID_PARAMETER H2OCheckpoint ,Handler or Handle is NULL. @retval EFI_OUT_OF_RESOURCES Allocate memory for Handle failed. **/ EFI_STATUS H2OCpRegister ( IN CONST EFI_GUID *H2OCheckpoint, IN H2O_CP_HANDLER Handler, IN H2O_CP_PRIORITY Priority, OUT H2O_CP_HANDLE *Handle, IN CONST CHAR8 *HandlerName, IN CONST CHAR8 *FunctionName, IN CONST CHAR8 *FileName ) { INTERNAL_H2O_CP_HANDLE *CpHandle; INTERNAL_H2O_CP_HANDLE_HEADER *CpHandleHeader; UINTN Index; if (H2OCheckpoint == NULL || Handler == NULL || Handle == NULL) { return EFI_INVALID_PARAMETER; } if (Priority != H2O_CP_LOW && Priority != H2O_CP_MEDIUM_LOW && Priority != H2O_CP_MEDIUM && Priority != H2O_CP_MEDIUM_HIGH && Priority != H2O_CP_HIGH) { return EFI_INVALID_PARAMETER; } CpHandleHeader = FindCpHeader (H2OCheckpoint); if (CpHandleHeader == NULL) { CpHandleHeader = AllocateZeroPool (sizeof (INTERNAL_H2O_CP_HANDLE_HEADER)); if (CpHandleHeader == NULL) { return EFI_OUT_OF_RESOURCES; } CpHandleHeader->Signature = H2O_PRIVATE_CP_HEADER_SIGNATURE; for (Index = 0; Index < NUMBER_OF_TPL; Index++) { InitializeListHead (&CpHandleHeader->RegisterLink[Index]); } CopyGuid (&CpHandleHeader->Guid, H2OCheckpoint); InsertTailList (mCpList, &CpHandleHeader->HandleLink); } CpHandle = AllocateZeroPool (sizeof (INTERNAL_H2O_CP_HANDLE)); if (CpHandle == NULL) { return EFI_OUT_OF_RESOURCES; } CpHandle->Signature = H2O_PRIVATE_CP_SIGNATURE; CopyGuid (&CpHandle->Guid, H2OCheckpoint); CpHandle->Tpl = Priority; CpHandle->Handler = Handler; DEBUG_CODE ( CpHandle->HandlerName = HandlerName; CpHandle->FunctionName = FunctionName; CpHandle->FileName = FileName; if (CpHandleHeader->TriggerTimes != 0) { DEBUG_CP((DEBUG_INFO, "CP==>WARNING: Registration of Checkpoint handler %g happened after 1st Trigger.\n", H2OCheckpoint)); } ); InsertTailList (&CpHandleHeader->RegisterLink[GetIndexFromTpl (Priority)], &CpHandle->RegisterLink); *Handle = (H2O_CP_HANDLE) CpHandle; // // To make sure lower or equal priority checkpoint can be triggered, insert registered checkpoint to trigger list if // the priority of this checkpoint is lower or equal the current trigger priority. // if (CpHandleHeader->CurrentTriggerTpl != 0 && CpHandleHeader->CurrentTriggerTpl >= Priority) { InsertTailList (&CpHandleHeader->TriggerLink[GetIndexFromTpl (Priority)], &CpHandle->TriggerLink); } return CpHandleHeader->TriggerTimes == 0 ? EFI_SUCCESS : EFI_WARN_STALE_DATA; } /** This function returns the checkpoint data structure that was installed when the checkpoint was triggered and, optionally, the GUID that was associated with the checkpoint. @param[in] Handle The handle associated with a previously registered checkpoint handler. @param[out] H2OCheckpointData The pointer to the checkpoint structure that was installed. @param[out] H2OCheckpoint Optional pointer to the returned pointer to the checkpoint GUID. @retval EFI_SUCCESS Get check point data successfully. @retval EFI_INVALID_PARAMETER Handle or H2OCheckpointData is NULL or Handle is invalid. @retval EFI_INVALID_PARAMETER It does not refer to a previously registered checkpoint handler. @return Others Other error occurred while getting check point information. **/ EFI_STATUS H2OCpLookup ( IN H2O_CP_HANDLE Handle, OUT VOID **H2OCheckpointData, OUT EFI_GUID *H2OCheckpoint OPTIONAL ) { INTERNAL_H2O_CP_HANDLE *CpHandle; INTERNAL_H2O_CP_HANDLE_HEADER *CpHandleHeader; if (Handle == NULL || H2OCheckpointData == NULL) { return EFI_INVALID_PARAMETER; } // // Check handle signature and using guid from handle to find CP header to make // sure this is a valid handle. // CpHandle = (INTERNAL_H2O_CP_HANDLE *) Handle; if (CpHandle->Signature != H2O_PRIVATE_CP_SIGNATURE) { return EFI_INVALID_PARAMETER; } CpHandleHeader = FindCpHeader (&CpHandle->Guid); if (CpHandleHeader == NULL || CpHandleHeader->Signature != H2O_PRIVATE_CP_HEADER_SIGNATURE) { return EFI_INVALID_PARAMETER; } *H2OCheckpointData = CpHandleHeader->CpData; if (H2OCheckpoint != NULL) { CopyGuid (H2OCheckpoint, &CpHandle->Guid); } return EFI_SUCCESS; } /** This function install checks point data to check point GUID and triggers check point according to check point GUID. @param[in] H2OCheckpoint Pointer to the GUID associated with the H2O checkpoint. @param[in] H2OCheckpointData Pointer to the data associated with the H2O checkpoint. @retval EFI_SUCCESS Trigger check point successfully. @retval EFI_INVALID_PARAMETER H2OCheckpoint or H2OCheckpointData is NULL. @retval Other Install H2OCheckpoint protocol failed. **/ EFI_STATUS H2OCpTrigger ( IN CONST EFI_GUID *H2OCheckpoint, IN CONST VOID *H2OCheckpointData ) { UINTN Index; LIST_ENTRY *List; LIST_ENTRY *Head; INTERNAL_H2O_CP_HANDLE_HEADER *CpHandleHeader; INTERNAL_H2O_CP_HANDLE *CpHandle; H2O_CP_DATA *CpDataHeader; if (H2OCheckpoint == NULL || H2OCheckpointData == NULL) { return EFI_INVALID_PARAMETER; } CpHandleHeader = FindCpHeader (H2OCheckpoint); if (CpHandleHeader == NULL) { // // Create a new INTERNAL_H2O_CP_HANDLE_HEADER to record trigger times if this checkpoint hasn't // been registerred before trigger. // CpHandleHeader = AllocateZeroPool (sizeof (INTERNAL_H2O_CP_HANDLE_HEADER)); if (CpHandleHeader == NULL) { return EFI_OUT_OF_RESOURCES; } CpHandleHeader->Signature = H2O_PRIVATE_CP_HEADER_SIGNATURE; for (Index = 0; Index < NUMBER_OF_TPL; Index++) { InitializeListHead (&CpHandleHeader->RegisterLink[Index]); } CopyGuid (&CpHandleHeader->Guid, H2OCheckpoint); InsertTailList (mCpList, &CpHandleHeader->HandleLink); CpHandleHeader->TriggerTimes++; return EFI_SUCCESS; } // // Assign CP data and trigger all CP hanlders. // CopyMem (&CpHandleHeader->CpData, &H2OCheckpointData, sizeof (VOID *)); CpDataHeader = (H2O_CP_DATA *) H2OCheckpointData; // // Link all of handler to be triggered to TriggerLink // for (Index = 0; Index < NUMBER_OF_TPL; Index++) { InitializeListHead (&CpHandleHeader->TriggerLink[Index]); Head = &CpHandleHeader->RegisterLink[Index]; for (List = GetFirstNode (Head); List != Head; List = GetNextNode (Head, List)) { CpHandle = H2O_CP_NODE_FROM_REGISTER_LINK (List); InsertTailList (&CpHandleHeader->TriggerLink[Index], &CpHandle->TriggerLink); } } // // Trigger all of the checkpoint handlers // for (Index = 0; Index < NUMBER_OF_TPL; Index++) { Head = &CpHandleHeader->TriggerLink[Index]; CpHandleHeader->CurrentTriggerTpl = mTpls[Index]; while (!IsListEmpty (Head)) { CpHandle = H2O_CP_NODE_FROM_TRIGGER_LINK(Head->ForwardLink); RemoveEntryList (&CpHandle->TriggerLink); CpHandle->TriggerLink.ForwardLink = NULL; DEBUG_CP((DEBUG_INFO, "CP==>Call Handler: %a, Priority: %a, Function: %a, File Name: %a\n", CpHandle->HandlerName == NULL ? "Unknown" : CpHandle->HandlerName, TplToStr(CpHandle->Tpl), CpHandle->FunctionName == NULL ? "Unknown" : CpHandle->FunctionName, CpHandle->FileName == NULL ? "Unknown" : CpHandle->FileName )); DEBUG_CODE (CopyMem (&mCurrentCpHandle, CpHandle, sizeof (INTERNAL_H2O_CP_HANDLE));); CpHandle->Handler (NULL, (H2O_CP_HANDLE) CpHandle); DEBUG_CP((DEBUG_INFO, "CP==>Handler: %a returned %a.\n", mCurrentCpHandle.HandlerName == NULL ? "Unknown" : mCurrentCpHandle.HandlerName, CpStatusToStr(CpDataHeader->Status) )); // // Clear H2O_CP_TASK_BREAK bit and return directly if callback function enables H2O_CP_TASK_BREAK bit to break this checkpoint. // if ((CpDataHeader->Status & H2O_CP_TASK_BREAK) == H2O_CP_TASK_BREAK) { CpDataHeader->Status &= ~H2O_CP_TASK_BREAK; CpHandleHeader->CurrentTriggerTpl = 0; CpHandleHeader->TriggerTimes++; DEBUG_CP((DEBUG_INFO, "CP==>Break Handler: %a Function: %a, File Name: %a.\n", mCurrentCpHandle.HandlerName == NULL ? "Unknown" : mCurrentCpHandle.HandlerName, mCurrentCpHandle.FunctionName == NULL ? "Unknown" : mCurrentCpHandle.FunctionName, mCurrentCpHandle.FileName == NULL ? "Unknown" : mCurrentCpHandle.FileName )); return EFI_SUCCESS; } } } CpHandleHeader->CurrentTriggerTpl = 0; CpHandleHeader->TriggerTimes++; return EFI_SUCCESS; } /** This function unregisters the handle and frees any associated resources. @param[in] Handle The handle that is associated with the registered checkpoint handler. @retval EFI_SUCCESS The function completed successfully. @return EFI_INVALID_PARAMETER Handle is NULL, Handle is invalid or does not refer to a previously registered checkpoint handler. **/ EFI_STATUS H2OCpUnregisterHandler ( IN H2O_CP_HANDLE Handle ) { LIST_ENTRY *List; LIST_ENTRY *Head; INTERNAL_H2O_CP_HANDLE *CpHandle; INTERNAL_H2O_CP_HANDLE_HEADER *CpHandleHeader; if (Handle == NULL) { return EFI_INVALID_PARAMETER; } // // Check handle signature and using guid from handle to find CP header to make // sure this is a valid handle. // CpHandle = (INTERNAL_H2O_CP_HANDLE *) Handle; if (CpHandle->Signature != H2O_PRIVATE_CP_SIGNATURE) { return EFI_INVALID_PARAMETER; } CpHandleHeader = FindCpHeader (&CpHandle->Guid); if (CpHandleHeader == NULL || CpHandleHeader->Signature != H2O_PRIVATE_CP_HEADER_SIGNATURE) { return EFI_INVALID_PARAMETER; } Head = &CpHandleHeader->RegisterLink[GetIndexFromTpl (CpHandle->Tpl)]; for (List = GetFirstNode (Head); List != Head; List = GetNextNode (Head, List)) { CpHandle = H2O_CP_NODE_FROM_REGISTER_LINK (List); if (CpHandle == Handle) { // // Remove the handler if queue in trigger list // if (CpHandle->TriggerLink.ForwardLink != NULL) { RemoveEntryList (&CpHandle->TriggerLink); } RemoveEntryList (List); FreePool (CpHandle); // // To prevent from all of the Handlers in the same checkpoint are unregistered in the H2OCpTrigger() // To make sure link list data in the H2OCpTrigger()is correct, Don't free INTERNAL_H2O_CP_HANDLE_HEADER // even if all of the Handlers in the same checkpoint are unregistered. // return EFI_SUCCESS; } } return EFI_INVALID_PARAMETER; } /** The constructor function saves the address of check point list address in an internal protocol. And then all of library instances can access the same database. It will ASSERT() if that operation fails and it will always return EFI_SUCCESS. @param ImageHandle The firmware allocated handle for the EFI image. @param SystemTable A pointer to the EFI System Table. @retval EFI_SUCCESS This function completes successfully. @return Others Any error occurred while saving or getting check point address from volatile variable. **/ EFI_STATUS EFIAPI DxeH2OCpLibConstructor ( IN EFI_HANDLE ImageHandle, IN EFI_SYSTEM_TABLE *SystemTable ) { EFI_STATUS Status; EFI_HANDLE Handle; EFI_SMM_BASE2_PROTOCOL *SmmBase; EFI_SMM_SYSTEM_TABLE2 *Smst; BOOLEAN InSmm; Status = gBS->LocateProtocol ( &gEfiSmmBase2ProtocolGuid, NULL, (VOID **)&SmmBase ); InSmm = FALSE; if (!EFI_ERROR (Status)) { SmmBase->InSmm (SmmBase, &InSmm); } if (InSmm) { Status = SmmBase->GetSmstLocation( SmmBase, &Smst ); if (EFI_ERROR (Status)) { return Status; } Status = Smst->SmmLocateProtocol ( &mInternalH2OCpListHeadGuid, NULL, (VOID **) &mCpList ); if (EFI_ERROR (Status)) { mCpList = AllocatePool (sizeof (LIST_ENTRY)); ASSERT (mCpList != NULL); if (mCpList == NULL) { return EFI_OUT_OF_RESOURCES; } InitializeListHead (mCpList); Handle = NULL; Status = Smst->SmmInstallProtocolInterface ( &Handle, &mInternalH2OCpListHeadGuid, EFI_NATIVE_INTERFACE, (VOID *) mCpList ); } } else { Status = gBS->LocateProtocol ( &mInternalH2OCpListHeadGuid, NULL, (VOID **) &mCpList ); if (EFI_ERROR (Status)) { mCpList = AllocatePool (sizeof (LIST_ENTRY)); ASSERT (mCpList != NULL); if (mCpList == NULL) { return EFI_OUT_OF_RESOURCES; } InitializeListHead (mCpList); Handle = NULL; Status = gBS->InstallProtocolInterface ( &Handle, &mInternalH2OCpListHeadGuid, EFI_NATIVE_INTERFACE, (VOID *) mCpList ); } } return Status; }