/** @file
  ACPI Debug feature driver implementation.

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

**/
#include <Uefi.h>
#include <Library/BaseLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/DebugLib.h>
#include <Library/PcdLib.h>
#include <Library/DxeServicesLib.h>
#include <Protocol/AcpiTable.h>
#include <IndustryStandard/Acpi.h>

#include <Protocol/SmmBase2.h>
#include <Protocol/SmmEndOfDxe.h>
#include <Protocol/SmmSwDispatch2.h>

#define ACPI_DEBUG_STR      "INTEL ACPI DEBUG"

//
// ASL NAME structure
//
#pragma pack(1)
typedef struct {
  UINT8  NameOp;            // Byte [0]=0x08:NameOp.
  UINT32 NameString;        // Byte [4:1]=Name of object.
  UINT8  DWordPrefix;       // Byte [5]=0x0C:DWord Prefix.
  UINT32 Value;             // 0 ; Value of named object.
} NAME_LAYOUT;
#pragma pack()

#pragma pack(1)
typedef struct {
  UINT8  Signature[16];     // "INTEL ACPI DEBUG"
  UINT32 BufferSize;        // Total size of Acpi Debug buffer including header structure
  UINT32 Head;              // Current buffer pointer for SMM to print out
  UINT32 Tail;              // Current buffer pointer for ASL to input
  UINT8  SmiTrigger;        // Value to trigger the SMI via B2 port
  UINT8  Wrap;              // If current Tail < Head
  UINT8  SmmVersion;        // If SMM version
  UINT8  Truncate;          // If the input from ASL > MAX_BUFFER_SIZE
} ACPI_DEBUG_HEAD;
#pragma pack()

#define AD_SIZE             sizeof (ACPI_DEBUG_HEAD) // This is 0x20

#define MAX_BUFFER_SIZE     32

UINT32                      mBufferEnd = 0;
ACPI_DEBUG_HEAD             *mAcpiDebug = NULL;

EFI_SMM_SYSTEM_TABLE2       *mSmst = NULL;

/**
  Patch and load ACPI table.

  @param[in] AcpiDebugAddress   Address of Acpi debug memory buffer.
  @param[in] BufferIndex        Index that starts after the Acpi Debug head.
  @param[in] BufferEnd          End of Acpi debug memory buffer.

**/
VOID
PatchAndLoadAcpiTable (
  IN ACPI_DEBUG_HEAD            *AcpiDebugAddress,
  IN UINT32                     BufferIndex,
  IN UINT32                     BufferEnd
  )
{
  EFI_STATUS                    Status;
  EFI_ACPI_TABLE_PROTOCOL       *AcpiTable;
  UINTN                         Size;
  EFI_ACPI_DESCRIPTION_HEADER   *TableHeader;
  UINTN                         TableKey;
  UINT8                         *CurrPtr;
  UINT32                        *Signature;
  NAME_LAYOUT                   *NamePtr;
  UINT8                         UpdateCounter;

  Status = GetSectionFromFv (
             &gEfiCallerIdGuid,
             EFI_SECTION_RAW,
             0,
             (VOID **) &TableHeader,
             &Size
             );
  ASSERT_EFI_ERROR (Status);
  if (EFI_ERROR (Status)) {
    return;
  }

  //
  // This is Acpi Debug SSDT. Acpi Debug should be enabled if we reach here so load the table.
  //
  ASSERT (((EFI_ACPI_DESCRIPTION_HEADER *) TableHeader)->OemTableId == SIGNATURE_64 ('A', 'D', 'e', 'b', 'T', 'a', 'b', 'l'));

  //
  // Patch some pointers for the ASL code before loading the SSDT.
  //

  //
  // Count pointer updates, so we can stop after all three pointers are patched.
  //
  UpdateCounter = 1;
  for (CurrPtr = (UINT8 *) TableHeader; CurrPtr <= ((UINT8 *) TableHeader + TableHeader->Length) && UpdateCounter < 4; CurrPtr++) {
    Signature = (UINT32 *) (CurrPtr + 1);
    //
    // patch DPTR (address of Acpi debug memory buffer)
    //
    if ((*CurrPtr == AML_NAME_OP) && *Signature == SIGNATURE_32 ('D', 'P', 'T', 'R')) {
      NamePtr = (NAME_LAYOUT *) CurrPtr;
      NamePtr->Value  = (UINT32) (UINTN) AcpiDebugAddress;
      UpdateCounter++;
    }
    //
    // patch EPTR (end of Acpi debug memory buffer)
    //
    if ((*CurrPtr == AML_NAME_OP) && *Signature == SIGNATURE_32 ('E', 'P', 'T', 'R')) {
      NamePtr = (NAME_LAYOUT *) CurrPtr;
      NamePtr->Value  = BufferEnd;
      UpdateCounter++;
    }
    //
    // patch CPTR (used as an index that starts after the Acpi Debug head)
    //
    if ((*CurrPtr == AML_NAME_OP) && *Signature == SIGNATURE_32 ('C', 'P', 'T', 'R')) {
      NamePtr = (NAME_LAYOUT *) CurrPtr;
      NamePtr->Value  = BufferIndex;
      UpdateCounter++;
    }
  }

  //
  // Add the table
  //
  Status = gBS->LocateProtocol (&gEfiAcpiTableProtocolGuid, NULL, (VOID **)&AcpiTable);
  ASSERT_EFI_ERROR (Status);
  if (EFI_ERROR (Status)) {
    goto Done;
  }

  TableKey = 0;
  Status = AcpiTable->InstallAcpiTable (
                        AcpiTable,
                        TableHeader,
                        Size,
                        &TableKey
                        );
  ASSERT_EFI_ERROR (Status);
  if (EFI_ERROR (Status)) {
    goto Done;
  }

Done:
  gBS->FreePool (TableHeader);
  return ;
}

