/** @file
  This is the driver that initializes the Intel System Agent.

  Copyright (c) 2021, Intel Corporation. All rights reserved.<BR>
  SPDX-License-Identifier: BSD-2-Clause-Patent
**/
#include "SaInitDxe.h"
#include "SaInit.h"
#include <SaConfigHob.h>
#include <Protocol/SaNvsArea.h>
#include <Library/PchInfoLib.h>
#include <CpuPcieInfo.h>
#include <Library/DxeCpuPcieRpLib.h>
#include <SaConfigHob.h>
#include <CpuPcieHob.h>
#include <HostBridgeDataHob.h>
#include <CpuDataStruct.h>

///
/// Global Variables
///
GLOBAL_REMOVE_IF_UNREFERENCED SYSTEM_AGENT_NVS_AREA_PROTOCOL  mSaNvsAreaProtocol;
GLOBAL_REMOVE_IF_UNREFERENCED SA_POLICY_PROTOCOL              *mSaPolicy;
extern SA_CONFIG_HOB                                          *mSaConfigHob;

/**
  A protocol callback which updates 64bits MMIO Base and Length in SA GNVS area
**/
VOID
UpdateSaGnvsForMmioResourceBaseLength (
  VOID
  )
{
  EFI_PHYSICAL_ADDRESS      PciBaseAddress;
  UINT32                    Tolud;
  UINT64                    Length;
  UINT64                    McD0BaseAddress;
  UINTN                     ResMemLimit1;
  UINT8                     EnableAbove4GBMmioBiosAssignemnt;
  HOST_BRIDGE_DATA_HOB      *HostBridgeDataHob;

  PciBaseAddress = 0;
  Tolud = 0;
  Length = 0;
  ResMemLimit1 = 0;
  EnableAbove4GBMmioBiosAssignemnt = 0;
  HostBridgeDataHob = NULL;
  //
  // Read memory map registers
  //
  McD0BaseAddress        = PCI_SEGMENT_LIB_ADDRESS (SA_SEG_NUM, SA_MC_BUS, 0, 0, 0);
  Tolud                  = PciSegmentRead32 (McD0BaseAddress + R_SA_TOLUD) & B_SA_TOLUD_TOLUD_MASK;
  PciBaseAddress         = Tolud;

  ResMemLimit1 = (UINTN) PcdGet64 (PcdSiPciExpressBaseAddress);

  Length = ResMemLimit1 - PciBaseAddress;

  //
  // Get HostBridgeData HOB and see if above 4GB MMIO BIOS assignment enabled
  //
  HostBridgeDataHob = (HOST_BRIDGE_DATA_HOB *) GetFirstGuidHob (&gHostBridgeDataHobGuid);
  if ((HostBridgeDataHob != NULL) && (HostBridgeDataHob->EnableAbove4GBMmio == 1)) {
    EnableAbove4GBMmioBiosAssignemnt = 1;
  }

  //
  // Enable Above 4GB MMIO when Aperture Size is 2GB or higher
  //
  if ((mSaConfigHob != NULL) && (mSaConfigHob->ApertureSize >= 15)) {
    EnableAbove4GBMmioBiosAssignemnt = 1;
  }

  //
  // Check Enable Above 4GB MMIO or not
  //

  DEBUG ((DEBUG_INFO, "Update SA GNVS Area.\n"));
  mSaNvsAreaProtocol.Area->Mmio32Base   = (UINT32) PciBaseAddress;
  mSaNvsAreaProtocol.Area->Mmio32Length = (UINT32) Length;
  if (EnableAbove4GBMmioBiosAssignemnt == 1) {
    mSaNvsAreaProtocol.Area->Mmio64Base   = BASE_256GB;
    mSaNvsAreaProtocol.Area->Mmio64Length = SIZE_256GB;
  }
  DEBUG ((DEBUG_INFO, "SaNvsAreaProtocol.Area->Mmio64Base = %lx\n", mSaNvsAreaProtocol.Area->Mmio64Base));
  DEBUG ((DEBUG_INFO, "SaNvsAreaProtocol.Area->Mmio64Length = %lx\n", mSaNvsAreaProtocol.Area->Mmio64Length));
  DEBUG ((DEBUG_INFO, "SaNvsAreaProtocol.Area->Mmio32Base = %lx\n", mSaNvsAreaProtocol.Area->Mmio32Base));
  DEBUG ((DEBUG_INFO, "SaNvsAreaProtocol.Area->Mmio32Length = %lx\n", mSaNvsAreaProtocol.Area->Mmio32Length));
}

