/** @file
  This driver is responsible for the registration of child drivers
  and the abstraction of the PCH SMI sources.

  Copyright (c) 2021, Intel Corporation. All rights reserved.<BR>
  SPDX-License-Identifier: BSD-2-Clause-Patent
**/
#include "PchSmm.h"
#include "PchSmmHelpers.h"
#include "PchSmmEspi.h"
#include <Library/SmiHandlerProfileLib.h>
#include <Register/GpioRegs.h>
#include <Register/PmcRegs.h>
#include <Register/RtcRegs.h>

#define PROGRESS_CODE_S3_SUSPEND_START  PcdGet32 (PcdProgressCodeS3SuspendStart)
//
// MODULE / GLOBAL DATA
//
// Module variables used by the both the main dispatcher and the source dispatchers
// Declared in PchSmm.h
//
GLOBAL_REMOVE_IF_UNREFERENCED UINT16                mAcpiBaseAddr;
GLOBAL_REMOVE_IF_UNREFERENCED UINT16                mTcoBaseAddr;
GLOBAL_REMOVE_IF_UNREFERENCED BOOLEAN               mReadyToLock;
GLOBAL_REMOVE_IF_UNREFERENCED BOOLEAN               mS3SusStart;

GLOBAL_REMOVE_IF_UNREFERENCED PRIVATE_DATA          mPrivateData = {
  {
    NULL,
    NULL
  },                                    // CallbackDataBase linked list head
  NULL,                                 // EFI handle returned when calling InstallMultipleProtocolInterfaces
  NULL,                                 //
  {                                     // protocol arrays
    //
    // elements within the array
    //
    {
      PROTOCOL_SIGNATURE,
      UsbType,
      &gEfiSmmUsbDispatch2ProtocolGuid,
      {{
        (PCH_SMM_GENERIC_REGISTER) PchPiSmmCoreRegister,
        (PCH_SMM_GENERIC_UNREGISTER) PchPiSmmCoreUnRegister
      }}
    },
    {
      PROTOCOL_SIGNATURE,
      SxType,
      &gEfiSmmSxDispatch2ProtocolGuid,
      {{
        (PCH_SMM_GENERIC_REGISTER) PchPiSmmCoreRegister,
        (PCH_SMM_GENERIC_UNREGISTER) PchPiSmmCoreUnRegister
      }}
    },
    {
      PROTOCOL_SIGNATURE,
      SwType,
      &gEfiSmmSwDispatch2ProtocolGuid,
      {{
        (PCH_SMM_GENERIC_REGISTER) PchSwSmiRegister,
        (PCH_SMM_GENERIC_UNREGISTER) PchSwSmiUnRegister,
        (UINTN) MAXIMUM_SWI_VALUE
      }}
    },
    {
      PROTOCOL_SIGNATURE,
      GpiType,
      &gEfiSmmGpiDispatch2ProtocolGuid,
      {{
        (PCH_SMM_GENERIC_REGISTER) PchGpiSmiRegister,
        (PCH_SMM_GENERIC_UNREGISTER) PchGpiSmiUnRegister,
        (UINTN) PCH_GPIO_NUM_SUPPORTED_GPIS
      }}
    },
    {
      PROTOCOL_SIGNATURE,
      PowerButtonType,
      &gEfiSmmPowerButtonDispatch2ProtocolGuid,
      {{
        (PCH_SMM_GENERIC_REGISTER) PchPiSmmCoreRegister,
        (PCH_SMM_GENERIC_UNREGISTER) PchPiSmmCoreUnRegister
      }}
    },
    {
      PROTOCOL_SIGNATURE,
      PeriodicTimerType,
      &gEfiSmmPeriodicTimerDispatch2ProtocolGuid,
      {{
        (PCH_SMM_GENERIC_REGISTER) PchPiSmmCoreRegister,
        (PCH_SMM_GENERIC_UNREGISTER) PchPiSmmCoreUnRegister,
        (UINTN) PchSmmPeriodicTimerDispatchGetNextShorterInterval
      }}
    },
  }
};

GLOBAL_REMOVE_IF_UNREFERENCED CONTEXT_FUNCTIONS     mContextFunctions[PCH_SMM_PROTOCOL_TYPE_MAX] = {
  {
    NULL,
    NULL,
    NULL
  },
  {
    SxGetContext,
    SxCmpContext,
    NULL
  },
  {
    NULL,
    NULL,
    NULL
  },
  {
    NULL,
    NULL,
    NULL
  },
  {
    PowerButtonGetContext,
    PowerButtonCmpContext,
    NULL
  },
  {
    PeriodicTimerGetContext,
    PeriodicTimerCmpContext,
    PeriodicTimerGetCommBuffer
  },
};

