/** @file
  This code provides a initialization of intel VT-d (Virtualization Technology for Directed I/O).
  
Copyright (c) 2017, Intel Corporation. All rights reserved.<BR>
SPDX-License-Identifier: BSD-2-Clause-Patent

**/
#include "SaInitDxe.h"
#include "SaInit.h"
#include "VTd.h"
#include <CpuRegs.h>
#include <SaConfigHob.h>
#include <Library/ConfigBlockLib.h>
#include <Library/PchP2sbLib.h>
#include <Library/PchInfoLib.h>

GLOBAL_REMOVE_IF_UNREFERENCED SA_POLICY_PROTOCOL              *mSaPolicy;
GLOBAL_REMOVE_IF_UNREFERENCED PCH_CONFIG_HOB                  *mPchConfigHob;
GLOBAL_REMOVE_IF_UNREFERENCED PCH_SERIES                      mPchSeries;
GLOBAL_REMOVE_IF_UNREFERENCED SA_CONFIG_HOB                   *SaConfigHob;
GLOBAL_REMOVE_IF_UNREFERENCED BOOLEAN                         mAnddI2C0Needed;
GLOBAL_REMOVE_IF_UNREFERENCED BOOLEAN                         mAnddI2C1Needed;
GLOBAL_REMOVE_IF_UNREFERENCED BOOLEAN                         mAnddI2C2Needed;
GLOBAL_REMOVE_IF_UNREFERENCED BOOLEAN                         mAnddI2C3Needed;
GLOBAL_REMOVE_IF_UNREFERENCED BOOLEAN                         mAnddI2C4Needed;
GLOBAL_REMOVE_IF_UNREFERENCED BOOLEAN                         mAnddI2C5Needed;
GLOBAL_REMOVE_IF_UNREFERENCED BOOLEAN                         mAnddSpi0Needed;
GLOBAL_REMOVE_IF_UNREFERENCED BOOLEAN                         mAnddSpi1Needed;
GLOBAL_REMOVE_IF_UNREFERENCED BOOLEAN                         mAnddUa00Needed;
GLOBAL_REMOVE_IF_UNREFERENCED BOOLEAN                         mAnddUa01Needed;
GLOBAL_REMOVE_IF_UNREFERENCED BOOLEAN                         mAnddUa02Needed;

/**
  For device that specified by Device Num and Function Num,
  mDevEnMap is used to check device presence.
  0x80 means use Device ID to detemine presence

  The structure is used to check if device scope is valid when update DMAR table
**/
UINT16  mDevEnMap[][2] = {{0x0200, 0x80}, {0x1400, 0x80}, {0x1401, 0x80}};

BOOLEAN mInterruptRemappingSupport;

/**
  Get the corresponding device Enable/Disable bit according DevNum and FunNum

  @param[in] DevNum  - Device Number
  @param[in] FunNum  - Function Number

  @retval If the device is found, return disable/Enable bit in FD/Deven reigster
  @retval If not found return 0xFF
**/
UINT16
GetFunDisableBit (
  UINT8 DevNum,
  UINT8 FunNum
  )
{
  UINTN Index;

  for (Index = 0; Index < sizeof (mDevEnMap) / 4; Index++) {
    if (mDevEnMap[Index][0] == ((DevNum << 0x08) | FunNum)) {
      return mDevEnMap[Index][1];
    }
  }

  return 0xFF;
}

/**
  Update the DRHD structure

  @param[in, out] DrhdEnginePtr       - A pointer to DRHD structure
**/
VOID
UpdateDrhd (
  IN OUT VOID *DrhdEnginePtr
  )
{
  UINT16                        Length;
  UINT16                        DisableBit;
  UINTN                         DeviceScopeNum;
  BOOLEAN                       NeedRemove;
  EFI_ACPI_DRHD_ENGINE1_STRUCT  *DrhdEngine;

  //
  // Convert DrhdEnginePtr to EFI_ACPI_DRHD_ENGINE1_STRUCT Pointer
  //
  DrhdEngine      = (EFI_ACPI_DRHD_ENGINE1_STRUCT *) DrhdEnginePtr;
  Length          = DrhdEngine->Length;
  DeviceScopeNum  = (DrhdEngine->Length - EFI_ACPI_DRHD_ENGINE_HEADER_LENGTH) / sizeof (EFI_ACPI_DEV_SCOPE_STRUCTURE);
  DisableBit = GetFunDisableBit (
                 DrhdEngine->DeviceScope[0].PciPath[0],
                 DrhdEngine->DeviceScope[0].PciPath[1]
                 );
  NeedRemove = FALSE;
  if ((DisableBit == 0xFF) ||
      (DrhdEngine->RegisterBaseAddress == 0) ||
      ((DisableBit == 0x80) &&
       (MmioRead32 (MmPciBase (0, DrhdEngine->DeviceScope[0].PciPath[0], DrhdEngine->DeviceScope[0].PciPath[1]) + 0x00) == 0xFFFFFFFF))
      ) {
    NeedRemove = TRUE;
  }
  if (NeedRemove) {
    Length -= sizeof (EFI_ACPI_DEV_SCOPE_STRUCTURE);
  }
  ///
  /// If no devicescope is left, we set the structure length as 0x00
  ///
  if ((Length > EFI_ACPI_DRHD_ENGINE_HEADER_LENGTH) || (DrhdEngine->Flags == 0x01)) {
    DrhdEngine->Length = Length;
  } else {
    DrhdEngine->Length = 0;
  }
}

