/** @file

Processor power management initialization code.

Copyright (c) 2013-2016 Intel Corporation.

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


**/

#include "SmmPowerManagement.h"

//
// Global variables
//
extern EFI_ACPI_SDT_PROTOCOL   *mAcpiSdt;
extern EFI_ACPI_TABLE_PROTOCOL *mAcpiTable;

extern EFI_GUID gPowerManagementAcpiTableStorageGuid;

/**
  This function is the entry of processor power management initialization code.
  It initializes the processor's power management features based on the user
  configurations and hardware capabilities.
**/
VOID
PpmInit (
  VOID
  )
{
  //
  // Processor Power Management Flags
  //
  mGlobalNvsAreaPtr->Cfgd = PcdGet32(PcdPpmFlags);

  //
  // Patch and publish power management related acpi tables
  //
  PpmPatchAndPublishAcpiTables();
}

/**
  This function is to patch and publish power management related acpi tables.
**/
VOID
PpmPatchAndPublishAcpiTables (
  VOID
  )
{
    //
    // Patch FADT table to enable C2,C3
    //
  PpmPatchFadtTable();

  //
    // Load all the power management acpi tables and patch IST table
    //
    PpmLoadAndPatchPMTables();
}

/**
  This function is to patch PLvl2Lat and PLvl3Lat to enable C2, C3 support in OS.
**/
VOID
PpmPatchFadtTable (
  VOID
  )
{
    EFI_STATUS                    Status;
  EFI_ACPI_DESCRIPTION_HEADER   *Table;
  EFI_ACPI_SDT_HEADER           *CurrentTable;
  EFI_ACPI_TABLE_VERSION        Version;
  UINTN                         Index;
  UINTN                         Handle;

  //
  // Scan all the acpi tables to find FADT 2.0
  //
  Index = 0;
  do {
    Status = mAcpiSdt->GetAcpiTable (
                       Index,
                       &CurrentTable,
                       &Version,
                       &Handle
                       );
    if (Status == EFI_NOT_FOUND) {
      break;
    }
    ASSERT_EFI_ERROR (Status);
    Index++;
  } while (CurrentTable->Signature != EFI_ACPI_3_0_FIXED_ACPI_DESCRIPTION_TABLE_SIGNATURE || CurrentTable->Revision != 0x03);

  ASSERT (CurrentTable->Signature == EFI_ACPI_3_0_FIXED_ACPI_DESCRIPTION_TABLE_SIGNATURE);

  Table  = NULL;
  Status = gBS->AllocatePool (EfiBootServicesData, CurrentTable->Length, (VOID **) &Table);
  ASSERT (Table != NULL);
  CopyMem (Table, CurrentTable, CurrentTable->Length);

  //
  // Update the ACPI table and recalculate checksum
  //
  Status = mAcpiTable->UninstallAcpiTable (mAcpiTable, Handle);
  if (EFI_ERROR (Status)) {
     //
     // Should not get an error here ever, but abort if we do.
     //
     return ;
  }

  //
  // Update the check sum
  // It needs to be zeroed before the checksum calculation
  //
  ((EFI_ACPI_SDT_HEADER *)Table)->Checksum = 0;
  ((EFI_ACPI_SDT_HEADER *)Table)->Checksum =
    CalculateCheckSum8 ((VOID *)Table, Table->Length);

  //
  // Add the table
  //
  Status = mAcpiTable->InstallAcpiTable (
                            mAcpiTable,
                            Table,
                            Table->Length,
                            &Handle
                            );
  ASSERT_EFI_ERROR (Status);
  gBS->FreePool (Table);
}

VOID
SsdtTableUpdate (
  IN OUT   EFI_ACPI_DESCRIPTION_HEADER  *TableHeader
  )
