/** @file
  ACPI Sdt Protocol Driver

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

**/

#include "AcpiTable.h"

/**
  Retrieve option term according to AmlByteEncoding and Buffer.

  @param[in]    AmlByteEncoding      AML Byte Encoding.
  @param[in]    Buffer               AML buffer.
  @param[in]    MaxBufferSize        AML buffer MAX size. The parser can not parse any data exceed this region.
  @param[in]    TermIndex            Index of the data to retrieve from the object.
  @param[out]   DataType             Points to the returned data type or EFI_ACPI_DATA_TYPE_NONE if no data exists
                                     for the specified index.
  @param[out]   Data                 Upon return, points to the pointer to the data.
  @param[out]   DataSize             Upon return, points to the size of Data.

  @retval       EFI_SUCCESS           Success.
  @retval       EFI_INVALID_PARAMETER Buffer does not refer to a valid ACPI object.
**/
EFI_STATUS
AmlParseOptionTerm (
  IN AML_BYTE_ENCODING   *AmlByteEncoding,
  IN UINT8               *Buffer,
  IN UINTN               MaxBufferSize,
  IN AML_OP_PARSE_INDEX  TermIndex,
  OUT EFI_ACPI_DATA_TYPE *DataType,
  OUT VOID               **Data,
  OUT UINTN              *DataSize
  )
{
  AML_BYTE_ENCODING   *ChildAmlByteEncoding;
  EFI_STATUS          Status;

  if (DataType != NULL) {
    *DataType = AmlTypeToAcpiType (AmlByteEncoding->Format[TermIndex - 1]);
  }
  if (Data != NULL) {
    *Data = Buffer;
  }
  //
  // Parse term according to AML type
  //
  switch (AmlByteEncoding->Format[TermIndex - 1]) {
  case AML_UINT8:
    *DataSize = sizeof(UINT8);
    break;
  case AML_UINT16:
    *DataSize = sizeof(UINT16);
    break;
  case AML_UINT32:
    *DataSize = sizeof(UINT32);
    break;
  case AML_UINT64:
    *DataSize = sizeof(UINT64);
    break;
  case AML_STRING:
    *DataSize = AsciiStrSize((CHAR8 *)Buffer);
    break;
  case AML_NAME:
    Status = AmlGetNameStringSize (Buffer, DataSize);
    if (EFI_ERROR (Status)) {
      return EFI_INVALID_PARAMETER;
    }
    break;
  case AML_OBJECT:
    ChildAmlByteEncoding = AmlSearchByOpByte (Buffer);
    if (ChildAmlByteEncoding == NULL) {
      return EFI_INVALID_PARAMETER;
    }

    //
    // NOTE: We need override DataType here, if there is a case the AML_OBJECT is AML_NAME.
    // We need convert type from EFI_ACPI_DATA_TYPE_CHILD to EFI_ACPI_DATA_TYPE_NAME_STRING.
    // We should not return CHILD because there is NO OpCode for NameString.
    //
    if ((ChildAmlByteEncoding->Attribute & AML_IS_NAME_CHAR) != 0) {
      if (DataType != NULL) {
        *DataType = AmlTypeToAcpiType (AML_NAME);
      }
      Status = AmlGetNameStringSize (Buffer, DataSize);
      if (EFI_ERROR (Status)) {
        return EFI_INVALID_PARAMETER;
      }
      break;
    }

    //
    // It is real AML_OBJECT
    //
    *DataSize = AmlGetObjectSize (
                     ChildAmlByteEncoding,
                     Buffer,
                     MaxBufferSize
                     );
    if (*DataSize == 0) {
      return EFI_INVALID_PARAMETER;
    }
    break;
  case AML_NONE:
    //
    // No term
    //
  case AML_OPCODE:
  default:
    ASSERT (FALSE);
    return EFI_INVALID_PARAMETER;
  }
  if (*DataSize > MaxBufferSize) {
    return EFI_INVALID_PARAMETER;
  }
  return EFI_SUCCESS;
}