//
// PROTOTYPES
//
// Functions use only in this file
//
EFI_STATUS
EFIAPI
PchSmmCoreDispatcher (
  IN       EFI_HANDLE         SmmImageHandle,
  IN CONST VOID               *PchSmmCore,     OPTIONAL
  IN OUT   VOID               *CommunicationBuffer,
  IN OUT   UINTN              *SourceSize
  );

//
// FUNCTIONS
//
/**
  SMM ready to lock notification event handler.

  @param  Protocol   Points to the protocol's unique identifier
  @param  Interface  Points to the interface instance
  @param  Handle     The handle on which the interface was installed

  @retval EFI_SUCCESS   SmmReadyToLockCallback runs successfully

**/
EFI_STATUS
EFIAPI
SmmReadyToLockCallback (
  IN CONST EFI_GUID                       *Protocol,
  IN VOID                                 *Interface,
  IN EFI_HANDLE                           Handle
  )
{
  mReadyToLock = TRUE;

  return EFI_SUCCESS;
}

/**
  <b>PchSmiDispatcher SMM Module Entry Point</b>\n
  - <b>Introduction</b>\n
    The PchSmiDispatcher module is an SMM driver which  provides SMI handler registration
    services for PCH generated SMIs.

  - <b>Details</b>\n
    This module provides SMI handler registration servicies for PCH SMIs.
    NOTE: All the register/unregister functions will be locked after SMM ready to boot signal event.
    Please make sure no handler is installed after that.

  - @pre
    - EFI_SMM_BASE2_PROTOCOL
      - Documented in the System Management Mode Core Interface Specification
    - EFI_PCI_ROOT_BRIDGE_IO_PROTOCOL
      - Documented in the UEFI 2.0 Specification and above
    - EFI_PCI_HOST_BRIDGE_RESOURCE_ALLOCATION_PROTOCOL
      - This is to ensure that PCI MMIO and IO resource has been prepared and available for this driver to allocate.
    - EFI_SMM_CPU_PROTOCOL

  - @result
    The PchSmiDispatcher driver produces:
    - EFI_SMM_USB_DISPATCH2_PROTOCOL
    - EFI_SMM_SX_DISPATCH2_PROTOCOL
    - EFI_SMM_SW_DISPATCH2_PROTOCOL
    - EFI_SMM_GPI_DISPATCH2_PROTOCOL
    - EFI_SMM_IO_TRAP_DISPATCH2_PROTOCOL
    - EFI_SMM_POWER_BUTTON_DISPATCH2_PROTOCOL
    - EFI_SMM_PERIODIC_TIMER_DISPATCH2_PROTOCOL
    - @link _PCH_SMM_IO_TRAP_CONTROL_PROTOCOL PCH_SMM_IO_TRAP_CONTROL_PROTOCOL @endlink
    - @link _PCH_PCIE_SMI_DISPATCH_PROTOCOL PCH_PCIE_SMI_DISPATCH_PROTOCOL @endlink
    - @link _PCH_TCO_SMI_DISPATCH_PROTOCOL PCH_TCO_SMI_DISPATCH_PROTOCOL @endlink
    - @link _PCH_ACPI_SMI_DISPATCH_PROTOCOL PCH_ACPI_SMI_DISPATCH_PROTOCOL @endlink
    - @link _PCH_SMI_DISPATCH_PROTOCOL PCH_SMI_DISPATCH_PROTOCOL @endlink
    - @link _PCH_ESPI_SMI_DISPATCH_PROTOCOL PCH_ESPI_SMI_DISPATCH_PROTOCOL @endlink

  @param[in] ImageHandle          Pointer to the loaded image protocol for this driver
  @param[in] SystemTable          Pointer to the EFI System Table

  @retval EFI_SUCCESS             PchSmmDispatcher Initialization completed.
**/
EFI_STATUS
EFIAPI
InitializePchSmmDispatcher (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
  EFI_STATUS           Status;
  VOID                 *SmmReadyToLockRegistration;

  mS3SusStart = FALSE;
  //
  // Access ACPI Base Addresses Register
  //

  mAcpiBaseAddr = PmcGetAcpiBase ();
  ASSERT (mAcpiBaseAddr != 0);

  //
  // Access TCO Base Addresses Register
  //
  PchTcoBaseGet (&mTcoBaseAddr);
  ASSERT (mTcoBaseAddr != 0);

  //
  // Register a callback function to handle subsequent SMIs.  This callback
  // will be called by SmmCoreDispatcher.
  //
  Status = gSmst->SmiHandlerRegister (PchSmmCoreDispatcher, NULL, &mPrivateData.SmiHandle);
  ASSERT_EFI_ERROR (Status);
  //
  // Initialize Callback DataBase
  //
  InitializeListHead (&mPrivateData.CallbackDataBase);

  //
  // Enable SMIs on the PCH now that we have a callback
  //
  PchSmmInitHardware ();

  //
  // Install and initialize all the needed protocols
  //
  PchSwDispatchInit ();
  PchSmmPublishDispatchProtocols ();
  InstallPchSmiDispatchProtocols ();
  InstallIoTrap (ImageHandle);
  InstallEspiSmi (ImageHandle);
  InstallPchSmmPeriodicTimerControlProtocol (mPrivateData.InstallMultProtHandle);

  //
  // Register EFI_SMM_READY_TO_LOCK_PROTOCOL_GUID notify function.
  //
  Status = gSmst->SmmRegisterProtocolNotify (
                    &gEfiSmmReadyToLockProtocolGuid,
                    SmmReadyToLockCallback,
                    &SmmReadyToLockRegistration
                    );
  ASSERT_EFI_ERROR (Status);

  return EFI_SUCCESS;
}

