/** @file

Copyright (c) 2018 - 2021, Intel Corporation. All rights reserved.<BR>
SPDX-License-Identifier: BSD-2-Clause-Patent

**/

#include "BoardAcpiDxe.h"

EFI_STATUS
PatchDsdtTable (
  IN OUT   EFI_ACPI_COMMON_HEADER   *Table
  );

#pragma optimize("",off)

BIOS_ACPI_PARAM                *mAcpiParameter;

EFI_IIO_UDS_PROTOCOL        *mIioUds;

UINT32                      mNumOfBitShift;
BOOLEAN                     mForceX2ApicId;
BOOLEAN                     mX2ApicEnabled;

struct SystemMemoryMapHob   *mSystemMemoryMap;

SOCKET_MP_LINK_CONFIGURATION  mSocketMpLinkConfiguration;
SOCKET_IIO_CONFIGURATION      mSocketIioConfiguration;
SOCKET_POWERMANAGEMENT_CONFIGURATION mSocketPowermanagementConfiguration;

BOOLEAN               mFirstNotify;
PCH_RC_CONFIGURATION  mPchRcConfiguration;

UINT8                 mKBPresent = 0;
UINT8                 mMousePresent = 0;

/**

  Locate the first instance of a protocol.  If the protocol requested is an
  FV protocol, then it will return the first FV that contains the ACPI table
  storage file.

  @param Protocol  -  The protocol to find.
  Instance  -  Return pointer to the first instance of the protocol.
  Type      -  The type of protocol to locate.

  @retval EFI_SUCCESS           -  The function completed successfully.
  @retval EFI_NOT_FOUND         -  The protocol could not be located.
  @retval EFI_OUT_OF_RESOURCES  -  There are not enough resources to find the protocol.

**/
EFI_STATUS
LocateSupportProtocol (
  IN   EFI_GUID       *Protocol,
  IN   EFI_GUID       *gEfiAcpiMultiTableStorageGuid,
  OUT  VOID           **Instance,
  IN   UINT32         Type
  )
{
  EFI_STATUS              Status;
  EFI_HANDLE              *HandleBuffer;
  UINTN                   NumberOfHandles;
  EFI_FV_FILETYPE         FileType;
  UINT32                  FvStatus;
  EFI_FV_FILE_ATTRIBUTES  Attributes;
  UINTN                   Size;
  UINTN                   Index;

  FvStatus = 0;
  //
  // Locate protocol.
  //
  Status = gBS->LocateHandleBuffer (
                  ByProtocol,
                  Protocol,
                  NULL,
                  &NumberOfHandles,
                  &HandleBuffer
                  );
  if (EFI_ERROR (Status)) {
    //
    // Defined errors at this time are not found and out of resources.
    //
    return Status;
  }
  //
  // Looking for FV with ACPI storage file
  //
  for (Index = 0; Index < NumberOfHandles; Index++) {
    //
    // Get the protocol on this handle
    // This should not fail because of LocateHandleBuffer
    //
    Status = gBS->HandleProtocol (
                    HandleBuffer[Index],
                    Protocol,
                    Instance
                    );
    ASSERT (!EFI_ERROR (Status));

    if (!Type) {
      //
      // Not looking for the FV protocol, so find the first instance of the
      // protocol.  There should not be any errors because our handle buffer
      // should always contain at least one or LocateHandleBuffer would have
      // returned not found.
      //
      break;
    }
    //
    // See if it has the ACPI storage file
    //
    Status = ((EFI_FIRMWARE_VOLUME2_PROTOCOL *) (*Instance))->ReadFile (
                                                              *Instance,
                                                              gEfiAcpiMultiTableStorageGuid,
                                                              NULL,
                                                              &Size,
                                                              &FileType,
                                                              &Attributes,
                                                              &FvStatus
                                                              );

    //
    // If we found it, then we are done
    //
    if (!EFI_ERROR (Status)) {
      break;
    }
  }
  //
  // Our exit status is determined by the success of the previous operations
  // If the protocol was found, Instance already points to it.
  //
  //
  // Free any allocated buffers
  //
  gBS->FreePool (HandleBuffer);

  return Status;
}

