/** @file SMI management. Copyright (c) 2009 - 2018, Intel Corporation. All rights reserved.
SPDX-License-Identifier: BSD-2-Clause-Patent **/ #include "PiSmmCore.h" // // mSmiManageCallingDepth is used to track the depth of recursive calls of SmiManage. // UINTN mSmiManageCallingDepth = 0; LIST_ENTRY mSmiEntryList = INITIALIZE_LIST_HEAD_VARIABLE (mSmiEntryList); SMI_ENTRY mRootSmiEntry = { SMI_ENTRY_SIGNATURE, INITIALIZE_LIST_HEAD_VARIABLE (mRootSmiEntry.AllEntries), { 0 }, INITIALIZE_LIST_HEAD_VARIABLE (mRootSmiEntry.SmiHandlers), }; /** Finds the SMI entry for the requested handler type. @param HandlerType The type of the interrupt @param Create Create a new entry if not found @return SMI entry **/ SMI_ENTRY * EFIAPI SmmCoreFindSmiEntry ( IN EFI_GUID *HandlerType, IN BOOLEAN Create ) { LIST_ENTRY *Link; SMI_ENTRY *Item; SMI_ENTRY *SmiEntry; // // Search the SMI entry list for the matching GUID // SmiEntry = NULL; for (Link = mSmiEntryList.ForwardLink; Link != &mSmiEntryList; Link = Link->ForwardLink) { Item = CR (Link, SMI_ENTRY, AllEntries, SMI_ENTRY_SIGNATURE); if (CompareGuid (&Item->HandlerType, HandlerType)) { // // This is the SMI entry // SmiEntry = Item; break; } } // // If the protocol entry was not found and Create is TRUE, then // allocate a new entry // if ((SmiEntry == NULL) && Create) { SmiEntry = AllocatePool (sizeof (SMI_ENTRY)); if (SmiEntry != NULL) { // // Initialize new SMI entry structure // SmiEntry->Signature = SMI_ENTRY_SIGNATURE; CopyGuid ((VOID *)&SmiEntry->HandlerType, HandlerType); InitializeListHead (&SmiEntry->SmiHandlers); // // Add it to SMI entry list // InsertTailList (&mSmiEntryList, &SmiEntry->AllEntries); } } return SmiEntry; } /** Remove SmiHandler and free the memory it used. If SmiEntry is empty, remove SmiEntry and free the memory it used. @param SmiHandler Points to SMI handler. @param SmiEntry Points to SMI Entry or NULL for root SMI handlers. @retval TRUE SmiEntry is removed. @retval FALSE SmiEntry is not removed. **/ BOOLEAN RemoveSmiHandler ( IN SMI_HANDLER *SmiHandler, IN SMI_ENTRY *SmiEntry ) { ASSERT (SmiHandler->ToRemove); RemoveEntryList (&SmiHandler->Link); FreePool (SmiHandler); // // Remove the SMI_ENTRY if all handlers have been removed. // if (SmiEntry != NULL) { if (IsListEmpty (&SmiEntry->SmiHandlers)) { RemoveEntryList (&SmiEntry->AllEntries); FreePool (SmiEntry); return TRUE; } } return FALSE; } /** Manage SMI of a particular type. @param HandlerType Points to the handler type or NULL for root SMI handlers. @param Context Points to an optional context buffer. @param CommBuffer Points to the optional communication buffer. @param CommBufferSize Points to the size of the optional communication buffer. @retval EFI_WARN_INTERRUPT_SOURCE_PENDING Interrupt source was processed successfully but not quiesced. @retval EFI_INTERRUPT_PENDING One or more SMI sources could not be quiesced. @retval EFI_NOT_FOUND Interrupt source was not handled or quiesced. @retval EFI_SUCCESS Interrupt source was handled and quiesced. **/ EFI_STATUS EFIAPI SmiManage ( IN CONST EFI_GUID *HandlerType, IN CONST VOID *Context OPTIONAL, IN OUT VOID *CommBuffer OPTIONAL, IN OUT UINTN *CommBufferSize OPTIONAL ) { LIST_ENTRY *Link; LIST_ENTRY *Head; LIST_ENTRY *EntryLink; SMI_ENTRY *SmiEntry; SMI_HANDLER *SmiHandler; EFI_STATUS ReturnStatus; BOOLEAN WillReturn; EFI_STATUS Status; PERF_FUNCTION_BEGIN (); mSmiManageCallingDepth++; Status = EFI_NOT_FOUND; ReturnStatus = Status; if (HandlerType == NULL) { // // Root SMI handler // SmiEntry = &mRootSmiEntry; } else { // // Non-root SMI handler // SmiEntry = SmmCoreFindSmiEntry ((EFI_GUID *)HandlerType, FALSE); if (SmiEntry == NULL) { // // There is no handler registered for this interrupt source // PERF_FUNCTION_END (); return Status; } } Head = &SmiEntry->SmiHandlers; for (Link = Head->ForwardLink; Link != Head; Link = Link->ForwardLink) { SmiHandler = CR (Link, SMI_HANDLER, Link, SMI_HANDLER_SIGNATURE); Status = SmiHandler->Handler ( (EFI_HANDLE)SmiHandler, Context, CommBuffer, CommBufferSize ); switch (Status) { case EFI_INTERRUPT_PENDING: // // If a handler returns EFI_INTERRUPT_PENDING and HandlerType is not NULL then // no additional handlers will be processed and EFI_INTERRUPT_PENDING will be returned. // if (HandlerType != NULL) { PERF_FUNCTION_END (); ReturnStatus = EFI_INTERRUPT_PENDING; WillReturn = TRUE; } else { // // If any other handler's result sets ReturnStatus as EFI_SUCCESS, the return status // will be EFI_SUCCESS. // if (ReturnStatus != EFI_SUCCESS) { ReturnStatus = Status; } } break; case EFI_SUCCESS: // // If at least one of the handlers returns EFI_SUCCESS then the function will return // EFI_SUCCESS. If a handler returns EFI_SUCCESS and HandlerType is not NULL then no // additional handlers will be processed. // if (HandlerType != NULL) { PERF_FUNCTION_END (); WillReturn = TRUE; } ReturnStatus = EFI_SUCCESS; break; case EFI_WARN_INTERRUPT_SOURCE_QUIESCED: // // If at least one of the handlers returns EFI_WARN_INTERRUPT_SOURCE_QUIESCED // then the function will return EFI_SUCCESS. // ReturnStatus = EFI_SUCCESS; break; case EFI_WARN_INTERRUPT_SOURCE_PENDING: // // If all the handlers returned EFI_WARN_INTERRUPT_SOURCE_PENDING // then EFI_WARN_INTERRUPT_SOURCE_PENDING will be returned. // if (ReturnStatus != EFI_SUCCESS) { ReturnStatus = Status; } break; default: // // Unexpected status code returned. // ASSERT (FALSE); break; } if (WillReturn) { break; } } ASSERT (mSmiManageCallingDepth > 0); mSmiManageCallingDepth--; // // SmiHandlerUnRegister() calls from SMI handlers are deferred till this point. // Before returned from SmiManage, delete the SmiHandler which is // marked as ToRemove. // Note that SmiManage can be called recursively. // if (mSmiManageCallingDepth == 0) { // // Go through all SmiHandler in root SMI handlers // for ( Link = GetFirstNode (&mRootSmiEntry.SmiHandlers) ; !IsNull (&mRootSmiEntry.SmiHandlers, Link); ) { // // SmiHandler might be removed in below, so cache the next link in Link // SmiHandler = CR (Link, SMI_HANDLER, Link, SMI_HANDLER_SIGNATURE); Link = GetNextNode (&mRootSmiEntry.SmiHandlers, Link); if (SmiHandler->ToRemove) { // // Remove SmiHandler if the ToRemove is set. // RemoveSmiHandler (SmiHandler, NULL); } } // // Go through all SmiHandler in non-root SMI handlers // for ( EntryLink = GetFirstNode (&mSmiEntryList) ; !IsNull (&mSmiEntryList, EntryLink); ) { // // SmiEntry might be removed in below, so cache the next link in EntryLink // SmiEntry = CR (EntryLink, SMI_ENTRY, AllEntries, SMI_ENTRY_SIGNATURE); EntryLink = GetNextNode (&mSmiEntryList, EntryLink); for ( Link = GetFirstNode (&SmiEntry->SmiHandlers) ; !IsNull (&SmiEntry->SmiHandlers, Link); ) { // // SmiHandler might be removed in below, so cache the next link in Link // SmiHandler = CR (Link, SMI_HANDLER, Link, SMI_HANDLER_SIGNATURE); Link = GetNextNode (&SmiEntry->SmiHandlers, Link); if (SmiHandler->ToRemove) { if (RemoveSmiHandler (SmiHandler, SmiEntry)) { break; } } } } } PERF_FUNCTION_END (); return ReturnStatus; } /** Registers a handler to execute within SMM. @param Handler Handler service function pointer. @param HandlerType Points to the handler type or NULL for root SMI handlers. @param DispatchHandle On return, contains a unique handle which can be used to later unregister the handler function. @retval EFI_SUCCESS Handler register success. @retval EFI_INVALID_PARAMETER Handler or DispatchHandle is NULL. **/ EFI_STATUS EFIAPI SmiHandlerRegister ( IN EFI_SMM_HANDLER_ENTRY_POINT2 Handler, IN CONST EFI_GUID *HandlerType OPTIONAL, OUT EFI_HANDLE *DispatchHandle ) { SMI_HANDLER *SmiHandler; SMI_ENTRY *SmiEntry; LIST_ENTRY *List; if ((Handler == NULL) || (DispatchHandle == NULL)) { return EFI_INVALID_PARAMETER; } SmiHandler = AllocateZeroPool (sizeof (SMI_HANDLER)); if (SmiHandler == NULL) { return EFI_OUT_OF_RESOURCES; } SmiHandler->Signature = SMI_HANDLER_SIGNATURE; SmiHandler->Handler = Handler; SmiHandler->CallerAddr = (UINTN)RETURN_ADDRESS (0); SmiHandler->ToRemove = FALSE; if (HandlerType == NULL) { // // This is root SMI handler // SmiEntry = &mRootSmiEntry; } else { // // None root SMI handler // SmiEntry = SmmCoreFindSmiEntry ((EFI_GUID *)HandlerType, TRUE); if (SmiEntry == NULL) { return EFI_OUT_OF_RESOURCES; } } List = &SmiEntry->SmiHandlers; SmiHandler->SmiEntry = SmiEntry; InsertTailList (List, &SmiHandler->Link); *DispatchHandle = (EFI_HANDLE)SmiHandler; return EFI_SUCCESS; } /** Unregister a handler in SMM. @param DispatchHandle The handle that was specified when the handler was registered. @retval EFI_SUCCESS Handler function was successfully unregistered. @retval EFI_INVALID_PARAMETER DispatchHandle does not refer to a valid handle. **/ EFI_STATUS EFIAPI SmiHandlerUnRegister ( IN EFI_HANDLE DispatchHandle ) { SMI_HANDLER *SmiHandler; SMI_ENTRY *SmiEntry; LIST_ENTRY *EntryLink; LIST_ENTRY *HandlerLink; if (DispatchHandle == NULL) { return EFI_INVALID_PARAMETER; } // // Look for it in root SMI handlers // SmiHandler = NULL; for ( HandlerLink = GetFirstNode (&mRootSmiEntry.SmiHandlers) ; !IsNull (&mRootSmiEntry.SmiHandlers, HandlerLink) && ((EFI_HANDLE)SmiHandler != DispatchHandle) ; HandlerLink = GetNextNode (&mRootSmiEntry.SmiHandlers, HandlerLink) ) { SmiHandler = CR (HandlerLink, SMI_HANDLER, Link, SMI_HANDLER_SIGNATURE); } // // Look for it in non-root SMI handlers // for ( EntryLink = GetFirstNode (&mSmiEntryList) ; !IsNull (&mSmiEntryList, EntryLink) && ((EFI_HANDLE)SmiHandler != DispatchHandle) ; EntryLink = GetNextNode (&mSmiEntryList, EntryLink) ) { SmiEntry = CR (EntryLink, SMI_ENTRY, AllEntries, SMI_ENTRY_SIGNATURE); for ( HandlerLink = GetFirstNode (&SmiEntry->SmiHandlers) ; !IsNull (&SmiEntry->SmiHandlers, HandlerLink) && ((EFI_HANDLE)SmiHandler != DispatchHandle) ; HandlerLink = GetNextNode (&SmiEntry->SmiHandlers, HandlerLink) ) { SmiHandler = CR (HandlerLink, SMI_HANDLER, Link, SMI_HANDLER_SIGNATURE); } } if (SmiHandler == NULL) { return EFI_INVALID_PARAMETER; } ASSERT ((EFI_HANDLE)SmiHandler == DispatchHandle); SmiHandler->ToRemove = TRUE; if (mSmiManageCallingDepth > 0) { // // This function is called from SmiManage() // Do not delete or remove SmiHandler or SmiEntry now. // SmiManage will handle it later // return EFI_SUCCESS; } SmiEntry = SmiHandler->SmiEntry; // // For root SMI handler, use NULL for SmiEntry // RemoveSmiHandler (SmiHandler, (SmiEntry == &mRootSmiEntry) ? NULL : SmiEntry); return EFI_SUCCESS; }