/**
  Install SSDT Table

  @retval EFI_SUCCESS - SSDT Table load successful.
**/
EFI_STATUS
InstallSsdtAcpiTable (
  IN GUID   SsdtTableGuid,
  IN UINT64 Signature
  )
{
  EFI_STATUS                    Status;
  EFI_HANDLE                    *HandleBuffer;
  BOOLEAN                       LoadTable;
  UINTN                         NumberOfHandles;
  UINTN                         Index;
  INTN                          Instance;
  UINTN                         Size;
  UINT32                        FvStatus;
  UINTN                         TableHandle;
  EFI_FV_FILETYPE               FileType;
  EFI_FV_FILE_ATTRIBUTES        Attributes;
  EFI_FIRMWARE_VOLUME2_PROTOCOL *FwVol;
  EFI_ACPI_TABLE_PROTOCOL       *AcpiTable;
  EFI_ACPI_DESCRIPTION_HEADER   *TableHeader;
  EFI_ACPI_COMMON_HEADER        *Table;

  FwVol         = NULL;
  Table         = NULL;

  DEBUG ((DEBUG_INFO, "Loading SSDT Table GUID: %g\n", SsdtTableGuid));

  ///
  /// Locate FV protocol.
  ///
  Status = gBS->LocateHandleBuffer (
                  ByProtocol,
                  &gEfiFirmwareVolume2ProtocolGuid,
                  NULL,
                  &NumberOfHandles,
                  &HandleBuffer
                  );
  ASSERT_EFI_ERROR (Status);

  ///
  /// Look 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],
                    &gEfiFirmwareVolume2ProtocolGuid,
                    (VOID **) &FwVol
                    );
    ASSERT_EFI_ERROR (Status);
    if (FwVol == NULL) {
      return EFI_NOT_FOUND;
    }
    ///
    /// See if it has the ACPI storage file
    ///
    Size      = 0;
    FvStatus  = 0;
    Status = FwVol->ReadFile (
                      FwVol,
                      &SsdtTableGuid,
                      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
  ///
  FreePool (HandleBuffer);

  ///
  /// Sanity check that we found our data file
  ///
  ASSERT (FwVol);

  ///
  /// Locate ACPI tables
  ///
  Status = gBS->LocateProtocol (&gEfiAcpiTableProtocolGuid, NULL, (VOID **) &AcpiTable);

  ///
  /// Read tables from the storage file.
  ///
  if (FwVol == NULL) {
    ASSERT_EFI_ERROR (EFI_NOT_FOUND);
    return EFI_NOT_FOUND;
  }
  Instance = 0;

  while (Status == EFI_SUCCESS) {
    ///
    /// Read the ACPI tables
    ///
    Status = FwVol->ReadSection (
                      FwVol,
                      &SsdtTableGuid,
                      EFI_SECTION_RAW,
                      Instance,
                      (VOID **) &Table,
                      &Size,
                      &FvStatus
                      );
    if (!EFI_ERROR (Status)) {
      ///
      /// check and load HybridGraphics SSDT table
      ///
      LoadTable   = FALSE;
      TableHeader = (EFI_ACPI_DESCRIPTION_HEADER *) Table;

      if (((EFI_ACPI_DESCRIPTION_HEADER *) TableHeader)->OemTableId == Signature) {
        ///
        /// This is the SSDT table that match the Signature
        ///
        DEBUG ((DEBUG_INFO, "Found out SSDT Table GUID: %g\n", SsdtTableGuid));
        LoadTable = TRUE;
      }

      ///
      /// Add the table
      ///
      if (LoadTable) {
        TableHandle = 0;
        Status = AcpiTable->InstallAcpiTable (
                              AcpiTable,
                              TableHeader,
                              TableHeader->Length,
                              &TableHandle
                              );
      }
      ///
      /// Increment the instance
      ///
      Instance++;
      Table = NULL;
    }
  }

  return EFI_SUCCESS;
}

