/** @file
  AML Node.

  Copyright (c) 2019 - 2020, Arm Limited. All rights reserved.<BR>

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

#include <Tree/AmlNode.h>

#include <AmlCoreInterface.h>
#include <Tree/AmlTree.h>

/** Initialize an AML_NODE_HEADER structure.

  @param  [in]  Node      Pointer to a node header.
  @param  [in]  NodeType  NodeType to initialize the Node with.
                          Must be an EAML_NODE_TYPE.

  @retval EFI_SUCCESS             The function completed successfully.
  @retval EFI_INVALID_PARAMETER   Invalid parameter.
**/
STATIC
EFI_STATUS
EFIAPI
AmlInitializeNodeHeader (
  IN  AML_NODE_HEADER   * Node,
  IN  EAML_NODE_TYPE      NodeType
  )
{
  if (Node == NULL) {
    ASSERT (0);
    return EFI_INVALID_PARAMETER;
  }

  InitializeListHead (&Node->Link);

  Node->Parent = NULL;
  Node->NodeType = NodeType;

  return EFI_SUCCESS;
}

/** Delete a root node and its ACPI DSDT/SSDT header.

  It is the caller's responsibility to check the RootNode has been removed
  from the tree and is not referencing any other node in the tree.

  @param  [in]  RootNode  Pointer to a root node.

  @retval EFI_SUCCESS             The function completed successfully.
  @retval EFI_INVALID_PARAMETER   Invalid parameter.
**/
STATIC
EFI_STATUS
EFIAPI
AmlDeleteRootNode (
  IN  AML_ROOT_NODE  * RootNode
  )
{
  if (!IS_AML_ROOT_NODE (RootNode)) {
    ASSERT (0);
    return EFI_INVALID_PARAMETER;
  }

  if ((RootNode->SdtHeader != NULL)) {
    FreePool (RootNode->SdtHeader);
  } else {
    ASSERT (0);
    return EFI_INVALID_PARAMETER;
  }

  FreePool (RootNode);
  return EFI_SUCCESS;
}

/** Create an AML_ROOT_NODE.
    This node will be the root of the tree.

  @param  [in]  SdtHeader       Pointer to an ACPI DSDT/SSDT header to copy
                                the data from.
  @param  [out] NewRootNodePtr  If success, contains the created
                                AML_ROOT_NODE.
                                Otherwise reset to NULL.

  @retval EFI_SUCCESS             The function completed successfully.
  @retval EFI_INVALID_PARAMETER   Invalid parameter.
  @retval EFI_OUT_OF_RESOURCES    Could not allocate memory.
**/
EFI_STATUS
EFIAPI
AmlCreateRootNode (
  IN  CONST EFI_ACPI_DESCRIPTION_HEADER   * SdtHeader,
  OUT       AML_ROOT_NODE                ** NewRootNodePtr
  )
{
  EFI_STATUS        Status;
  AML_ROOT_NODE   * RootNode;

  if ((SdtHeader == NULL) ||
      (NewRootNodePtr == NULL)) {
    ASSERT (0);
    return EFI_INVALID_PARAMETER;
  }

  *NewRootNodePtr = NULL;

  RootNode = AllocateZeroPool (sizeof (AML_ROOT_NODE));
  if (RootNode == NULL) {
    ASSERT (0);
    return EFI_OUT_OF_RESOURCES;
  }

  Status = AmlInitializeNodeHeader (&RootNode->NodeHeader, EAmlNodeRoot);
  if (EFI_ERROR (Status)) {
    FreePool (RootNode);
    ASSERT (0);
    return Status;
  }

  InitializeListHead (&RootNode->VariableArgs);

  RootNode->SdtHeader = AllocateCopyPool (
                          sizeof (EFI_ACPI_DESCRIPTION_HEADER),
                          SdtHeader
                          );
  if (RootNode->SdtHeader == NULL) {
    ASSERT (0);
    AmlDeleteRootNode (RootNode);
    return EFI_OUT_OF_RESOURCES;
  }

  *NewRootNodePtr = RootNode;

  return EFI_SUCCESS;
}