/**

    GC_TODO: add routine description

    @param None

    @retval EFI_SUCCESS - GC_TODO: add retval description

**/
EFI_STATUS
PlatformHookInit (
  VOID
  )
{
  EFI_STATUS                    Status;
  EFI_PHYSICAL_ADDRESS          AcpiParameterAddr;
  UINT32                        RegEax;
  UINT32                        RegEbx;
  UINT32                        RegEcx;
  UINT32                        RegEdx;

  CopyMem (&mSocketMpLinkConfiguration, PcdGetPtr(PcdSocketMpLinkConfigData), sizeof(SOCKET_MP_LINK_CONFIGURATION));
  CopyMem (&mSocketPowermanagementConfiguration, PcdGetPtr(PcdSocketPowerManagementConfigData), sizeof(SOCKET_POWERMANAGEMENT_CONFIGURATION));
  CopyMem (&mSocketIioConfiguration, PcdGetPtr(PcdSocketIioConfigData), sizeof(SOCKET_IIO_CONFIGURATION));
  CopyMem (&mPchRcConfiguration, PcdGetPtr(PcdPchRcConfigurationData), sizeof(PCH_RC_CONFIGURATION));

  DEBUG ((DEBUG_INFO, "mX2ApicEnabled - 0x%x\n", mX2ApicEnabled));
  DEBUG ((DEBUG_INFO, "mForceX2ApicId - 0x%x\n", mForceX2ApicId));

  {
    UINT32  Index;

    for (Index = 0; Index < 4; Index++) {
      AsmCpuidEx(CPUID_EXTENDED_TOPOLOGY, Index, &RegEax, &RegEbx, &RegEcx, &RegEdx);
      DEBUG ((DEBUG_INFO, "CPUID(0xB - %d) - 0x%08x.0x%08x.0x%08x.0x%08x\n", Index, RegEax, RegEbx, RegEcx, RegEdx));
    }
  }

  //
  // Allocate 256 runtime memory to pass ACPI parameter
  // This Address must be < 4G because we only have 32bit in the dsdt
  //
  AcpiParameterAddr = 0xffffffff;
  Status = gBS->AllocatePages (
                  AllocateMaxAddress,
                  EfiACPIMemoryNVS,
                  EFI_SIZE_TO_PAGES (sizeof(BIOS_ACPI_PARAM)),
                  &AcpiParameterAddr
                  );
  ASSERT_EFI_ERROR (Status);
  mAcpiParameter = (BIOS_ACPI_PARAM *)AcpiParameterAddr;

  DEBUG ((EFI_D_ERROR, "ACPI Parameter Block Address: 0x%X\n", mAcpiParameter));
  Status = PcdSet64S (PcdAcpiGnvsAddress, (UINT64)(UINTN)mAcpiParameter);
  ASSERT_EFI_ERROR (Status);

  ZeroMem (mAcpiParameter, sizeof (BIOS_ACPI_PARAM));
  mAcpiParameter->PlatformId    = 0;
#if MAX_SOCKET > 4
  mAcpiParameter->IoApicEnable  = PcdGet32 (PcdPcIoApicEnable);
#else
  mAcpiParameter->IoApicEnable  = (PcdGet32 (PcdPcIoApicEnable) << 1) | 1;
#endif
  DEBUG((EFI_D_ERROR, "io apic settings:%d\n", mAcpiParameter->IoApicEnable));

  AsmCpuid (CPUID_VERSION_INFO,  &RegEax, &RegEbx, &RegEcx, &RegEdx);
  mAcpiParameter->ProcessorId = (RegEax & 0xFFFF0);

  // support up to 64 threads/socket
  AsmCpuidEx(CPUID_EXTENDED_TOPOLOGY, 1, &mNumOfBitShift, NULL, NULL, NULL);
  mNumOfBitShift &= 0x1F;

  // Set the bit shift value for CPU SKU
  mAcpiParameter->CpuSkuNumOfBitShift = (UINT8) mNumOfBitShift;

  mAcpiParameter->ProcessorApicIdBase[0] = (UINT32) (0 << mNumOfBitShift);
  mAcpiParameter->ProcessorApicIdBase[1] = (UINT32) (1 << mNumOfBitShift);
  mAcpiParameter->ProcessorApicIdBase[2] = (UINT32) (2 << mNumOfBitShift);
  mAcpiParameter->ProcessorApicIdBase[3] = (UINT32) (3 << mNumOfBitShift);
  mAcpiParameter->ProcessorApicIdBase[4] = (UINT32) (4 << mNumOfBitShift);
  mAcpiParameter->ProcessorApicIdBase[5] = (UINT32) (5 << mNumOfBitShift);
  mAcpiParameter->ProcessorApicIdBase[6] = (UINT32) (6 << mNumOfBitShift);
  mAcpiParameter->ProcessorApicIdBase[7] = (UINT32) (7 << mNumOfBitShift);

  if(mForceX2ApicId) {
    mAcpiParameter->ProcessorApicIdBase[0] = 0x7F00;
    mAcpiParameter->ProcessorApicIdBase[1] = 0x7F20;
    mAcpiParameter->ProcessorApicIdBase[2] = 0x7F40;
    mAcpiParameter->ProcessorApicIdBase[3] = 0x7F60;
    mAcpiParameter->ProcessorApicIdBase[4] = 0x7F80;
    mAcpiParameter->ProcessorApicIdBase[5] = 0x7Fa0;
    mAcpiParameter->ProcessorApicIdBase[6] = 0x7Fc0;
    mAcpiParameter->ProcessorApicIdBase[7] = 0x7Fe0;

     if (mNumOfBitShift == 4) {
      mAcpiParameter->ProcessorApicIdBase[0] = 0x7F00;
      mAcpiParameter->ProcessorApicIdBase[1] = 0x7F10;
      mAcpiParameter->ProcessorApicIdBase[2] = 0x7F20;
      mAcpiParameter->ProcessorApicIdBase[3] = 0x7F30;
      mAcpiParameter->ProcessorApicIdBase[4] = 0x7F40;
      mAcpiParameter->ProcessorApicIdBase[5] = 0x7F50;
      mAcpiParameter->ProcessorApicIdBase[6] = 0x7F60;
      mAcpiParameter->ProcessorApicIdBase[7] = 0x7F70;
    } else if(mNumOfBitShift == 6) {
      mAcpiParameter->ProcessorApicIdBase[0] = 0x7E00;
      mAcpiParameter->ProcessorApicIdBase[1] = 0x7E20;
      mAcpiParameter->ProcessorApicIdBase[2] = 0x7E40;
      mAcpiParameter->ProcessorApicIdBase[3] = 0x7E60;
      mAcpiParameter->ProcessorApicIdBase[4] = 0x7E80;
      mAcpiParameter->ProcessorApicIdBase[5] = 0x7Ea0;
      mAcpiParameter->ProcessorApicIdBase[6] = 0x7Ec0;
      mAcpiParameter->ProcessorApicIdBase[7] = 0x7Ee0;
    }
  }

  //
  // If SNC is enabled, and NumOfCluster is 2, set the ACPI variable for PXM value
  //
  if(mIioUds->IioUdsPtr->SystemStatus.OutSncEn && (mIioUds->IioUdsPtr->SystemStatus.OutNumOfCluster == 2)){
    mAcpiParameter->SncAnd2Cluster = 1;
  } else {
    mAcpiParameter->SncAnd2Cluster = 0;
  }

   mAcpiParameter->MmCfg = (UINT32)mIioUds->IioUdsPtr->PlatformData.PciExpressBase;
   mAcpiParameter->TsegSize = (UINT32)(mIioUds->IioUdsPtr->PlatformData.MemTsegSize >> 20);

  return EFI_SUCCESS;
}

