/** @file
  BMC ACPI.

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

**/

//
// Statements that include other header files
//
#include <PiDxe.h>

#include <IndustryStandard/Acpi.h>
#include <Protocol/AcpiSystemDescriptionTable.h>
#include <Protocol/FirmwareVolume2.h>
#include <Protocol/AcpiTable.h>

#include <Library/BaseLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/UefiRuntimeServicesTableLib.h>
#include <Library/DebugLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/UefiLib.h>

#ifndef EFI_ACPI_CREATOR_ID
#define EFI_ACPI_CREATOR_ID       SIGNATURE_32 ('M', 'S', 'F', 'T')
#endif
#ifndef EFI_ACPI_CREATOR_REVISION
#define EFI_ACPI_CREATOR_REVISION 0x0100000D
#endif

/**

  Locate the first instance of a protocol.  If the protocol requested is an
  FV protocol, then it will return the first FV that contains the ACPI table
  storage file.

  @param Protocol  -  The protocol to find.
  Instance  -  Return pointer to the first instance of the protocol.
  Type      -  The type of protocol to locate.

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

**/
EFI_STATUS
LocateSupportProtocol (
  IN   EFI_GUID       *Protocol,
  OUT  VOID           **Instance,
  IN   UINT32         Type
  )
{
  EFI_STATUS              Status;
  EFI_HANDLE              *HandleBuffer;
  UINTN                   NumberOfHandles;
  EFI_FV_FILETYPE         FileType;
  UINT32                  FvStatus = 0;
  EFI_FV_FILE_ATTRIBUTES  Attributes;
  UINTN                   Size;
  UINTN                   Index;

  Status = gBS->LocateHandleBuffer (ByProtocol, Protocol, NULL, &NumberOfHandles, &HandleBuffer);
  if (EFI_ERROR (Status)) {
    return Status;
  }
  //
  // Looking for FV with ACPI storage file
  //
  for (Index = 0; Index < NumberOfHandles; Index++) {
    Status = gBS->HandleProtocol (HandleBuffer[Index], Protocol, Instance);
    ASSERT (!EFI_ERROR (Status));

    if (!Type) {
      //
      // Not looking for the FV protocol, so find the first instance of the
      // protocol.  There should not be any errors because our handle buffer
      // should always contain at least one or LocateHandleBuffer would have
      // returned not found.
      //
      break;
    }
    //
    // See if it has the ACPI storage file
    //
    Status = ((EFI_FIRMWARE_VOLUME2_PROTOCOL *) (*Instance))->ReadFile (
                                                              *Instance,
                                                              &gEfiCallerIdGuid,
                                                              NULL,
                                                              &Size,
                                                              &FileType,
                                                              &Attributes,
                                                              &FvStatus
                                                              );

    //
    // If we found it, then we are done
    //
    if (!EFI_ERROR (Status)) {
      break;
    }
  }

  gBS->FreePool (HandleBuffer);
  return Status;
}


