/** @file
  HMAT table parser

  Copyright (c) 2020, Arm Limited.
  SPDX-License-Identifier: BSD-2-Clause-Patent

  @par Reference(s):
    - ACPI 6.3 Specification - January 2019

  @par Glossary:
    - MPDA  - Memory Proximity Domain Attributes
    - SLLBI - System Locality Latency and Bandwidth Information
    - MSCI  - Memory Side Cache Information
    - Dom   - Domain
**/

#include <Library/PrintLib.h>
#include <Library/BaseLib.h>
#include <Library/UefiLib.h>
#include "AcpiParser.h"
#include "AcpiView.h"

// Maximum Memory Domain matrix print size.
#define MAX_MEMORY_DOMAIN_TARGET_PRINT_MATRIX  10

// Local variables
STATIC CONST UINT16  *HmatStructureType;
STATIC CONST UINT32  *HmatStructureLength;

STATIC CONST UINT32  *NumberInitiatorProximityDomain;
STATIC CONST UINT32  *NumberTargetProximityDomain;
STATIC CONST
EFI_ACPI_6_4_HMAT_STRUCTURE_SYSTEM_LOCALITY_LATENCY_AND_BANDWIDTH_INFO_FLAGS *
  SllbiFlags;

STATIC CONST UINT8   *SllbiDataType;
STATIC CONST UINT16  *NumberSMBIOSHandles;

STATIC ACPI_DESCRIPTION_HEADER_INFO  AcpiHdrInfo;

/**
  Names of System Locality Latency Bandwidth Information (SLLBI) data types
**/
STATIC CONST CHAR16  *SllbiNames[] = {
  L"Access %sLatency%s",
  L"Read %sLatency%s",
  L"Write %sLatency%s",
  L"Access %sBandwidth%s",
  L"Read %sBandwidth%s",
  L"Write %sBandwidth%s"
};

/**
  This function validates the Cache Attributes field.

  @param [in] Ptr     Pointer to the start of the field data.
  @param [in] Context Pointer to context specific information e.g. this
                      could be a pointer to the ACPI table header.
**/
STATIC
VOID
EFIAPI
ValidateCacheAttributes (
  IN UINT8  *Ptr,
  IN VOID   *Context
  )
{
  EFI_ACPI_6_4_HMAT_STRUCTURE_MEMORY_SIDE_CACHE_INFO_CACHE_ATTRIBUTES *
    Attributes;

  Attributes =
    (EFI_ACPI_6_4_HMAT_STRUCTURE_MEMORY_SIDE_CACHE_INFO_CACHE_ATTRIBUTES *)Ptr;

  if (Attributes->TotalCacheLevels > 0x3) {
    IncrementErrorCount ();
    Print (
      L"\nERROR: Attributes bits [3:0] have invalid value: 0x%x",
      Attributes->TotalCacheLevels
      );
  }

  if (Attributes->CacheLevel > 0x3) {
    IncrementErrorCount ();
    Print (
      L"\nERROR: Attributes bits [7:4] have invalid value: 0x%x",
      Attributes->CacheLevel
      );
  }

  if (Attributes->CacheAssociativity > 0x2) {
    IncrementErrorCount ();
    Print (
      L"\nERROR: Attributes bits [11:8] have invalid value: 0x%x",
      Attributes->CacheAssociativity
      );
  }

  if (Attributes->WritePolicy > 0x2) {
    IncrementErrorCount ();
    Print (
      L"\nERROR: Attributes bits [15:12] have invalid value: 0x%x",
      Attributes->WritePolicy
      );
  }
}