/**

  This function will update any runtime platform specific information.
  This currently includes:
    Setting OEM table values, ID, table ID, creator ID and creator revision.
    Enabling the proper processor entries in the APIC tables.

  @param Table  -  The table to update

  @retval EFI_SUCCESS  -  The function completed successfully.

**/
EFI_STATUS
PlatformUpdateTables (
  IN OUT EFI_ACPI_COMMON_HEADER     *Table,
  IN OUT EFI_ACPI_TABLE_VERSION     *Version
  )
{
  EFI_STATUS                                Status;

  Status = EFI_SUCCESS;

  //
  // By default, a table belongs in all ACPI table versions published.
  // Some tables will override this because they have different versions of the table.
  //
  *Version = EFI_ACPI_TABLE_VERSION_2_0;
  //
  // Update the processors in the APIC table
  //
  switch (Table->Signature) {

  case EFI_ACPI_3_0_DIFFERENTIATED_SYSTEM_DESCRIPTION_TABLE_SIGNATURE:
    //
    // Patch the memory resource
    //
    Status = PatchDsdtTable(Table);
    break;

  default:
    ASSERT(FALSE);
    break;
  }
  //
  //
  // Update the hardware signature in the FACS structure
  //
  //
  //
  return Status;
}



/**

  GC_TODO: Add function description

  @param Event   - GC_TODO: add argument description
  @param Context - GC_TODO: add argument description

  @retval GC_TODO: add return values

**/
STATIC
VOID
EFIAPI
OnReadyToBoot (
  IN      EFI_EVENT                 Event,
  IN      VOID                      *Context
  )
{
  UINT32                        RegEax;
  UINT32                        RegEbx;
  UINT32                        RegEcx;
  UINT32                        RegEdx;

  if (mFirstNotify) {
    return ;
  }

  mFirstNotify = TRUE;

  CopyMem (&mSocketIioConfiguration, PcdGetPtr(PcdSocketIioConfigData), sizeof(SOCKET_IIO_CONFIGURATION));
  CopyMem (&mSocketPowermanagementConfiguration,PcdGetPtr(PcdSocketPowerManagementConfigData), sizeof(SOCKET_POWERMANAGEMENT_CONFIGURATION));
  CopyMem (&mPchRcConfiguration, PcdGetPtr(PcdPchRcConfigurationData), sizeof(PCH_RC_CONFIGURATION));

  // CpuPm.Asl: External (CSEN, FieldUnitObj)
  mAcpiParameter->CStateEnable = !mSocketPowermanagementConfiguration.ProcessorAutonomousCstateEnable;
  // CpuPm.Asl: External (C3EN, FieldUnitObj)
  mAcpiParameter->C3Enable     = mSocketPowermanagementConfiguration.C3Enable;
  // CpuPm.Asl: External (C6EN, FieldUnitObj)
  if (mSocketPowermanagementConfiguration.C6Enable == PPM_AUTO) {
    mAcpiParameter->C6Enable     = 1;  //POR Default = Enabled
  } else {
    mAcpiParameter->C6Enable     = mSocketPowermanagementConfiguration.C6Enable;
  }
  if(mAcpiParameter->C6Enable && mAcpiParameter->C3Enable) {  //C3 and C6 enable are exclusive
    mAcpiParameter->C6Enable = 1;
    mAcpiParameter->C3Enable = 0;
  }
  // CpuPm.Asl: External (C7EN, FieldUnitObj)
  mAcpiParameter->C7Enable     = 0;
  // CpuPm.Asl: External (OSCX, FieldUnitObj)
  mAcpiParameter->OSCX         = mSocketPowermanagementConfiguration.OSCx;
  // CpuPm.Asl: External (MWOS, FieldUnitObj)
  mAcpiParameter->MonitorMwaitEnable = 1;
  // CpuPm.Asl: External (PSEN, FieldUnitObj)
  mAcpiParameter->PStateEnable = mSocketPowermanagementConfiguration.ProcessorEistEnable;
  // CpuPm.Asl: External (HWAL, FieldUnitObj)
  mAcpiParameter->HWAllEnable = 0; //Update in PatchGv3SsdtTable

  mAcpiParameter->KBPresent    = mKBPresent;
  mAcpiParameter->MousePresent = mMousePresent;
  mAcpiParameter->TStateEnable = mSocketPowermanagementConfiguration.TStateEnable;
  //Fine grained T state
  AsmCpuid (CPUID_THERMAL_POWER_MANAGEMENT,  &RegEax, &RegEbx, &RegEcx, &RegEdx);
  if ((RegEax & EFI_FINE_GRAINED_CLOCK_MODULATION) && (mSocketPowermanagementConfiguration.OnDieThermalThrottling > 0)){
    mAcpiParameter->TStateFineGrained = 1;
  }
  if(RegEax & B_CPUID_POWER_MANAGEMENT_EAX_HWP_LVT_INTERRUPT_SUPPORT) {
    mAcpiParameter->HwpInterrupt = 1;
  }
  // CpuPm.Asl: External (HWEN, FieldUnitObj)
  mAcpiParameter->HWPMEnable = mSocketPowermanagementConfiguration.ProcessorHWPMEnable;
  // CpuPm.Asl: External (ACEN, FieldUnitObj)
  mAcpiParameter->AutoCstate = mSocketPowermanagementConfiguration.ProcessorAutonomousCstateEnable;

  mAcpiParameter->EmcaEn    = 0;

  mAcpiParameter->PcieAcpiHotPlugEnable = (UINT8) (BOOLEAN) (mSocketIioConfiguration.PcieAcpiHotPlugEnable != 0);
  //
  // Initialize USB3 mode from setup data
  //
  // If mode != manual control
  //  just copy mode from setup
  if (mPchRcConfiguration.PchUsbManualMode != 1) {
    mAcpiParameter->XhciMode = mPchRcConfiguration.PchUsbManualMode;
  }

}