/** Delete an object node.

  It is the caller's responsibility to check the ObjectNode has been removed
  from the tree and is not referencing any other node in the tree.

  @param  [in]  ObjectNode  Pointer to an object node.

  @retval EFI_SUCCESS             The function completed successfully.
  @retval EFI_INVALID_PARAMETER   Invalid parameter.
**/
STATIC
EFI_STATUS
EFIAPI
AmlDeleteObjectNode (
  IN  AML_OBJECT_NODE   * ObjectNode
  )
{
  if (!IS_AML_OBJECT_NODE (ObjectNode)) {
    ASSERT (0);
    return EFI_INVALID_PARAMETER;
  }

  FreePool (ObjectNode);
  return EFI_SUCCESS;
}

/** Create an AML_OBJECT_NODE.

  @param  [in]  AmlByteEncoding   Byte encoding entry.
  @param  [in]  PkgLength         PkgLength of the node if the AmlByteEncoding
                                  has the PkgLen attribute.
                                  0 otherwise.
  @param  [out] NewObjectNodePtr  If success, contains the created
                                  AML_OBJECT_NODE.
                                  Otherwise reset to NULL.

  @retval EFI_SUCCESS             The function completed successfully.
  @retval EFI_INVALID_PARAMETER   Invalid parameter.
  @retval EFI_OUT_OF_RESOURCES    Could not allocate memory.
**/
EFI_STATUS
EFIAPI
AmlCreateObjectNode (
  IN  CONST  AML_BYTE_ENCODING   * AmlByteEncoding,
  IN         UINT32                PkgLength,
  OUT        AML_OBJECT_NODE    ** NewObjectNodePtr
  )
{
  EFI_STATUS            Status;
  AML_OBJECT_NODE     * ObjectNode;

  if ((AmlByteEncoding == NULL)  ||
      (NewObjectNodePtr == NULL)) {
    ASSERT (0);
    return EFI_INVALID_PARAMETER;
  }

  *NewObjectNodePtr = NULL;

  ObjectNode = AllocateZeroPool (sizeof (AML_OBJECT_NODE));
  if (ObjectNode == NULL) {
    ASSERT (0);
    return EFI_OUT_OF_RESOURCES;
  }

  Status = AmlInitializeNodeHeader (&ObjectNode->NodeHeader, EAmlNodeObject);
  if (EFI_ERROR (Status)) {
    FreePool (ObjectNode);
    ASSERT (0);
    return Status;
  }

  InitializeListHead (&ObjectNode->VariableArgs);

  // ObjectNode->FixedArgs[...] is already initialised to NULL as the
  // ObjectNode is Zero allocated.
  ObjectNode->AmlByteEncoding = AmlByteEncoding;
  ObjectNode->PkgLen = PkgLength;

  *NewObjectNodePtr = ObjectNode;

  return EFI_SUCCESS;
}

/** Delete a data node and its buffer.

  It is the caller's responsibility to check the DataNode has been removed
  from the tree and is not referencing any other node in the tree.

  @param  [in]  DataNode  Pointer to a data node.

  @retval EFI_SUCCESS             The function completed successfully.
  @retval EFI_INVALID_PARAMETER   Invalid parameter.
  @retval EFI_OUT_OF_RESOURCES    Could not allocate memory.
**/
STATIC
EFI_STATUS
EFIAPI
AmlDeleteDataNode (
  IN  AML_DATA_NODE   * DataNode
  )
{
  if (!IS_AML_DATA_NODE (DataNode)) {
    ASSERT (0);
    return EFI_INVALID_PARAMETER;
  }

  if (DataNode->Buffer != NULL) {
    FreePool (DataNode->Buffer);
  } else {
    ASSERT (0);
    return EFI_INVALID_PARAMETER;
  }

  FreePool (DataNode);
  return EFI_SUCCESS;
}