/**
  The internal function used to create and insert a database record

  @param[in]  InsertRecord              Record to insert to database.
  @param[out] DispatchHandle            Handle of dispatch function to register.

  @retval EFI_INVALID_PARAMETER         Error with NULL SMI source description
  @retval EFI_OUT_OF_RESOURCES          Fail to allocate pool for database record
  @retval EFI_SUCCESS                   The database record is created successfully.
**/
EFI_STATUS
SmmCoreInsertRecord (
  IN  DATABASE_RECORD                   *NewRecord,
  OUT EFI_HANDLE                        *DispatchHandle
  )
{
  EFI_STATUS                            Status;
  DATABASE_RECORD                       *Record;

  if ((NewRecord == NULL) ||
      (NewRecord->Signature != DATABASE_RECORD_SIGNATURE))
  {
    ASSERT (FALSE);
    return EFI_INVALID_PARAMETER;
  }

  Status = gSmst->SmmAllocatePool (EfiRuntimeServicesData, sizeof (DATABASE_RECORD), (VOID **) &Record);
  if (EFI_ERROR (Status)) {
    ASSERT (FALSE);
    return EFI_OUT_OF_RESOURCES;
  }
  CopyMem (Record, NewRecord, sizeof (DATABASE_RECORD));

  //
  // After ensuring the source of event is not null, we will insert the record into the database
  //
  InsertTailList (&mPrivateData.CallbackDataBase, &Record->Link);

  //
  // Child's handle will be the address linked list link in the record
  //
  *DispatchHandle = (EFI_HANDLE) (&Record->Link);

  return EFI_SUCCESS;
}

/**
  Unregister a child SMI source dispatch function with a parent SMM driver

  @param[in] This                       Protocol instance pointer.
  @param[in] DispatchHandle             Handle of dispatch function to deregister.

  @retval EFI_SUCCESS                   The dispatch function has been successfully
                                        unregistered and the SMI source has been disabled
                                        if there are no other registered child dispatch
                                        functions for this SMI source.
  @retval EFI_INVALID_PARAMETER         Handle is invalid.
  @retval EFI_ACCESS_DENIED             Return access denied if the SmmReadyToLock event has been triggered
**/
EFI_STATUS
EFIAPI
PchPiSmmCoreUnRegister (
  IN PCH_SMM_GENERIC_PROTOCOL                           *This,
  IN EFI_HANDLE                                         *DispatchHandle
  )
{
  DATABASE_RECORD                   *RecordToDelete;
  EFI_STATUS                        Status;
  PCH_SMM_QUALIFIED_PROTOCOL        *Qualified;

  Qualified      = QUALIFIED_PROTOCOL_FROM_GENERIC (This);
  RecordToDelete = DATABASE_RECORD_FROM_LINK (DispatchHandle);
  Status         = PchSmmCoreUnRegister (NULL, DispatchHandle);
  if (!EFI_ERROR (Status)) {
    SmiHandlerProfileUnregisterHandler (
      Qualified->Guid,
      RecordToDelete->Callback,
      &RecordToDelete->ChildContext,
      RecordToDelete->ContextSize
      );
  }
  return Status;
}