/*++

  Routine Description:

    Update the SSDT table

  Arguments:

    Table   - The SSDT table to be patched

  Returns:

    None

--*/
{
  UINT8      *CurrPtr;
  UINT8      *SsdtPointer;
  UINT32     *Signature;

  //
  // Loop through the ASL looking for values that we must fix up.
  //
  CurrPtr = (UINT8 *) TableHeader;
  for (SsdtPointer = CurrPtr;
       SsdtPointer <= (CurrPtr + ((EFI_ACPI_COMMON_HEADER *) CurrPtr)->Length);
       SsdtPointer++
      )
  {
    Signature = (UINT32 *) SsdtPointer;
    if ((*Signature) == SIGNATURE_32 ('P', 'M', 'B', 'A')) {
      switch (*(Signature+1)) {
      case (SIGNATURE_32 ('L', 'V', 'L', '0')):
        Signature[0] = PcdGet16(PcdPmbaIoBaseAddress);
        Signature[1] = 0;
        break;
      case (SIGNATURE_32 ('L', 'V', 'L', '2')):
        Signature[0] = PcdGet16(PcdPmbaIoLVL2);
        Signature[1] = 0;
        break;
      }
    }
  }
}

EFI_STATUS
LocateSupportProtocol (
  IN  EFI_GUID                       *Protocol,
  OUT VOID                           **Instance,
  IN  UINT32                         Type
  )
/*++

Routine Description:

  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.

Arguments:

  Protocol      The protocol to find.
  Instance      Return pointer to the first instance of the protocol

Returns:

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

--*/
{
  EFI_STATUS              Status;
  EFI_HANDLE              *HandleBuffer;
  UINTN                   NumberOfHandles;
  EFI_FV_FILETYPE         FileType;
  UINT32                  FvStatus;
  EFI_FV_FILE_ATTRIBUTES  Attributes;
  UINTN                   Size;
  UINTN                   i;

  FvStatus = 0;

  //
  // Locate protocol.
  //
  Status = gBS->LocateHandleBuffer (
                   ByProtocol,
                   Protocol,
                   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 (i = 0; i < NumberOfHandles; i++) {
    //
    // Get the protocol on this handle
    // This should not fail because of LocateHandleBuffer
    //
    Status = gBS->HandleProtocol (
                     HandleBuffer[i],
                     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,
                                                              &gPowerManagementAcpiTableStorageGuid,
                                                              NULL,
                                                              &Size,
                                                              &FileType,
                                                              &Attributes,
                                                              &FvStatus
                                                              );

    //
    // If we found it, then we are done
    //
    if (Status == EFI_SUCCESS) {
      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 is to load all the power management acpi tables and patch IST table.
**/
VOID
PpmLoadAndPatchPMTables (
  VOID
  )
{
    EFI_FIRMWARE_VOLUME2_PROTOCOL *FwVol;
    EFI_STATUS                    Status;
    INTN                          Instance;
  EFI_ACPI_COMMON_HEADER        *CurrentTable;
  UINTN                         TableHandle;
  UINT32                        FvStatus;
  UINTN                         Size;

    Status = LocateSupportProtocol (&gEfiFirmwareVolume2ProtocolGuid, (VOID**)&FwVol, 1);
    if (EFI_ERROR (Status)) {
    return;
  }

  //
  // Read tables from the storage file.
  //
  Instance = 0;
  CurrentTable = NULL;

  while (Status == EFI_SUCCESS) {

    Status = FwVol->ReadSection (
                      FwVol,
                      &gPowerManagementAcpiTableStorageGuid,
                      EFI_SECTION_RAW,
                      Instance,
                      (VOID**)&CurrentTable,
                      &Size,
                      &FvStatus
                      );

    if (!EFI_ERROR(Status)) {
      SsdtTableUpdate ((EFI_ACPI_DESCRIPTION_HEADER *) CurrentTable);

      //
      // Update the check sum
      // It needs to be zeroed before the checksum calculation
      //
      ((EFI_ACPI_SDT_HEADER *)CurrentTable)->Checksum = 0;
      ((EFI_ACPI_SDT_HEADER *)CurrentTable)->Checksum = (UINT8)
        CalculateCheckSum8 ((VOID *)CurrentTable, CurrentTable->Length);

      //
      // Add the table
      //
      TableHandle = 0;
      Status = mAcpiTable->InstallAcpiTable (
                              mAcpiTable,
                              CurrentTable,
                              CurrentTable->Length,
                              &TableHandle
                              );

      ASSERT_EFI_ERROR (Status);

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

}
