/** @file
*
*  Copyright (c) 2014-2015, ARM Limited. All rights reserved.
*
*  This program and the accompanying materials
*  are licensed and made available under the terms and conditions of the BSD License
*  which accompanies this distribution.  The full text of the license may be found at
*  http://opensource.org/licenses/bsd-license.php
*
*  THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
*  WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
*
**/

#include <Uefi.h>

#include <Library/AcpiLib.h>
#include <Library/DebugLib.h>
#include <Library/UefiBootServicesTableLib.h>

#include <Protocol/AcpiTable.h>
#include <Protocol/FirmwareVolume2.h>

#include <IndustryStandard/Acpi.h>

/**
  Locate and Install the ACPI tables from the Firmware Volume if it verifies
  the function condition.

  @param  AcpiFile                Guid of the ACPI file into the Firmware Volume
  @param  CheckAcpiTableFunction  Function that checks if the ACPI table should be installed

  @return EFI_SUCCESS             The function completed successfully.
  @return EFI_NOT_FOUND           The protocol could not be located.
  @return EFI_OUT_OF_RESOURCES    There are not enough resources to find the protocol.

**/
EFI_STATUS
LocateAndInstallAcpiFromFvConditional (
  IN CONST EFI_GUID*        AcpiFile,
  IN EFI_LOCATE_ACPI_CHECK  CheckAcpiTableFunction
  )
{
  EFI_STATUS                    Status;
  EFI_ACPI_TABLE_PROTOCOL       *AcpiProtocol;
  EFI_HANDLE                    *HandleBuffer;
  UINTN                         NumberOfHandles;
  UINT32                        FvStatus;
  UINTN                         Index;
  EFI_FIRMWARE_VOLUME2_PROTOCOL *FvInstance;
  INTN                          SectionInstance;
  UINTN                         SectionSize;
  EFI_ACPI_COMMON_HEADER       *AcpiTable;
  UINTN                         AcpiTableSize;
  UINTN                         AcpiTableKey;
  BOOLEAN                       Valid;

  // Ensure the ACPI Table is present
  Status = gBS->LocateProtocol (
                  &gEfiAcpiTableProtocolGuid,
                  NULL,
                  (VOID**)&AcpiProtocol
                  );
  if (EFI_ERROR (Status)) {
    return Status;
  }

  FvStatus        = 0;
  SectionInstance = 0;

  // Locate all the Firmware Volume protocols.
  Status = gBS->LocateHandleBuffer (
                   ByProtocol,
                   &gEfiFirmwareVolume2ProtocolGuid,
                   NULL,
                   &NumberOfHandles,
                   &HandleBuffer
                   );
  if (EFI_ERROR (Status)) {
    return Status;
  }

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

    while (Status == EFI_SUCCESS) {
      // AcpiTable must be allocated by ReadSection (ie: AcpiTable == NULL)
      AcpiTable = NULL;

      // See if it has the ACPI storage file
      Status = FvInstance->ReadSection (
                        FvInstance,
                        AcpiFile,
                        EFI_SECTION_RAW,
                        SectionInstance,
                        (VOID**) &AcpiTable,
                        &SectionSize,
                        &FvStatus
                        );
      if (!EFI_ERROR (Status)) {
        AcpiTableKey = 0;
        AcpiTableSize = ((EFI_ACPI_DESCRIPTION_HEADER *) AcpiTable)->Length;
        ASSERT (SectionSize >= AcpiTableSize);

        DEBUG ((EFI_D_ERROR, "- Found '%c%c%c%c' ACPI Table\n",
            (((EFI_ACPI_DESCRIPTION_HEADER *) AcpiTable)->Signature & 0xFF),
            ((((EFI_ACPI_DESCRIPTION_HEADER *) AcpiTable)->Signature >> 8) & 0xFF),
            ((((EFI_ACPI_DESCRIPTION_HEADER *) AcpiTable)->Signature >> 16) & 0xFF),
            ((((EFI_ACPI_DESCRIPTION_HEADER *) AcpiTable)->Signature >> 24) & 0xFF)));

        // Is the ACPI table valid?
        if (CheckAcpiTableFunction) {
          Valid = CheckAcpiTableFunction ((EFI_ACPI_DESCRIPTION_HEADER *)AcpiTable);
        } else {
          Valid = TRUE;
        }

        // Install the ACPI Table
        if (Valid) {
          Status = AcpiProtocol->InstallAcpiTable (
                                 AcpiProtocol,
                                 AcpiTable,
                                 AcpiTableSize,
                                 &AcpiTableKey
                                 );
        }

        // Free memory allocated by ReadSection
        gBS->FreePool (AcpiTable);

        if (EFI_ERROR (Status)) {
          break;
        }

        // Increment the section instance
        SectionInstance++;
      }
    }
  }

FREE_HANDLE_BUFFER:
  //
  // Free any allocated buffers
  //
  gBS->FreePool (HandleBuffer);

  return EFI_SUCCESS;
}

/**
  Locate and Install the ACPI tables from the Firmware Volume

  @param  AcpiFile              Guid of the ACPI file into the Firmware Volume

  @return EFI_SUCCESS           The function completed successfully.
  @return EFI_NOT_FOUND         The protocol could not be located.
  @return EFI_OUT_OF_RESOURCES  There are not enough resources to find the protocol.

**/
EFI_STATUS
LocateAndInstallAcpiFromFv (
  IN CONST EFI_GUID* AcpiFile
  )
{
  return LocateAndInstallAcpiFromFvConditional (AcpiFile, NULL);
}
