/** @file
  OVMF ACPI Cloud Hypervisor support

  Copyright (c) 2021, Intel Corporation. All rights reserved.

  SPDX-License-Identifier: BSD-2-Clause-Patent

**/

#include <IndustryStandard/Acpi.h>                        // EFI_ACPI_DESCRIPTION_HEADER
#include <IndustryStandard/CloudHv.h>                     // CLOUDHV_RSDP_ADDRESS
#include <IndustryStandard/Xen/arch-x86/hvm/start_info.h> // hvm_start_info
#include <Library/BaseLib.h>                              // CpuDeadLoop()
#include <Library/DebugLib.h>                             // DEBUG()
#include <Library/PcdLib.h>                               // PcdGet32()
#include <Library/HobLib.h>                               // GetFirstGuidHob(), GetNextGuidHob()
#include <Library/UefiBootServicesTableLib.h>             // gBS
#include <Protocol/AcpiSystemDescriptionTable.h>
#include <Protocol/AcpiTable.h>

#include "AcpiPlatform.h"

EFI_STATUS
EFIAPI
InstallCloudHvTablesTdx (
  IN   EFI_ACPI_TABLE_PROTOCOL  *AcpiProtocol
  )
{
  EFI_STATUS  Status;
  UINTN       TableHandle;
  EFI_HANDLE  ChAcpiHandle;

  EFI_PEI_HOB_POINTERS         Hob;
  EFI_ACPI_DESCRIPTION_HEADER  *CurrentTable;
  EFI_ACPI_DESCRIPTION_HEADER  *DsdtTable;

  DsdtTable   = NULL;
  TableHandle = 0;

  Hob.Guid = (EFI_HOB_GUID_TYPE *)GetFirstGuidHob (&gUefiOvmfPkgTdxAcpiHobGuid);

  while (Hob.Guid != NULL) {
    CurrentTable = (EFI_ACPI_DESCRIPTION_HEADER *)(&Hob.Guid->Name + 1);
    if (!AsciiStrnCmp ((CHAR8 *)&CurrentTable->Signature, "DSDT", 4)) {
      DsdtTable = CurrentTable;
    } else {
      //
      // Install the tables
      //
      Status = AcpiProtocol->InstallAcpiTable (
                               AcpiProtocol,
                               CurrentTable,
                               CurrentTable->Length,
                               &TableHandle
                               );
      if (EFI_ERROR (Status)) {
        ASSERT_EFI_ERROR (Status);
        return Status;
      }

      for (UINTN i = 0; i < CurrentTable->Length; i++) {
        DEBUG ((DEBUG_INFO, " %x", *((UINT8 *)CurrentTable + i)));
      }

      DEBUG ((DEBUG_INFO, "\n"));
    }

    Hob.Raw  = GET_NEXT_HOB (Hob.Raw);
    Hob.Guid = (EFI_HOB_GUID_TYPE *)GetNextGuidHob (&gUefiOvmfPkgTdxAcpiHobGuid, Hob.Raw);
  }

  //
  // Install DSDT table. If we reached this point without finding the DSDT,
  // then we're out of sync with the hypervisor, and cannot continue.
  //
  if (DsdtTable == NULL) {
    DEBUG ((DEBUG_ERROR, "%a: no DSDT found\n", __func__));
    ASSERT (FALSE);
    CpuDeadLoop ();
  }

  Status = AcpiProtocol->InstallAcpiTable (
                           AcpiProtocol,
                           DsdtTable,
                           DsdtTable->Length,
                           &TableHandle
                           );
  if (EFI_ERROR (Status)) {
    ASSERT_EFI_ERROR (Status);
    return Status;
  }

  //
  // Install a protocol to notify that the ACPI table provided by CH is
  // ready.
  //
  ChAcpiHandle = NULL;
  Status       = gBS->InstallProtocolInterface (
                        &ChAcpiHandle,
                        &gQemuAcpiTableNotifyProtocolGuid,
                        EFI_NATIVE_INTERFACE,
                        NULL
                        );
  if (EFI_ERROR (Status)) {
    ASSERT_EFI_ERROR (Status);
    return Status;
  }

  return EFI_SUCCESS;
}