/** Create an AML_DATA_NODE.

  @param  [in]  DataType        DataType of the node.
  @param  [in]  Data            Pointer to the AML bytecode corresponding to
                                this node. Data is copied from there.
  @param  [in]  DataSize        Number of bytes to consider at the address
                                pointed by Data.
  @param  [out] NewDataNodePtr  If success, contains the created
                                AML_DATA_NODE.
                                Otherwise reset to NULL.

  @retval EFI_SUCCESS             The function completed successfully.
  @retval EFI_INVALID_PARAMETER   Invalid parameter.
  @retval EFI_OUT_OF_RESOURCES    Could not allocate memory.
**/
EFI_STATUS
EFIAPI
AmlCreateDataNode (
  IN        EAML_NODE_DATA_TYPE     DataType,
  IN  CONST UINT8                 * Data,
  IN        UINT32                  DataSize,
  OUT       AML_DATA_NODE        ** NewDataNodePtr
  )
{
  EFI_STATUS        Status;
  AML_DATA_NODE   * DataNode;

  // A data node must not be created for certain data types.
  if ((DataType == EAmlNodeDataTypeNone)       ||
      (DataType == EAmlNodeDataTypeReserved1)  ||
      (DataType == EAmlNodeDataTypeReserved2)  ||
      (DataType == EAmlNodeDataTypeReserved3)  ||
      (DataType == EAmlNodeDataTypeReserved4)  ||
      (DataType == EAmlNodeDataTypeReserved5)  ||
      (Data == NULL)                           ||
      (DataSize == 0)                          ||
      (NewDataNodePtr == NULL)) {
    ASSERT (0);
    return EFI_INVALID_PARAMETER;
  }

  *NewDataNodePtr = NULL;

  DataNode = AllocateZeroPool (sizeof (AML_DATA_NODE));
  if (DataNode == NULL) {
    ASSERT (0);
    return EFI_OUT_OF_RESOURCES;
  }

  Status = AmlInitializeNodeHeader (&DataNode->NodeHeader, EAmlNodeData);
  if (EFI_ERROR (Status)) {
    FreePool (DataNode);
    ASSERT (0);
    return Status;
  }

  DataNode->Buffer = AllocateCopyPool (DataSize, Data);
  if (DataNode->Buffer == NULL) {
    AmlDeleteDataNode (DataNode);
    ASSERT (0);
    return EFI_OUT_OF_RESOURCES;
  }

  DataNode->DataType = DataType;
  DataNode->Size = DataSize;

  *NewDataNodePtr = DataNode;

  return EFI_SUCCESS;
}

/** Delete a Node.

  @param  [in]  Node  Pointer to a Node.

  @retval EFI_SUCCESS             The function completed successfully.
  @retval EFI_INVALID_PARAMETER   Invalid parameter.
**/
EFI_STATUS
EFIAPI
AmlDeleteNode (
  IN  AML_NODE_HEADER   * Node
  )
{
  EFI_STATUS          Status;
  EAML_PARSE_INDEX    Index;

  // Check that the node being deleted is unlinked.
  // When removing the node, its parent and list are reset
  // with InitializeListHead. Thus it must be empty.
  if (!IS_AML_NODE_VALID (Node) ||
      !AML_NODE_IS_DETACHED (Node)) {
    ASSERT (0);
    return EFI_INVALID_PARAMETER;
  }

  switch (Node->NodeType) {
    case EAmlNodeRoot:
    {
      // Check the variable list of arguments has been cleaned.
      if (!IsListEmpty (AmlNodeGetVariableArgList (Node))) {
        ASSERT (0);
        return EFI_INVALID_PARAMETER;
      }

      Status = AmlDeleteRootNode ((AML_ROOT_NODE*)Node);
      if (EFI_ERROR (Status)) {
        ASSERT (0);
      }
      break;
    }

    case EAmlNodeObject:
    {
      // Check the variable list of arguments has been cleaned.
      if (!IsListEmpty (AmlNodeGetVariableArgList (Node))) {
        ASSERT (0);
        return EFI_INVALID_PARAMETER;
      }

      // Check the fixed argument list has been cleaned.
      for (Index = EAmlParseIndexTerm0; Index < EAmlParseIndexMax; Index++) {
        if (((AML_OBJECT_NODE*)Node)->FixedArgs[Index] != NULL) {
          ASSERT (0);
          return EFI_INVALID_PARAMETER;
        }
      }

      Status = AmlDeleteObjectNode ((AML_OBJECT_NODE*)Node);
      if (EFI_ERROR (Status)) {
        ASSERT (0);
      }
      break;
    }

    case EAmlNodeData:
    {
      Status = AmlDeleteDataNode ((AML_DATA_NODE*)Node);
      if (EFI_ERROR (Status)) {
        ASSERT (0);
      }
      break;
    }

    default:
    {
      ASSERT (0);
      Status = EFI_INVALID_PARAMETER;
      break;
    }
  } // switch

  return Status;
}