/**
  Allocate Acpi Debug memory.

  @param[out] BufferSize    Pointer to Acpi debug memory buffer size.

  @return Address of Acpi debug memory buffer.

**/
EFI_PHYSICAL_ADDRESS
AllocateAcpiDebugMemory (
  OUT UINT32           *BufferSize
  )
{
  EFI_STATUS           Status;
  EFI_PHYSICAL_ADDRESS AcpiDebugAddress;
  UINTN                PagesNum;

  AcpiDebugAddress = 0;
  *BufferSize = 0;

  //
  // Reserve memory to store Acpi Debug data.
  //
  AcpiDebugAddress = 0xFFFFFFFF;
  PagesNum = EFI_SIZE_TO_PAGES (PcdGet32 (PcdAcpiDebugBufferSize));
  Status = gBS->AllocatePages (
                  AllocateMaxAddress,
                  EfiReservedMemoryType,
                  PagesNum,
                  &AcpiDebugAddress
                  );
  ASSERT_EFI_ERROR (Status);
  if (EFI_ERROR (Status)) {
    return 0;
  }

  DEBUG ((DEBUG_INFO, "AcpiDebugAddress - 0x%08x\n", AcpiDebugAddress));

  Status = PcdSet32S (PcdAcpiDebugAddress, (UINT32) AcpiDebugAddress);
  ASSERT_EFI_ERROR (Status);

  if (EFI_ERROR (Status)) {
    gBS->FreePages (AcpiDebugAddress, PagesNum);
    return 0;
  }

  *BufferSize = PcdGet32 (PcdAcpiDebugBufferSize);

  return AcpiDebugAddress;
}

/**
  Acpi Debug EndOfDxe notification.

  @param[in] Event      Event whose notification function is being invoked.
  @param[in] Context    Pointer to the notification function's context.

**/
VOID
EFIAPI
AcpiDebugEndOfDxeNotification (
  IN EFI_EVENT  Event,
  IN VOID       *Context
  )
{
  UINT32        BufferSize;
  UINT32        BufferIndex;

  mAcpiDebug = (ACPI_DEBUG_HEAD *) (UINTN) AllocateAcpiDebugMemory (&BufferSize);
  if (mAcpiDebug != NULL) {
    //
    // Init ACPI DEBUG buffer to lower case 'x'.
    //
    SetMem ((VOID *) mAcpiDebug, BufferSize, 0x78);

    //
    // Clear header of AD_SIZE bytes.
    //
    ZeroMem ((VOID *) mAcpiDebug, AD_SIZE);

    //
    // Write a signature to the first line of the buffer, "INTEL ACPI DEBUG".
    //
    CopyMem ((VOID *) mAcpiDebug, ACPI_DEBUG_STR, sizeof (ACPI_DEBUG_STR) - 1);

    BufferIndex = (UINT32) (UINTN) mAcpiDebug;
    mBufferEnd = BufferIndex + BufferSize;

    //
    // Leave the Index after the Acpi Debug head.
    //
    BufferIndex += AD_SIZE;

    //
    // Patch and Load the SSDT ACPI Tables.
    //
    PatchAndLoadAcpiTable (mAcpiDebug, BufferIndex, mBufferEnd);

    mAcpiDebug->Head = BufferIndex;
    mAcpiDebug->Tail = BufferIndex;
    mAcpiDebug->BufferSize = BufferSize;
  }

  //
  // Close event, so it will not be invoked again.
  //
  gBS->CloseEvent (Event);

  return ;
}