// Get the ACPI tables from EBDA start
EFI_STATUS
EFIAPI
InstallCloudHvTables (
  IN   EFI_ACPI_TABLE_PROTOCOL  *AcpiProtocol
  )
{
  EFI_STATUS  Status;
  UINTN       TableHandle;

  EFI_ACPI_DESCRIPTION_HEADER                   *Xsdt;
  VOID                                          *CurrentTableEntry;
  UINTN                                         CurrentTablePointer;
  EFI_ACPI_DESCRIPTION_HEADER                   *CurrentTable;
  UINTN                                         Index;
  UINTN                                         NumberOfTableEntries;
  EFI_ACPI_2_0_FIXED_ACPI_DESCRIPTION_TABLE     *Fadt2Table;
  EFI_ACPI_DESCRIPTION_HEADER                   *DsdtTable;
  EFI_ACPI_2_0_ROOT_SYSTEM_DESCRIPTION_POINTER  *AcpiRsdpStructurePtr;
  UINT32                                        *PVHResetVectorData;
  struct hvm_start_info                         *pvh_start_info;

  Fadt2Table           = NULL;
  DsdtTable            = NULL;
  TableHandle          = 0;
  NumberOfTableEntries = 0;
  AcpiRsdpStructurePtr = NULL;
  PVHResetVectorData   = NULL;
  pvh_start_info       = NULL;

  PVHResetVectorData = (VOID *)(UINTN)PcdGet32 (PcdXenPvhStartOfDayStructPtr);
  if (PVHResetVectorData == 0) {
    return EFI_NOT_FOUND;
  }

  pvh_start_info       = (struct hvm_start_info *)(UINTN)PVHResetVectorData[0];
  AcpiRsdpStructurePtr = (EFI_ACPI_2_0_ROOT_SYSTEM_DESCRIPTION_POINTER *)(UINTN)pvh_start_info->rsdp_paddr;

  // If XSDT table is found, just install its tables.
  // Otherwise, try to find and install the RSDT tables.
  //
  if (AcpiRsdpStructurePtr->XsdtAddress) {
    //
    // Retrieve the addresses of XSDT and
    // calculate the number of its table entries.
    //
    Xsdt = (EFI_ACPI_DESCRIPTION_HEADER *)(UINTN)
           AcpiRsdpStructurePtr->XsdtAddress;
    NumberOfTableEntries = (Xsdt->Length -
                            sizeof (EFI_ACPI_DESCRIPTION_HEADER)) /
                           sizeof (UINT64);

    //
    // Install ACPI tables found in XSDT.
    //
    for (Index = 0; Index < NumberOfTableEntries; Index++) {
      //
      // Get the table entry from XSDT
      //
      CurrentTableEntry = (VOID *)((UINT8 *)Xsdt +
                                   sizeof (EFI_ACPI_DESCRIPTION_HEADER) +
                                   Index * sizeof (UINT64));
      CurrentTablePointer = (UINTN)*(UINT64 *)CurrentTableEntry;
      CurrentTable        = (EFI_ACPI_DESCRIPTION_HEADER *)CurrentTablePointer;

      //
      // Install the XSDT tables
      //
      Status = AcpiProtocol->InstallAcpiTable (
                               AcpiProtocol,
                               CurrentTable,
                               CurrentTable->Length,
                               &TableHandle
                               );

      if (EFI_ERROR (Status)) {
        ASSERT_EFI_ERROR (Status);
        return Status;
      }

      //
      // Get the X-DSDT table address from the table FADT
      //
      if (!AsciiStrnCmp ((CHAR8 *)&CurrentTable->Signature, "FACP", 4)) {
        Fadt2Table = (EFI_ACPI_2_0_FIXED_ACPI_DESCRIPTION_TABLE *)
                     (UINTN)CurrentTablePointer;
        DsdtTable = (EFI_ACPI_DESCRIPTION_HEADER *)(UINTN)Fadt2Table->XDsdt;
      }
    }
  } else {
    return EFI_NOT_FOUND;
  }

  //
  // Install DSDT table. If we reached this point without finding the DSDT,
  // then we're out of sync with the hypervisor, and cannot continue.
  //
  if (DsdtTable == NULL) {
    DEBUG ((DEBUG_ERROR, "%a: no DSDT found\n", __func__));
    ASSERT (FALSE);
    CpuDeadLoop ();
  }

  Status = AcpiProtocol->InstallAcpiTable (
                           AcpiProtocol,
                           DsdtTable,
                           DsdtTable->Length,
                           &TableHandle
                           );
  if (EFI_ERROR (Status)) {
    ASSERT_EFI_ERROR (Status);
    return Status;
  }

  return EFI_SUCCESS;
}
