/** @file
  SC BIOS Write Protect Driver.

@copyright
  INTEL CONFIDENTIAL
  Copyright 2011 - 2017 Intel Corporation.

  The source code contained or described herein and all documents related to the
  source code ("Material") are owned by Intel Corporation or its suppliers or
  licensors. Title to the Material remains with Intel Corporation or its suppliers
  and licensors. The Material may contain trade secrets and proprietary and
  confidential information of Intel Corporation and its suppliers and licensors,
  and is protected by worldwide copyright and trade secret laws and treaty
  provisions. No part of the Material may be used, copied, reproduced, modified,
  published, uploaded, posted, transmitted, distributed, or disclosed in any way
  without Intel's prior express written permission.

  No license under any patent, copyright, trade secret or other intellectual
  property right is granted to or conferred upon you by disclosure or delivery
  of the Materials, either expressly, by implication, inducement, estoppel or
  otherwise. Any license under such intellectual property rights must be
  express and approved by Intel in writing.

  Unless otherwise agreed by Intel in writing, you may not remove or alter
  this notice or any other notice embedded in Materials by Intel or
  Intel's suppliers or licensors in any way.

  This file contains a 'Sample Driver' and is licensed as such under the terms
  of your license agreement with Intel or your vendor. This file may be modified
  by the user, subject to the additional terms of the license agreement.

@par Specification
**/
#include "EspiSmi.h"


 ///
/// IchneSPISMI
///
 SC_SMM_SOURCE_DESC mSrcDescEspiSlave = {
  SC_SMM_NO_FLAGS,
  {
	{
	  {
		ACPI_ADDR_TYPE,
		R_SC_SMI_IO_EN
	  },
	  S_SC_SMI_IO_EN,
	  N_SC_SMI_IO_EN_ESPI
	},
	NULL_BIT_DESC_INITIALIZER
  },
  {
	{
	  {
		ACPI_ADDR_TYPE,
		R_SC_SMI_IO_STS
	  },
	  S_SC_SMI_IO_STS,
	  N_SC_SMI_IO_STS_ESPI
	}
  }
};





  ESPI_SMI_INSTANCE mEspiSmiInstance = {
  //
  // Signature
  //
  ESPI_SMI_INSTANCE_SIGNATURE,
  //
  // Handle
  //
  NULL,
  //
  // PchEspiSmiDispatchProtocol
  //
  {
    SC_ESPI_SMI_DISPATCH_REVISION,
    EspiSmiUnRegister,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    EspiSlaveSmiRegister
  },
  //
  // PchSmiEspiHandle[EspiTopLevelTypeMax]
  //
  {
    NULL, NULL, NULL
  },
  //
  // CallbackDataBase[EspiSmiTypeMax]
  //
  {
    {NULL, NULL}, {NULL, NULL}, {NULL, NULL}, {NULL, NULL}, {NULL, NULL},
    {NULL, NULL}, {NULL, NULL}, {NULL, NULL}, {NULL, NULL}, {NULL, NULL}
  },
  //
  // EspiSmiEventCounter[EspiSmiTypeMax]
  //
  {
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0
  },
  //
  // Barrier[EspiTopLevelTypeMax]
  //
  {
    {
      BiosWrProtect,
      BiosWrProtect
    },
    {
      BiosWrReport,
      LnkType1Err
    },
    {
      EspiSlaveSmi,
      EspiSlaveSmi
    }
  }
};