/**
  Get IOAPIC ID from LPC

  @retval APIC ID
**/
UINT8
GetIoApicId (
  VOID
  )
{
  UINT8 volatile  *IoapicIndex;
  UINT32 volatile *IoapicData;
  UINT32          IoApicIndexAddr;
  UINT32          IoApicDataAddr;
  UINT32          Data32;

  ///
  /// Get IOAPIC base
  ///
  PchIoApicBaseGet (&IoApicIndexAddr, &IoApicDataAddr);
  IoapicIndex = (UINT8 *) (UINTN) IoApicIndexAddr;
  IoapicData  = (UINT32 *) (UINTN) IoApicDataAddr;

  ///
  /// Get APIC ID from Identification Register (Index 0)
  ///
  *IoapicIndex  = 0;
  Data32        = (*IoapicData & 0x0F000000) >> 24;

  return (UINT8) Data32;
}

/**
  Initialize some data required for ANDD table generation.

**/
VOID
AnddTablePreInit (
  VOID
  )
{
  /*
  For Serial IO devices, they do not appear in PCI space, use platform policy or SPT SKU to determine existence
    - I2C0  (D21: F0)
    - I2C1  (D21: F1)
    - I2C2  (D21: F2) (LP Only)
    - I2C3  (D21: F3) (LP Only)
    - I2C4  (D25: F2) (LP Only)
    - I2C5  (D25: F1) (LP Only)
    - SPI0  (D30: F2)
    - SPI1  (D30: F3)
    - UART0 (D30: F0)
    - UART1 (D30: F1)
    - UART2 (D25: F0)
    - PEMC  (D30: F4) (LP Only) (PCI mode only)
    - PSDO  (D30: F5) (LP Only) (Deprecated)
    - PSDC  (D30: F6) (LP Only) (PCI mode only)
  */
  mAnddI2C0Needed = TRUE;
  mAnddI2C1Needed = TRUE;
  mAnddI2C2Needed = TRUE;
  mAnddI2C3Needed = TRUE;
  mAnddI2C4Needed = TRUE;
  mAnddI2C5Needed = TRUE;
  mAnddSpi0Needed = TRUE;
  mAnddSpi1Needed = TRUE;
  mAnddUa00Needed = TRUE;
  mAnddUa01Needed = TRUE;
  mAnddUa02Needed = TRUE;

  if (mPchConfigHob->SerialIo.DevMode[0] != 2) {
    mAnddI2C0Needed = FALSE;
    if (mPchConfigHob->SerialIo.DevMode[0] == 0) {
      //
      // Function 0 disabled so no sub-functions
      //
      mAnddI2C1Needed = FALSE;
      mAnddI2C2Needed = FALSE;
      mAnddI2C3Needed = FALSE;
    }
  }
  if (mPchConfigHob->SerialIo.DevMode[1] != 2) {
    mAnddI2C1Needed = FALSE;
  }
  if ((mPchSeries != PchLp) || (mPchConfigHob->SerialIo.DevMode[2] != 2)) {
    mAnddI2C2Needed = FALSE;
  }
  if ((mPchSeries != PchLp) || (mPchConfigHob->SerialIo.DevMode[3] != 2)) {
    mAnddI2C3Needed = FALSE;
  }
  if ((mPchSeries != PchLp) || (mPchConfigHob->SerialIo.DevMode[4] != 2)) {
    mAnddI2C4Needed = FALSE;
  }
  if ((mPchSeries != PchLp) || (mPchConfigHob->SerialIo.DevMode[5] != 2)) {
    mAnddI2C5Needed = FALSE;
  }
  if (mPchConfigHob->SerialIo.DevMode[6] != 2) {
    mAnddSpi0Needed = FALSE;
  }
  if (mPchConfigHob->SerialIo.DevMode[7] != 2) {
    mAnddSpi1Needed = FALSE;
  }
  if (mPchConfigHob->SerialIo.DevMode[8] != 2) {
    mAnddUa00Needed = FALSE;
    if (mPchConfigHob->SerialIo.DevMode[8] == 0) {
      //
      // Function 0 disabled so no sub-functions
      //
      mAnddUa01Needed = FALSE;
      mAnddSpi0Needed = FALSE;
      mAnddSpi1Needed = FALSE;
    }
  }
  if (mPchConfigHob->SerialIo.DevMode[9] != 2) {
    mAnddUa01Needed = FALSE;
  }
  if (mPchConfigHob->SerialIo.DevMode[10] != 2) {
    mAnddUa02Needed = FALSE;
    if (mPchConfigHob->SerialIo.DevMode[10] == 0) {
      //
      // Function 0 disabled so no sub-functions
      //
      mAnddI2C4Needed = FALSE;
      mAnddI2C5Needed = FALSE;
    }
  }
}

