/** @file
  SMI management.

  Copyright (c) 2009 - 2018, Intel Corporation. All rights reserved.<BR>
  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++;
  WillReturn   = FALSE;
  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;
}
