/* $Id: AcpiPlatform.c $ */
/** @file
 * APCI Platform Driver.
 */

/*
 * Copyright (C) 2012-2013 Oracle Corporation
 *
 * This file is part of VirtualBox Open Source Edition (OSE), as
 * available from http://www.virtualbox.org. This file is free software;
 * you can redistribute it and/or modify it under the terms of the GNU
 * General Public License (GPL) as published by the Free Software
 * Foundation, in version 2 as it comes in the "COPYING" file of the
 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
 *
 * The contents of this file may alternatively be used under the terms
 * of the Common Development and Distribution License Version 1.0
 * (CDDL) only, as it comes in the "COPYING.CDDL" file of the
 * VirtualBox OSE distribution, in which case the provisions of the
 * CDDL are applicable instead of those of the GPL.
 *
 * You may elect to license modified versions of this file under the
 * terms and conditions of either the GPL or the CDDL or both.
 * --------------------------------------------------------------------
 *
 * This code is based on MdeModulePkg/Universal/Acpi/AcpiPlatformDxe/AcpiPlatform.c
 * r13361, UDK2010.SR1:
 *
 * Sample ACPI Platform Driver
 *
 * Copyright (c) 2008 - 2011, Intel Corporation. All rights reserved.<BR>
 * 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 <PiDxe.h>

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

#include <Library/BaseLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/DebugLib.h>
#include <Library/PcdLib.h>

#include <IndustryStandard/Acpi.h>

/**
  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  Instance      Return pointer to the first instance of the protocol

  @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
LocateFvInstanceWithTables (
  OUT EFI_FIRMWARE_VOLUME2_PROTOCOL **Instance
  )
{
  EFI_STATUS                    Status;
  EFI_HANDLE                    *HandleBuffer;
  UINTN                         NumberOfHandles;
  EFI_FV_FILETYPE               FileType;
  UINT32                        FvStatus;
  EFI_FV_FILE_ATTRIBUTES        Attributes;
  UINTN                         Size;
  UINTN                         Index;
  EFI_FIRMWARE_VOLUME2_PROTOCOL *FvInstance;

  FvStatus = 0;

  //
  // Locate protocol.
  //
  Status = gBS->LocateHandleBuffer (
                   ByProtocol,
                   &gEfiFirmwareVolume2ProtocolGuid,
                   NULL,
                   &NumberOfHandles,
                   &HandleBuffer
                   );
  if (EFI_ERROR (Status)) {
    //
    // Defined errors at this time are not found and out of resources.
    //
    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
                     );
    ASSERT_EFI_ERROR (Status);

    //
    // See if it has the ACPI storage file
    //
    Status = FvInstance->ReadFile (
                           FvInstance,
                           (EFI_GUID*)PcdGetPtr (PcdAcpiTableStorageFile),
                           NULL,
                           &Size,
                           &FileType,
                           &Attributes,
                           &FvStatus
                           );

    //
    // If we found it, then we are done
    //
    if (Status == EFI_SUCCESS) {
      *Instance = FvInstance;
      break;
    }
  }

  //
  // Our exit status is determined by the success of the previous operations
  // If the protocol was found, Instance already points to it.
  //

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

  return Status;
}


/**
  This function calculates and updates an UINT8 checksum.

  @param  Buffer          Pointer to buffer to checksum
  @param  Size            Number of bytes to checksum

**/
VOID
AcpiPlatformChecksum (
  IN UINT8      *Buffer,
  IN UINTN      Size
  )
{
  UINTN ChecksumOffset;

  ChecksumOffset = OFFSET_OF (EFI_ACPI_DESCRIPTION_HEADER, Checksum);

  //
  // Set checksum to 0 first
  //
  Buffer[ChecksumOffset] = 0;

  //
  // Update checksum value
  //
  Buffer[ChecksumOffset] = CalculateCheckSum8(Buffer, Size);
}

#ifdef VBOX

# define ACPI_RSD_PTR       SIGNATURE_64('R', 'S', 'D', ' ', 'P', 'T', 'R', ' ')
# define EBDA_BASE          (0x9FC0 << 4)