/**
  eSPI SMI Dispatch Protocol instance to register a eSPI slave SMI
  This routine will also lock down ESPI_SMI_LOCK bit after registration and prevent
  this handler from unregistration.
  On platform that supports more than 1 device through another chip select (SPT-H),
  the SMI handler itself needs to inspect both the eSPI devices' interrupt status registers
  (implementation specific for each Slave) in order to identify and service the cause.
  After servicing it, it has to clear the Slaves' internal SMI# status registers

  @param[in]  This                      Not used
  @param[in]  DispatchFunction          The callback to execute
  @param[out] DispatchHandle            The handle for this callback registration

  @retval     EFI_SUCCESS               Registration succeed
  @retval     EFI_ACCESS_DENIED         Return access denied if the SmmReadyToLock event has been triggered
  @retval     EFI_ACCESS_DENIED         The ESPI_SMI_LOCK is set and register is blocked.
  @retval     others                    Registration failed
**/
EFI_STATUS
EFIAPI
EspiSlaveSmiRegister (
  IN  SC_ESPI_SMI_DISPATCH_PROTOCOL    *This,
  IN  SC_ESPI_SMI_DISPATCH_CALLBACK    DispatchFunction,
  OUT EFI_HANDLE                        *DispatchHandle
  )
{
  EFI_STATUS                            Status;
  //UINT32                                GenPmConA;
  //UINTN                                 PmcBaseAddress;

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

  //
  // If ESPI_SMI_LOCK is set, the register is blocked.
  //

  /*PmcBaseAddress = MmPciBase (
                     DEFAULT_PCI_BUS_NUMBER_PCH,
                     PCI_DEVICE_NUMBER_PCH_PMC,
                     PCI_FUNCTION_NUMBER_PCH_PMC
                     );
  GenPmConA = MmioRead32 (PmcBaseAddress + R_PCH_PMC_GEN_PMCON_A);
  if (GenPmConA & B_PCH_PMC_GEN_PMCON_A_ESPI_SMI_LOCK) {
    return EFI_ACCESS_DENIED;
  }
  */

  //
  // @note: This service doesn't utilize the data base of mEspiSmiInstance.
  //        While SMI is triggered it directly goes to the registing DispatchFunction
  //        instead of EspiSmiCallback.
  //
  Status = ScSmiRecordInsert (
             &mSrcDescEspiSlave,
             (SC_SMI_CALLBACK_FUNCTIONS) DispatchFunction,
             IchnEspiSmi,
             DispatchHandle
             );
  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_ERROR, "Fail to insert the record \n"));
    ASSERT (FALSE);
    return Status;
  }
  ScSmmClearSource (&mSrcDescEspiSlave);
  ScSmmEnableSource (&mSrcDescEspiSlave);

  //
  // Lock down the ESPI_SMI_LOCK after ESPI SMI is enabled.
  //
  //  MmioOr8 (PmcBaseAddress + R_PCH_PMC_GEN_PMCON_A, B_PCH_PMC_GEN_PMCON_A_ESPI_SMI_LOCK);
  //
  // Keep the DispatchHandle which will be used for unregister function.
  //
  mEspiSmiInstance.ScSmiEspiHandle[EspiPmc] = *DispatchHandle;

  return Status;
}