/**
  Register a child SMI dispatch function with a parent SMM driver.

  @param[in] This                 Pointer to the PCH_SMM_GENERIC_PROTOCOL instance.
  @param[in] DispatchFunction     Pointer to dispatch function to be invoked for this SMI source.
  @param[in] DispatchContext      Pointer to the dispatch function's context.
  @param[out] DispatchHandle      Handle of dispatch function, for when interfacing
                                  with the parent SMM driver, will be the address of linked
                                  list link in the call back record.

  @retval EFI_OUT_OF_RESOURCES    Insufficient resources to create database record
  @retval EFI_INVALID_PARAMETER   The input parameter is invalid
  @retval EFI_SUCCESS             The dispatch function has been successfully
                                  registered and the SMI source has been enabled.
**/
EFI_STATUS
EFIAPI
PchPiSmmCoreRegister (
  IN  PCH_SMM_GENERIC_PROTOCOL                          *This,
  IN  EFI_SMM_HANDLER_ENTRY_POINT2                      DispatchFunction,
  IN  PCH_SMM_CONTEXT                                   *DispatchContext,
  OUT EFI_HANDLE                                        *DispatchHandle
  )
{
  EFI_STATUS                  Status;
  DATABASE_RECORD             Record;
  PCH_SMM_QUALIFIED_PROTOCOL  *Qualified;
  PCH_SMM_SOURCE_DESC         NullSourceDesc;

  //
  // Initialize NullSourceDesc
  //
  NullInitSourceDesc (&NullSourceDesc);
  //
  // Return access denied if the SmmReadyToLock event has been triggered
  //
  if (mReadyToLock == TRUE) {
    DEBUG ((DEBUG_ERROR, "Register is not allowed if the EndOfDxe event has been triggered! \n"));
    return EFI_ACCESS_DENIED;
  }

  ZeroMem (&Record, sizeof (DATABASE_RECORD));

  //
  // Gather information about the registration request
  //
  Record.Callback          = DispatchFunction;

  Qualified                = QUALIFIED_PROTOCOL_FROM_GENERIC (This);

  Record.ProtocolType      = Qualified->Type;

  Record.ContextFunctions  = mContextFunctions[Qualified->Type];
  //
  // Perform linked list housekeeping
  //
  Record.Signature         = DATABASE_RECORD_SIGNATURE;

  switch (Qualified->Type) {
    //
    // By the end of this switch statement, we'll know the
    // source description the child is registering for
    //
    case UsbType:
      Record.ContextSize = sizeof (EFI_SMM_USB_REGISTER_CONTEXT);
      CopyMem (&Record.ChildContext, DispatchContext, Record.ContextSize);
      //
      // Check the validity of Context Type
      //
      if ((Record.ChildContext.Usb.Type < UsbLegacy) || (Record.ChildContext.Usb.Type > UsbWake)) {
        return EFI_INVALID_PARAMETER;
      }

      MapUsbToSrcDesc (DispatchContext, &Record.SrcDesc);
      Record.ClearSource = NULL;
      //
      // use default clear source function
      //
      break;

    case SxType:
      Record.ContextSize = sizeof (EFI_SMM_SX_REGISTER_CONTEXT);
      CopyMem (&Record.ChildContext, DispatchContext, Record.ContextSize);
      //
      // Check the validity of Context Type and Phase
      //
      if ((Record.ChildContext.Sx.Type < SxS0) ||
          (Record.ChildContext.Sx.Type >= EfiMaximumSleepType) ||
          (Record.ChildContext.Sx.Phase < SxEntry) ||
          (Record.ChildContext.Sx.Phase >= EfiMaximumPhase)
          ) {
        return EFI_INVALID_PARAMETER;
      }

      CopyMem (&Record.SrcDesc, &mSxSourceDesc, sizeof (PCH_SMM_SOURCE_DESC));
      Record.ClearSource = NULL;
      //
      // use default clear source function
      //
      break;

    case PowerButtonType:
      Record.ContextSize = sizeof (EFI_SMM_POWER_BUTTON_REGISTER_CONTEXT);
      CopyMem (&Record.ChildContext, DispatchContext, Record.ContextSize);
      //
      // Check the validity of Context Phase
      //
      if ((Record.ChildContext.PowerButton.Phase < EfiPowerButtonEntry) ||
          (Record.ChildContext.PowerButton.Phase > EfiPowerButtonExit))
      {
        return EFI_INVALID_PARAMETER;
      }

      CopyMem (&Record.SrcDesc, &mPowerButtonSourceDesc, sizeof (PCH_SMM_SOURCE_DESC));
      Record.ClearSource = NULL;
      //
      // use default clear source function
      //
      break;

    case PeriodicTimerType:
      Record.ContextSize = sizeof (EFI_SMM_PERIODIC_TIMER_REGISTER_CONTEXT);
      CopyMem (&Record.ChildContext, DispatchContext, Record.ContextSize);
      //
      // Check the validity of timer value
      //
      if (DispatchContext->PeriodicTimer.SmiTickInterval <= 0) {
        return EFI_INVALID_PARAMETER;
      }

      MapPeriodicTimerToSrcDesc (DispatchContext, &Record.SrcDesc);
      Record.MiscData.TimerSmiEnabled = TRUE;
      Record.ClearSource = PchSmmPeriodicTimerClearSource;
      break;

    default:
      return EFI_INVALID_PARAMETER;
      break;
  }

  if (CompareSources (&Record.SrcDesc, &NullSourceDesc)) {
    return EFI_INVALID_PARAMETER;
  }

  //
  // After ensuring the source of event is not null, we will insert the record into the database
  // Child's handle will be the address linked list link in the record
  //
  Status = SmmCoreInsertRecord (
             &Record,
             DispatchHandle
             );
  ASSERT_EFI_ERROR (Status);

  if (Record.ClearSource == NULL) {
    //
    // Clear the SMI associated w/ the source using the default function
    //
    PchSmmClearSource (&Record.SrcDesc);
  } else {
    //
    // This source requires special handling to clear
    //
    Record.ClearSource (&Record.SrcDesc);
  }

  PchSmmEnableSource (&Record.SrcDesc);
  SmiHandlerProfileRegisterHandler (
    Qualified->Guid,
    DispatchFunction,
    (UINTN)RETURN_ADDRESS (0),
    DispatchContext,
    Record.ContextSize
    );

  return EFI_SUCCESS;
}