/**

  Entry point for Acpi platform driver.

  @param ImageHandle  -  A handle for the image that is initializing this driver.
  @param SystemTable  -  A pointer to the EFI system table.

  @retval EFI_SUCCESS           -  Driver initialized successfully.
  @retval EFI_LOAD_ERROR        -  Failed to Initialize or has been loaded.
  @retval EFI_OUT_OF_RESOURCES  -  Could not allocate needed resources.

**/
EFI_STATUS
EFIAPI
InstallAcpiBoard (
  IN EFI_HANDLE         ImageHandle,
  IN EFI_SYSTEM_TABLE   *SystemTable
  )
{
  EFI_STATUS                    Status;
  EFI_STATUS                    AcpiStatus;
  EFI_ACPI_TABLE_PROTOCOL       *AcpiTable;
  EFI_FIRMWARE_VOLUME2_PROTOCOL  *FwVol;
  INTN                          Instance;
  EFI_ACPI_COMMON_HEADER        *CurrentTable;
  UINTN                         TableHandle;
  UINT32                        FvStatus;
  UINT32                        Size;
  EFI_EVENT                     Event;
  EFI_ACPI_TABLE_VERSION        TableVersion;
  EFI_HOB_GUID_TYPE             *GuidHob;

  mFirstNotify      = FALSE;

  TableVersion      = EFI_ACPI_TABLE_VERSION_NONE;
  Instance          = 0;
  CurrentTable      = NULL;
  TableHandle       = 0;

  //
  // Locate the IIO Protocol Interface
  //
  Status = gBS->LocateProtocol (&gEfiIioUdsProtocolGuid,NULL,&mIioUds);
  ASSERT_EFI_ERROR (Status);

  GuidHob    = GetFirstGuidHob (&gEfiMemoryMapGuid);
  ASSERT (GuidHob != NULL);
  if (GuidHob == NULL) {
    return EFI_NOT_FOUND;
  }
  mSystemMemoryMap = GET_GUID_HOB_DATA(GuidHob);

  PlatformHookInit ();

  //
  // Find the AcpiTable protocol
  //
  Status = gBS->LocateProtocol (&gEfiAcpiTableProtocolGuid, NULL, &AcpiTable);
  ASSERT_EFI_ERROR (Status);

  //
  // Locate the firmware volume protocol
  //
  Status = LocateSupportProtocol (
            &gEfiFirmwareVolume2ProtocolGuid,
            &gEfiCallerIdGuid,
            &FwVol,
            TRUE
            );
  ASSERT_EFI_ERROR (Status);

  Status    = EFI_SUCCESS;
  Instance  = 0;

  //
  // Read tables from the storage file.
  //
  while (!EFI_ERROR (Status)) {
    CurrentTable = NULL;

    Status = FwVol->ReadSection (
                      FwVol,
                      &gEfiCallerIdGuid,
                      EFI_SECTION_RAW,
                      Instance,
                      &CurrentTable,
                      (UINTN *) &Size,
                      &FvStatus
                      );

    if (!EFI_ERROR (Status)) {
      //
      // Allow platform specific code to reject the table or update it
      //
      {
        //
        // Perform any table specific updates.
        //
        AcpiStatus = PlatformUpdateTables (CurrentTable, &TableVersion);
        if (!EFI_ERROR (AcpiStatus)) {
          //
          // Add the table
          //
          TableHandle = 0;
          if (TableVersion != EFI_ACPI_TABLE_VERSION_NONE) {
            AcpiStatus = AcpiTable->InstallAcpiTable (
                                      AcpiTable,
                                      CurrentTable,
                                      CurrentTable->Length,
                                      &TableHandle
                                      );
          }
          ASSERT_EFI_ERROR (AcpiStatus);
        }
      }
      //
      // Increment the instance
      //
      Instance++;
    }
  }

  Status = EfiCreateEventReadyToBootEx(
             TPL_NOTIFY,
             OnReadyToBoot,
             NULL,
             &Event
             );

  //
  // Finished
  //
  return EFI_SUCCESS;
}