/**
  Dumps the cache attributes field

  @param [in] Format  Optional format string for tracing the data.
  @param [in] Ptr     Pointer to the start of the buffer.
**/
STATIC
VOID
EFIAPI
DumpCacheAttributes (
  IN CONST CHAR16  *Format OPTIONAL,
  IN UINT8         *Ptr
  )
{
  EFI_ACPI_6_4_HMAT_STRUCTURE_MEMORY_SIDE_CACHE_INFO_CACHE_ATTRIBUTES *
    Attributes;

  Attributes =
    (EFI_ACPI_6_4_HMAT_STRUCTURE_MEMORY_SIDE_CACHE_INFO_CACHE_ATTRIBUTES *)Ptr;

  Print (L"\n");
  PrintFieldName (4, L"Total Cache Levels");
  Print (L"%d\n", Attributes->TotalCacheLevels);
  PrintFieldName (4, L"Cache Level");
  Print (L"%d\n", Attributes->CacheLevel);
  PrintFieldName (4, L"Cache Associativity");
  Print (L"%d\n", Attributes->CacheAssociativity);
  PrintFieldName (4, L"Write Policy");
  Print (L"%d\n", Attributes->WritePolicy);
  PrintFieldName (4, L"Cache Line Size");
  Print (L"%d\n", Attributes->CacheLineSize);
}

/**
  An ACPI_PARSER array describing the ACPI HMAT Table.
*/
STATIC CONST ACPI_PARSER  HmatParser[] = {
  PARSE_ACPI_HEADER (&AcpiHdrInfo),
  { L"Reserved",                   4,36, NULL, NULL, NULL, NULL, NULL }
};

/**
  An ACPI_PARSER array describing the HMAT structure header.
*/
STATIC CONST ACPI_PARSER  HmatStructureHeaderParser[] = {
  { L"Type",     2, 0, NULL, NULL, (VOID **)&HmatStructureType,   NULL, NULL },
  { L"Reserved", 2, 2, NULL, NULL, NULL,                          NULL, NULL },
  { L"Length",   4, 4, NULL, NULL, (VOID **)&HmatStructureLength, NULL, NULL }
};

/**
  An ACPI PARSER array describing the Memory Proximity Domain Attributes
  Structure - Type 0.
*/
STATIC CONST ACPI_PARSER  MemProximityDomainAttributeParser[] = {
  { L"Type",                        2, 0,  L"0x%x",  NULL, NULL, NULL, NULL },
  { L"Reserved",                    2, 2,  L"0x%x",  NULL, NULL, NULL, NULL },
  { L"Length",                      4, 4,  L"%d",    NULL, NULL, NULL, NULL },
  { L"Flags",                       2, 8,  L"0x%x",  NULL, NULL, NULL, NULL },
  { L"Reserved",                    2, 10, L"0x%x",  NULL, NULL, NULL, NULL },
  { L"Proximity Dom for initiator", 4, 12, L"0x%x",  NULL, NULL, NULL, NULL },
  { L"Proximity Dom for memory",    4, 16, L"0x%x",  NULL, NULL, NULL, NULL },
  { L"Reserved",                    4, 20, L"0x%x",  NULL, NULL, NULL, NULL },
  { L"Reserved",                    8, 24, L"0x%lx", NULL, NULL, NULL, NULL },
  { L"Reserved",                    8, 32, L"0x%lx", NULL, NULL, NULL, NULL }
};