/**
  Unregister a child SMI source dispatch function with a parent SMM driver.

  @param[in] This                 Pointer to the PCH_SMM_GENERIC_PROTOCOL instance.
  @param[in] DispatchHandle       Handle of dispatch function to deregister.

  @retval EFI_SUCCESS             The dispatch function has been successfully
                                  unregistered and the SMI source has been disabled
                                  if there are no other registered child dispatch
                                  functions for this SMI source.
  @retval EFI_INVALID_PARAMETER   Handle is invalid.
**/
EFI_STATUS
EFIAPI
PchSmmCoreUnRegister (
  IN PCH_SMM_GENERIC_PROTOCOL                           *This,
  IN EFI_HANDLE                                         *DispatchHandle
  )
{
  EFI_STATUS                   Status;
  BOOLEAN                      NeedClearEnable;
  UINTN                        DescIndex;
  DATABASE_RECORD              *RecordToDelete;
  DATABASE_RECORD              *RecordInDb;
  LIST_ENTRY                   *LinkInDb;

  if (DispatchHandle == NULL) {
    return EFI_INVALID_PARAMETER;
  }

  //
  // Return access denied if the SmmReadyToLock event has been triggered
  //
  if (mReadyToLock == TRUE) {
    DEBUG ((DEBUG_ERROR, "UnRegister is not allowed if the SmmReadyToLock event has been triggered! \n"));
    return EFI_ACCESS_DENIED;
  }

  RecordToDelete = DATABASE_RECORD_FROM_LINK (DispatchHandle);

  //
  // Take the entry out of the linked list
  //
  if (RecordToDelete->Link.ForwardLink == (LIST_ENTRY *) EFI_BAD_POINTER) {
    return EFI_INVALID_PARAMETER;
  }

  RemoveEntryList (&RecordToDelete->Link);

  //
  // Loop through all the souces in record linked list to see if any source enable is equal.
  // If any source enable is equal, we do not want to disable it.
  //
  for (DescIndex = 0; DescIndex < NUM_EN_BITS; ++DescIndex) {
    if (IS_BIT_DESC_NULL (RecordToDelete->SrcDesc.En[DescIndex])) {
      continue;
    }
    NeedClearEnable = TRUE;
    LinkInDb = GetFirstNode (&mPrivateData.CallbackDataBase);
    while (!IsNull (&mPrivateData.CallbackDataBase, LinkInDb)) {
      RecordInDb = DATABASE_RECORD_FROM_LINK (LinkInDb);
      if (IsBitEqualToAnySourceEn (&RecordToDelete->SrcDesc.En[DescIndex], &RecordInDb->SrcDesc)) {
        NeedClearEnable = FALSE;
        break;
      }
      LinkInDb = GetNextNode (&mPrivateData.CallbackDataBase, &RecordInDb->Link);
    }
    if (NeedClearEnable == FALSE) {
      continue;
    }
    WriteBitDesc (&RecordToDelete->SrcDesc.En[DescIndex], 0, FALSE);
  }
  Status = gSmst->SmmFreePool (RecordToDelete);
  if (EFI_ERROR (Status)) {
    ASSERT (FALSE);
    return Status;
  }
  return EFI_SUCCESS;
}

