/** @file
  This module provides help function for finding ACPI table.

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

**/

#include "UefiLibInternal.h"
#include <IndustryStandard/Acpi.h>
#include <Guid/Acpi.h>

/**
  This function scans ACPI table in XSDT/RSDT.

  @param Sdt                    ACPI XSDT/RSDT.
  @param TablePointerSize       Size of table pointer: 8(XSDT) or 4(RSDT).
  @param Signature              ACPI table signature.
  @param PreviousTable          Pointer to previous returned table to locate
                                next table, or NULL to locate first table.
  @param PreviousTableLocated   Pointer to the indicator about whether the
                                previous returned table could be located, or
                                NULL if PreviousTable is NULL.

  If PreviousTable is NULL and PreviousTableLocated is not NULL, then ASSERT().
  If PreviousTable is not NULL and PreviousTableLocated is NULL, then ASSERT().

  @return ACPI table or NULL if not found.

**/
EFI_ACPI_COMMON_HEADER *
ScanTableInSDT (
  IN  EFI_ACPI_DESCRIPTION_HEADER  *Sdt,
  IN  UINTN                        TablePointerSize,
  IN  UINT32                       Signature,
  IN  EFI_ACPI_COMMON_HEADER       *PreviousTable  OPTIONAL,
  OUT BOOLEAN                      *PreviousTableLocated OPTIONAL
  )
{
  UINTN                   Index;
  UINTN                   EntryCount;
  UINT64                  EntryPtr;
  UINTN                   BasePtr;
  EFI_ACPI_COMMON_HEADER  *Table;

  if (PreviousTableLocated != NULL) {
    ASSERT (PreviousTable != NULL);
    *PreviousTableLocated = FALSE;
  } else {
    ASSERT (PreviousTable == NULL);
  }

  if (Sdt == NULL) {
    return NULL;
  }

  EntryCount = (Sdt->Length - sizeof (EFI_ACPI_DESCRIPTION_HEADER)) / TablePointerSize;

  BasePtr = (UINTN)(Sdt + 1);
  for (Index = 0; Index < EntryCount; Index++) {
    EntryPtr = 0;
    CopyMem (&EntryPtr, (VOID *)(BasePtr + Index * TablePointerSize), TablePointerSize);
    Table = (EFI_ACPI_COMMON_HEADER *)((UINTN)(EntryPtr));
    if ((Table != NULL) && (Table->Signature == Signature)) {
      if (PreviousTable != NULL) {
        if (Table == PreviousTable) {
          *PreviousTableLocated = TRUE;
        } else if (*PreviousTableLocated) {
          //
          // Return next table.
          //
          return Table;
        }
      } else {
        //
        // Return first table.
        //
        return Table;
      }
    }
  }

  return NULL;
}

/**
  To locate FACS in FADT.

  @param Fadt   FADT table pointer.

  @return FACS table pointer or NULL if not found.

**/
EFI_ACPI_COMMON_HEADER *
LocateAcpiFacsFromFadt (
  IN EFI_ACPI_2_0_FIXED_ACPI_DESCRIPTION_TABLE  *Fadt
  )
{
  EFI_ACPI_COMMON_HEADER  *Facs;
  UINT64                  Data64;

  if (Fadt == NULL) {
    return NULL;
  }

  if (Fadt->Header.Revision < EFI_ACPI_2_0_FIXED_ACPI_DESCRIPTION_TABLE_REVISION) {
    Facs = (EFI_ACPI_COMMON_HEADER *)(UINTN)Fadt->FirmwareCtrl;
  } else {
    CopyMem (&Data64, &Fadt->XFirmwareCtrl, sizeof (UINT64));
    if (Data64 != 0) {
      Facs = (EFI_ACPI_COMMON_HEADER *)(UINTN)Data64;
    } else {
      Facs = (EFI_ACPI_COMMON_HEADER *)(UINTN)Fadt->FirmwareCtrl;
    }
  }

  return Facs;
}