/**
  Retrieve information according to AmlByteEncoding and Buffer.

  @param[in]    AmlByteEncoding      AML Byte Encoding.
  @param[in]    Buffer               AML buffer.
  @param[in]    MaxBufferSize        AML buffer MAX size. The parser can not parse any data exceed this region.
  @param[in]    Index                Index of the data to retrieve from the object. In general, indexes read from left-to-right
                                     in the ACPI encoding, with index 0 always being the ACPI opcode.
  @param[out]   DataType             Points to the returned data type or EFI_ACPI_DATA_TYPE_NONE if no data exists
                                     for the specified index.
  @param[out]   Data                 Upon return, points to the pointer to the data.
  @param[out]   DataSize             Upon return, points to the size of Data.

  @retval       EFI_SUCCESS           Success.
  @retval       EFI_INVALID_PARAMETER Buffer does not refer to a valid ACPI object.
**/
EFI_STATUS
AmlParseOptionCommon (
  IN AML_BYTE_ENCODING   *AmlByteEncoding,
  IN UINT8               *Buffer,
  IN UINTN               MaxBufferSize,
  IN AML_OP_PARSE_INDEX  Index,
  OUT EFI_ACPI_DATA_TYPE *DataType,
  OUT VOID               **Data,
  OUT UINTN              *DataSize
  )
{
  UINT8               *CurrentBuffer;
  UINTN               PkgLength;
  UINTN               OpLength;
  UINTN               PkgOffset;
  AML_OP_PARSE_INDEX  TermIndex;
  EFI_STATUS          Status;

  ASSERT ((Index <= AmlByteEncoding->MaxIndex) || (Index == AML_OP_PARSE_INDEX_GET_SIZE));

  //
  // 0. Check if this is NAME string.
  //
  if ((AmlByteEncoding->Attribute & AML_IS_NAME_CHAR) != 0) {
    //
    // Only allow GET_SIZE
    //
    if (Index != AML_OP_PARSE_INDEX_GET_SIZE) {
      return EFI_INVALID_PARAMETER;
    }
    //
    // return NameString size
    //
    Status = AmlGetNameStringSize (Buffer, DataSize);
    if (EFI_ERROR (Status)) {
      return EFI_INVALID_PARAMETER;
    }
    if (*DataSize > MaxBufferSize) {
      return EFI_INVALID_PARAMETER;
    }
    return EFI_SUCCESS;
  }

  //
  // Not NAME string, start parsing
  //
  CurrentBuffer = Buffer;

  //
  // 1. Get OpCode
  //
  if (Index != AML_OP_PARSE_INDEX_GET_SIZE) {
    *DataType = EFI_ACPI_DATA_TYPE_OPCODE;
    *Data = (VOID *)CurrentBuffer;
  }
  if (*CurrentBuffer == AML_EXT_OP) {
    OpLength = 2;
  } else {
    OpLength = 1;
  }
  *DataSize = OpLength;
  if (Index == AML_OP_PARSE_INDEX_GET_OPCODE) {
    return EFI_SUCCESS;
  }
  if (OpLength > MaxBufferSize) {
    return EFI_INVALID_PARAMETER;
  }
  CurrentBuffer += OpLength;

  //
  // 2. Skip PkgLength field, if have
  //
  if ((AmlByteEncoding->Attribute & AML_HAS_PKG_LENGTH) != 0) {
    PkgOffset = AmlGetPkgLength(CurrentBuffer, &PkgLength);
    //
    // Override MaxBufferSize if it is valid PkgLength
    //
    if (OpLength + PkgLength > MaxBufferSize) {
      return EFI_INVALID_PARAMETER;
    } else {
      MaxBufferSize = OpLength + PkgLength;
    }
  } else {
    PkgOffset = 0;
    PkgLength = 0;
  }
  CurrentBuffer += PkgOffset;

  //
  // 3. Get Term one by one.
  //
  TermIndex = AML_OP_PARSE_INDEX_GET_TERM1;
  while ((Index >= TermIndex) && (TermIndex <= AmlByteEncoding->MaxIndex) && ((UINTN)CurrentBuffer < (UINTN)Buffer + MaxBufferSize)) {
    Status = AmlParseOptionTerm (
               AmlByteEncoding,
               CurrentBuffer,
               (UINTN)Buffer + MaxBufferSize - (UINTN)CurrentBuffer,
               TermIndex,
               DataType,
               Data,
               DataSize
               );
    if (EFI_ERROR (Status)) {
      return EFI_INVALID_PARAMETER;
    }

    if (Index == TermIndex) {
      //
      // Done
      //
      return EFI_SUCCESS;
    }

    //
    // Parse next one
    //
    CurrentBuffer += *DataSize;
    TermIndex ++;
  }

  //
  // Finish all options, but no option found.
  //
  if ((UINTN)CurrentBuffer > (UINTN)Buffer + MaxBufferSize) {
    return EFI_INVALID_PARAMETER;
  }
  if ((UINTN)CurrentBuffer == (UINTN)Buffer + MaxBufferSize) {
    if (Index != AML_OP_PARSE_INDEX_GET_SIZE) {
      return EFI_INVALID_PARAMETER;
    }
  }

  //
  // 4. Finish parsing all node, return size
  //
  ASSERT (Index == AML_OP_PARSE_INDEX_GET_SIZE);
  if ((AmlByteEncoding->Attribute & AML_HAS_PKG_LENGTH) != 0) {
    *DataSize = OpLength + PkgLength;
  } else {
    *DataSize = (UINTN)CurrentBuffer - (UINTN)Buffer;
  }

  return EFI_SUCCESS;
}