/**
  This function clears the pending SMI status before set EOS.
  NOTE: This only clears the pending SMI with known reason.
        Please do not clear unknown pending SMI status since that will hide potential issues.

  @param[in] SmiStsValue                SMI status
  @param[in] SciEn                      Sci Enable status
**/
STATIC
VOID
ClearPendingSmiStatus (
  UINT32  SmiStsValue,
  BOOLEAN SciEn
  )
{
  //
  // Clear NewCentury status if it's not handled.
  //
  if (SmiStsValue & B_ACPI_IO_SMI_STS_TCO) {
    if (IoRead16 (mTcoBaseAddr + R_TCO_IO_TCO1_STS) & B_TCO_IO_TCO1_STS_NEWCENTURY) {
      PchTcoSmiClearSourceAndBlock (&mSrcDescNewCentury);
    }
  }
  // Clear PWRBTNOR_STS if it's not handled.
  //
  if (IoRead16 (mAcpiBaseAddr + R_ACPI_IO_PM1_STS) & B_ACPI_IO_PM1_STS_PRBTNOR) {
    IoWrite16 (mAcpiBaseAddr + R_ACPI_IO_PM1_STS, B_ACPI_IO_PM1_STS_PRBTNOR);
  }
  //
  // Clear WADT_STS if this is triggered by WADT timer.
  //
  if (!SciEn) {
    if (IoRead32 (mAcpiBaseAddr + R_ACPI_IO_GPE0_STS_127_96) & B_ACPI_IO_GPE0_STS_127_96_WADT) {
      IoWrite32 (mAcpiBaseAddr + R_ACPI_IO_GPE0_STS_127_96, B_ACPI_IO_GPE0_STS_127_96_WADT);
    }
  }
  //
  // Clear GPIO_UNLOCK_SMI_STS in case it is set as GPIO Unlock SMI is not supported
  //
  if (SmiStsValue & B_ACPI_IO_SMI_STS_GPIO_UNLOCK) {
    IoWrite32 (mAcpiBaseAddr + R_ACPI_IO_SMI_STS, B_ACPI_IO_SMI_STS_GPIO_UNLOCK);
  }
}