/**
  To locate DSDT in FADT.

  @param Fadt   FADT table pointer.

  @return DSDT table pointer or NULL if not found.

**/
EFI_ACPI_COMMON_HEADER *
LocateAcpiDsdtFromFadt (
  IN EFI_ACPI_2_0_FIXED_ACPI_DESCRIPTION_TABLE  *Fadt
  )
{
  EFI_ACPI_COMMON_HEADER  *Dsdt;
  UINT64                  Data64;

  if (Fadt == NULL) {
    return NULL;
  }

  if (Fadt->Header.Revision < EFI_ACPI_2_0_FIXED_ACPI_DESCRIPTION_TABLE_REVISION) {
    Dsdt = (EFI_ACPI_COMMON_HEADER *)(UINTN)Fadt->Dsdt;
  } else {
    CopyMem (&Data64, &Fadt->XDsdt, sizeof (UINT64));
    if (Data64 != 0) {
      Dsdt = (EFI_ACPI_COMMON_HEADER *)(UINTN)Data64;
    } else {
      Dsdt = (EFI_ACPI_COMMON_HEADER *)(UINTN)Fadt->Dsdt;
    }
  }

  return Dsdt;
}

/**
  To locate ACPI table in ACPI ConfigurationTable.

  @param AcpiGuid               The GUID used to get ACPI ConfigurationTable.
  @param Signature              ACPI table signature.
  @param PreviousTable          Pointer to previous returned table to locate
                                next table, or NULL to locate first table.
  @param PreviousTableLocated   Pointer to the indicator to return whether the
                                previous returned table could be located or not,
                                or NULL if PreviousTable is NULL.

  If PreviousTable is NULL and PreviousTableLocated is not NULL, then ASSERT().
  If PreviousTable is not NULL and PreviousTableLocated is NULL, then ASSERT().
  If AcpiGuid is NULL, then ASSERT().

  @return ACPI table or NULL if not found.

**/
EFI_ACPI_COMMON_HEADER *
LocateAcpiTableInAcpiConfigurationTable (
  IN  EFI_GUID                *AcpiGuid,
  IN  UINT32                  Signature,
  IN  EFI_ACPI_COMMON_HEADER  *PreviousTable  OPTIONAL,
  OUT BOOLEAN                 *PreviousTableLocated OPTIONAL
  )
{
  EFI_STATUS                                    Status;
  EFI_ACPI_COMMON_HEADER                        *Table;
  EFI_ACPI_2_0_ROOT_SYSTEM_DESCRIPTION_POINTER  *Rsdp;
  EFI_ACPI_DESCRIPTION_HEADER                   *Rsdt;
  EFI_ACPI_DESCRIPTION_HEADER                   *Xsdt;
  EFI_ACPI_2_0_FIXED_ACPI_DESCRIPTION_TABLE     *Fadt;

  if (PreviousTableLocated != NULL) {
    ASSERT (PreviousTable != NULL);
    *PreviousTableLocated = FALSE;
  } else {
    ASSERT (PreviousTable == NULL);
  }

  Rsdp = NULL;
  //
  // Get ACPI ConfigurationTable (RSD_PTR)
  //
  Status = EfiGetSystemConfigurationTable (AcpiGuid, (VOID **)&Rsdp);
  if (EFI_ERROR (Status) || (Rsdp == NULL)) {
    return NULL;
  }

  Table = NULL;

  //
  // Search XSDT
  //
  if (Rsdp->Revision >= EFI_ACPI_2_0_ROOT_SYSTEM_DESCRIPTION_POINTER_REVISION) {
    Xsdt = (EFI_ACPI_DESCRIPTION_HEADER *)(UINTN)Rsdp->XsdtAddress;
    if (Signature == EFI_ACPI_2_0_DIFFERENTIATED_SYSTEM_DESCRIPTION_TABLE_SIGNATURE) {
      ASSERT (PreviousTable == NULL);
      //
      // It is to locate DSDT,
      // need to locate FADT first.
      //
      Fadt = (EFI_ACPI_2_0_FIXED_ACPI_DESCRIPTION_TABLE *)ScanTableInSDT (
                                                            Xsdt,
                                                            sizeof (UINT64),
                                                            EFI_ACPI_2_0_FIXED_ACPI_DESCRIPTION_TABLE_SIGNATURE,
                                                            NULL,
                                                            NULL
                                                            );
      Table = LocateAcpiDsdtFromFadt (Fadt);
    } else if (Signature == EFI_ACPI_2_0_FIRMWARE_ACPI_CONTROL_STRUCTURE_SIGNATURE) {
      ASSERT (PreviousTable == NULL);
      //
      // It is to locate FACS,
      // need to locate FADT first.
      //
      Fadt = (EFI_ACPI_2_0_FIXED_ACPI_DESCRIPTION_TABLE *)ScanTableInSDT (
                                                            Xsdt,
                                                            sizeof (UINT64),
                                                            EFI_ACPI_2_0_FIXED_ACPI_DESCRIPTION_TABLE_SIGNATURE,
                                                            NULL,
                                                            NULL
                                                            );
      Table = LocateAcpiFacsFromFadt (Fadt);
    } else {
      Table = ScanTableInSDT (
                Xsdt,
                sizeof (UINT64),
                Signature,
                PreviousTable,
                PreviousTableLocated
                );
    }
  }

  if (Table != NULL) {
    return Table;
  } else if ((PreviousTableLocated != NULL) &&
             *PreviousTableLocated)
  {
    //
    // PreviousTable could be located in XSDT,
    // but next table could not be located in XSDT.
    //
    return NULL;
  }

  //
  // Search RSDT
  //
  Rsdt = (EFI_ACPI_DESCRIPTION_HEADER *)(UINTN)Rsdp->RsdtAddress;
  if (Signature == EFI_ACPI_2_0_DIFFERENTIATED_SYSTEM_DESCRIPTION_TABLE_SIGNATURE) {
    ASSERT (PreviousTable == NULL);
    //
    // It is to locate DSDT,
    // need to locate FADT first.
    //
    Fadt = (EFI_ACPI_2_0_FIXED_ACPI_DESCRIPTION_TABLE *)ScanTableInSDT (
                                                          Rsdt,
                                                          sizeof (UINT32),
                                                          EFI_ACPI_2_0_FIXED_ACPI_DESCRIPTION_TABLE_SIGNATURE,
                                                          NULL,
                                                          NULL
                                                          );
    Table = LocateAcpiDsdtFromFadt (Fadt);
  } else if (Signature == EFI_ACPI_2_0_FIRMWARE_ACPI_CONTROL_STRUCTURE_SIGNATURE) {
    ASSERT (PreviousTable == NULL);
    //
    // It is to locate FACS,
    // need to locate FADT first.
    //
    Fadt = (EFI_ACPI_2_0_FIXED_ACPI_DESCRIPTION_TABLE *)ScanTableInSDT (
                                                          Rsdt,
                                                          sizeof (UINT32),
                                                          EFI_ACPI_2_0_FIXED_ACPI_DESCRIPTION_TABLE_SIGNATURE,
                                                          NULL,
                                                          NULL
                                                          );
    Table = LocateAcpiFacsFromFadt (Fadt);
  } else {
    Table = ScanTableInSDT (
              Rsdt,
              sizeof (UINT32),
              Signature,
              PreviousTable,
              PreviousTableLocated
              );
  }

  return Table;
}

