/** @file
  IPMI BMC ACPI driver to update APCI SSDT table.

  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>

#include <Library/ManageabilityTransportHelperLib.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 [in] Protocol   The protocol to find.
  @param [out] Instance  Return pointer to the first instance of the protocol.
  @param [in] 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;
  EFI_FV_FILE_ATTRIBUTES  Attributes;
  UINTN                   Size;
  UINTN                   Index;

  FvStatus = 0;
  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 != TRUE) {
      //
      // 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;
}

/**
  Update ACPI SSDT for BMC IPMI KCS device.

  @param [in, out] Table  Pointer to ACPI SSDT

  @retval EFI_SUCCESS  SSDT is updated according to PCD settings
**/
EFI_STATUS
UpdateDeviceSsdtTable (
  IN OUT EFI_ACPI_COMMON_HEADER  *Table
  )
{
  EFI_ACPI_DESCRIPTION_HEADER  *TableHeader;
  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_MANAGEABILITY_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_MANAGEABILITY_INFO, "IPMI IO Base in ASL update - 0x%04x <= 0x%04x\n", IoRsc->BaseAddressMin, PcdGet16 (PcdIpmiKcsIoBaseAddress)));
      IoRsc->BaseAddressMin = PcdGet16 (PcdIpmiKcsIoBaseAddress);
      IoRsc->BaseAddressMax = PcdGet16 (PcdIpmiKcsIoBaseAddress);
    }
  }

  return EFI_SUCCESS;
}

/**

  Entry point for Acpi platform driver.

  @param [in] ImageHandle  A handle for the image that is initializing this driver.
  @param [in] 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;
  EFI_ACPI_COMMON_HEADER         *CurrentTable;
  UINTN                          TableHandle;
  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;
}