/**
  An ACPI PARSER array describing the System Locality Latency and Bandwidth
  Information Structure - Type 1.
*/
STATIC CONST ACPI_PARSER  SllbiParser[] = {
  { L"Type",                          2, 0,  L"0x%x",  NULL, NULL,                    NULL, NULL },
  { L"Reserved",                      2, 2,  L"0x%x",  NULL, NULL,                    NULL, NULL },
  { L"Length",                        4, 4,  L"%d",    NULL, NULL,                    NULL, NULL },
  { L"Flags",                         1, 8,  L"0x%x",  NULL, (VOID **)&SllbiFlags,    NULL, NULL },
  { L"Data type",                     1, 9,  L"0x%x",  NULL, (VOID **)&SllbiDataType, NULL, NULL },
  { L"Min Transfer Size",             1, 10, L"%d",    NULL, NULL,                    NULL, NULL },
  { L"Reserved",                      1, 11, L"0x%x",  NULL, NULL,                    NULL, NULL },
  { L"Initiator Proximity Dom Count", 4, 12, L"%d",    NULL,
    (VOID **)&NumberInitiatorProximityDomain, NULL, NULL },
  { L"Target Proximity Dom Count",    4, 16, L"%d",    NULL,
    (VOID **)&NumberTargetProximityDomain, NULL, NULL },
  { L"Reserved",                      4, 20, L"0x%x",  NULL, NULL,                    NULL, NULL },
  { L"Entry Base Unit",               8, 24, L"0x%lx", NULL, NULL,                    NULL, NULL }
  // initiator Proximity Domain list ...
  // target Proximity Domain list ...
  // Latency/Bandwidth matrix ...
};

/**
  An ACPI PARSER array describing the Memory Side Cache Information
  Structure - Type 2.
*/
STATIC CONST ACPI_PARSER  MemSideCacheInfoParser[] = {
  { L"Type",                     2, 0,  L"0x%x",  NULL,                NULL, NULL, NULL },
  { L"Reserved",                 2, 2,  L"0x%x",  NULL,                NULL, NULL, NULL },
  { L"Length",                   4, 4,  L"%d",    NULL,                NULL, NULL, NULL },
  { L"Proximity Dom for memory", 4, 8,  L"0x%x",  NULL,                NULL, NULL, NULL },
  { L"Reserved",                 4, 12, L"0x%x",  NULL,                NULL, NULL, NULL },
  { L"Memory Side Cache Size",   8, 16, L"0x%lx", NULL,                NULL, NULL, NULL },
  { L"Cache Attributes",         4, 24, NULL,     DumpCacheAttributes, NULL,
    ValidateCacheAttributes, NULL },
  { L"Reserved",                 2, 28, L"0x%x",  NULL,                NULL, NULL, NULL },
  { L"SMBIOS Handle Count",      2, 30, L"%d",    NULL,
    (VOID **)&NumberSMBIOSHandles, NULL, NULL }
  // SMBIOS handles List ...
};

/**
  This function parses the Memory Proximity Domain Attributes
  Structure (Type 0).

  @param [in] Ptr     Pointer to the start of the Memory Proximity Domain
                      Attributes Structure data.
  @param [in] Length  Length of the Memory Proximity Domain Attributes
                      Structure.
**/
STATIC
VOID
DumpMpda (
  IN UINT8   *Ptr,
  IN UINT32  Length
  )
{
  ParseAcpi (
    TRUE,
    2,
    "Memory Proximity Domain Attributes Structure",
    Ptr,
    Length,
    PARSER_PARAMS (MemProximityDomainAttributeParser)
    );
}