/**
  Update the second DRHD structure

  @param[in, out] DrhdEnginePtr       - A pointer to DRHD structure
**/
VOID
UpdateDrhd2 (
  IN OUT VOID *DrhdEnginePtr
  )
{
  UINT16                        Length;
  UINTN                         DeviceScopeNum;
  UINTN                         ValidDeviceScopeNum;
  UINT16                        Data16;
  UINT16                        Index;
  UINT8                         Bus;
  UINT8                         Path[2];
  BOOLEAN                       NeedRemove;
  EFI_ACPI_DRHD_ENGINE2_STRUCT  *DrhdEngine;

  ///
  /// Convert DrhdEnginePtr to EFI_ACPI_DRHD_ENGINE2_STRUCT Pointer
  ///
  DrhdEngine      = (EFI_ACPI_DRHD_ENGINE2_STRUCT *) DrhdEnginePtr;

  Length          = DrhdEngine->Length;
  DeviceScopeNum  = (DrhdEngine->Length - EFI_ACPI_DRHD_ENGINE_HEADER_LENGTH) / sizeof (EFI_ACPI_DEV_SCOPE_STRUCTURE);
  Data16          = 0;
  Bus             = 0;
  ValidDeviceScopeNum = 0;
  Path[0]         = 0;
  Path[1]         = 0;

  AnddTablePreInit ();

  for (Index = 0; Index < DeviceScopeNum; Index++) {
    NeedRemove = FALSE;
    /**
      For HPET and APIC, update device scope if Interrupt remapping is supported. remove device scope
      if interrupt remapping is not supported.
      - Index = 0 - IOAPIC
      - Index = 1 - HPET
    **/
    if (mInterruptRemappingSupport) {
      if (Index == 0) {
        ///
        /// Update source id for IoApic's device scope entry
        ///
        PchP2sbCfgGet16 (R_PCH_P2SB_IBDF, &Data16);
        Bus     = (UINT8) (Data16 >> 8);
        if (Bus != 0x00) {
          Path[0] = (UINT8) ((Data16 & 0xff) >> 3);
          Path[1] = (UINT8) (Data16 & 0x7);
        } else {
          DEBUG ((DEBUG_WARN, "P2SB APIC Bus, Device and Function numbers were not programmed yet or invalid, use hardcoding values instead!\n"));
          Bus     = 0xF0;
          Path[0] = 0x1F;
          Path[1] = 0x0;
        }
        DrhdEngine->DeviceScope[Index].StartBusNumber = Bus;
        DrhdEngine->DeviceScope[Index].PciPath[0] = Path[0];
        DrhdEngine->DeviceScope[Index].PciPath[1] = Path[1];
        //
        // Update APIC ID
        //
        DrhdEngine->DeviceScope[Index].EnumId = GetIoApicId ();
      }
      if (Index == 1) {
        ///
        /// Update source id for HPET's device scope entry
        ///
        PchP2sbCfgGet16 (R_PCH_P2SB_HBDF, &Data16);
        Bus     = (UINT8) (Data16 >> 8);
        Path[0] = (UINT8) ((Data16 & 0xFF) >> 3);
        Path[1] = (UINT8) (Data16 & 0x7);
        DrhdEngine->DeviceScope[Index].StartBusNumber = Bus;
        DrhdEngine->DeviceScope[Index].PciPath[0] = Path[0];
        DrhdEngine->DeviceScope[Index].PciPath[1] = Path[1];
      }
    } else {
      if ((Index == 0) || (Index == 1)) {
        NeedRemove = TRUE;
      }
    }

    switch (Index) {
      case 0:
      case 1:
        break;
      case 2:
        if (mAnddI2C0Needed == FALSE) {
          NeedRemove = TRUE;
        }
        break;
      case 3:
        if (mAnddI2C1Needed == FALSE) {
          NeedRemove = TRUE;
        }
        break;
      case 4:
        if (mAnddI2C2Needed == FALSE) {
          NeedRemove = TRUE;
        }
        break;
      case 5:
        if (mAnddI2C3Needed == FALSE) {
          NeedRemove = TRUE;
        }
        break;
      case 6:
        if (mAnddI2C4Needed == FALSE) {
          NeedRemove = TRUE;
        }
        break;
      case 7:
        if (mAnddI2C5Needed == FALSE) {
          NeedRemove = TRUE;
        }
        break;
      case 8:
        if (mAnddSpi0Needed == FALSE) {
          NeedRemove = TRUE;
        }
        break;
      case 9:
        if (mAnddSpi1Needed == FALSE) {
          NeedRemove = TRUE;
        }
        break;
      case 10:
        if (mAnddUa00Needed == FALSE) {
          NeedRemove = TRUE;
        }
        break;
      case 11:
        if (mAnddUa01Needed == FALSE) {
          NeedRemove = TRUE;
        }
        break;
      case 12:
        if (mAnddUa02Needed == FALSE) {
          NeedRemove = TRUE;
        }
        break;
      default:
        NeedRemove = TRUE;
        break;
    }

    CopyMem (
      &DrhdEngine->DeviceScope[ValidDeviceScopeNum],
      &DrhdEngine->DeviceScope[Index],
      sizeof (EFI_ACPI_DEV_SCOPE_STRUCTURE)
      );
    if (NeedRemove) {
      Length -= sizeof (EFI_ACPI_DEV_SCOPE_STRUCTURE);
    } else {
      ValidDeviceScopeNum++;
    }
  }
  ///
  /// If no devicescope is left, we set the structure length as 0x00
  ///
  if ((Length > EFI_ACPI_DRHD_ENGINE_HEADER_LENGTH) || (DrhdEngine->Flags == 0x01)) {
    DrhdEngine->Length = Length;
  } else {
    DrhdEngine->Length = 0;
  }
}