/** Check whether ObjectNode has the input attribute.
    This function can be used to check ObjectNode is an object node
    at the same time.

  @param  [in]  ObjectNode  Pointer to an object node.
  @param  [in]  Attribute   Attribute to check for.

  @retval TRUE    The node is an AML object and the attribute is present.
  @retval FALSE   Otherwise.
**/
BOOLEAN
EFIAPI
AmlNodeHasAttribute (
  IN  CONST AML_OBJECT_NODE   * ObjectNode,
  IN        AML_OP_ATTRIBUTE    Attribute
  )
{
  if (!IS_AML_OBJECT_NODE (ObjectNode) ||
      (ObjectNode->AmlByteEncoding == NULL)) {
    return FALSE;
  }

  return ((ObjectNode->AmlByteEncoding->Attribute &
           Attribute) == 0 ? FALSE : TRUE);
}

/** Check whether ObjectNode has the input OpCode/SubOpcode couple.

  @param  [in]  ObjectNode  Pointer to an object node.
  @param  [in]  OpCode      OpCode to check
  @param  [in]  SubOpCode   SubOpCode to check

  @retval TRUE    The node is an AML object and
                  the Opcode and the SubOpCode match.
  @retval FALSE   Otherwise.
**/
BOOLEAN
EFIAPI
AmlNodeCompareOpCode (
  IN  CONST  AML_OBJECT_NODE  * ObjectNode,
  IN         UINT8              OpCode,
  IN         UINT8              SubOpCode
  )
{
  if (!IS_AML_OBJECT_NODE (ObjectNode) ||
      (ObjectNode->AmlByteEncoding == NULL)) {
    return FALSE;
  }

  ASSERT (AmlIsOpCodeValid (OpCode, SubOpCode));

  return ((ObjectNode->AmlByteEncoding->OpCode == OpCode) &&
           (ObjectNode->AmlByteEncoding->SubOpCode == SubOpCode)) ?
           TRUE : FALSE;
}

/** Check whether a Node is an integer node.

  By integer node we mean an object node having one of the following opcode:
   - AML_BYTE_PREFIX;
   - AML_WORD_PREFIX;
   - AML_DWORD_PREFIX;
   - AML_QWORD_PREFIX.

  @param  [in]  Node  The node to check.

  @retval TRUE  The Node is an integer node.
  @retval FALSE Otherwise.
**/
BOOLEAN
EFIAPI
IsIntegerNode (
  IN  AML_OBJECT_NODE   * Node
  )
{
  UINT8   OpCode;

  if (!IS_AML_OBJECT_NODE (Node)  ||
      (Node->AmlByteEncoding == NULL)) {
    return FALSE;
  }

  // Check Node is an integer node.
  OpCode = Node->AmlByteEncoding->OpCode;
  if ((OpCode != AML_BYTE_PREFIX)   &&
      (OpCode != AML_WORD_PREFIX)   &&
      (OpCode != AML_DWORD_PREFIX)  &&
      (OpCode != AML_QWORD_PREFIX)) {
    return FALSE;
  }

  return TRUE;
}

/** Check whether a Node is a ZeroOp, a OneOp or a OnesOp.

  These two objects don't have a data node holding
  a value. This require special handling.

  @param  [in]  Node  The node to check.

  @retval TRUE  The Node is a ZeroOp or OneOp.
  @retval FALSE Otherwise.
**/
BOOLEAN
EFIAPI
IsSpecialIntegerNode (
  IN  AML_OBJECT_NODE   * Node
  )
{
  UINT8   OpCode;

  if (!IS_AML_OBJECT_NODE (Node)  ||
      (Node->AmlByteEncoding == NULL)) {
    return FALSE;
  }

  OpCode = Node->AmlByteEncoding->OpCode;

  if ((OpCode != AML_ZERO_OP) &&
      (OpCode != AML_ONE_OP)  &&
      (OpCode != AML_ONES_OP)) {
    return FALSE;
  }

  return TRUE;
}