/**
  Initialize ACPI Debug.

  @param[in] ImageHandle    The firmware allocated handle for the EFI image.
  @param[in] SystemTable    A pointer to the EFI System Table.

  @retval EFI_SUCCESS       The driver initializes correctly.

**/
EFI_STATUS
EFIAPI
InitializeAcpiDebugDxe (
  IN EFI_HANDLE         ImageHandle,
  IN EFI_SYSTEM_TABLE   *SystemTable
  )
{
  EFI_STATUS            Status;
  EFI_EVENT             EndOfDxeEvent;

  if (!PcdGetBool (PcdAcpiDebugFeatureActive)) {
    return EFI_SUCCESS;
  }

  //
  // Register EndOfDxe notification
  // that point could ensure the Acpi Debug related PCDs initialized.
  //
  Status = gBS->CreateEventEx (
                  EVT_NOTIFY_SIGNAL,
                  TPL_CALLBACK,
                  AcpiDebugEndOfDxeNotification,
                  NULL,
                  &gEfiEndOfDxeEventGroupGuid,
                  &EndOfDxeEvent
                  );
  ASSERT_EFI_ERROR (Status);

  return Status;
}

/**
  Software SMI callback for ACPI Debug which is called from ACPI method.

  @param[in]      DispatchHandle    The unique handle assigned to this handler by SmiHandlerRegister().
  @param[in]      Context           Points to an optional handler context which was specified when the
                                    handler was registered.
  @param[in, out] CommBuffer        A pointer to a collection of data in memory that will
                                    be conveyed from a non-SMM environment into an SMM environment.
  @param[in, out] CommBufferSize    The size of the CommBuffer.

  @retval EFI_SUCCESS               The interrupt was handled successfully.

**/
EFI_STATUS
EFIAPI
AcpiDebugSmmCallback (
  IN EFI_HANDLE     DispatchHandle,
  IN CONST VOID     *Context,
  IN OUT VOID       *CommBuffer,
  IN OUT UINTN      *CommBufferSize
  )
{
  UINT8             Buffer[MAX_BUFFER_SIZE];

  //
  // Validate the fields in mAcpiDebug to ensure there is no harm to SMI handler.
  // mAcpiDebug is below 4GB and the start address of whole buffer.
  //
  if ((mAcpiDebug->BufferSize != (mBufferEnd - (UINT32) (UINTN) mAcpiDebug)) ||
      (mAcpiDebug->Head < (UINT32) ((UINTN) mAcpiDebug + AD_SIZE)) ||
      (mAcpiDebug->Head > mBufferEnd) ||
      (mAcpiDebug->Tail < (UINT32) ((UINTN) mAcpiDebug + AD_SIZE)) ||
      (mAcpiDebug->Tail > mBufferEnd)) {
    //
    // If some fields in mAcpiDebug are invaid, return directly.
    //
    return EFI_SUCCESS;
  }

  if (!(BOOLEAN)mAcpiDebug->Wrap && ((mAcpiDebug->Head >= (UINT32) ((UINTN) mAcpiDebug + AD_SIZE))
    && (mAcpiDebug->Head < mAcpiDebug->Tail))){
    //
    // If curent ----- buffer + 020
    //                 ...
    //                 ... Head
    //                 ... Data for SMM print
    //                 ... Tail
    //                 ... Vacant for ASL input
    //           ----- buffer end
    //
    // skip NULL block
    //
    while ((*(CHAR8 *) (UINTN) mAcpiDebug->Head == '\0') && (mAcpiDebug->Head < mAcpiDebug->Tail)) {
      mAcpiDebug->Head ++;
    }

    if (mAcpiDebug->Head < mAcpiDebug->Tail) {
      ZeroMem (Buffer, MAX_BUFFER_SIZE);
      AsciiStrnCpyS ((CHAR8 *) Buffer, MAX_BUFFER_SIZE, (CHAR8 *) (UINTN) mAcpiDebug->Head, MAX_BUFFER_SIZE - 1);

      DEBUG ((DEBUG_INFO | DEBUG_ERROR, "%a%a\n", Buffer, (BOOLEAN) mAcpiDebug->Truncate ? "..." : ""));
      mAcpiDebug->Head += MAX_BUFFER_SIZE;

      if (mAcpiDebug->Head >= (mAcpiDebug->Tail)) {
        //
        // When head == tail, we do nothing in handler.
        //
        mAcpiDebug->Head = mAcpiDebug->Tail;
      }
    }
  } else if ((BOOLEAN) mAcpiDebug->Wrap && ((mAcpiDebug->Head > mAcpiDebug->Tail)
    && (mAcpiDebug->Head < (UINT32) ((UINTN) mAcpiDebug + mAcpiDebug->BufferSize)))){
    //
    // If curent ----- buffer + 020
    //                 ... Tail
    //                 ... Vacant for ASL input
    //                 ... Head
    //                 ... Data for SMM print
    //           ----- buffer end
    //
    while ((*(CHAR8 *) (UINTN) mAcpiDebug->Head == '\0') && (mAcpiDebug->Head < (UINT32) ((UINTN) mAcpiDebug + mAcpiDebug->BufferSize))) {
      mAcpiDebug->Head ++;
    }
    if (mAcpiDebug->Head < (UINT32) ((UINTN) mAcpiDebug + mAcpiDebug->BufferSize)){
      ZeroMem (Buffer, MAX_BUFFER_SIZE);
      AsciiStrnCpyS ((CHAR8 *) Buffer, MAX_BUFFER_SIZE, (CHAR8 *) (UINTN) mAcpiDebug->Head, MAX_BUFFER_SIZE - 1);
      DEBUG ((DEBUG_INFO | DEBUG_ERROR, "%a%a\n", Buffer, (BOOLEAN) mAcpiDebug->Truncate ? "..." : ""));
      mAcpiDebug->Head += MAX_BUFFER_SIZE;

      if (mAcpiDebug->Head >= (UINT32) ((UINTN) mAcpiDebug + mAcpiDebug->BufferSize)) {
        //
        // We met end of buffer.
        //
        mAcpiDebug->Wrap = 0;
        mAcpiDebug->Head = (UINT32) ((UINTN) mAcpiDebug + AD_SIZE);
      }
    }
  }

  return EFI_SUCCESS;
}