/**
  Update the RMRR structure

  @param[in, out] RmrrPtr             - A pointer to RMRR structure
**/
VOID
UpdateRmrr (
  IN OUT VOID *RmrrPtr
  )
{
  UINT16                  Length;
  UINT16                  DisableBit;
  UINTN                   DeviceScopeNum;
  UINTN                   ValidDeviceScopeNum;
  UINTN                   Index;
  BOOLEAN                 NeedRemove;
  EFI_ACPI_RMRR_USB_STRUC *Rmrr;

  ///
  /// To make sure all devicescope can be checked,
  /// we convert the RmrrPtr to EFI_ACPI_RMRR_USB_STRUC pointer
  ///
  Rmrr                = (EFI_ACPI_RMRR_USB_STRUC *) RmrrPtr;

  Length              = Rmrr->Length;
  ValidDeviceScopeNum = 0;
  DeviceScopeNum      = (Rmrr->Length - EFI_ACPI_RMRR_HEADER_LENGTH) / sizeof (EFI_ACPI_DEV_SCOPE_STRUCTURE);
  for (Index = 0; Index < DeviceScopeNum; Index++) {
    DisableBit = GetFunDisableBit (
                   Rmrr->DeviceScope[Index].PciPath[0],
                   Rmrr->DeviceScope[Index].PciPath[1]
                   );
    NeedRemove = FALSE;
    if ((DisableBit == 0xFF) ||
        ((DisableBit == 0x80) &&
         (MmioRead32 (MmPciBase (0, Rmrr->DeviceScope[Index].PciPath[0], Rmrr->DeviceScope[Index].PciPath[1]) + 0x00) == 0xFFFFFFFF))
        ) {
      NeedRemove = TRUE;
    }
    CopyMem (
      &Rmrr->DeviceScope[ValidDeviceScopeNum],
      &Rmrr->DeviceScope[Index],
      sizeof (EFI_ACPI_DEV_SCOPE_STRUCTURE)
      );

    if (Rmrr->RmrLimitAddress == 0x0) {
      NeedRemove = TRUE;
    }

    if (NeedRemove) {
      Length -= sizeof (EFI_ACPI_DEV_SCOPE_STRUCTURE);
    } else {
      ValidDeviceScopeNum++;
    }
  }
  ///
  /// If No deviceScope is left, set length as 0x00
  ///
  if (Length > EFI_ACPI_RMRR_HEADER_LENGTH) {
    Rmrr->Length = Length;
  } else {
    Rmrr->Length = 0;
  }
}