EFI_STATUS
EFIAPI
EspiSmiUnRegister (
  IN  SC_ESPI_SMI_DISPATCH_PROTOCOL  *This,
  IN  EFI_HANDLE                      DispatchHandle
  )
{
  EFI_STATUS          Status;
  ESPI_TOP_LEVEL_TYPE EspiTopLevelType;
  ESPI_SMI_TYPE       EspiSmiType;
  BOOLEAN             SafeToDisable;
  LIST_ENTRY          *LinkInDb;
  ESPI_SMI_RECORD     *RecordPointer;

  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;
  //}

  if (((LIST_ENTRY *) DispatchHandle)->ForwardLink == (LIST_ENTRY *) EFI_BAD_POINTER) {
    return EFI_INVALID_PARAMETER;
  }

  //
  // For DispatchHandle belongs to Espi Slave SMI, refuses the request of unregistration.
  //
  if (mEspiSmiInstance.ScSmiEspiHandle[EspiPmc] == DispatchHandle) {
    DEBUG ((DEBUG_ERROR, "UnRegister is not allowed for ESPI Slave SMI handle! \n"));
    return EFI_ACCESS_DENIED;
  }

  //
  // Iterate through all the database to find the record
  //
  for (EspiSmiType = 0; EspiSmiType < EspiSmiTypeMax; ++EspiSmiType) {
    LinkInDb = GetFirstNode (&mEspiSmiInstance.CallbackDataBase[EspiSmiType]);

    while (!IsNull (&mEspiSmiInstance.CallbackDataBase[EspiSmiType], LinkInDb)) {
      if (LinkInDb != (LIST_ENTRY *) DispatchHandle) {
        LinkInDb = GetNextNode (&mEspiSmiInstance.CallbackDataBase[EspiSmiType], LinkInDb);

      } else {
        //
        // Found the source to be from this list
        //
        RemoveEntryList (LinkInDb);
        RecordPointer = (ESPI_RECORD_FROM_LINK (LinkInDb));

        if (mEspiSmiInstance.EspiSmiEventCounter[EspiSmiType] != 0) {
          if (--mEspiSmiInstance.EspiSmiEventCounter[EspiSmiType] == 0) {
           // EspiSmiDisableSource (EspiSmiType);
          }
        }

        Status = gSmst->SmmFreePool (RecordPointer);
        if (EFI_ERROR (Status)) {
          ASSERT (FALSE);
        }

        goto EspiSmiUnRegisterEnd;
      }
    }
  }
  //
  // If the code reach here, the handle passed in cannot be found
  //
  DEBUG ((DEBUG_ERROR, "eSPI SMI handle is not in record database \n"));
  ASSERT (FALSE);
  return EFI_INVALID_PARAMETER;

EspiSmiUnRegisterEnd:

  //
  // Unregister and clear the handle from PchSmiDispatch
  //
  for (EspiTopLevelType = 0; EspiTopLevelType < EspiTopLevelTypeMax; ++EspiTopLevelType) {
    SafeToDisable = TRUE;
    //
    // Checks all the child events that belongs to a top level status in PMC
    //
    for (EspiSmiType = mEspiSmiInstance.Barrier[EspiTopLevelType].Start; EspiSmiType <= mEspiSmiInstance.Barrier[EspiTopLevelType].End; ++EspiSmiType) {
      if (mEspiSmiInstance.EspiSmiEventCounter[EspiSmiType] != 0) {
        SafeToDisable = FALSE;
      }
    }
    //
    // Finally, disable the top level event in PMC
    //
    if (SafeToDisable) {
      if (mEspiSmiInstance.ScSmiEspiHandle[EspiTopLevelType] != NULL) {
        Status = ScSmmCoreUnRegister (NULL, mEspiSmiInstance.ScSmiEspiHandle[EspiTopLevelType]);
        ASSERT_EFI_ERROR (Status);
        mEspiSmiInstance.ScSmiEspiHandle[EspiTopLevelType] = NULL;
      }
    }
  }

  return EFI_SUCCESS;
}


EFI_STATUS
EFIAPI
InstallEspiSmi (
  IN EFI_HANDLE           ImageHandle
  
  )
{
  EFI_STATUS    Status;
  ESPI_SMI_TYPE EspiType;

  //
  // InitializeListHead for mEspiSmiInstance.CallBackDataBase[EspiTopLevelTypeMax]
  //
  for (EspiType = 0; EspiType < EspiSmiTypeMax; ++EspiType) {
    InitializeListHead (&mEspiSmiInstance.CallbackDataBase[EspiType]);
  }

  //
  // Install EfiEspiSmiDispatchProtocol
  //
  Status = gSmst->SmmInstallProtocolInterface (
                    &mEspiSmiInstance.Handle,
                    &gScEspiSmiDispatchProtocolGuid,
                    EFI_NATIVE_INTERFACE,
                    &mEspiSmiInstance.ScEspiSmiDispatchProtocol
                    );
  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_ERROR, "Fail to install eSPI SMI Dispatch Protocol \n"));
    ASSERT (FALSE);
    return Status;
  }

  return EFI_SUCCESS;
}