/**
  This function parses the System Locality Latency and Bandwidth Information
  Structure (Type 1).

  @param [in] Ptr     Pointer to the start of the System Locality Latency and
                      Bandwidth Information Structure data.
  @param [in] Length  Length of the System Locality Latency and Bandwidth
                      Information Structure.
**/
STATIC
VOID
DumpSllbi (
  IN UINT8   *Ptr,
  IN UINT32  Length
  )
{
  CONST UINT32  *InitiatorProximityDomainList;
  CONST UINT32  *TargetProximityDomainList;
  CONST UINT16  *LatencyBandwidthMatrix;
  UINT32        Offset;
  CHAR16        Buffer[OUTPUT_FIELD_COLUMN_WIDTH];
  CHAR16        SecondBuffer[OUTPUT_FIELD_COLUMN_WIDTH];
  UINT32        RequiredTableSize;
  UINT32        Index;
  UINT32        IndexInitiator;
  UINT32        IndexTarget;
  UINT32        TargetStartOffset;

  Offset = ParseAcpi (
             TRUE,
             2,
             "System Locality Latency and Bandwidth Information Structure",
             Ptr,
             Length,
             PARSER_PARAMS (SllbiParser)
             );

  // Check if the values used to control the parsing logic have been
  // successfully read.
  if ((SllbiFlags == NULL)                     ||
      (SllbiDataType == NULL)                  ||
      (NumberInitiatorProximityDomain == NULL) ||
      (NumberTargetProximityDomain == NULL))
  {
    IncrementErrorCount ();
    Print (
      L"ERROR: Insufficient remaining table buffer length to read the " \
      L"SLLBI structure header. Length = %d.\n",
      Length
      );
    return;
  }

  RequiredTableSize = (*NumberInitiatorProximityDomain * sizeof (UINT32)) +
                      (*NumberTargetProximityDomain * sizeof (UINT32)) +
                      (*NumberInitiatorProximityDomain *
                       *NumberTargetProximityDomain * sizeof (UINT16)) +
                      Offset;

  if (RequiredTableSize > Length) {
    IncrementErrorCount ();
    Print (
      L"ERROR: Insufficient System Locality Latency and Bandwidth" \
      L"Information Structure length. TableLength = %d. " \
      L"RequiredTableLength = %d.\n",
      Length,
      RequiredTableSize
      );
    return;
  }

  InitiatorProximityDomainList = (UINT32 *)(Ptr + Offset);
  TargetProximityDomainList    = InitiatorProximityDomainList +
                                 *NumberInitiatorProximityDomain;
  LatencyBandwidthMatrix = (UINT16 *)(TargetProximityDomainList +
                                      *NumberTargetProximityDomain);

  // Display each element of the Initiator Proximity Domain list
  for (Index = 0; Index < *NumberInitiatorProximityDomain; Index++) {
    UnicodeSPrint (
      Buffer,
      sizeof (Buffer),
      L"Initiator Proximity Dom [%d]",
      Index
      );

    PrintFieldName (4, Buffer);
    Print (
      L"0x%x\n",
      InitiatorProximityDomainList[Index]
      );
  }

  // Display each element of the Target Proximity Domain list
  for (Index = 0; Index < *NumberTargetProximityDomain; Index++) {
    UnicodeSPrint (
      Buffer,
      sizeof (Buffer),
      L"Target Proximity Dom [%d]",
      Index
      );

    PrintFieldName (4, Buffer);
    Print (
      L"0x%x\n",
      TargetProximityDomainList[Index]
      );
  }

  // Create base name depending on Data Type in this Structure
  if (*SllbiDataType >= ARRAY_SIZE (SllbiNames)) {
    IncrementErrorCount ();
    Print (L"Error: Unkown Data Type. DataType = 0x%x.\n", *SllbiDataType);
    return;
  }

  StrCpyS (Buffer, sizeof (Buffer), SllbiNames[*SllbiDataType]);

  // Adjust base name depending on Memory Hierarchy in this Structure
  switch (SllbiFlags->MemoryHierarchy) {
    case 0:
      UnicodeSPrint (
        SecondBuffer,
        sizeof (SecondBuffer),
        Buffer,
        L"",
        L"%s"
        );
      break;
    case 1:
    case 2:
    case 3:
      UnicodeSPrint (
        SecondBuffer,
        sizeof (SecondBuffer),
        Buffer,
        L"Hit ",
        L"%s"
        );
      break;
    default:
      IncrementErrorCount ();
      Print (
        L"Error: Invalid Memory Hierarchy. MemoryHierarchy = %d.\n",
        SllbiFlags->MemoryHierarchy
        );
      return;
  } // switch

  if (*NumberTargetProximityDomain <= MAX_MEMORY_DOMAIN_TARGET_PRINT_MATRIX) {
    // Display the latency/bandwidth matrix as a matrix
    UnicodeSPrint (
      Buffer,
      sizeof (Buffer),
      SecondBuffer,
      L""
      );
    PrintFieldName (4, Buffer);

    Print (L"\n      Target    : X-axis (Horizontal)");
    Print (L"\n      Initiator : Y-axis (Vertical)");
    Print (L"\n         |");

    for (IndexTarget = 0;
         IndexTarget < *NumberTargetProximityDomain;
         IndexTarget++)
    {
      Print (L"    %2d", IndexTarget);
    }

    Print (L"\n      ---+");
    for (IndexTarget = 0;
         IndexTarget < *NumberTargetProximityDomain;
         IndexTarget++)
    {
      Print (L"------");
    }

    Print (L"\n");

    TargetStartOffset = 0;
    for (IndexInitiator = 0;
         IndexInitiator < *NumberInitiatorProximityDomain;
         IndexInitiator++)
    {
      Print (L"      %2d |", IndexInitiator);
      for (IndexTarget = 0;
           IndexTarget < *NumberTargetProximityDomain;
           IndexTarget++)
      {
        Print (
          L" %5d",
          LatencyBandwidthMatrix[TargetStartOffset + IndexTarget]
          );
      } // for Target

      Print (L"\n");
      TargetStartOffset += (*NumberTargetProximityDomain);
    } // for Initiator

    Print (L"\n");
  } else {
    // Display the latency/bandwidth matrix as a list
    UnicodeSPrint (
      Buffer,
      sizeof (Buffer),
      SecondBuffer,
      L" [%d][%d]"
      );

    TargetStartOffset = 0;
    for (IndexInitiator = 0;
         IndexInitiator < *NumberInitiatorProximityDomain;
         IndexInitiator++)
    {
      for (IndexTarget = 0;
           IndexTarget < *NumberTargetProximityDomain;
           IndexTarget++)
      {
        UnicodeSPrint (
          SecondBuffer,
          sizeof (SecondBuffer),
          Buffer,
          IndexInitiator,
          IndexTarget
          );

        PrintFieldName (4, SecondBuffer);
        Print (
          L"%d\n",
          LatencyBandwidthMatrix[TargetStartOffset + IndexTarget]
          );
      } // for Target

      TargetStartOffset += (*NumberTargetProximityDomain);
    } // for Initiator
  }
}