VOID *
FindAcpiRsdPtr(VOID)
{
  UINTN Address;
  UINTN Index;

  //
  // First Search 0x0e0000 - 0x0fffff for RSD Ptr
  //
  for (Address = 0xe0000; Address < 0xfffff; Address += 0x10) {
    if (*(UINT64 *)(Address) == ACPI_RSD_PTR) {
      return (VOID *)Address;
    }
  }

  //
  // Search EBDA
  //
  Address = EBDA_BASE;
  for (Index = 0; Index < 0x400 ; Index += 16) {
    if (*(UINT64 *)(Address + Index) == ACPI_RSD_PTR) {
      return (VOID *)Address;
    }
  }
  return NULL;
}

VOID *FindSignature(VOID* Start, UINT32 Signature, BOOLEAN NoChecksum)
{
  UINT8 *Ptr = (UINT8*)Start;
  UINT32 Count = 0x10000; // 16 pages

  while (Count-- > 0) {
    if (   *(UINT32*)Ptr == Signature
        && ((EFI_ACPI_DESCRIPTION_HEADER *)Ptr)->Length <= Count
        && (NoChecksum ||
            CalculateCheckSum8(Ptr, ((EFI_ACPI_DESCRIPTION_HEADER *)Ptr)->Length) == 0
            )) {
      return Ptr;
    }

    Ptr++;
  }
  return NULL;
}

VOID
FillSysTablesInfo(VOID **Tables, UINT32 TablesSize)
{
    UINT32 Table = 0;
    EFI_ACPI_2_0_ROOT_SYSTEM_DESCRIPTION_POINTER *RsdPtr;
    VOID *TablesPage;
#define FLAG_OPTIONAL    1<<0
#define FLAG_NO_CHECKSUM 1<<1
    static struct {
        UINT32 Signature;
        UINT32 Flags;
        CHAR8* Name;
    } TableInfo[] = {
        // MADT, optional
        { SIGNATURE_32('A', 'P', 'I', 'C'), FLAG_OPTIONAL, "MADT"},
        // FACP (also called FADT)
        { SIGNATURE_32('F', 'A', 'C', 'P'), 0, "FADT"},
        // FACS, according 5.2.9 of ACPI v2. spec FACS doesn't have checksum field
        { SIGNATURE_32('F', 'A', 'C', 'S'), FLAG_NO_CHECKSUM, "FACS"},
        // DSDT
        { SIGNATURE_32('D', 'S', 'D', 'T'), 0, "DSDT"},
        // SSDT
        { SIGNATURE_32('S', 'S', 'D', 'T'), FLAG_OPTIONAL, "SSDT"},
        // HPET
        { SIGNATURE_32('H', 'P', 'E', 'T'), FLAG_OPTIONAL, "HPET"},
        // MCFG
        { SIGNATURE_32('M', 'C', 'F', 'G'), FLAG_OPTIONAL, "MCFG"}
    };
    UINT32 Index;

    RsdPtr = (EFI_ACPI_2_0_ROOT_SYSTEM_DESCRIPTION_POINTER*)FindAcpiRsdPtr();
    ASSERT(RsdPtr != NULL);
    TablesPage = (VOID*)(UINTN)((RsdPtr->RsdtAddress) & ~0xfff);
    DEBUG((DEBUG_INFO, "TablesPage:%p\n", TablesPage));

    for (Index = 0; Index < sizeof TableInfo / sizeof TableInfo[0]; Index++)
    {
        VOID *Ptr = FindSignature(TablesPage, TableInfo[Index].Signature,
                                  (BOOLEAN)((TableInfo[Index].Flags & FLAG_NO_CHECKSUM) != 0));
        if (TableInfo[Index].Signature == SIGNATURE_32('F', 'A', 'C', 'P'))
        {
             // we actually have 2 FADTs, see https://xtracker.innotek.de/index.php?bug=4082
            Ptr = FindSignature((UINT8*)Ptr+32, SIGNATURE_32('F', 'A', 'C', 'P'), FALSE);
        }
        if (!(TableInfo[Index].Flags & FLAG_OPTIONAL))
        {
             if (!Ptr)
               DEBUG((EFI_D_ERROR, "%a: isn't optional %p\n", TableInfo[Index].Name, Ptr));
             ASSERT(Ptr != NULL);
        }
        DEBUG((EFI_D_ERROR, "%a: %p\n", TableInfo[Index].Name, Ptr));
        if (Ptr)
           Tables[Table++] = Ptr;
    }

#if 0
    // RSDT
    ASSERT(Table < TablesSize);
    Tables[Table] = FindSignature(TablesPage, SIGNATURE_32('R', 'S', 'D', 'T'));
    DEBUG ((EFI_D_ERROR, "RSDT: %p\n", Tables[Table]));
    ASSERT(Tables[Table] != NULL);
    Table++;

    // XSDT
    ASSERT(Table < TablesSize);
    Tables[Table] = FindSignature(TablesPage, SIGNATURE_32('X', 'S', 'D', 'T'));
    DEBUG ((EFI_D_ERROR, "XSDT: %p\n", Tables[Table]));
    ASSERT(Tables[Table] != NULL);
    Table++;
#endif

    DEBUG((DEBUG_INFO, "We found %d tables from %d\n", Table, TablesSize));
    Tables[Table] = NULL;
}

