/** @file

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

**/

#include <Uefi.h>
#include <PiDxe.h>
#include <Library/TestPointCheckLib.h>
#include <Library/TestPointLib.h>
#include <Library/DebugLib.h>
#include <Library/UefiLib.h>
#include <Library/PrintLib.h>
#include <Library/BaseMemoryLib.h>
#include <IndustryStandard/Acpi.h>
#include <IndustryStandard/DmaRemappingReportingTable.h>

VOID
DumpCharArray (
  IN CHAR8  *Ch,
  IN UINTN  Size
  );

VOID
DumpAcpiTableHeader (
  IN EFI_ACPI_DESCRIPTION_HEADER  *Table
  );

BOOLEAN
IsMmioExit (
  IN EFI_PHYSICAL_ADDRESS  BaseAddress,
  IN UINT64                Length,
  IN BOOLEAN               CheckAllocated
  );

CHAR8 *mDmarTypeString[] = {
  "DRHD  ",
  "RMRR  ",
  "ATSR  ",
  "RHSA  ",
  "ANDD  ",
};

STATIC CHAR8 mUnknownStr[11];

CHAR8 *
ShortNameOfDmarType(
  IN UINT16 Type
  )
{
  if (Type < sizeof(mDmarTypeString) / sizeof(mDmarTypeString[0])) {
    return mDmarTypeString[Type];
  } else {
    AsciiSPrint(mUnknownStr, sizeof(mUnknownStr), "[%04x]", Type);
    return mUnknownStr;
  }
}

CHAR8 *mDmarDeviceTypeString[] = {
  "[00]",
  "PCI",
  "P2P",
  "IOAPIC",
  "HPET",
  "ACPI",
};

CHAR8 *
ShortNameOfDmarDeviceType(
  IN UINT8 Type
  )
{
  if (Type < sizeof(mDmarDeviceTypeString) / sizeof(mDmarDeviceTypeString[0])) {
    return mDmarDeviceTypeString[Type];
  } else {
    AsciiSPrint(mUnknownStr, sizeof(mUnknownStr), "[%02x]", Type);
    return mUnknownStr;
  }
}

VOID
DumpDmarDeviceScope (
  IN EFI_ACPI_DMAR_DEVICE_SCOPE_STRUCTURE_HEADER *DmarDeviceScope,
  IN UINTN                                       Size
  )
{
  EFI_ACPI_DMAR_DEVICE_SCOPE_STRUCTURE_HEADER *Device;
  EFI_ACPI_DMAR_PCI_PATH                      *PciPathScope;
  EFI_ACPI_DMAR_PCI_PATH                      *PciPath;
  UINTN                                       PciPathSize;

  Device = DmarDeviceScope;
  while ((UINTN)Device < (UINTN)DmarDeviceScope + Size) {
    DEBUG ((DEBUG_INFO, " ("));
    DEBUG ((DEBUG_INFO, ShortNameOfDmarDeviceType(Device->Type)));
    DEBUG ((DEBUG_INFO, ": [0x%02x]", Device->Type));
    DEBUG ((DEBUG_INFO, " EnumId=0x%02x", Device->EnumerationId));
    DEBUG ((DEBUG_INFO, " Bus=0x%02x", Device->StartBusNumber));

    PciPathScope = (EFI_ACPI_DMAR_PCI_PATH *)(DmarDeviceScope + 1);
    PciPathSize = Device->Length - sizeof(EFI_ACPI_DMAR_DEVICE_SCOPE_STRUCTURE_HEADER);
    PciPath = PciPathScope;
    while ((UINTN)PciPath < (UINTN)PciPathScope + PciPathSize) {
      DEBUG ((DEBUG_INFO, " Pci=0x%02x/0x%02x", PciPath->Device, PciPath->Function));
      PciPath ++;
    }

    DEBUG ((DEBUG_INFO, ")"));
    Device = (EFI_ACPI_DMAR_DEVICE_SCOPE_STRUCTURE_HEADER *)((UINTN)Device + Device->Length);
  }
}

