/** @file
  Library to get Global VTd PMR alignment information.
  Copyright (c) 2019, Intel Corporation. All rights reserved.<BR>

  SPDX-License-Identifier: BSD-2-Clause-Patent

**/
#include <PiPei.h>
#include <Library/BaseLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/IoLib.h>
#include <Library/DebugLib.h>
#include <Library/PeiGetVtdPmrAlignmentLib.h>
#include <Library/PeiServicesLib.h>
#include <IndustryStandard/DmaRemappingReportingTable.h>
#include <IndustryStandard/Vtd.h>

typedef union {
  struct {
    UINT32  Low;
    UINT32  High;
  } Data32;
  UINT64 Data;
} UINT64_STRUCT;

/**
  Get the protected low memory alignment.

  @param HostAddressWidth   The host address width.
  @param VtdUnitBaseAddress The base address of the VTd engine.

  @return protected low memory alignment. Ex: 0x100000
**/
UINT32
GetGlobalVTdPlmrAlignment (
  IN UINT8         HostAddressWidth,
  IN UINTN         VtdUnitBaseAddress
  )
{
  UINT32        Data32;

  MmioWrite32 (VtdUnitBaseAddress + R_PMEN_LOW_BASE_REG, 0xFFFFFFFF);
  Data32 = MmioRead32 (VtdUnitBaseAddress + R_PMEN_LOW_BASE_REG);
  Data32 = ~Data32 + 1;

  return Data32;
}

/**
  Get the protected high memory alignment.

  @param HostAddressWidth   The host address width.
  @param VtdUnitBaseAddress The base address of the VTd engine.

  @return protected high memory alignment. Ex: 0x100000
**/
UINT64_STRUCT
GetGlobalVTdPhmrAlignment (
  IN UINT8         HostAddressWidth,
  IN UINTN         VtdUnitBaseAddress
  )
{
  UINT64_STRUCT        Data64;

  MmioWrite64 (VtdUnitBaseAddress + R_PMEN_HIGH_BASE_REG, 0xFFFFFFFFFFFFFFFF);
  Data64.Data = MmioRead64 (VtdUnitBaseAddress + R_PMEN_HIGH_BASE_REG);
  Data64.Data = ~Data64.Data + 1;
  Data64.Data = Data64.Data & (LShiftU64 (1, HostAddressWidth) - 1);

  return Data64;
}

/**
  Get the global VT-d protected memory alignment.
  @return The maximum protected memory alignment. Ex: 0x100000
**/
UINTN
EFIAPI
GetGlobalVtdPmrAlignment (
)
{
  UINT32                            LowMemoryAlignment;
  UINT64_STRUCT                     HighMemoryAlignment;
  UINTN                             MemoryAlignment;
  UINT32                            GlobalVTdBaseAddress;
  EFI_STATUS                        Status;
  UINTN                             VtdIndex;
  EFI_ACPI_DMAR_STRUCTURE_HEADER    *DmarHeader;
  EFI_ACPI_DMAR_DRHD_HEADER         *DrhdHeader;
  EFI_ACPI_DMAR_HEADER              *AcpiDmarTable;

  //
  // Initialization
  //
  GlobalVTdBaseAddress = 0xFFFFFFFF;
  LowMemoryAlignment = 0;
  HighMemoryAlignment.Data = 0;
  MemoryAlignment = 0;
  Status = EFI_UNSUPPORTED;
  VtdIndex = 0;
  DmarHeader = NULL;
  DrhdHeader = NULL;
  AcpiDmarTable = NULL;

  //
  // Fetch the PEI DMAR ACPI Table that created and installed in PlatformVTdInfoSamplePei.c
  //
  Status = PeiServicesLocatePpi (
             &gEdkiiVTdInfoPpiGuid,
             0,
             NULL,
             (VOID **)&AcpiDmarTable
             );
  if (EFI_ERROR (Status)) {

    DEBUG ((DEBUG_ERROR, "PeiServicesLocatePpi gEdkiiVTdInfoPpiGuid failed\n"));
    Status = EFI_NOT_FOUND;
    MemoryAlignment = SIZE_1MB;

  } else {

    //
    // Seatch the DRHD structure with INCLUDE_PCI_ALL flag Set -> Global VT-d
    //
    DmarHeader = (EFI_ACPI_DMAR_STRUCTURE_HEADER *)((UINTN)(AcpiDmarTable + 1));
    while ((UINTN)DmarHeader < (UINTN)AcpiDmarTable + AcpiDmarTable->Header.Length) {
      switch (DmarHeader->Type) {
      case EFI_ACPI_DMAR_TYPE_DRHD:
        DrhdHeader = (EFI_ACPI_DMAR_DRHD_HEADER *) DmarHeader;
        if ((DrhdHeader->Flags & EFI_ACPI_DMAR_DRHD_FLAGS_INCLUDE_PCI_ALL) == EFI_ACPI_DMAR_DRHD_FLAGS_INCLUDE_PCI_ALL) {
          GlobalVTdBaseAddress = (UINT32)DrhdHeader->RegisterBaseAddress;
          DEBUG ((DEBUG_INFO,"  GlobalVTdBaseAddress: %x\n", GlobalVTdBaseAddress));
        }
        VtdIndex++;

        break;

      default:
        break;
      }
      DmarHeader = (EFI_ACPI_DMAR_STRUCTURE_HEADER *)((UINTN)DmarHeader + DmarHeader->Length);
    }

    if (GlobalVTdBaseAddress == 0xFFFFFFFF) {

      DEBUG ((DEBUG_ERROR, "Error! Please set INCLUDE_PCI_ALL flag to your Global VT-d\n"));
      MemoryAlignment = SIZE_1MB;

    } else {
      //
      // Get the alignment information from VT-d register
      //
      LowMemoryAlignment = GetGlobalVTdPlmrAlignment (AcpiDmarTable->HostAddressWidth, GlobalVTdBaseAddress);
      HighMemoryAlignment = GetGlobalVTdPhmrAlignment (AcpiDmarTable->HostAddressWidth, GlobalVTdBaseAddress);
      if (LowMemoryAlignment < HighMemoryAlignment.Data) {
        MemoryAlignment = (UINTN)HighMemoryAlignment.Data;
      } else {
        MemoryAlignment = LowMemoryAlignment;
      }
    }
  }

  return MemoryAlignment;
}