/**
  Acpi Debug SmmEndOfDxe notification.

  @param[in] Protocol   Points to the protocol's unique identifier.
  @param[in] Interface  Points to the interface instance.
  @param[in] Handle     The handle on which the interface was installed.

  @retval EFI_SUCCESS   Notification runs successfully.

 **/
EFI_STATUS
EFIAPI
AcpiDebugSmmEndOfDxeNotification (
  IN CONST EFI_GUID     *Protocol,
  IN VOID               *Interface,
  IN EFI_HANDLE         Handle
  )
{
  EFI_STATUS                        Status;
  EFI_SMM_SW_DISPATCH2_PROTOCOL     *SwDispatch;
  EFI_SMM_SW_REGISTER_CONTEXT       SwContext;
  EFI_HANDLE                        SwHandle;

  AcpiDebugEndOfDxeNotification (NULL, NULL);

  if (mAcpiDebug != NULL) {
    //
    // Get the Sw dispatch protocol and register SMI callback function.
    //
    SwDispatch = NULL;
    Status = mSmst->SmmLocateProtocol (&gEfiSmmSwDispatch2ProtocolGuid, NULL, (VOID **) &SwDispatch);
    ASSERT_EFI_ERROR (Status);
    if (EFI_ERROR (Status)) {
      return Status;
    }

    SwContext.SwSmiInputValue = (UINTN) -1;
    Status = SwDispatch->Register (SwDispatch, AcpiDebugSmmCallback, &SwContext, &SwHandle);
    ASSERT_EFI_ERROR (Status);
    if (EFI_ERROR (Status)) {
      return Status;
    }

    mAcpiDebug->SmiTrigger = (UINT8) SwContext.SwSmiInputValue;
    mAcpiDebug->SmmVersion = 1;
  }

  return EFI_SUCCESS;
}

/**
  Initialize ACPI Debug.

  @param[in] ImageHandle    The firmware allocated handle for the EFI image.
  @param[in] SystemTable    A pointer to the EFI System Table.

  @retval EFI_SUCCESS       The driver initializes correctly.

**/
EFI_STATUS
EFIAPI
InitializeAcpiDebugSmm (
  IN EFI_HANDLE         ImageHandle,
  IN EFI_SYSTEM_TABLE   *SystemTable
  )
{
  EFI_STATUS                Status;
  VOID                      *Registration;
  EFI_SMM_BASE2_PROTOCOL    *SmmBase2;
  BOOLEAN                   InSmm;

  if (!PcdGetBool (PcdAcpiDebugFeatureActive)) {
    return EFI_SUCCESS;
  }

  Status = gBS->LocateProtocol (&gEfiSmmBase2ProtocolGuid, NULL, (VOID **) &SmmBase2);
  ASSERT_EFI_ERROR (Status);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  Status = SmmBase2->InSmm (SmmBase2, &InSmm);
  ASSERT_EFI_ERROR (Status);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  ASSERT (InSmm);

  if (!InSmm) {
    return EFI_UNSUPPORTED;
  }

  Status = SmmBase2->GetSmstLocation (SmmBase2, &mSmst);
  ASSERT_EFI_ERROR (Status);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  //
  // Register SmmEndOfDxe notification
  // that point could ensure the Acpi Debug related PCDs initialized.
  //
  Registration = NULL;
  Status = mSmst->SmmRegisterProtocolNotify (
                    &gEfiSmmEndOfDxeProtocolGuid,
                    AcpiDebugSmmEndOfDxeNotification,
                    &Registration
                    );
  ASSERT_EFI_ERROR (Status);

  return Status;
}