/**
  This function locates next ACPI table in XSDT/RSDT based on Signature and
  previous returned Table.

  If PreviousTable is NULL:
  This function will locate the first ACPI table in XSDT/RSDT based on
  Signature in gEfiAcpi20TableGuid system configuration table first, and then
  gEfiAcpi10TableGuid system configuration table.
  This function will locate in XSDT first, and then RSDT.
  For DSDT, this function will locate XDsdt in FADT first, and then Dsdt in
  FADT.
  For FACS, this function will locate XFirmwareCtrl in FADT first, and then
  FirmwareCtrl in FADT.

  If PreviousTable is not NULL:
  1. If it could be located in XSDT in gEfiAcpi20TableGuid system configuration
     table, then this function will just locate next table in XSDT in
     gEfiAcpi20TableGuid system configuration table.
  2. If it could be located in RSDT in gEfiAcpi20TableGuid system configuration
     table, then this function will just locate next table in RSDT in
     gEfiAcpi20TableGuid system configuration table.
  3. If it could be located in RSDT in gEfiAcpi10TableGuid system configuration
     table, then this function will just locate next table in RSDT in
     gEfiAcpi10TableGuid system configuration table.

  It's not supported that PreviousTable is not NULL but PreviousTable->Signature
  is not same with Signature, NULL will be returned.

  @param Signature          ACPI table signature.
  @param PreviousTable      Pointer to previous returned table to locate next
                            table, or NULL to locate first table.

  @return Next ACPI table or NULL if not found.

**/
EFI_ACPI_COMMON_HEADER *
EFIAPI
EfiLocateNextAcpiTable (
  IN UINT32                  Signature,
  IN EFI_ACPI_COMMON_HEADER  *PreviousTable OPTIONAL
  )
{
  EFI_ACPI_COMMON_HEADER  *Table;
  BOOLEAN                 TempPreviousTableLocated;
  BOOLEAN                 *PreviousTableLocated;

  if (PreviousTable != NULL) {
    if (PreviousTable->Signature != Signature) {
      //
      // PreviousTable->Signature is not same with Signature.
      //
      return NULL;
    } else if ((Signature == EFI_ACPI_2_0_FIXED_ACPI_DESCRIPTION_TABLE_SIGNATURE) ||
               (Signature == EFI_ACPI_2_0_DIFFERENTIATED_SYSTEM_DESCRIPTION_TABLE_SIGNATURE) ||
               (Signature == EFI_ACPI_2_0_FIRMWARE_ACPI_CONTROL_STRUCTURE_SIGNATURE))
    {
      //
      // There is only one FADT/DSDT/FACS table,
      // so don't try to locate next one.
      //
      return NULL;
    }

    PreviousTableLocated  = &TempPreviousTableLocated;
    *PreviousTableLocated = FALSE;
  } else {
    PreviousTableLocated = NULL;
  }

  Table = LocateAcpiTableInAcpiConfigurationTable (
            &gEfiAcpi20TableGuid,
            Signature,
            PreviousTable,
            PreviousTableLocated
            );
  if (Table != NULL) {
    return Table;
  } else if ((PreviousTableLocated != NULL) &&
             *PreviousTableLocated)
  {
    //
    // PreviousTable could be located in gEfiAcpi20TableGuid system
    // configuration table, but next table could not be located in
    // gEfiAcpi20TableGuid system configuration table.
    //
    return NULL;
  }

  return LocateAcpiTableInAcpiConfigurationTable (
           &gEfiAcpi10TableGuid,
           Signature,
           PreviousTable,
           PreviousTableLocated
           );
}

/**
  This function locates first ACPI table in XSDT/RSDT based on Signature.

  This function will locate the first ACPI table in XSDT/RSDT based on
  Signature in gEfiAcpi20TableGuid system configuration table first, and then
  gEfiAcpi10TableGuid system configuration table.
  This function will locate in XSDT first, and then RSDT.
  For DSDT, this function will locate XDsdt in FADT first, and then Dsdt in
  FADT.
  For FACS, this function will locate XFirmwareCtrl in FADT first, and then
  FirmwareCtrl in FADT.

  @param Signature          ACPI table signature.

  @return First ACPI table or NULL if not found.

**/
EFI_ACPI_COMMON_HEADER *
EFIAPI
EfiLocateFirstAcpiTable (
  IN UINT32  Signature
  )
{
  return EfiLocateNextAcpiTable (Signature, NULL);
}