/**
  Update the DMAR table

  @param[in, out] TableHeader         - The table to be set
  @param[in, out] Version             - Version to publish
**/
VOID
DmarTableUpdate (
  IN OUT   EFI_ACPI_DESCRIPTION_HEADER       *TableHeader,
  IN OUT   EFI_ACPI_TABLE_VERSION            *Version
  )
{
  EFI_ACPI_DMAR_TABLE *DmarTable;
  EFI_ACPI_DMAR_TABLE TempDmarTable;
  UINTN               Offset;
  UINTN               StructureLen;
  UINTN               McD0BaseAddress;
  UINT32              MchBar;
  UINT16              IgdMode;
  UINT16              GttMode;
  UINT32              IgdMemSize;
  UINT32              GttMemSize;
  EFI_STATUS          Status;
  MISC_DXE_CONFIG     *MiscDxeConfig;
  CPU_GENERATION      CpuGeneration;
  UINT64              OemTableId;

  IgdMemSize  = 0;
  GttMemSize  = 0;
  DmarTable   = (EFI_ACPI_DMAR_TABLE *) TableHeader;
  CpuGeneration = GetCpuGeneration ();
  //
  // The Default OEM Table ID is "KBL "
  //
  OemTableId  = KBL_OEM_TABLE_ID;

  Status = GetConfigBlock ((VOID *) mSaPolicy, &gMiscDxeConfigGuid, (VOID *)&MiscDxeConfig);
  ASSERT_EFI_ERROR (Status);

  ///
  /// Set INTR_REMAP bit (BIT 0) if interrupt remapping is supported
  ///
  if (mInterruptRemappingSupport) {
    DmarTable->Flags |= BIT0;
  }

  if (SaConfigHob->VtdData.X2ApicOptOut == 1) {
    DmarTable->Flags |= BIT1;
  } else {
    DmarTable->Flags &= 0xFD;
  }

  ///
  /// Get OemId
  ///
  CopyMem (DmarTable->Header.OemId, PcdGetPtr (PcdAcpiDefaultOemId), sizeof (DmarTable->Header.OemId));

  if (CpuGeneration == EnumSklCpu) {
    //
    // Use "SKL " as OEM Table ID if the CpuGeneration is SKL
    //
    OemTableId = SKL_OEM_TABLE_ID;
  }
  //
  // Update OEM Table ID
  //
  DmarTable->Header.OemTableId = OemTableId;

  ///
  /// Calculate IGD memsize
  ///
  McD0BaseAddress = MmPciBase (SA_MC_BUS, 0, 0);
  MchBar          = (UINT32) MmioRead64 (McD0BaseAddress + R_SA_MCHBAR) & ~BIT0;
  IgdMode = ((MmioRead16 (McD0BaseAddress + R_SA_GGC) & B_SKL_SA_GGC_GMS_MASK) >> N_SKL_SA_GGC_GMS_OFFSET) & 0xFF;
  if (IgdMode < 0xF0) {
    IgdMemSize = IgdMode * 32 * (1024) * (1024);
  } else {
    IgdMemSize = 4 * (IgdMode - 0xF0 + 1) * (1024) * (1024);
  }
  ///
  /// Calculate GTT mem size
  ///
  GttMemSize = 0;
  GttMode = (MmioRead16 (McD0BaseAddress + R_SA_GGC) & B_SKL_SA_GGC_GGMS_MASK) >> N_SKL_SA_GGC_GGMS_OFFSET;
  if (GttMode <= V_SKL_SA_GGC_GGMS_8MB) {
    GttMemSize = (1 << GttMode) * (1024) * (1024);
  }

  DmarTable->RmrrIgd.RmrBaseAddress   = (MmioRead32 (McD0BaseAddress + R_SA_TOLUD) & ~(0x01)) - IgdMemSize - GttMemSize;
  DmarTable->RmrrIgd.RmrLimitAddress  = DmarTable->RmrrIgd.RmrBaseAddress + IgdMemSize + GttMemSize - 1;
  DEBUG ((DEBUG_INFO, "RMRR Base  address IGD %016lX\n", DmarTable->RmrrIgd.RmrBaseAddress));
  DEBUG ((DEBUG_INFO, "RMRR Limit address IGD %016lX\n", DmarTable->RmrrIgd.RmrLimitAddress));

  DmarTable->RmrrUsb.RmrBaseAddress   = MiscDxeConfig->RmrrUsbBaseAddress[0];
  DmarTable->RmrrUsb.RmrLimitAddress  = MiscDxeConfig->RmrrUsbBaseAddress[1];

  ///
  /// Convert to 4KB alignment.
  ///
  if (DmarTable->RmrrUsb.RmrLimitAddress != 0x0) {
    DmarTable->RmrrUsb.RmrBaseAddress  &= (EFI_PHYSICAL_ADDRESS) ~0xFFF;
    DmarTable->RmrrUsb.RmrLimitAddress &= (EFI_PHYSICAL_ADDRESS) ~0xFFF;
    DmarTable->RmrrUsb.RmrLimitAddress += 0x1000-1;
  }

  DEBUG ((DEBUG_INFO, "RMRR Base  address USB %016lX\n", DmarTable->RmrrUsb.RmrBaseAddress));
  DEBUG ((DEBUG_INFO, "RMRR Limit address USB %016lX\n", DmarTable->RmrrUsb.RmrLimitAddress));

  if (DmarTable->RmrrUsb.RmrBaseAddress == 0) {
    DEBUG ((DEBUG_WARN, "WARNING:  RmrrUsb.RmrBaseAddress is 0.\n"));
  }
  ///
  /// Update DRHD structures of DmarTable
  ///
  DmarTable->DrhdEngine1.RegisterBaseAddress = (MmioRead32 (MchBar + R_SA_MCHBAR_VTD1_OFFSET) &~1);
  DmarTable->DrhdEngine2.RegisterBaseAddress = (MmioRead32 (MchBar + R_SA_MCHBAR_VTD2_OFFSET) &~1);

  DEBUG ((DEBUG_INFO, "VTD base address1 %x\n", DmarTable->DrhdEngine1.RegisterBaseAddress));
  DEBUG ((DEBUG_INFO, "VTD base address2 %x\n", DmarTable->DrhdEngine2.RegisterBaseAddress));
  ///
  /// copy DmarTable to TempDmarTable to be processed
  ///
  CopyMem (&TempDmarTable, DmarTable, sizeof (EFI_ACPI_DMAR_TABLE));

  UpdateDrhd (&TempDmarTable.DrhdEngine1);
  UpdateDrhd2 (&TempDmarTable.DrhdEngine2);

  ///
  /// Update RMRR structures of temp DMAR table
  ///
  UpdateRmrr ((VOID *) &TempDmarTable.RmrrUsb);
  UpdateRmrr ((VOID *) &TempDmarTable.RmrrIgd);

  ///
  /// Remove unused device scope or entire DRHD structures
  ///
  Offset = (UINTN) (&TempDmarTable.DrhdEngine1);
  if (TempDmarTable.DrhdEngine1.Length != 0) {
    Offset += TempDmarTable.DrhdEngine1.Length;
  }
  if (TempDmarTable.DrhdEngine2.Length != 0) {
    StructureLen = TempDmarTable.DrhdEngine2.Length;
    CopyMem ((VOID *) Offset, (VOID *) &TempDmarTable.DrhdEngine2, TempDmarTable.DrhdEngine2.Length);
    Offset += StructureLen;
  }
  ///
  /// Remove unused device scope or entire RMRR structures
  ///
  if (TempDmarTable.RmrrUsb.Length != 0) {
    StructureLen = TempDmarTable.RmrrUsb.Length;
    CopyMem ((VOID *) Offset, (VOID *) &TempDmarTable.RmrrUsb, TempDmarTable.RmrrUsb.Length);
    Offset += StructureLen;
  }
  if (TempDmarTable.RmrrIgd.Length != 0) {
    StructureLen = TempDmarTable.RmrrIgd.Length;
    CopyMem ((VOID *) Offset, (VOID *) &TempDmarTable.RmrrIgd, TempDmarTable.RmrrIgd.Length);
    Offset += StructureLen;
  }

  ///
  /// Include necessary ANDD structures for SPT
  ///
  if (mAnddI2C0Needed == TRUE) {
    StructureLen = TempDmarTable.AnddI2C0.Length;
    CopyMem ((VOID *) Offset, (VOID *) &TempDmarTable.AnddI2C0, TempDmarTable.AnddI2C0.Length);
    Offset += StructureLen;
  }
  if (mAnddI2C1Needed == TRUE) {
    StructureLen = TempDmarTable.AnddI2C1.Length;
    CopyMem ((VOID *) Offset, (VOID *) &TempDmarTable.AnddI2C1, TempDmarTable.AnddI2C1.Length);
    Offset += StructureLen;
  }
  if (mAnddI2C2Needed == TRUE) {
    StructureLen = TempDmarTable.AnddI2C2.Length;
    CopyMem ((VOID *) Offset, (VOID *) &TempDmarTable.AnddI2C2, TempDmarTable.AnddI2C2.Length);
    Offset += StructureLen;
  }
  if (mAnddI2C3Needed == TRUE) {
    StructureLen = TempDmarTable.AnddI2C3.Length;
    CopyMem ((VOID *) Offset, (VOID *) &TempDmarTable.AnddI2C3, TempDmarTable.AnddI2C3.Length);
    Offset += StructureLen;
  }
  if (mAnddI2C4Needed == TRUE) {
    StructureLen = TempDmarTable.AnddI2C4.Length;
    CopyMem ((VOID *) Offset, (VOID *) &TempDmarTable.AnddI2C4, TempDmarTable.AnddI2C4.Length);
    Offset += StructureLen;
  }
  if (mAnddI2C5Needed == TRUE) {
    StructureLen = TempDmarTable.AnddI2C5.Length;
    CopyMem ((VOID *) Offset, (VOID *) &TempDmarTable.AnddI2C5, TempDmarTable.AnddI2C5.Length);
    Offset += StructureLen;
  }
  if (mAnddSpi0Needed == TRUE) {
    StructureLen = TempDmarTable.AnddSpi0.Length;
    CopyMem ((VOID *) Offset, (VOID *) &TempDmarTable.AnddSpi0, TempDmarTable.AnddSpi0.Length);
    Offset += StructureLen;
  }
  if (mAnddSpi1Needed == TRUE) {
    StructureLen = TempDmarTable.AnddSpi1.Length;
    CopyMem ((VOID *) Offset, (VOID *) &TempDmarTable.AnddSpi1, TempDmarTable.AnddSpi1.Length);
    Offset += StructureLen;
  }
  if (mAnddUa00Needed == TRUE) {
    StructureLen = TempDmarTable.AnddUa00.Length;
    CopyMem ((VOID *) Offset, (VOID *) &TempDmarTable.AnddUa00, TempDmarTable.AnddUa00.Length);
    Offset += StructureLen;
  }
  if (mAnddUa01Needed == TRUE) {
    StructureLen = TempDmarTable.AnddUa01.Length;
    CopyMem ((VOID *) Offset, (VOID *) &TempDmarTable.AnddUa01, TempDmarTable.AnddUa01.Length);
    Offset += StructureLen;
  }
  if (mAnddUa02Needed == TRUE) {
    StructureLen = TempDmarTable.AnddUa02.Length;
    CopyMem ((VOID *) Offset, (VOID *) &TempDmarTable.AnddUa02, TempDmarTable.AnddUa02.Length);
    Offset += StructureLen;
  }

  Offset = Offset - (UINTN) &TempDmarTable;
  ///
  /// Re-calculate DMAR table check sum
  ///
  TempDmarTable.Header.Checksum = (UINT8) (TempDmarTable.Header.Checksum + TempDmarTable.Header.Length - Offset);
  ///
  /// Set DMAR table length
  ///
  TempDmarTable.Header.Length = (UINT32) Offset;
  ///
  /// Replace DMAR table with rebuilt table TempDmarTable
  ///
  CopyMem ((VOID *) DmarTable, (VOID *) &TempDmarTable, TempDmarTable.Header.Length);
}