/**
  This function parses the Memory Side Cache Information Structure (Type 2).

  @param [in] Ptr     Pointer to the start of the Memory Side Cache Information
                      Structure data.
  @param [in] Length  Length of the Memory Side Cache Information Structure.
**/
STATIC
VOID
DumpMsci (
  IN UINT8   *Ptr,
  IN UINT32  Length
  )
{
  CONST UINT16  *SMBIOSHandlesList;
  CHAR16        Buffer[OUTPUT_FIELD_COLUMN_WIDTH];
  UINT32        Offset;
  UINT16        Index;

  Offset = ParseAcpi (
             TRUE,
             2,
             "Memory Side Cache Information Structure",
             Ptr,
             Length,
             PARSER_PARAMS (MemSideCacheInfoParser)
             );

  // Check if the values used to control the parsing logic have been
  // successfully read.
  if (NumberSMBIOSHandles == NULL) {
    IncrementErrorCount ();
    Print (
      L"ERROR: Insufficient remaining table buffer length to read the " \
      L"MSCI structure header. Length = %d.\n",
      Length
      );
    return;
  }

  if ((*NumberSMBIOSHandles * sizeof (UINT16)) > (Length - Offset)) {
    IncrementErrorCount ();
    Print (
      L"ERROR: Invalid Number of SMBIOS Handles. SMBIOSHandlesCount = %d." \
      L"RemainingBufferLength = %d.\n",
      *NumberSMBIOSHandles,
      Length - Offset
      );
    return;
  }

  SMBIOSHandlesList = (UINT16 *)(Ptr + Offset);

  for (Index = 0; Index < *NumberSMBIOSHandles; Index++) {
    UnicodeSPrint (
      Buffer,
      sizeof (Buffer),
      L"SMBIOS Handles [%d]",
      Index
      );

    PrintFieldName (4, Buffer);
    Print (
      L"0x%x\n",
      SMBIOSHandlesList[Index]
      );
  }
}

