/** @file MMI management. Copyright (c) 2009 - 2013, Intel Corporation. All rights reserved.
Copyright (c) 2016 - 2021, Arm Limited. All rights reserved.
SPDX-License-Identifier: BSD-2-Clause-Patent **/ #include "StandaloneMmCore.h" // // MM_HANDLER_STATE_NOTIFIER // // // MM_HANDLER - used for each MM handler // #define MMI_ENTRY_SIGNATURE SIGNATURE_32('m','m','i','e') typedef struct { UINTN Signature; LIST_ENTRY AllEntries; // All entries EFI_GUID HandlerType; // Type of interrupt LIST_ENTRY MmiHandlers; // All handlers } MMI_ENTRY; #define MMI_HANDLER_SIGNATURE SIGNATURE_32('m','m','i','h') typedef struct { UINTN Signature; LIST_ENTRY Link; // Link on MMI_ENTRY.MmiHandlers EFI_MM_HANDLER_ENTRY_POINT Handler; // The mm handler's entry point MMI_ENTRY *MmiEntry; BOOLEAN ToRemove; // To remove this MMI_HANDLER later } MMI_HANDLER; // // mMmiManageCallingDepth is used to track the depth of recursive calls of MmiManage. // UINTN mMmiManageCallingDepth = 0; LIST_ENTRY mRootMmiHandlerList = INITIALIZE_LIST_HEAD_VARIABLE (mRootMmiHandlerList); LIST_ENTRY mMmiEntryList = INITIALIZE_LIST_HEAD_VARIABLE (mMmiEntryList); /** Remove MmiHandler and free the memory it used. If MmiEntry is empty, remove MmiEntry and free the memory it used. @param MmiHandler Points to MMI handler. @param MmiEntry Points to MMI Entry or NULL for root MMI handlers. @retval TRUE MmiEntry is removed. @retval FALSE MmiEntry is not removed. **/ BOOLEAN RemoveMmiHandler ( IN MMI_HANDLER *MmiHandler, IN MMI_ENTRY *MmiEntry ) { ASSERT (MmiHandler->ToRemove); RemoveEntryList (&MmiHandler->Link); FreePool (MmiHandler); // // Remove the MMI_ENTRY if all handlers have been removed. // if (MmiEntry != NULL) { if (IsListEmpty (&MmiEntry->MmiHandlers)) { RemoveEntryList (&MmiEntry->AllEntries); FreePool (MmiEntry); return TRUE; } } return FALSE; } /** Finds the MMI entry for the requested handler type. @param HandlerType The type of the interrupt @param Create Create a new entry if not found @return MMI entry **/ MMI_ENTRY * EFIAPI MmCoreFindMmiEntry ( IN EFI_GUID *HandlerType, IN BOOLEAN Create ) { LIST_ENTRY *Link; MMI_ENTRY *Item; MMI_ENTRY *MmiEntry; // // Search the MMI entry list for the matching GUID // MmiEntry = NULL; for (Link = mMmiEntryList.ForwardLink; Link != &mMmiEntryList; Link = Link->ForwardLink) { Item = CR (Link, MMI_ENTRY, AllEntries, MMI_ENTRY_SIGNATURE); if (CompareGuid (&Item->HandlerType, HandlerType)) { // // This is the MMI entry // MmiEntry = Item; break; } } // // If the protocol entry was not found and Create is TRUE, then // allocate a new entry // if ((MmiEntry == NULL) && Create) { MmiEntry = AllocatePool (sizeof (MMI_ENTRY)); if (MmiEntry != NULL) { // // Initialize new MMI entry structure // MmiEntry->Signature = MMI_ENTRY_SIGNATURE; CopyGuid ((VOID *)&MmiEntry->HandlerType, HandlerType); InitializeListHead (&MmiEntry->MmiHandlers); // // Add it to MMI entry list // InsertTailList (&mMmiEntryList, &MmiEntry->AllEntries); } } return MmiEntry; } /** Manage MMI of a particular type. @param HandlerType Points to the handler type or NULL for root MMI 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 MMI 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 MmiManage ( 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; MMI_ENTRY *MmiEntry; MMI_HANDLER *MmiHandler; EFI_STATUS ReturnStatus; BOOLEAN WillReturn; EFI_STATUS Status; mMmiManageCallingDepth++; WillReturn = FALSE; Status = EFI_NOT_FOUND; ReturnStatus = Status; if (HandlerType == NULL) { // // Root MMI handler // Head = &mRootMmiHandlerList; } else { // // Non-root MMI handler // MmiEntry = MmCoreFindMmiEntry ((EFI_GUID *)HandlerType, FALSE); if (MmiEntry == NULL) { // // There is no handler registered for this interrupt source // return Status; } Head = &MmiEntry->MmiHandlers; } for (Link = Head->ForwardLink; Link != Head; Link = Link->ForwardLink) { MmiHandler = CR (Link, MMI_HANDLER, Link, MMI_HANDLER_SIGNATURE); Status = MmiHandler->Handler ( (EFI_HANDLE)MmiHandler, 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) { 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) { 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_EFI_ERROR (Status); break; } if (WillReturn) { break; } } ASSERT (mMmiManageCallingDepth > 0); mMmiManageCallingDepth--; // // MmiHandlerUnRegister() calls from MMI handlers are deferred till this point. // Before returned from MmiManage, delete the MmiHandler which is // marked as ToRemove. // Note that MmiManage can be called recursively. // if (mMmiManageCallingDepth == 0) { // // Go through all MmiHandler in root Mmi handlers // for ( Link = GetFirstNode (&mRootMmiHandlerList) ; !IsNull (&mRootMmiHandlerList, Link); ) { // // MmiHandler might be removed in below, so cache the next link in Link // MmiHandler = CR (Link, MMI_HANDLER, Link, MMI_HANDLER_SIGNATURE); Link = GetNextNode (&mRootMmiHandlerList, Link); if (MmiHandler->ToRemove) { // // Remove MmiHandler if the ToRemove is set. // RemoveMmiHandler (MmiHandler, NULL); } } // // Go through all MmiHandler in non-root MMI handlers // for ( EntryLink = GetFirstNode (&mMmiEntryList) ; !IsNull (&mMmiEntryList, EntryLink); ) { // // MmiEntry might be removed in below, so cache the next link in EntryLink // MmiEntry = CR (EntryLink, MMI_ENTRY, AllEntries, MMI_ENTRY_SIGNATURE); EntryLink = GetNextNode (&mMmiEntryList, EntryLink); for ( Link = GetFirstNode (&MmiEntry->MmiHandlers) ; !IsNull (&MmiEntry->MmiHandlers, Link); ) { // // MmiHandler might be removed in below, so cache the next link in Link // MmiHandler = CR (Link, MMI_HANDLER, Link, MMI_HANDLER_SIGNATURE); Link = GetNextNode (&MmiEntry->MmiHandlers, Link); if (MmiHandler->ToRemove) { // // Remove MmiHandler if the ToRemove is set. // if (RemoveMmiHandler (MmiHandler, MmiEntry)) { break; } } } } } return ReturnStatus; } /** Registers a handler to execute within MM. @param Handler Handler service function pointer. @param HandlerType Points to the handler type or NULL for root MMI 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 MmiHandlerRegister ( IN EFI_MM_HANDLER_ENTRY_POINT Handler, IN CONST EFI_GUID *HandlerType OPTIONAL, OUT EFI_HANDLE *DispatchHandle ) { MMI_HANDLER *MmiHandler; MMI_ENTRY *MmiEntry; LIST_ENTRY *List; if ((Handler == NULL) || (DispatchHandle == NULL)) { return EFI_INVALID_PARAMETER; } MmiHandler = AllocateZeroPool (sizeof (MMI_HANDLER)); if (MmiHandler == NULL) { return EFI_OUT_OF_RESOURCES; } MmiHandler->Signature = MMI_HANDLER_SIGNATURE; MmiHandler->Handler = Handler; MmiHandler->ToRemove = FALSE; if (HandlerType == NULL) { // // This is root MMI handler // MmiEntry = NULL; List = &mRootMmiHandlerList; } else { // // None root MMI handler // MmiEntry = MmCoreFindMmiEntry ((EFI_GUID *)HandlerType, TRUE); if (MmiEntry == NULL) { return EFI_OUT_OF_RESOURCES; } List = &MmiEntry->MmiHandlers; } MmiHandler->MmiEntry = MmiEntry; InsertTailList (List, &MmiHandler->Link); *DispatchHandle = (EFI_HANDLE)MmiHandler; return EFI_SUCCESS; } /** Unregister a handler in MM. @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 MmiHandlerUnRegister ( IN EFI_HANDLE DispatchHandle ) { MMI_HANDLER *MmiHandler; MMI_ENTRY *MmiEntry; MmiHandler = (MMI_HANDLER *)DispatchHandle; if (MmiHandler == NULL) { return EFI_INVALID_PARAMETER; } if (MmiHandler->Signature != MMI_HANDLER_SIGNATURE) { return EFI_INVALID_PARAMETER; } MmiHandler->ToRemove = TRUE; if (mMmiManageCallingDepth > 0) { // // This function is called from MmiManage() // Do not delete or remove MmiHandler or MmiEntry now. // return EFI_SUCCESS; } MmiEntry = MmiHandler->MmiEntry; RemoveMmiHandler (MmiHandler, MmiEntry); return EFI_SUCCESS; }