/** Check whether Node corresponds to a method definition.

  A method definition can be introduced:
   - By a method object, having an AML_METHOD_OP OpCode;
   - By an external definition of a method, having an AML_EXTERNAL_OP OpCode
     and an ObjectType byte set to the MethodObj.

  Note:
  An alias node, having an AML_ALIAS_OP, can be resolved to a method
  definition. This function doesn't handle this case.

  @param [in] Node    Node to check whether it is a method definition.

  @retval TRUE  The Node is a method definition.
  @retval FALSE Otherwise.
**/
BOOLEAN
EFIAPI
AmlIsMethodDefinitionNode (
  IN  CONST AML_OBJECT_NODE   * Node
  )
{
  AML_DATA_NODE   * ObjectType;

  // Node is checked to be an object node aswell.
  if (AmlNodeCompareOpCode (Node, AML_METHOD_OP, 0)) {
    return TRUE;
  } else if (AmlNodeCompareOpCode (Node, AML_EXTERNAL_OP, 0)) {
    // If the node is an external definition, check this is a method.
    // DefExternal := ExternalOp NameString ObjectType ArgumentCount
    // ExternalOp := 0x15
    // ObjectType := ByteData
    // ArgumentCount := ByteData (0 - 7)
    ObjectType = (AML_DATA_NODE*)AmlGetFixedArgument (
                                   (AML_OBJECT_NODE*)Node,
                                   EAmlParseIndexTerm1
                                   );
    if (IS_AML_DATA_NODE (ObjectType)                   &&
        (ObjectType->DataType == EAmlNodeDataTypeUInt)  &&
        ((ObjectType->Size == 1))) {
      if (*((UINT8*)ObjectType->Buffer) == (UINT8)EAmlObjTypeMethodObj) {
        // The external definition is a method.
        return TRUE;
      } else {
        // The external definition is not a method.
        return FALSE;
      }
    } else {
      // The tree is inconsistent.
      ASSERT (0);
      return FALSE;
    }
  }

  // This is not a method definition.
  return FALSE;
}

/** Get the index at which the name of the node is stored.

  @param  [in]  ObjectNode  Pointer to an object node.
                            Must have the AML_IN_NAMESPACE attribute.
  @param  [out] Index       Index of the name in the fixed list of arguments.

  @retval EFI_SUCCESS             The function completed successfully.
  @retval EFI_INVALID_PARAMETER   Invalid parameter.
**/
EFI_STATUS
AmlNodeGetNameIndex (
  IN  CONST AML_OBJECT_NODE     * ObjectNode,
  OUT       EAML_PARSE_INDEX    * Index
  )
{
  EAML_PARSE_INDEX    NameIndex;

  if (!AmlNodeHasAttribute (ObjectNode, AML_IN_NAMESPACE)   ||
      (ObjectNode->AmlByteEncoding == NULL)                 ||
      (Index == NULL)) {
    ASSERT (0);
    return EFI_INVALID_PARAMETER;
  }

  NameIndex = ObjectNode->AmlByteEncoding->NameIndex;

  if ((NameIndex > ObjectNode->AmlByteEncoding->MaxIndex)   ||
      (ObjectNode->AmlByteEncoding->Format[NameIndex] != EAmlName)) {
    ASSERT (0);
    return EFI_INVALID_PARAMETER;
  }

  *Index = NameIndex;

  return EFI_SUCCESS;
}

/** Get the name of the Node.

  Node must be part of the namespace.

  @param [in] ObjectNode    Pointer to an object node,
                            which is part of the namespace.

  @return A pointer to the name.
          NULL otherwise.
          Return NULL for the root node.
**/
CHAR8 *
EFIAPI
AmlNodeGetName (
  IN  CONST AML_OBJECT_NODE   * ObjectNode
  )
{
  EFI_STATUS          Status;
  EAML_PARSE_INDEX    NameIndex;
  AML_DATA_NODE     * DataNode;

  if (!AmlNodeHasAttribute (ObjectNode, AML_IN_NAMESPACE)) {
    ASSERT (0);
    return NULL;
  }

  // Get the index at which the name is stored in the fixed arguments list.
  Status = AmlNodeGetNameIndex (ObjectNode, &NameIndex);
  if (EFI_ERROR (Status)) {
    ASSERT (0);
    return NULL;
  }

  // The name is stored in a Data node.
  DataNode = (AML_DATA_NODE*)ObjectNode->FixedArgs[NameIndex];
  if (IS_AML_DATA_NODE (DataNode) &&
      (DataNode->DataType == EAmlNodeDataTypeNameString)) {
    return (CHAR8*)DataNode->Buffer;
  }

  /* Return NULL if no name is found.
     This can occur if the name of a node is defined as a further
     fixed argument.
     E.g.:  CreateField (BD03, 0x28, Add (ID03 + 0x08), BF33)
                                     ^
                             The parser is here.
     The parent of the Add statement is the CreateField statement. This
     statement defines a name in the AML namespace. This name defined as
     the fourth fixed argument. It hasn't been parsed yet.
  */
  return NULL;
}