# define USE_MEM_TABLES 1

#endif /* VBOX */


/**
  Entrypoint of Acpi Platform driver.

  @param  ImageHandle
  @param  SystemTable

  @return EFI_SUCCESS
  @return EFI_LOAD_ERROR
  @return EFI_OUT_OF_RESOURCES

**/
EFI_STATUS
EFIAPI
AcpiPlatformEntryPoint (
  IN EFI_HANDLE         ImageHandle,
  IN EFI_SYSTEM_TABLE   *SystemTable
  )
{
  EFI_STATUS                     Status;
  EFI_ACPI_TABLE_PROTOCOL        *AcpiTable;
#ifdef USE_MEM_TABLES
  VOID                           *VBoxTables[10];
#else
  EFI_FIRMWARE_VOLUME2_PROTOCOL  *FwVol;
#endif
  INTN                           Instance;
  EFI_ACPI_COMMON_HEADER         *CurrentTable;
  UINTN                          TableHandle;
#ifndef USE_MEM_TABLES
  UINT32                         FvStatus;
#endif
  UINTN                          TableSize;
  UINTN                          Size;

  Size         = 0;
  Instance     = 0;
  CurrentTable = NULL;
  TableHandle  = 0;

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

#ifdef USE_MEM_TABLES
  //
  // VBOX already has tables prepared in memory - just reuse them.
  //
  FillSysTablesInfo(VBoxTables, sizeof(VBoxTables)/sizeof(VBoxTables[0]));
#else
  //
  // Locate the firmware volume protocol
  //
  Status = LocateFvInstanceWithTables (&FwVol);
  if (EFI_ERROR (Status)) {
    return EFI_ABORTED;
  }
  //
  // Read tables from the storage file.
  //
#endif
  while (Status == EFI_SUCCESS) {

#ifdef USE_MEM_TABLES
    CurrentTable = (EFI_ACPI_COMMON_HEADER *)VBoxTables[Instance];
    Status = (CurrentTable == NULL) ? EFI_NOT_FOUND : EFI_SUCCESS;
    if (CurrentTable) {
      Size = CurrentTable->Length;
      DEBUG((EFI_D_ERROR, "adding %p %d\n", CurrentTable, Size));
    }
#else
    Status = FwVol->ReadSection (
                      FwVol,
                      (EFI_GUID*)PcdGetPtr (PcdAcpiTableStorageFile),
                      EFI_SECTION_RAW,
                      Instance,
                      (VOID**) &CurrentTable,
                      &Size,
                      &FvStatus
                      );
#endif
    if (!EFI_ERROR(Status)) {
      //
      // Add the table
      //
      TableHandle = 0;

      TableSize = ((EFI_ACPI_DESCRIPTION_HEADER *) CurrentTable)->Length;
#ifdef VBOX
      DEBUG((DEBUG_INFO, "Size:%d, TableSize:%d\n", Size, TableSize));
#endif
      ASSERT (Size >= TableSize);

      //
      // Checksum ACPI table
      //
      AcpiPlatformChecksum ((UINT8*)CurrentTable, TableSize);

      //
      // Install ACPI table
      //
      Status = AcpiTable->InstallAcpiTable (
                            AcpiTable,
                            CurrentTable,
                            TableSize,
                            &TableHandle
                            );

#ifndef USE_MEM_TABLES /* In case we're reading ACPI tables from memory we haven't
                          allocated this memory, so it isn't required to free it */
      //
      // Free memory allocated by ReadSection
      //
      gBS->FreePool (CurrentTable);
#endif

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

      //
      // Increment the instance
      //
      Instance++;
      CurrentTable = NULL;
    }
  }

  return EFI_SUCCESS;
}