/**
  This function gets registered as a callback to perform Dmar Igd

  @param[in] Event     - A pointer to the Event that triggered the callback.
  @param[in] Context   - A pointer to private data registered with the callback function.
**/
VOID
EFIAPI
SaAcpiEndOfDxeCallback (
  IN EFI_EVENT    Event,
  IN VOID         *Context
  )
{
  EFI_STATUS          Status;

  if (PciSegmentRead16 (PCI_SEGMENT_LIB_ADDRESS (SA_SEG_NUM, IGD_BUS_NUM, IGD_DEV_NUM, IGD_FUN_NUM, PCI_VENDOR_ID_OFFSET)) != 0xFFFF) {
    Status = PostPmInitEndOfDxe ();
    if (EFI_SUCCESS != Status) {
      DEBUG ((DEBUG_WARN, "[SA] EndOfDxe GraphicsInit Error, Status = %r \n", Status));
      ASSERT_EFI_ERROR (Status);
    }
  }

  if (PciSegmentRead16 (PCI_SEGMENT_LIB_ADDRESS (SA_SEG_NUM, IGD_BUS_NUM, IGD_DEV_NUM, IGD_FUN_NUM, PCI_VENDOR_ID_OFFSET)) != 0xFFFF) {
    Status = GetVBiosVbtEndOfDxe ();
    if (EFI_SUCCESS != Status) {
      DEBUG ((DEBUG_WARN, "[SA] EndOfDxe Op Region Error, Status = %r \n", Status));
    }

    Status = UpdateIgdOpRegionEndOfDxe ();
    if (EFI_SUCCESS != Status) {
      DEBUG ((DEBUG_WARN, "[SA] EndOfDxe Update Op Region Error, Status = %r \n", Status));
    }
  }

  return;
}