EFI_STATUS
UpdateDeviceSsdtTable (
  IN OUT EFI_ACPI_COMMON_HEADER     *Table
  )
{
  EFI_ACPI_DESCRIPTION_HEADER               *TableHeader = NULL;
  UINT64                                    TempOemTableId;
  UINT8                                     *DataPtr;
  EFI_ACPI_IO_PORT_DESCRIPTOR               *IoRsc;

  TableHeader = (EFI_ACPI_DESCRIPTION_HEADER *)Table;

  //
  // Update the OEMID and OEM Table ID.
  //
  CopyMem (&TableHeader->OemId, PcdGetPtr (PcdAcpiDefaultOemId), sizeof(TableHeader->OemId));
  TempOemTableId = PcdGet64 (PcdAcpiDefaultOemTableId);
  CopyMem (&TableHeader->OemTableId, &TempOemTableId, sizeof(UINT64));
  TableHeader->CreatorId        = EFI_ACPI_CREATOR_ID;
  TableHeader->CreatorRevision  = EFI_ACPI_CREATOR_REVISION;

  //
  // Update IO(Decode16, 0xCA2, 0xCA2, 0, 2)
  //
  DEBUG ((DEBUG_INFO, "UpdateDeviceSsdtTable - IPMI\n"));
  for (DataPtr = (UINT8 *)(Table + 1);
       DataPtr < (UINT8 *) ((UINT8 *) Table + Table->Length - 4);
       DataPtr++) {
    if (CompareMem(DataPtr, "_CRS", 4) == 0) {
      DataPtr += 4; // Skip _CRS
      ASSERT (*DataPtr == AML_BUFFER_OP);
      DataPtr ++; // Skip AML_BUFFER_OP
      ASSERT ((*DataPtr & (BIT7|BIT6)) == 0);
      DataPtr ++; // Skip PkgLength - 0xD
      ASSERT ((*DataPtr) == AML_BYTE_PREFIX);
      DataPtr ++; // Skip BufferSize OpCode
      DataPtr ++; // Skip BufferSize - 0xA
      IoRsc = (VOID *)DataPtr;
      ASSERT (IoRsc->Header.Bits.Type == ACPI_SMALL_ITEM_FLAG);
      ASSERT (IoRsc->Header.Bits.Name == ACPI_SMALL_IO_PORT_DESCRIPTOR_NAME);
      ASSERT (IoRsc->Header.Bits.Length == sizeof(EFI_ACPI_IO_PORT_DESCRIPTOR) - sizeof(ACPI_SMALL_RESOURCE_HEADER));
      DEBUG ((DEBUG_INFO, "IPMI IO Base in ASL update - 0x%04x <= 0x%04x\n", IoRsc->BaseAddressMin, PcdGet16(PcdIpmiIoBaseAddress)));
      IoRsc->BaseAddressMin = PcdGet16(PcdIpmiIoBaseAddress);
      IoRsc->BaseAddressMax = PcdGet16(PcdIpmiIoBaseAddress);
    }
  }

  return EFI_SUCCESS;
}

/**

  Entry point for Acpi platform driver.

  @param ImageHandle  -  A handle for the image that is initializing this driver.
  @param SystemTable  -  A pointer to the EFI system table.

  @retval EFI_SUCCESS           -  Driver initialized successfully.
  @retval EFI_LOAD_ERROR        -  Failed to Initialize or has been loaded.
  @retval EFI_OUT_OF_RESOURCES  -  Could not allocate needed resources.

**/
EFI_STATUS
EFIAPI
BmcAcpiEntryPoint (
  IN EFI_HANDLE         ImageHandle,
  IN EFI_SYSTEM_TABLE   *SystemTable
  )
{
  EFI_STATUS                    Status;
  EFI_STATUS                    AcpiStatus;

  EFI_FIRMWARE_VOLUME2_PROTOCOL  *FwVol;
  INTN                          Instance = 0;
  EFI_ACPI_COMMON_HEADER        *CurrentTable = NULL;
  UINTN                         TableHandle = 0;
  UINT32                        FvStatus;
  UINT32                        Size;

  EFI_ACPI_TABLE_PROTOCOL        *AcpiTable;
  UINTN                          TableSize;


  //
  // Find the AcpiTable protocol
  //
  Status = gBS->LocateProtocol (&gEfiAcpiTableProtocolGuid, NULL, (VOID**)&AcpiTable);
  if (EFI_ERROR (Status)) {
    return EFI_ABORTED;
  }

  //
  // Locate the firmware volume protocol
  //
  Status = LocateSupportProtocol (&gEfiFirmwareVolume2ProtocolGuid, (VOID **) &FwVol, 1);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  Status    = EFI_SUCCESS;
  Instance  = 0;

  //
  // Read tables from the storage file.
  //
  while (!EFI_ERROR (Status)) {
    CurrentTable = NULL;

    Status = FwVol->ReadSection (
                      FwVol,
                      &gEfiCallerIdGuid,
                      EFI_SECTION_RAW,
                      Instance,
                      (VOID **) &CurrentTable,
                      (UINTN *) &Size,
                      &FvStatus
                      );
    if (!EFI_ERROR (Status)) {
      //
      // Perform any table specific updates.
      //
      AcpiStatus = UpdateDeviceSsdtTable (CurrentTable);
      if (!EFI_ERROR (AcpiStatus)) {

        TableHandle = 0;
        TableSize = ((EFI_ACPI_DESCRIPTION_HEADER *) CurrentTable)->Length;
        ASSERT (Size >= TableSize);

        Status = AcpiTable->InstallAcpiTable (
                              AcpiTable,
                              CurrentTable,
                              TableSize,
                              &TableHandle
                              );

        ASSERT_EFI_ERROR (Status);
      }

      //
      // Increment the instance
      //
      Instance++;
    }
  }

  return EFI_SUCCESS;
}