/**
  This function parses the ACPI HMAT table.
  When trace is enabled this function parses the HMAT table and
  traces the ACPI table fields.

  This function parses the following HMAT structures:
    - Memory Proximity Domain Attributes Structure (Type 0)
    - System Locality Latency and Bandwidth Info Structure (Type 1)
    - Memory Side Cache Info structure (Type 2)

  This function also performs validation of the ACPI table fields.

  @param [in] Trace              If TRUE, trace the ACPI fields.
  @param [in] Ptr                Pointer to the start of the buffer.
  @param [in] AcpiTableLength    Length of the ACPI table.
  @param [in] AcpiTableRevision  Revision of the ACPI table.
**/
VOID
EFIAPI
ParseAcpiHmat (
  IN BOOLEAN  Trace,
  IN UINT8    *Ptr,
  IN UINT32   AcpiTableLength,
  IN UINT8    AcpiTableRevision
  )
{
  UINT32  Offset;
  UINT8   *HmatStructurePtr;

  if (!Trace) {
    return;
  }

  Offset = ParseAcpi (
             Trace,
             0,
             "HMAT",
             Ptr,
             AcpiTableLength,
             PARSER_PARAMS (HmatParser)
             );

  HmatStructurePtr = Ptr + Offset;

  while (Offset < AcpiTableLength) {
    // Parse HMAT Structure Header to obtain Type and Length.
    ParseAcpi (
      FALSE,
      0,
      NULL,
      HmatStructurePtr,
      AcpiTableLength - Offset,
      PARSER_PARAMS (HmatStructureHeaderParser)
      );

    // Check if the values used to control the parsing logic have been
    // successfully read.
    if ((HmatStructureType == NULL) ||
        (HmatStructureLength == NULL))
    {
      IncrementErrorCount ();
      Print (
        L"ERROR: Insufficient remaining table buffer length to read the " \
        L"HMAT structure header. Length = %d.\n",
        AcpiTableLength - Offset
        );
      return;
    }

    // Validate HMAT Structure length.
    if ((*HmatStructureLength == 0) ||
        ((Offset + (*HmatStructureLength)) > AcpiTableLength))
    {
      IncrementErrorCount ();
      Print (
        L"ERROR: Invalid HMAT Structure length. " \
        L"Length = %d. Offset = %d. AcpiTableLength = %d.\n",
        *HmatStructureLength,
        Offset,
        AcpiTableLength
        );
      return;
    }

    switch (*HmatStructureType) {
      case EFI_ACPI_6_4_HMAT_TYPE_MEMORY_PROXIMITY_DOMAIN_ATTRIBUTES:
        DumpMpda (
          HmatStructurePtr,
          *HmatStructureLength
          );
        break;
      case EFI_ACPI_6_4_HMAT_TYPE_SYSTEM_LOCALITY_LATENCY_AND_BANDWIDTH_INFO:
        DumpSllbi (
          HmatStructurePtr,
          *HmatStructureLength
          );
        break;
      case EFI_ACPI_6_4_HMAT_TYPE_MEMORY_SIDE_CACHE_INFO:
        DumpMsci (
          HmatStructurePtr,
          *HmatStructureLength
          );
        break;
      default:
        IncrementErrorCount ();
        Print (
          L"ERROR: Unknown HMAT structure:"
          L" Type = %d, Length = %d\n",
          *HmatStructureType,
          *HmatStructureLength
          );
        break;
    } // switch

    HmatStructurePtr += *HmatStructureLength;
    Offset           += *HmatStructureLength;
  } // while
}