VOID
DumpAcpiDmar (
  IN EFI_ACPI_DMAR_HEADER  *Dmar
  )
{
  EFI_ACPI_DMAR_STRUCTURE_HEADER        *DmarStructHeader;
  INTN                                  DmarLen;
  EFI_ACPI_DMAR_DRHD_HEADER             *Drhd;
  EFI_ACPI_DMAR_RMRR_HEADER             *Rmrr;
  EFI_ACPI_DMAR_ATSR_HEADER             *Atsr;
  EFI_ACPI_DMAR_RHSA_HEADER             *Rhsa;
  EFI_ACPI_DMAR_ANDD_HEADER             *Andd;

  DumpAcpiTableHeader (&Dmar->Header);
  DEBUG ((DEBUG_INFO, "         "));
  DEBUG ((DEBUG_INFO, " HostAddressWidth=0x%02x Flags=0x%02x\n", Dmar->HostAddressWidth, Dmar->Flags));
    
  //
  // Sub table
  //
  DmarLen  = Dmar->Header.Length - sizeof(EFI_ACPI_DMAR_HEADER);
  DmarStructHeader = (EFI_ACPI_DMAR_STRUCTURE_HEADER *)(Dmar + 1);
  while (DmarLen >= sizeof (*DmarStructHeader)) {
    switch (DmarStructHeader->Type) {
    case EFI_ACPI_DMAR_TYPE_DRHD:
      Drhd = (EFI_ACPI_DMAR_DRHD_HEADER *)DmarStructHeader;
      DEBUG ((DEBUG_INFO, "         "));
      DEBUG ((DEBUG_INFO, " "));
      DEBUG ((DEBUG_INFO, ShortNameOfDmarType(DmarStructHeader->Type)));
      DEBUG ((DEBUG_INFO, ": [0x%04x]", DmarStructHeader->Type));
      DEBUG ((DEBUG_INFO, " Flags=0x%02x", Drhd->Flags));
      DEBUG ((DEBUG_INFO, " Segment=0x%04x", Drhd->SegmentNumber));
      DEBUG ((DEBUG_INFO, " Register=0x%016lx", Drhd->RegisterBaseAddress));
      if ((Drhd->Flags & EFI_ACPI_DMAR_DRHD_FLAGS_INCLUDE_PCI_ALL) != 0) {
        DEBUG ((DEBUG_INFO, " (INCLUDE_PCI_ALL)"));
      } else {
        DumpDmarDeviceScope ((EFI_ACPI_DMAR_DEVICE_SCOPE_STRUCTURE_HEADER *)(Drhd + 1), Drhd->Header.Length - sizeof(EFI_ACPI_DMAR_DRHD_HEADER));
      }
      DEBUG ((DEBUG_INFO, "\n"));
      break;
    case EFI_ACPI_DMAR_TYPE_RMRR:
      Rmrr = (EFI_ACPI_DMAR_RMRR_HEADER *)DmarStructHeader;
      DEBUG ((DEBUG_INFO, "         "));
      DEBUG ((DEBUG_INFO, " "));
      DEBUG ((DEBUG_INFO, ShortNameOfDmarType(DmarStructHeader->Type)));
      DEBUG ((DEBUG_INFO, ": [0x%04x]", DmarStructHeader->Type));
      DEBUG ((DEBUG_INFO, " Segment=0x%04x", Rmrr->SegmentNumber));
      DEBUG ((DEBUG_INFO, " Reserved=(0x%016lx-0x%016lx)", Rmrr->ReservedMemoryRegionBaseAddress, Rmrr->ReservedMemoryRegionLimitAddress));
      DumpDmarDeviceScope ((EFI_ACPI_DMAR_DEVICE_SCOPE_STRUCTURE_HEADER *)(Rmrr + 1), Rmrr->Header.Length - sizeof(EFI_ACPI_DMAR_RMRR_HEADER));
      DEBUG ((DEBUG_INFO, "\n"));
      break;
    case EFI_ACPI_DMAR_TYPE_ATSR:
      Atsr = (EFI_ACPI_DMAR_ATSR_HEADER *)DmarStructHeader;
      DEBUG ((DEBUG_INFO, "         "));
      DEBUG ((DEBUG_INFO, " "));
      DEBUG ((DEBUG_INFO, ShortNameOfDmarType(DmarStructHeader->Type)));
      DEBUG ((DEBUG_INFO, ": [0x%04x]", DmarStructHeader->Type));
      DEBUG ((DEBUG_INFO, " Flags=0x%02x", Atsr->Flags));
      DEBUG ((DEBUG_INFO, " Segment=0x%04x", Atsr->SegmentNumber));
      if ((Atsr->Flags & EFI_ACPI_DMAR_ATSR_FLAGS_ALL_PORTS) != 0) {
        DEBUG ((DEBUG_INFO, " (ALL_PORTS)"));
      } else {
        DumpDmarDeviceScope ((EFI_ACPI_DMAR_DEVICE_SCOPE_STRUCTURE_HEADER *)(Atsr + 1), Atsr->Header.Length - sizeof(EFI_ACPI_DMAR_ATSR_HEADER));
      }
      DEBUG ((DEBUG_INFO, "\n"));
      break;
    case EFI_ACPI_DMAR_TYPE_RHSA:
      Rhsa = (EFI_ACPI_DMAR_RHSA_HEADER *)DmarStructHeader;
      DEBUG ((DEBUG_INFO, "         "));
      DEBUG ((DEBUG_INFO, " "));
      DEBUG ((DEBUG_INFO, ShortNameOfDmarType(DmarStructHeader->Type)));
      DEBUG ((DEBUG_INFO, ": [0x%04x]", DmarStructHeader->Type));
      DEBUG ((DEBUG_INFO, " Register=0x%016lx", Rhsa->RegisterBaseAddress));
      DEBUG ((DEBUG_INFO, " ProximityDomain=0x%08x", Rhsa->ProximityDomain));
      DEBUG ((DEBUG_INFO, "\n"));
      break;
    case EFI_ACPI_DMAR_TYPE_ANDD:
      Andd = (EFI_ACPI_DMAR_ANDD_HEADER *)DmarStructHeader;
      DEBUG ((DEBUG_INFO, "         "));
      DEBUG ((DEBUG_INFO, " "));
      DEBUG ((DEBUG_INFO, ShortNameOfDmarType(DmarStructHeader->Type)));
      DEBUG ((DEBUG_INFO, ": [0x%04x]", DmarStructHeader->Type));
      DEBUG ((DEBUG_INFO, " DeviceNumber=0x%02x", Andd->AcpiDeviceNumber));
      DEBUG ((DEBUG_INFO, " ObjectName=%a", Andd + 1));
      DEBUG ((DEBUG_INFO, "\n"));
      break;
    default:
      DEBUG ((DEBUG_INFO, "         "));
      DEBUG ((DEBUG_INFO, " "));
      DEBUG ((DEBUG_INFO, ShortNameOfDmarType(DmarStructHeader->Type)));
      DEBUG ((DEBUG_INFO, "\n"));
      break;
    }
    DmarLen         -= DmarStructHeader->Length;
    DmarStructHeader = (EFI_ACPI_DMAR_STRUCTURE_HEADER *)((UINT8 *)DmarStructHeader + DmarStructHeader->Length);
  }
}