/**
  SystemAgent Acpi Initialization.

  @param[in] ImageHandle             Handle for the image of this driver

  @retval EFI_SUCCESS             The function completed successfully
  @retval EFI_OUT_OF_RESOURCES    No enough buffer to allocate
**/
EFI_STATUS
EFIAPI
SaAcpiInit (
  IN EFI_HANDLE         ImageHandle
  )
{
  EFI_STATUS                Status;
  EFI_CPUID_REGISTER        CpuidRegs;
  EFI_EVENT                 EndOfDxeEvent;

  CPU_PCIE_HOB              *CpuPcieHob;

  AsmCpuid (1, &CpuidRegs.RegEax, 0, 0, 0);
  ///
  /// Get the platform setup policy.
  ///
  Status = gBS->LocateProtocol (&gSaPolicyProtocolGuid, NULL, (VOID **) &mSaPolicy);
  ASSERT_EFI_ERROR (Status);

  ///
  /// Install System Agent Global NVS protocol
  ///
  DEBUG ((DEBUG_INFO, "Install SA GNVS protocol\n"));
  Status = (gBS->AllocatePool) (EfiACPIMemoryNVS, sizeof (SYSTEM_AGENT_NVS_AREA), (VOID **) &mSaNvsAreaProtocol.Area);
  ASSERT_EFI_ERROR (Status);
  ZeroMem ((VOID *) mSaNvsAreaProtocol.Area, sizeof (SYSTEM_AGENT_NVS_AREA));
  mSaNvsAreaProtocol.Area->XPcieCfgBaseAddress  = (UINT32) (PcdGet64 (PcdSiPciExpressBaseAddress));
  mSaNvsAreaProtocol.Area->CpuIdInfo            = CpuidRegs.RegEax;

  ///
  /// Get CpuPcieHob HOB
  ///
  CpuPcieHob = NULL;
  CpuPcieHob = (CPU_PCIE_HOB *) GetFirstGuidHob (&gCpuPcieHobGuid);
  if (CpuPcieHob == NULL) {
    DEBUG((DEBUG_ERROR, "CpuPcieHob not found\n"));
    // @todo: Will add it back once it will get add into NVS library since currently it is failing for JSL
    //ASSERT(CpuPcieHob != NULL);
    //return EFI_NOT_FOUND;
  } else {
    mSaNvsAreaProtocol.Area->SlotSelection = CpuPcieHob->SlotSelection;
    DEBUG((DEBUG_INFO, "RpEnabledMask == %x\n", CpuPcieHob->RpEnabledMask));
    if (CpuPcieHob->RpEnabledMask == 0) {
      DEBUG ((DEBUG_ERROR, "All CPU PCIe root ports are disabled!!\n"));
    } else {
      if (CpuPcieHob->RpEnabledMask & BIT0) {
        mSaNvsAreaProtocol.Area->CpuPcieRp0Enable = 1;
      }
      if (CpuPcieHob->RpEnabledMask & BIT1) {
        mSaNvsAreaProtocol.Area->CpuPcieRp1Enable = 1;
      }
      if (CpuPcieHob->RpEnabledMask & BIT2) {
        mSaNvsAreaProtocol.Area->CpuPcieRp2Enable = 1;
      }
      if (CpuPcieHob->RpEnabledMask & BIT3) {
        mSaNvsAreaProtocol.Area->CpuPcieRp3Enable = 1;
      }
    }
    mSaNvsAreaProtocol.Area->MaxPegPortNumber = GetMaxCpuPciePortNum ();
  }

  mSaNvsAreaProtocol.Area->SimicsEnvironment = 0;

  Status = gBS->InstallMultipleProtocolInterfaces (
                  &ImageHandle,
                  &gSaNvsAreaProtocolGuid,
                  &mSaNvsAreaProtocol,
                  NULL
                  );
  ASSERT_EFI_ERROR (Status);

  ///
  /// GtPostInit Initialization
  ///
  DEBUG ((DEBUG_INFO, "Initializing GT ACPI tables\n"));
  GraphicsInit (ImageHandle, mSaPolicy);

  ///
  /// Audio (dHDA) Initialization
  ///
  ///
  /// Vtd Initialization
  ///
  DEBUG ((DEBUG_INFO, "Initializing VT-d ACPI tables\n"));
  VtdInit (mSaPolicy);

  ///
  /// IgdOpRegion Install Initialization
  ///
  DEBUG ((DEBUG_INFO, "Initializing IGD OpRegion\n"));
  IgdOpRegionInit ();

  ///
  /// Register an end of DXE event for SA ACPI to do tasks before invoking any UEFI drivers,
  /// applications, or connecting consoles,...
  ///
  Status = gBS->CreateEventEx (
                  EVT_NOTIFY_SIGNAL,
                  TPL_CALLBACK,
                  SaAcpiEndOfDxeCallback,
                  NULL,
                  &gEfiEndOfDxeEventGroupGuid,
                  &EndOfDxeEvent
                  );

  ///
  /// Install System Agent Global NVS ACPI table
  ///
  Status = InstallSsdtAcpiTable (gSaSsdtAcpiTableStorageGuid, SIGNATURE_64 ('S', 'a', 'S', 's', 'd', 't', ' ', 0));
  ASSERT_EFI_ERROR (Status);

  ///
  /// Update CPU PCIE RP NVS AREA
  ///
  UpdateCpuPcieNVS();
  ///
  /// Install Intel Graphics SSDT
  ///
  Status = InstallSsdtAcpiTable (gGraphicsAcpiTableStorageGuid, SIGNATURE_64 ('I','g','f','x','S','s','d','t'));
  ASSERT_EFI_ERROR (Status);

  ///
  /// Install IPU SSDT if IPU is present.
  ///
  if (PciSegmentRead16 (PCI_SEGMENT_LIB_ADDRESS (SA_SEG_NUM, IPU_BUS_NUM, IPU_DEV_NUM, IPU_FUN_NUM, 0)) != V_SA_DEVICE_ID_INVALID) {
      Status = InstallSsdtAcpiTable (gIpuAcpiTableStorageGuid, SIGNATURE_64 ('I','p','u','S','s','d','t',0));
      ASSERT_EFI_ERROR (Status);

  }

  return EFI_SUCCESS;
}