/**
  Return object size.

  @param[in]    AmlByteEncoding      AML Byte Encoding.
  @param[in]    Buffer               AML object buffer.
  @param[in]    MaxBufferSize        AML object buffer MAX size. The parser can not parse any data exceed this region.

  @return       Size of the object.
**/
UINTN
AmlGetObjectSize (
  IN AML_BYTE_ENCODING   *AmlByteEncoding,
  IN UINT8               *Buffer,
  IN UINTN               MaxBufferSize
  )
{
  EFI_STATUS   Status;
  UINTN        DataSize;

  Status = AmlParseOptionCommon (
               AmlByteEncoding,
               Buffer,
               MaxBufferSize,
               AML_OP_PARSE_INDEX_GET_SIZE,
               NULL,
               NULL,
               &DataSize
               );
  if (EFI_ERROR (Status)) {
    return 0;
  } else {
    return DataSize;
  }
}

/**
  Return object name.

  @param[in]    AmlHandle            AML handle.

  @return       Name of the object.
**/
CHAR8 *
AmlGetObjectName (
  IN EFI_AML_HANDLE      *AmlHandle
  )
{
  AML_BYTE_ENCODING   *AmlByteEncoding;
  VOID                *NameString;
  UINTN               NameSize;
  AML_OP_PARSE_INDEX  TermIndex;
  EFI_STATUS          Status;
  EFI_ACPI_DATA_TYPE  DataType;

  AmlByteEncoding = AmlHandle->AmlByteEncoding;

  ASSERT ((AmlByteEncoding->Attribute & AML_IN_NAMESPACE) != 0);

  //
  // Find out Last Name index, according to OpCode table.
  // The last name will be the node name by design.
  //
  TermIndex = AmlByteEncoding->MaxIndex;
  for (TermIndex = AmlByteEncoding->MaxIndex; TermIndex > 0; TermIndex--) {
    if (AmlByteEncoding->Format[TermIndex - 1] == AML_NAME) {
      break;
    }
  }
  ASSERT (TermIndex != 0);

  //
  // Get Name for this node.
  //
  Status = AmlParseOptionHandleCommon (
             AmlHandle,
             TermIndex,
             &DataType,
             &NameString,
             &NameSize
             );
  if (EFI_ERROR (Status)) {
    return NULL;
  }
  ASSERT (DataType == EFI_ACPI_DATA_TYPE_NAME_STRING);

  return NameString;
}

/**
  Return offset of last option.

  @param[in]    AmlHandle            AML Handle.
  @param[out]   Buffer               Upon return, points to the offset after last option.

  @retval       EFI_SUCCESS           Success.
  @retval       EFI_INVALID_PARAMETER AmlHandle does not refer to a valid ACPI object.
**/
EFI_STATUS
AmlGetOffsetAfterLastOption (
  IN EFI_AML_HANDLE         *AmlHandle,
  OUT UINT8                 **Buffer
  )
{
  EFI_ACPI_DATA_TYPE  DataType;
  VOID                *Data;
  UINTN               DataSize;
  EFI_STATUS          Status;

  Status = AmlParseOptionHandleCommon (
             AmlHandle,
             AmlHandle->AmlByteEncoding->MaxIndex,
             &DataType,
             &Data,
             &DataSize
             );
  if (EFI_ERROR (Status)) {
    return EFI_INVALID_PARAMETER;
  }

  //
  // We need to parse the rest buffer after last node.
  //
  *Buffer = (UINT8 *)((UINTN)Data + DataSize);

  //
  // We need skip PkgLength if no Option
  //
  if (DataType == EFI_ACPI_DATA_TYPE_OPCODE) {
    *Buffer += AmlGetPkgLength (*Buffer, &DataSize);
  }
  return EFI_SUCCESS;
}

/**
  Retrieve information according to AmlHandle

  @param[in]    AmlHandle            AML handle.
  @param[in]    Index                Index of the data to retrieve from the object. In general, indexes read from left-to-right
                                     in the ACPI encoding, with index 0 always being the ACPI opcode.
  @param[out]   DataType             Points to the returned data type or EFI_ACPI_DATA_TYPE_NONE if no data exists
                                     for the specified index.
  @param[out]   Data                 Upon return, points to the pointer to the data.
  @param[out]   DataSize             Upon return, points to the size of Data.

  @retval       EFI_SUCCESS           Success.
  @retval       EFI_INVALID_PARAMETER AmlHandle does not refer to a valid ACPI object.
**/
EFI_STATUS
AmlParseOptionHandleCommon (
  IN EFI_AML_HANDLE      *AmlHandle,
  IN AML_OP_PARSE_INDEX  Index,
  OUT EFI_ACPI_DATA_TYPE *DataType,
  OUT VOID               **Data,
  OUT UINTN              *DataSize
  )
{
  return AmlParseOptionCommon (
           AmlHandle->AmlByteEncoding,
           AmlHandle->Buffer,
           AmlHandle->Size,
           Index,
           DataType,
           Data,
           DataSize
           );
}