/**
  EndOfDxe routine for update DMAR
**/
VOID
UpdateDmarEndOfDxe (
  VOID
  )
{
  EFI_STATUS                      Status;
  EFI_HANDLE                      *HandleBuffer;
  UINTN                           NumberOfHandles;
  EFI_FV_FILETYPE                 FileType;
  UINT32                          FvStatus;
  EFI_FV_FILE_ATTRIBUTES          Attributes;
  UINTN                           Size;
  UINTN                           i;
  INTN                            Instance;
  EFI_ACPI_TABLE_VERSION          Version;
  EFI_ACPI_COMMON_HEADER          *CurrentTable;
  UINTN                           AcpiTableHandle;
  EFI_FIRMWARE_VOLUME2_PROTOCOL   *FwVol;
  EFI_ACPI_TABLE_PROTOCOL         *AcpiTable;
  EFI_ACPI_DESCRIPTION_HEADER     *VtdAcpiTable;
  STATIC BOOLEAN                  Triggered = FALSE;
  EFI_PEI_HOB_POINTERS            HobPtr;
  if (Triggered) {
    return;
  }

  Triggered     = TRUE;

  FwVol         = NULL;
  AcpiTable     = NULL;
  VtdAcpiTable  = NULL;

  ///
  /// Locate PCH Policy HOB and PCH series to support feature enabling/disabling
  ///
  //
  // Get PCH Config HOB.
  //
  HobPtr.Guid  = GetFirstGuidHob (&gPchConfigHobGuid);
  ASSERT (HobPtr.Guid != NULL);
  mPchConfigHob = GET_GUID_HOB_DATA (HobPtr.Guid);

  mPchSeries    = GetPchSeries ();

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

  ///
  /// Locate protocol.
  /// There is little chance we can't find an FV protocol
  ///
  Status = gBS->LocateHandleBuffer (
                  ByProtocol,
                  &gEfiFirmwareVolume2ProtocolGuid,
                  NULL,
                  &NumberOfHandles,
                  &HandleBuffer
                  );
  ASSERT_EFI_ERROR (Status);

  ///
  /// Looking for FV with ACPI storage file
  ///
  for (i = 0; i < NumberOfHandles; i++) {
    ///
    /// Get the protocol on this handle
    /// This should not fail because of LocateHandleBuffer
    ///
    Status = gBS->HandleProtocol (
                    HandleBuffer[i],
                    &gEfiFirmwareVolume2ProtocolGuid,
                    (VOID **) &FwVol
                    );
    ASSERT_EFI_ERROR (Status);

    ///
    /// See if it has the ACPI storage file
    ///
    Size      = 0;
    FvStatus  = 0;
    Status = FwVol->ReadFile (
                      FwVol,
                      &gSaAcpiTableStorageGuid,
                      NULL,
                      &Size,
                      &FileType,
                      &Attributes,
                      &FvStatus
                      );

    ///
    /// If we found it, then we are done
    ///
    if (Status == EFI_SUCCESS) {
      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);
  if (FwVol == NULL) {
    return;
  }
  ///
  /// By default, a table belongs in all ACPI table versions published.
  ///
  Version = EFI_ACPI_TABLE_VERSION_1_0B | EFI_ACPI_TABLE_VERSION_2_0 | EFI_ACPI_TABLE_VERSION_3_0;

  ///
  /// Read tables from the storage file.
  ///
  Instance      = 0;
  CurrentTable  = NULL;

  while (Status == EFI_SUCCESS) {
    Status = FwVol->ReadSection (
                      FwVol,
                      &gSaAcpiTableStorageGuid,
                      EFI_SECTION_RAW,
                      Instance,
                      (VOID **) &CurrentTable,
                      &Size,
                      &FvStatus
                      );

    if (!EFI_ERROR (Status)) {
      ///
      /// Check the Signature ID to modify the table
      ///
      switch (((EFI_ACPI_DESCRIPTION_HEADER *) CurrentTable)->Signature) {

        case EFI_ACPI_VTD_DMAR_TABLE_SIGNATURE:
          VtdAcpiTable = (EFI_ACPI_DESCRIPTION_HEADER *) CurrentTable;
          DmarTableUpdate (VtdAcpiTable, &Version);
          break;

        default:
          break;
      }
      ///
      /// Increment the instance
      ///
      Instance++;
      CurrentTable = NULL;
    }
  }
  ///
  /// Update the VTD table in the ACPI tables.
  ///
  AcpiTableHandle = 0;
  if (VtdAcpiTable != NULL) {
    Status = AcpiTable->InstallAcpiTable (
                          AcpiTable,
                          VtdAcpiTable,
                          VtdAcpiTable->Length,
                          &AcpiTableHandle
                          );
    FreePool (VtdAcpiTable);
  }
}

/**
  Locate the VT-d ACPI tables data file and read ACPI SSDT tables.
  Publish the appropriate SSDT based on current configuration and capabilities.

  @param[in] SaPolicy     -  SA DXE Policy protocol

  @retval EFI_SUCCESS     - Vtd initialization complete
  @exception EFI_UNSUPPORTED - Vtd is not enabled by policy
**/
EFI_STATUS
VtdInit (
  IN  SA_POLICY_PROTOCOL    *SaPolicy
  )
{
  UINTN        McD0BaseAddress;

  mInterruptRemappingSupport  = FALSE;
  SaConfigHob       = NULL;
  SaConfigHob = GetFirstGuidHob (&gSaConfigHobGuid);
  if (SaConfigHob != NULL) {
    mInterruptRemappingSupport  = SaConfigHob->VtdData.InterruptRemappingSupport;
  }

  McD0BaseAddress             = MmPciBase (SA_MC_BUS, 0, 0);
  mSaPolicy                   = SaPolicy;

  if (((SaConfigHob != NULL) && (SaConfigHob->VtdData.VtdDisable)) || (MmioRead32 (McD0BaseAddress + R_SA_MC_CAPID0_A_OFFSET) & BIT23)) {
    DEBUG ((DEBUG_WARN, "VTd disabled or no capability!\n"));
    return EFI_UNSUPPORTED;
  }
  ///
  /// Check SA supports VTD and VTD is enabled in setup menu
  ///
  DEBUG ((DEBUG_INFO, "VTd enabled\n"));

  return EFI_SUCCESS;
}