/**
  The callback function to handle subsequent SMIs.  This callback will be called by SmmCoreDispatcher.

  @param[in] SmmImageHandle             Not used
  @param[in] PchSmmCore                 Not used
  @param[in, out] CommunicationBuffer   Not used
  @param[in, out] SourceSize            Not used

  @retval EFI_SUCCESS                   Function successfully completed
**/
EFI_STATUS
EFIAPI
PchSmmCoreDispatcher (
  IN       EFI_HANDLE         SmmImageHandle,
  IN CONST VOID               *PchSmmCore,
  IN OUT   VOID               *CommunicationBuffer,
  IN OUT   UINTN              *SourceSize
  )
{
  //
  // Used to prevent infinite loops
  //
  UINTN               EscapeCount;

  BOOLEAN             ContextsMatch;
  BOOLEAN             EosSet;
  BOOLEAN             SxChildWasDispatched;

  DATABASE_RECORD     *RecordInDb;
  LIST_ENTRY          *LinkInDb;
  DATABASE_RECORD     *RecordToExhaust;
  LIST_ENTRY          *LinkToExhaust;

  PCH_SMM_CONTEXT     Context;
  VOID                *CommBuffer;
  UINTN               CommBufferSize;

  EFI_STATUS          Status;
  BOOLEAN             SciEn;
  UINT32              SmiEnValue;
  UINT32              SmiStsValue;
  UINT8               Port74Save;
  UINT8               Port76Save;

  PCH_SMM_SOURCE_DESC ActiveSource;

  //
  // Initialize ActiveSource
  //
  NullInitSourceDesc (&ActiveSource);

  EscapeCount           = 3;
  ContextsMatch         = FALSE;
  EosSet                = FALSE;
  SxChildWasDispatched  = FALSE;
  Status                = EFI_SUCCESS;

  //
  // Save IO index registers
  // @note: Save/Restore port 70h directly might break NMI_EN# setting,
  //       then save/restore 74h/76h instead.
  // @note: CF8 is not saved. Prefer method is to use MMIO instead of CF8
  //
  Port76Save = IoRead8 (R_RTC_IO_EXT_INDEX_ALT);
  Port74Save = IoRead8 (R_RTC_IO_INDEX_ALT);

  if (!IsListEmpty (&mPrivateData.CallbackDataBase)) {
    //
    // We have children registered w/ us -- continue
    //
    while ((!EosSet) && (EscapeCount > 0)) {
      EscapeCount--;

      LinkInDb = GetFirstNode (&mPrivateData.CallbackDataBase);

      //
      // Cache SciEn, SmiEnValue and SmiStsValue to determine if source is active
      //
      SciEn       = PchSmmGetSciEn ();
      SmiEnValue  = IoRead32 ((UINTN) (mAcpiBaseAddr + R_ACPI_IO_SMI_EN));
      SmiStsValue = IoRead32 ((UINTN) (mAcpiBaseAddr + R_ACPI_IO_SMI_STS));

      while (!IsNull (&mPrivateData.CallbackDataBase, LinkInDb)) {
        RecordInDb = DATABASE_RECORD_FROM_LINK (LinkInDb);

        //
        // look for the first active source
        //
        if (!SourceIsActive (&RecordInDb->SrcDesc, SciEn, SmiEnValue, SmiStsValue)) {
          //
          // Didn't find the source yet, keep looking
          //
          LinkInDb = GetNextNode (&mPrivateData.CallbackDataBase, &RecordInDb->Link);

          //
          // if it's the last one, try to clear EOS
          //
          if (IsNull (&mPrivateData.CallbackDataBase, LinkInDb)) {
            //
            // Clear pending SMI status before EOS
            //
            ClearPendingSmiStatus (SmiStsValue, SciEn);
            EosSet = PchSmmSetAndCheckEos ();
          }
        } else {
          //
          // We found a source. If this is a sleep type, we have to go to
          // appropriate sleep state anyway.No matter there is sleep child or not
          //
          if (RecordInDb->ProtocolType == SxType) {
            SxChildWasDispatched = TRUE;
          }
          //
          // "cache" the source description and don't query I/O anymore
          //
          CopyMem ((VOID *) &ActiveSource, (VOID *) &(RecordInDb->SrcDesc), sizeof (PCH_SMM_SOURCE_DESC));
          LinkToExhaust = LinkInDb;

          //
          // exhaust the rest of the queue looking for the same source
          //
          while (!IsNull (&mPrivateData.CallbackDataBase, LinkToExhaust)) {
            RecordToExhaust = DATABASE_RECORD_FROM_LINK (LinkToExhaust);
            //
            // RecordToExhaust->Link might be removed (unregistered) by Callback function, and then the
            // system will hang in ASSERT() while calling GetNextNode().
            // To prevent the issue, we need to get next record in DB here (before Callback function).
            //
            LinkToExhaust = GetNextNode (&mPrivateData.CallbackDataBase, &RecordToExhaust->Link);

            if (CompareSources (&RecordToExhaust->SrcDesc, &ActiveSource)) {
              //
              // These source descriptions are equal, so this callback should be
              // dispatched.
              //
              if (RecordToExhaust->ContextFunctions.GetContext != NULL) {
                //
                // This child requires that we get a calling context from
                // hardware and compare that context to the one supplied
                // by the child.
                //
                ASSERT (RecordToExhaust->ContextFunctions.CmpContext != NULL);

                //
                // Make sure contexts match before dispatching event to child
                //
                RecordToExhaust->ContextFunctions.GetContext (RecordToExhaust, &Context);
                ContextsMatch = RecordToExhaust->ContextFunctions.CmpContext (&Context, &RecordToExhaust->ChildContext);

              } else {
                //
                // This child doesn't require any more calling context beyond what
                // it supplied in registration.  Simply pass back what it gave us.
                //
                Context       = RecordToExhaust->ChildContext;
                ContextsMatch = TRUE;
              }

              if (ContextsMatch) {
                if (RecordToExhaust->ProtocolType == PchSmiDispatchType) {
                  //
                  // For PCH SMI dispatch protocols
                  //
                  PchSmiTypeCallbackDispatcher (RecordToExhaust);
                } else {
                  if ((RecordToExhaust->ProtocolType == SxType) && (Context.Sx.Type == SxS3) && (Context.Sx.Phase == SxEntry) && !mS3SusStart) {
                    REPORT_STATUS_CODE (EFI_PROGRESS_CODE, PROGRESS_CODE_S3_SUSPEND_START);
                    mS3SusStart = TRUE;
                  }
                  //
                  // For EFI standard SMI dispatch protocols
                  //
                  if (RecordToExhaust->Callback != NULL) {
                    if (RecordToExhaust->ContextFunctions.GetCommBuffer != NULL) {
                      //
                      // This callback function needs CommBuffer and CommBufferSize.
                      // Get those from child and then pass to callback function.
                      //
                      RecordToExhaust->ContextFunctions.GetCommBuffer (RecordToExhaust, &CommBuffer, &CommBufferSize);
                    } else {
                      //
                      // Child doesn't support the CommBuffer and CommBufferSize.
                      // Just pass NULL value to callback function.
                      //
                      CommBuffer     = NULL;
                      CommBufferSize = 0;
                    }

                    PERF_START_EX (NULL, "SmmFunction", NULL, AsmReadTsc (), RecordToExhaust->ProtocolType);
                    RecordToExhaust->Callback ((EFI_HANDLE) & RecordToExhaust->Link, &Context, CommBuffer, &CommBufferSize);
                    PERF_END_EX (NULL, "SmmFunction", NULL, AsmReadTsc (), RecordToExhaust->ProtocolType);
                    if (RecordToExhaust->ProtocolType == SxType) {
                      SxChildWasDispatched = TRUE;
                    }
                  } else {
                    ASSERT (FALSE);
                  }
                }
              }
            }
          }

          if (RecordInDb->ClearSource == NULL) {
            //
            // Clear the SMI associated w/ the source using the default function
            //
            PchSmmClearSource (&ActiveSource);
          } else {
            //
            // This source requires special handling to clear
            //
            RecordInDb->ClearSource (&ActiveSource);
          }
          //
          // Clear pending SMI status before EOS
          //
          ClearPendingSmiStatus (SmiStsValue, SciEn);
          //
          // Also, try to clear EOS
          //
          EosSet = PchSmmSetAndCheckEos ();
          //
          // Queue is empty, reset the search
          //
          break;
        }
      }
    }
  }
  //
  // If you arrive here, there are two possible reasons:
  // (1) you've got problems with clearing the SMI status bits in the
  // ACPI table.  If you don't properly clear the SMI bits, then you won't be able to set the
  // EOS bit.  If this happens too many times, the loop exits.
  // (2) there was a SMM communicate for callback messages that was received prior
  // to this driver.
  // If there is an asynchronous SMI that occurs while processing the Callback, let
  // all of the drivers (including this one) have an opportunity to scan for the SMI
  // and handle it.
  // If not, we don't want to exit and have the foreground app. clear EOS without letting
  // these other sources get serviced.
  //
  // This assert is not valid with CSM legacy solution because it generates software SMI
  // to test for legacy USB support presence.
  // This may not be illegal, so we cannot assert at this time.
  //
  //  ASSERT (EscapeCount > 0);
  //
  if (SxChildWasDispatched) {
    //
    // A child of the SmmSxDispatch protocol was dispatched during this call;
    // put the system to sleep.
    //
    PchSmmSxGoToSleep ();
  }
  //
  // Restore IO index registers
  // @note: Save/Restore port 70h directly might break NMI_EN# setting,
  //       then save/restore 74h/76h instead.
  //
  IoWrite8 (R_RTC_IO_EXT_INDEX_ALT, Port76Save);
  IoWrite8 (R_RTC_IO_INDEX_ALT, Port74Save);

  return Status;
}