EFI_STATUS
CheckAcpiDmar (
  IN EFI_ACPI_DMAR_HEADER  *Dmar
  )
{
  EFI_ACPI_DMAR_STRUCTURE_HEADER        *DmarStructHeader;
  INTN                                  DmarLen;
  EFI_ACPI_DMAR_DRHD_HEADER             *Drhd;
    
  DmarLen  = Dmar->Header.Length - sizeof(EFI_ACPI_DMAR_HEADER);
  DmarStructHeader = (EFI_ACPI_DMAR_STRUCTURE_HEADER *)(Dmar + 1);
  while (DmarLen >= sizeof (*DmarStructHeader)) {
    switch (DmarStructHeader->Type) {
    case EFI_ACPI_DMAR_TYPE_DRHD:
      Drhd = (EFI_ACPI_DMAR_DRHD_HEADER *)DmarStructHeader;
      if (!IsMmioExit (Drhd->RegisterBaseAddress, SIZE_4KB, TRUE)) {
        DEBUG ((DEBUG_ERROR, "DMAR.DRHD resource (0x%x) is not reported correctly.\n", Drhd->RegisterBaseAddress));
        return EFI_NOT_STARTED;
      }
      break;
    default:
      break;
    }
    DmarLen         -= DmarStructHeader->Length;
    DmarStructHeader = (EFI_ACPI_DMAR_STRUCTURE_HEADER *)((UINT8 *)DmarStructHeader + DmarStructHeader->Length);
  }
  return EFI_SUCCESS;
}