/** @file
  AML Parser.

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

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

#include <Parser/AmlParser.h>

#include <AmlCoreInterface.h>
#include <AmlDbgPrint/AmlDbgPrint.h>
#include <Parser/AmlFieldListParser.h>
#include <Parser/AmlMethodParser.h>
#include <Parser/AmlResourceDataParser.h>
#include <String/AmlString.h>
#include <Tree/AmlNode.h>
#include <Tree/AmlTree.h>

/*
  AML Tree
  --------

  Each ASL Statement is represented in AML as and ObjectNode.
  Each ObjectNode has an Opcode and has up to six FixedArguments
  followed by a list of VariableArguments.
  (ObjectNode)
    \
    |- [0][1][2][3][4][5]                        # Fixed Arguments
    |- {(VarArg1)->(VarArg2)->(VarArg3)->...N}   # Variable Arguments

  A RootNode is a special type of Object Node that does not have an
  Opcode or Fixed Arguments. It only has a list of VariableArguments
  (RootNode)
    \
    |- {(VarArg1)->(VarArg2)->(VarArg3)->...N}   # Variable Arguments

  A DataNode consists of a data buffer.

  A FixedArgument or VariableArgument can be either an ObjectNode or
  a DataNode.

  Example:
  ASL code sample:
  Device (DEV0) {
    Name (VAR0, 0x6)
  }

  Tree generated from the ASL code:
  (RootNode)
    \
    |- {(Device statement (ObjectNode))}                # Variable Arg of the
          \                                             #   RootNode
           |
           |- [0] - Device Name (DataNode)(="DEV0")     # Fixed Arg0 of the
           |                                            #   Device() statement
           |
           |- {(Name statement (ObjectNode))}           # Variable Arg of the
                \                                       #   Device() statement
                |
                |- [0] - Name statement(DataNode)(="VAR0")  # Fixed Arg0 of the
                |                                           #   Name() statement
                |- [1] - Value(DataNode)(=0x6)              # Fixed Arg1 of the
                                                            #   Name() statement
*/

// Forward declaration.
STATIC
EFI_STATUS
EFIAPI
AmlParseStream (
  IN      AML_NODE_HEADER  *Node,
  IN  OUT AML_STREAM       *FStream,
  IN  OUT LIST_ENTRY       *NameSpaceRefList
  );

/** Function pointer to parse an AML construct.

  The expected format of the AML construct is passed in the
  ExpectedFormat argument. The available formats are available in
  the AML_PARSE_FORMAT enum definition.

  An object node or a data node is created in the function,
  and returned through the OutNode parameter. This node should
  be attached after this function returns.

  @param  [in]      ParentNode      Parent node to which the parsed
                                    AML construct will be attached.
  @param  [in]      ExpectedFormat  Format of the AML construct to parse.
  @param  [in, out] FStream         Forward stream containing the AML bytecode
                                    to parse.
                                    The stream must not be at its end.
  @param  [out]     OutNode         Pointer holding the node created from the
                                    parsed AML bytecode.

  @retval EFI_SUCCESS             The function completed successfully.
  @retval EFI_BUFFER_TOO_SMALL    No space left in the buffer.
  @retval EFI_INVALID_PARAMETER   Invalid parameter.
  @retval EFI_OUT_OF_RESOURCES    Could not allocate memory.
**/
typedef
EFI_STATUS
(EFIAPI *AML_PARSE_FUNCTION)(
  IN      CONST AML_NODE_HEADER   *Node,
  IN            AML_PARSE_FORMAT  ExpectedFormat,
  IN  OUT       AML_STREAM        *FStream,
  OUT       AML_NODE_HEADER       **OutNode
  );

/** Parse a UInt<X> (where X=8, 16, 32 or 64).

  A data node is created and returned through the OutNode parameter.

  @param  [in]      ParentNode      Parent node to which the parsed
                                    AML construct will be attached.
  @param  [in]      ExpectedFormat  Format of the AML construct to parse.
  @param  [in, out] FStream         Forward stream containing the AML bytecode
                                    to parse.
                                    The stream must not be at its end.
  @param  [out]     OutNode         Pointer holding the node created from the
                                    parsed AML bytecode.

  @retval EFI_SUCCESS             The function completed successfully.
  @retval EFI_BUFFER_TOO_SMALL    No space left in the buffer.
  @retval EFI_INVALID_PARAMETER   Invalid parameter.
  @retval EFI_OUT_OF_RESOURCES    Could not allocate memory.
**/
STATIC
EFI_STATUS
EFIAPI
AmlParseUIntX (
  IN      CONST AML_NODE_HEADER   *ParentNode,
  IN            AML_PARSE_FORMAT  ExpectedFormat,
  IN  OUT       AML_STREAM        *FStream,
  OUT       AML_NODE_HEADER       **OutNode
  )
{
  EFI_STATUS  Status;
  UINT32      UIntXSize;

  if ((!IS_AML_ROOT_NODE (ParentNode)       &&
       !IS_AML_OBJECT_NODE (ParentNode))    ||
      ((ExpectedFormat != EAmlUInt8)        &&
       (ExpectedFormat != EAmlUInt16)       &&
       (ExpectedFormat != EAmlUInt32)       &&
       (ExpectedFormat != EAmlUInt64))      ||
      !IS_STREAM (FStream)                  ||
      IS_END_OF_STREAM (FStream)            ||
      !IS_STREAM_FORWARD (FStream)          ||
      (OutNode == NULL))
  {
    ASSERT (0);
    return EFI_INVALID_PARAMETER;
  }

  switch (ExpectedFormat) {
    case EAmlUInt8:
      UIntXSize = 1;
      break;
    case EAmlUInt16:
      UIntXSize = 2;
      break;
    case EAmlUInt32:
      UIntXSize = 4;
      break;
    case EAmlUInt64:
      UIntXSize = 8;
      break;
    default:
      ASSERT (0);
      return EFI_INVALID_PARAMETER;
  }

  Status = AmlCreateDataNode (
             AmlTypeToNodeDataType (ExpectedFormat),
             AmlStreamGetCurrPos (FStream),
             UIntXSize,
             (AML_DATA_NODE **)OutNode
             );
  if (EFI_ERROR (Status)) {
    ASSERT (0);
    return Status;
  }

  AMLDBG_DUMP_RAW (AmlStreamGetCurrPos (FStream), UIntXSize);

  // Move stream forward by the size of UIntX.
  Status = AmlStreamProgress (FStream, UIntXSize);
  if (EFI_ERROR (Status)) {
    AmlDeleteTree (*OutNode);
    ASSERT (0);
  }

  return Status;
}

/** Parse an AML NameString.

  A data node is created and returned through the OutNode parameter.

  @param  [in]      ParentNode      Parent node to which the parsed
                                    AML construct will be attached.
  @param  [in]      ExpectedFormat  Format of the AML construct to parse.
  @param  [in, out] FStream         Forward stream containing the AML bytecode
                                    to parse.
                                    The stream must not be at its end.
  @param  [out]     OutNode         Pointer holding the node created from the
                                    parsed AML bytecode.

  @retval EFI_SUCCESS             The function completed successfully.
  @retval EFI_BUFFER_TOO_SMALL    No space left in the buffer.
  @retval EFI_INVALID_PARAMETER   Invalid parameter.
  @retval EFI_OUT_OF_RESOURCES    Could not allocate memory.
**/
STATIC
EFI_STATUS
EFIAPI
AmlParseNameString (
  IN      CONST AML_NODE_HEADER   *ParentNode,
  IN            AML_PARSE_FORMAT  ExpectedFormat,
  IN  OUT       AML_STREAM        *FStream,
  OUT       AML_NODE_HEADER       **OutNode
  )
{
  EFI_STATUS  Status;

  CONST UINT8              *Buffer;
  CONST AML_BYTE_ENCODING  *ByteEncoding;
  UINT32                   StrSize;

  if ((!IS_AML_ROOT_NODE (ParentNode)     &&
       !IS_AML_OBJECT_NODE (ParentNode))  ||
      (ExpectedFormat != EAmlName)        ||
      !IS_STREAM (FStream)                ||
      IS_END_OF_STREAM (FStream)          ||
      !IS_STREAM_FORWARD (FStream)        ||
      (OutNode == NULL))
  {
    ASSERT (0);
    return EFI_INVALID_PARAMETER;
  }

  Buffer       = (CONST UINT8 *)AmlStreamGetCurrPos (FStream);
  ByteEncoding = AmlGetByteEncoding (Buffer);
  if ((ByteEncoding == NULL)    ||
      ((ByteEncoding->Attribute & AML_IS_NAME_CHAR) == 0))
  {
    ASSERT (0);
    return EFI_INVALID_PARAMETER;
  }

  // Parse the NameString.
  Status = AmlGetNameStringSize ((CONST CHAR8 *)Buffer, &StrSize);
  if ((EFI_ERROR (Status))  ||
      (StrSize > AmlStreamGetFreeSpace (FStream)))
  {
    ASSERT (0);
    return EFI_INVALID_PARAMETER;
  }

  Status = AmlCreateDataNode (
             EAmlNodeDataTypeNameString,
             Buffer,
             StrSize,
             (AML_DATA_NODE **)OutNode
             );
  if (EFI_ERROR (Status)) {
    ASSERT (0);
    return Status;
  }

  AMLDBG_DUMP_RAW (AmlStreamGetCurrPos (FStream), StrSize);

  // Move the stream forward by StrSize.
  Status = AmlStreamProgress (FStream, StrSize);
  if (EFI_ERROR (Status)) {
    AmlDeleteTree (*OutNode);
    ASSERT (0);
  }

  return Status;
}

/** Parse an AML String.

  A data node is created and returned through the OutNode parameter.

  @param  [in]      ParentNode      Parent node to which the parsed
                                    AML construct will be attached.
  @param  [in]      ExpectedFormat  Format of the AML construct to parse.
  @param  [in, out] FStream         Forward stream containing the AML bytecode
                                    to parse.
                                    The stream must not be at its end.
  @param  [out]     OutNode         Pointer holding the node created from the
                                    parsed AML bytecode.

  @retval EFI_SUCCESS             The function completed successfully.
  @retval EFI_BUFFER_TOO_SMALL    No space left in the buffer.
  @retval EFI_INVALID_PARAMETER   Invalid parameter.
  @retval EFI_OUT_OF_RESOURCES    Could not allocate memory.
**/
STATIC
EFI_STATUS
EFIAPI
AmlParseString (
  IN      CONST AML_NODE_HEADER   *ParentNode,
  IN            AML_PARSE_FORMAT  ExpectedFormat,
  IN  OUT       AML_STREAM        *FStream,
  OUT       AML_NODE_HEADER       **OutNode
  )
{
  EFI_STATUS   Status;
  UINT32       StrSize;
  UINT8        Byte;
  CONST UINT8  *Buffer;

  if ((!IS_AML_ROOT_NODE (ParentNode)     &&
       !IS_AML_OBJECT_NODE (ParentNode))  ||
      (ExpectedFormat != EAmlString)      ||
      !IS_STREAM (FStream)                ||
      IS_END_OF_STREAM (FStream)          ||
      !IS_STREAM_FORWARD (FStream)        ||
      (OutNode == NULL))
  {
    ASSERT (0);
    return EFI_INVALID_PARAMETER;
  }

  Buffer  = (CONST UINT8 *)AmlStreamGetCurrPos (FStream);
  StrSize = 0;
  // AML String is NULL terminated.
  do {
    // Reading the stream moves the stream forward as well.
    Status = AmlStreamReadByte (FStream, &Byte);
    if (EFI_ERROR (Status)) {
      ASSERT (0);
      return Status;
    }

    StrSize++;
  } while (Byte != '\0');

  AMLDBG_DUMP_RAW (Buffer, StrSize);

  Status = AmlCreateDataNode (
             AmlTypeToNodeDataType (ExpectedFormat),
             Buffer,
             StrSize,
             (AML_DATA_NODE **)OutNode
             );
  ASSERT_EFI_ERROR (Status);

  return Status;
}

/** Parse an AML object.

  An object can be resolved as an AML object with an OpCode,
  or a NameString. An object node or a data node is created
  and returned through the OutNode parameter.

  @param  [in]      ParentNode      Parent node to which the parsed
                                    AML construct will be attached.
  @param  [in]      ExpectedFormat  Format of the AML construct to parse.
  @param  [in, out] FStream         Forward stream containing the AML bytecode
                                    to parse.
                                    The stream must not be at its end.
  @param  [out]     OutNode         Pointer holding the node created from the
                                    parsed AML bytecode.

  @retval EFI_SUCCESS             The function completed successfully.
  @retval EFI_BUFFER_TOO_SMALL    No space left in the buffer.
  @retval EFI_INVALID_PARAMETER   Invalid parameter.
  @retval EFI_OUT_OF_RESOURCES    Could not allocate memory.
**/
STATIC
EFI_STATUS
EFIAPI
AmlParseObject (
  IN      CONST AML_NODE_HEADER   *ParentNode,
  IN            AML_PARSE_FORMAT  ExpectedFormat,
  IN  OUT       AML_STREAM        *FStream,
  OUT       AML_NODE_HEADER       **OutNode
  )
{
  EFI_STATUS  Status;

  UINT8   OpCodeSize;
  UINT32  PkgLength;
  UINT32  PkgOffset;
  UINT32  FreeSpace;

  CONST AML_BYTE_ENCODING  *AmlByteEncoding;
  CONST UINT8              *Buffer;

  if ((!IS_AML_ROOT_NODE (ParentNode)     &&
       !IS_AML_OBJECT_NODE (ParentNode))  ||
      (ExpectedFormat != EAmlObject)      ||
      !IS_STREAM (FStream)                ||
      IS_END_OF_STREAM (FStream)          ||
      !IS_STREAM_FORWARD (FStream)        ||
      (OutNode == NULL))
  {
    ASSERT (0);
    return EFI_INVALID_PARAMETER;
  }

  PkgLength = 0;

  // 0. Get the AML Byte encoding.
  AmlByteEncoding = AmlGetByteEncoding (AmlStreamGetCurrPos (FStream));
  if (AmlByteEncoding == NULL) {
    ASSERT (0);
    return EFI_INVALID_PARAMETER;
  }

  // 1. Check for NameString.
  //    Indeed a NameString can be found when an AML object is expected.
  //    e.g. VAR0 = 3         // VAR0 is assigned an object which is a UINT.
  //         VAR1 = VAR2      // VAR2 is a NameString.
  //    If this is a NameString, return. A NameString can be a variable, a
  //    method invocation, etc.
  if ((AmlByteEncoding->Attribute & AML_IS_NAME_CHAR) != 0) {
    Status = AmlParseNameString (
               ParentNode,
               EAmlName,
               FStream,
               OutNode
               );
    if (EFI_ERROR (Status)) {
      ASSERT (0);
    }

    return Status;
  }

  // 2. Determine the OpCode size to move the stream forward.
  Buffer = (CONST UINT8 *)AmlStreamGetCurrPos (FStream);
  if (*Buffer == AML_EXT_OP) {
    OpCodeSize = 2;
  } else {
    OpCodeSize = 1;
  }

  Status = AmlStreamProgress (FStream, OpCodeSize);
  if (EFI_ERROR (Status)) {
    ASSERT (0);
    return Status;
  }

  // Print the opcode.
  AMLDBG_DUMP_RAW (Buffer, OpCodeSize);

  if (!IS_END_OF_STREAM (FStream)) {
    // 3. Parse the PkgLength field, if present.
    if ((AmlByteEncoding->Attribute & AML_HAS_PKG_LENGTH) != 0) {
      Buffer    = (CONST UINT8 *)AmlStreamGetCurrPos (FStream);
      PkgOffset = AmlGetPkgLength (Buffer, &PkgLength);
      if (PkgOffset == 0) {
        ASSERT (0);
        return EFI_INVALID_PARAMETER;
      }

      // Print the package length.
      AMLDBG_DUMP_RAW (Buffer, PkgOffset);

      // Adjust the size of the stream if it is valid  package length.
      FreeSpace = AmlStreamGetFreeSpace (FStream);
      if (FreeSpace > PkgLength) {
        // Reduce the stream size by (FreeSpace - PkgLength) bytes.
        AmlStreamReduceMaxBufferSize (FStream, FreeSpace - PkgLength);
      } else if (FreeSpace != PkgLength) {
        ASSERT (0);
        return EFI_INVALID_PARAMETER;
      }

      Status = AmlStreamProgress (FStream, PkgOffset);
      if (EFI_ERROR (Status)) {
        ASSERT (0);
        return Status;
      }
    }
  } else if ((AmlByteEncoding->Attribute & AML_HAS_PKG_LENGTH) != 0) {
    // The stream terminated unexpectedly. A PkgLen had to be parsed.
    ASSERT (0);
    return EFI_INVALID_PARAMETER;
  }

  // 4. Create an Object Node.
  Status = AmlCreateObjectNode (
             AmlByteEncoding,
             PkgLength,
             (AML_OBJECT_NODE **)OutNode
             );
  ASSERT_EFI_ERROR (Status);

  return Status;
}

/** Parse a FieldPkgLen.

  A FieldPkgLen can only be found in a field list, i.e. in a NamedField field
  element. The PkgLen is otherwise part of the object node structure.
  A data node is created and returned through the OutNode parameter.

  @param  [in]      ParentNode      Parent node to which the parsed
                                    AML construct will be attached.
  @param  [in]      ExpectedFormat  Format of the AML construct to parse.
  @param  [in, out] FStream         Forward stream containing the AML bytecode
                                    to parse.
                                    The stream must not be at its end.
  @param  [out]     OutNode         Pointer holding the node created from the
                                    parsed AML bytecode.

  @retval EFI_SUCCESS             The function completed successfully.
  @retval EFI_BUFFER_TOO_SMALL    No space left in the buffer.
  @retval EFI_INVALID_PARAMETER   Invalid parameter.
  @retval EFI_OUT_OF_RESOURCES    Could not allocate memory.
**/
STATIC
EFI_STATUS
EFIAPI
AmlParseFieldPkgLen (
  IN      CONST AML_NODE_HEADER   *ParentNode,
  IN            AML_PARSE_FORMAT  ExpectedFormat,
  IN  OUT       AML_STREAM        *FStream,
  OUT       AML_NODE_HEADER       **OutNode
  )
{
  EFI_STATUS   Status;
  EFI_STATUS   Status1;
  CONST UINT8  *Buffer;
  UINT32       PkgOffset;
  UINT32       PkgLength;

  if (!AmlNodeHasAttribute (
         (CONST AML_OBJECT_NODE *)ParentNode,
         AML_IS_FIELD_ELEMENT
         )                                ||
      (ExpectedFormat != EAmlFieldPkgLen) ||
      !IS_STREAM (FStream)                ||
      IS_END_OF_STREAM (FStream)          ||
      !IS_STREAM_FORWARD (FStream)        ||
      (OutNode == NULL))
  {
    ASSERT (0);
    return EFI_INVALID_PARAMETER;
  }

  Buffer = (CONST UINT8 *)AmlStreamGetCurrPos (FStream);

  PkgOffset = AmlGetPkgLength (Buffer, &PkgLength);
  if (PkgOffset == 0) {
    ASSERT (0);
    return EFI_INVALID_PARAMETER;
  }

  // Warning: Since, updating of field elements is not supported, store the
  // FieldPkgLength in a Data Node as a raw buffer.
  Status = AmlCreateDataNode (
             AmlTypeToNodeDataType (ExpectedFormat),
             Buffer,
             PkgOffset,
             (AML_DATA_NODE **)OutNode
             );
  if (EFI_ERROR (Status)) {
    ASSERT (0);
    return Status;
  }

  AMLDBG_DUMP_RAW (Buffer, PkgOffset);

  Status = AmlStreamProgress (FStream, PkgOffset);
  if (EFI_ERROR (Status)) {
    Status1 = AmlDeleteNode (*OutNode);
    ASSERT_EFI_ERROR (Status1);
    ASSERT (0);
  }

  return Status;
}

/** Array of functions pointers to parse the AML constructs.

  The AML Byte encoding tables in Aml.c describe the format of the AML
  statements. The AML_PARSE_FORMAT enum definition lists these constructs
  and the corresponding parsing functions.
*/
AML_PARSE_FUNCTION  mParseType[EAmlParseFormatMax] = {
  NULL,                    // EAmlNone
  AmlParseUIntX,           // EAmlUInt8
  AmlParseUIntX,           // EAmlUInt16
  AmlParseUIntX,           // EAmlUInt32
  AmlParseUIntX,           // EAmlUInt64
  AmlParseObject,          // EAmlObject
  AmlParseNameString,      // EAmlName
  AmlParseString,          // EAmlString
  AmlParseFieldPkgLen      // EAmlFieldPkgLen
};

/** Check whether the NameString stored in the data node is a method invocation.
    If so, create a method invocation node and return it.

  @param  [in]      ParentNode        Node to which the parsed AML construct
                                      will be attached.
  @param  [in]      DataNode          Data node containing a NameString,
                                      potentially being a method invocation.
  @param  [in, out] NameSpaceRefList  List of namespace reference nodes.
  @param  [out]     OutNode           Pointer holding the method invocation
                                      node if the NameString contained in the
                                      data node is a method invocation.
                                      NULL otherwise.

  @retval EFI_SUCCESS             The function completed successfully.
  @retval EFI_BUFFER_TOO_SMALL    No space left in the buffer.
  @retval EFI_INVALID_PARAMETER   Invalid parameter.
  @retval EFI_OUT_OF_RESOURCES    Could not allocate memory.
**/
STATIC
EFI_STATUS
EFIAPI
AmlCheckAndParseMethodInvoc (
  IN  CONST AML_NODE_HEADER  *ParentNode,
  IN        AML_DATA_NODE    *DataNode,
  IN  OUT   LIST_ENTRY       *NameSpaceRefList,
  OUT   AML_OBJECT_NODE      **OutNode
  )
{
  EFI_STATUS              Status;
  AML_NAMESPACE_REF_NODE  *NameSpaceRefNode;
  AML_OBJECT_NODE         *MethodInvocationNode;
  AML_STREAM              FStream;

  if ((!IS_AML_ROOT_NODE (ParentNode)                     &&
       !IS_AML_OBJECT_NODE (ParentNode))                  ||
      !IS_AML_DATA_NODE (DataNode)                        ||
      (DataNode->DataType != EAmlNodeDataTypeNameString)  ||
      (NameSpaceRefList == NULL)                          ||
      (OutNode == NULL))
  {
    ASSERT (0);
    return EFI_INVALID_PARAMETER;
  }

  // Initialize a stream containing the NameString which is checked.
  Status = AmlStreamInit (
             &FStream,
             DataNode->Buffer,
             DataNode->Size,
             EAmlStreamDirectionForward
             );
  if (EFI_ERROR (Status)) {
    ASSERT (0);
    return Status;
  }

  // Check whether the NameString is a method invocation.
  NameSpaceRefNode = NULL;
  Status           = AmlIsMethodInvocation (
                       ParentNode,
                       &FStream,
                       NameSpaceRefList,
                       &NameSpaceRefNode
                       );
  if (EFI_ERROR (Status)) {
    ASSERT (0);
    return Status;
  }

  MethodInvocationNode = NULL;
  if (NameSpaceRefNode != NULL) {
    // A matching method definition has been found.
    // Create a method invocation node.
    Status = AmlCreateMethodInvocationNode (
               NameSpaceRefNode,
               (AML_DATA_NODE *)DataNode,
               &MethodInvocationNode
               );
    if (EFI_ERROR (Status)) {
      ASSERT (0);
      return Status;
    }
  }

  *OutNode = MethodInvocationNode;

  return EFI_SUCCESS;
}

/** Call the appropriate function to parse the AML construct in the stream.

  The ExpectedFormat parameter allows to choose the right parsing function.
  An object node or a data node is created according to format.

  @param  [in]      ParentNode        Node to which the parsed AML construct
                                      will be attached.
  @param  [in]      ExpectedFormat    Format of the AML construct to parse.
  @param  [in, out] FStream           Forward stream containing the AML
                                      bytecode to parse.
                                      The stream must not be at its end.
  @param  [in, out] NameSpaceRefList  List of namespace reference nodes.
  @param  [out]     OutNode           Pointer holding the node created from the
                                      parsed AML bytecode.

  @retval EFI_SUCCESS             The function completed successfully.
  @retval EFI_BUFFER_TOO_SMALL    No space left in the buffer.
  @retval EFI_INVALID_PARAMETER   Invalid parameter.
  @retval EFI_OUT_OF_RESOURCES    Could not allocate memory.
**/
STATIC
EFI_STATUS
EFIAPI
AmlParseArgument (
  IN      CONST AML_NODE_HEADER   *ParentNode,
  IN            AML_PARSE_FORMAT  ExpectedFormat,
  IN  OUT       AML_STREAM        *FStream,
  IN  OUT       LIST_ENTRY        *NameSpaceRefList,
  OUT       AML_NODE_HEADER       **OutNode
  )
{
  EFI_STATUS          Status;
  AML_PARSE_FUNCTION  ParsingFunction;
  AML_DATA_NODE       *DataNode;
  AML_OBJECT_NODE     *MethodInvocationNode;

  if ((!IS_AML_ROOT_NODE (ParentNode)         &&
       !IS_AML_OBJECT_NODE (ParentNode))      ||
      (ExpectedFormat >= EAmlParseFormatMax)  ||
      !IS_STREAM (FStream)                    ||
      IS_END_OF_STREAM (FStream)              ||
      !IS_STREAM_FORWARD (FStream)            ||
      (NameSpaceRefList == NULL)              ||
      (OutNode == NULL))
  {
    ASSERT (0);
    return EFI_INVALID_PARAMETER;
  }

  ParsingFunction = mParseType[ExpectedFormat];
  if (ParsingFunction == NULL) {
    ASSERT (0);
    return EFI_INVALID_PARAMETER;
  }

  // Note: The ParsingFunction moves the stream forward as it
  // consumes the AML bytecode
  Status = ParsingFunction (
             ParentNode,
             ExpectedFormat,
             FStream,
             OutNode
             );
  if (EFI_ERROR (Status)) {
    ASSERT (0);
    return Status;
  }

  // Check whether the parsed argument is a NameString when an object
  // is expected. In such case, it could be a method invocation.
  DataNode = (AML_DATA_NODE *)*OutNode;
  if (IS_AML_DATA_NODE (DataNode)                         &&
      (DataNode->DataType == EAmlNodeDataTypeNameString)  &&
      (ExpectedFormat == EAmlObject))
  {
    Status = AmlCheckAndParseMethodInvoc (
               ParentNode,
               (AML_DATA_NODE *)*OutNode,
               NameSpaceRefList,
               &MethodInvocationNode
               );
    if (EFI_ERROR (Status)) {
      ASSERT (0);
      return Status;
    }

    // A method invocation node has been created and the DataNode containing
    // the NameString has been attached to the MethodInvocationNode.
    // Replace the OutNode with the MethodInvocationNode.
    if (MethodInvocationNode != NULL) {
      *OutNode = (AML_NODE_HEADER *)MethodInvocationNode;
    }
  }

  return Status;
}

/** Parse the Bytelist in the stream.
    According to the content of the stream, create data node(s)
    and add them to the variable list of arguments.
    The byte list may be a list of resource data element or a simple byte list.

  @param  [in]  BufferNode    Object node having a byte list.
  @param  [in, out] FStream   Forward stream containing the AML bytecode
                              to parse.
                              The stream must not be at its end.

  @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
AmlParseByteList (
  IN      AML_OBJECT_NODE  *BufferNode,
  IN  OUT AML_STREAM       *FStream
  )
{
  EFI_STATUS       Status;
  AML_NODE_HEADER  *NewNode;
  CONST UINT8      *Buffer;
  UINT32           BufferSize;

  // Check whether the node is an Object Node and has byte list.
  if (!AmlNodeHasAttribute (BufferNode, AML_HAS_BYTE_LIST)  ||
      !IS_STREAM (FStream)                                  ||
      IS_END_OF_STREAM (FStream)                            ||
      !IS_STREAM_FORWARD (FStream))
  {
    ASSERT (0);
    return EFI_INVALID_PARAMETER;
  }

  // The buffer contains a list of resource data elements.
  if (AmlRdIsResourceDataBuffer (FStream)) {
    // Parse the resource data elements and add them as data nodes.
    // AmlParseResourceData() moves the stream forward.
    Status = AmlParseResourceData (BufferNode, FStream);
    if (EFI_ERROR (Status)) {
      ASSERT (0);
    }
  } else {
    // The buffer doesn't contain a list of resource data elements.
    // Create a single node holding the whole buffer data.

    // CreateDataNode checks the Buffer and BufferSize values.
    Buffer     = (CONST UINT8 *)AmlStreamGetCurrPos (FStream);
    BufferSize = AmlStreamGetFreeSpace (FStream);

    Status = AmlCreateDataNode (
               EAmlNodeDataTypeRaw,
               Buffer,
               BufferSize,
               (AML_DATA_NODE **)&NewNode
               );
    if (EFI_ERROR (Status)) {
      ASSERT (0);
      return Status;
    }

    Status = AmlVarListAddTailInternal (
               (AML_NODE_HEADER *)BufferNode,
               NewNode
               );
    if (EFI_ERROR (Status)) {
      ASSERT (0);
      AmlDeleteTree (NewNode);
      return Status;
    }

    AMLDBG_DUMP_RAW (Buffer, BufferSize);

    // Move the stream forward as we have consumed the Buffer.
    Status = AmlStreamProgress (FStream, BufferSize);
    if (EFI_ERROR (Status)) {
      ASSERT (0);
    }
  }

  return Status;
}

/** Parse the list of fixed arguments of the input ObjectNode.

  For each argument, create a node and add it to the fixed argument list
  of the Node.
  If a fixed argument has children, parse them.

  @param  [in]  ObjectNode        Object node to parse the fixed arguments
                                  from.
  @param  [in]  FStream           Forward stream containing the AML
                                  bytecode to parse.
                                  The stream must not be at its end.
  @param  [in]  NameSpaceRefList  List of namespace reference nodes.

  @retval EFI_SUCCESS             The function completed successfully.
  @retval EFI_BUFFER_TOO_SMALL    No space left in the buffer.
  @retval EFI_INVALID_PARAMETER   Invalid parameter.
  @retval EFI_OUT_OF_RESOURCES    Could not allocate memory.
**/
EFI_STATUS
EFIAPI
AmlParseFixedArguments (
  IN  AML_OBJECT_NODE  *ObjectNode,
  IN  AML_STREAM       *FStream,
  IN  LIST_ENTRY       *NameSpaceRefList
  )
{
  EFI_STATUS  Status;

  AML_NODE_HEADER  *FixedArgNode;
  AML_STREAM       FixedArgFStream;

  EAML_PARSE_INDEX        TermIndex;
  EAML_PARSE_INDEX        MaxIndex;
  CONST AML_PARSE_FORMAT  *Format;

  // Fixed arguments of method invocations node are handled differently.
  if (!IS_AML_OBJECT_NODE (ObjectNode)                              ||
      AmlNodeCompareOpCode (ObjectNode, AML_METHOD_INVOC_OP, 0)     ||
      !IS_STREAM (FStream)                                          ||
      IS_END_OF_STREAM (FStream)                                    ||
      !IS_STREAM_FORWARD (FStream)                                  ||
      (NameSpaceRefList == NULL))
  {
    ASSERT (0);
    return EFI_INVALID_PARAMETER;
  }

  TermIndex = EAmlParseIndexTerm0;
  MaxIndex  = (EAML_PARSE_INDEX)AmlGetFixedArgumentCount (
                                  (AML_OBJECT_NODE *)ObjectNode
                                  );
  if ((ObjectNode->AmlByteEncoding != NULL)   &&
      (ObjectNode->AmlByteEncoding->Format != NULL))
  {
    Format = ObjectNode->AmlByteEncoding->Format;
  } else {
    ASSERT (0);
    return EFI_INVALID_PARAMETER;
  }

  // Parse all the FixedArgs.
  while ((TermIndex < MaxIndex)       &&
         !IS_END_OF_STREAM (FStream)  &&
         (Format[TermIndex] != EAmlNone))
  {
    // Initialize a FixedArgStream to parse the current fixed argument.
    Status = AmlStreamInitSubStream (FStream, &FixedArgFStream);
    if (EFI_ERROR (Status)) {
      ASSERT (0);
      return Status;
    }

    // Parse the current fixed argument.
    Status = AmlParseArgument (
               (CONST AML_NODE_HEADER *)ObjectNode,
               Format[TermIndex],
               &FixedArgFStream,
               NameSpaceRefList,
               &FixedArgNode
               );
    if (EFI_ERROR (Status)) {
      ASSERT (0);
      return Status;
    }

    // Add the fixed argument to the parent node's fixed argument list.
    // FixedArgNode can be an object or data node.
    Status = AmlSetFixedArgument (
               (AML_OBJECT_NODE *)ObjectNode,
               TermIndex,
               FixedArgNode
               );
    if (EFI_ERROR (Status)) {
      ASSERT (0);
      // Delete the sub-tree if the insertion failed.
      // Otherwise its reference will be lost.
      // Use DeleteTree because if the argument was a method invocation,
      // multiple nodes have been created.
      AmlDeleteTree (FixedArgNode);
      return Status;
    }

    // Parse the AML bytecode of the FixedArgNode if this is an object node.
    if (IS_AML_OBJECT_NODE (FixedArgNode) &&
        !IS_END_OF_STREAM (&FixedArgFStream))
    {
      Status = AmlParseStream (
                 FixedArgNode,
                 &FixedArgFStream,
                 NameSpaceRefList
                 );
      if (EFI_ERROR (Status)) {
        ASSERT (0);
        return Status;
      }
    }

    // Move the stream forward as we have consumed the sub-stream.
    Status = AmlStreamProgress (
               FStream,
               AmlStreamGetIndex (&FixedArgFStream)
               );
    if (EFI_ERROR (Status)) {
      ASSERT (0);
      return Status;
    }

    TermIndex++;
  } // while

  return EFI_SUCCESS;
}

/** Parse the variable list of arguments of the input ObjectNode.

  For each variable argument, create a node and add it to the variable list of
  arguments of the Node.
  If a variable argument has children, parse them recursively.

  The arguments of method invocation nodes are added to the variable list of
  arguments of the method invocation node. It is necessary to first get
  the number of arguments to parse for this kind of node. A method invocation
  can have at most 7 fixed arguments.

  @param  [in]  Node              Node to parse the variable arguments
                                  from.
  @param  [in]  FStream           Forward stream containing the AML
                                  bytecode to parse.
                                  The stream must not be at its end.
  @param  [in]  NameSpaceRefList  List of namespace reference nodes.

  @retval EFI_SUCCESS             The function completed successfully.
  @retval EFI_BUFFER_TOO_SMALL    No space left in the buffer.
  @retval EFI_INVALID_PARAMETER   Invalid parameter.
  @retval EFI_OUT_OF_RESOURCES    Could not allocate memory.
**/
EFI_STATUS
EFIAPI
AmlParseVariableArguments (
  IN  AML_NODE_HEADER  *Node,
  IN  AML_STREAM       *FStream,
  IN  LIST_ENTRY       *NameSpaceRefList
  )
{
  EFI_STATUS  Status;

  BOOLEAN  IsMethodInvocation;
  UINT8    MethodInvocationArgCount;

  AML_NODE_HEADER  *VarArgNode;
  AML_STREAM       VarArgFStream;

  if ((!AmlNodeHasAttribute (
          (CONST AML_OBJECT_NODE *)Node,
          AML_HAS_CHILD_OBJ
          ) &&
       !IS_AML_ROOT_NODE (Node))        ||
      !IS_STREAM (FStream)              ||
      IS_END_OF_STREAM (FStream)        ||
      !IS_STREAM_FORWARD (FStream)      ||
      (NameSpaceRefList == NULL))
  {
    ASSERT (0);
    return EFI_INVALID_PARAMETER;
  }

  Status = AmlGetMethodInvocationArgCount (
             (CONST AML_OBJECT_NODE *)Node,
             &IsMethodInvocation,
             &MethodInvocationArgCount
             );
  if (EFI_ERROR (Status)) {
    ASSERT (0);
    return Status;
  }

  // Parse variable arguments while the Stream is not empty.
  while (!IS_END_OF_STREAM (FStream)) {
    // If the number of variable arguments are counted, decrement the counter.
    if ((IsMethodInvocation) && (MethodInvocationArgCount-- == 0)) {
      return EFI_SUCCESS;
    }

    // Initialize a VarArgStream to parse the current variable argument.
    Status = AmlStreamInitSubStream (FStream, &VarArgFStream);
    if (EFI_ERROR (Status)) {
      ASSERT (0);
      return Status;
    }

    // Parse the current variable argument.
    Status = AmlParseArgument (
               Node,
               EAmlObject,
               &VarArgFStream,
               NameSpaceRefList,
               &VarArgNode
               );
    if (EFI_ERROR (Status)) {
      ASSERT (0);
      return Status;
    }

    // Add the variable argument to its parent variable list of arguments.
    // VarArgNode can be an object or data node.
    Status = AmlVarListAddTailInternal (
               (AML_NODE_HEADER *)Node,
               VarArgNode
               );
    if (EFI_ERROR (Status)) {
      ASSERT (0);
      // Delete the sub-tree if the insertion failed.
      // Otherwise its reference will be lost.
      // Use DeleteTree because if the argument was a method invocation,
      // multiple nodes have been created.
      AmlDeleteTree (VarArgNode);
      return Status;
    }

    // Parse the AML bytecode of the VarArgNode if this is an object node.
    if (IS_AML_OBJECT_NODE (VarArgNode)       &&
        (!IS_END_OF_STREAM (&VarArgFStream)))
    {
      Status = AmlParseStream (VarArgNode, &VarArgFStream, NameSpaceRefList);
      if (EFI_ERROR (Status)) {
        ASSERT (0);
        return Status;
      }
    }

    // Move the stream forward as we have consumed the sub-stream.
    Status = AmlStreamProgress (
               FStream,
               AmlStreamGetIndex (&VarArgFStream)
               );
    if (EFI_ERROR (Status)) {
      ASSERT (0);
      return Status;
    }
  } // while

  // If the number of variable arguments are counted, check all the
  // MethodInvocationArgCount have been parsed.
  if (IsMethodInvocation && (MethodInvocationArgCount != 0)) {
    ASSERT (0);
    return EFI_INVALID_PARAMETER;
  }

  return Status;
}

/** Parse the AML stream and populate the root node.

  @param  [in]      RootNode          RootNode to which the children are
                                      added.
  @param  [in, out] FStream           Forward stream containing the AML
                                      bytecode to parse.
                                      The stream must not be at its end.
  @param  [in, out] NameSpaceRefList  List of namespace reference nodes.

  @retval EFI_SUCCESS             The function completed successfully.
  @retval EFI_BUFFER_TOO_SMALL    No space left in the buffer.
  @retval EFI_INVALID_PARAMETER   Invalid parameter.
  @retval EFI_OUT_OF_RESOURCES    Could not allocate memory.
**/
STATIC
EFI_STATUS
EFIAPI
AmlPopulateRootNode (
  IN      AML_ROOT_NODE  *RootNode,
  IN  OUT AML_STREAM     *FStream,
  IN  OUT LIST_ENTRY     *NameSpaceRefList
  )
{
  EFI_STATUS  Status;

  if (!IS_AML_ROOT_NODE (RootNode)  ||
      !IS_STREAM (FStream)          ||
      IS_END_OF_STREAM (FStream)    ||
      !IS_STREAM_FORWARD (FStream)  ||
      (NameSpaceRefList == NULL))
  {
    ASSERT (0);
    return EFI_INVALID_PARAMETER;
  }

  // A Root Node only has variable arguments.
  Status = AmlParseVariableArguments (
             (AML_NODE_HEADER *)RootNode,
             FStream,
             NameSpaceRefList
             );
  ASSERT_EFI_ERROR (Status);

  return Status;
}

/** Parse the AML stream an populate the object node.

  @param  [in]      ObjectNode        ObjectNode to which the children are
                                      added.
  @param  [in, out] FStream           Forward stream containing the AML
                                      bytecode to parse.
                                      The stream must not be at its end.
  @param  [in, out] NameSpaceRefList  List of namespace reference nodes.

  @retval EFI_SUCCESS             The function completed successfully.
  @retval EFI_BUFFER_TOO_SMALL    No space left in the buffer.
  @retval EFI_INVALID_PARAMETER   Invalid parameter.
  @retval EFI_OUT_OF_RESOURCES    Could not allocate memory.
**/
STATIC
EFI_STATUS
EFIAPI
AmlPopulateObjectNode (
  IN      AML_OBJECT_NODE  *ObjectNode,
  IN  OUT AML_STREAM       *FStream,
  IN  OUT LIST_ENTRY       *NameSpaceRefList
  )
{
  EFI_STATUS  Status;

  if (!IS_AML_OBJECT_NODE (ObjectNode)  ||
      !IS_STREAM (FStream)              ||
      IS_END_OF_STREAM (FStream)        ||
      !IS_STREAM_FORWARD (FStream)      ||
      (NameSpaceRefList == NULL))
  {
    ASSERT (0);
    return EFI_INVALID_PARAMETER;
  }

  Status = EFI_SUCCESS;

  // Don't parse the fixed arguments of method invocation nodes.
  // The AML encoding for method invocations in the ACPI specification 6.3 is:
  // MethodInvocation := NameString TermArgList
  // Since the AML specification does not define an OpCode for method
  // invocation, this AML parser defines a pseudo opcode and redefines the
  // grammar for simplicity as:
  // MethodInvocation := MethodInvocationOp NameString ArgumentCount TermArgList
  // ArgumentCount    := ByteData
  // Due to this difference, the MethodInvocationOp and the fixed argument
  // i.e. ArgumentCount is not available in the AML stream and need to be
  // handled differently.
  if (!AmlNodeCompareOpCode (ObjectNode, AML_METHOD_INVOC_OP, 0)) {
    // Parse the fixed list of arguments.
    Status = AmlParseFixedArguments (
               ObjectNode,
               FStream,
               NameSpaceRefList
               );
    if (EFI_ERROR (Status)) {
      ASSERT (0);
      return Status;
    }
  }

  // Save the association [node reference/pathname] in the NameSpaceRefList.
  // This allows to identify method invocations from other namespace
  // paths. Method invocation need to be parsed differently.
  if (AmlNodeHasAttribute (
        (CONST AML_OBJECT_NODE *)ObjectNode,
        AML_IN_NAMESPACE
        ))
  {
    Status = AmlAddNameSpaceReference (
               (CONST AML_OBJECT_NODE *)ObjectNode,
               NameSpaceRefList
               );
    if (EFI_ERROR (Status)) {
      ASSERT (0);
      return Status;
    }
  }

  if (!IS_END_OF_STREAM (FStream)) {
    // Parse the variable list of arguments if present.
    if (AmlNodeHasAttribute (ObjectNode, AML_HAS_CHILD_OBJ)) {
      Status = AmlParseVariableArguments (
                 (AML_NODE_HEADER *)ObjectNode,
                 FStream,
                 NameSpaceRefList
                 );
    } else if (AmlNodeHasAttribute (ObjectNode, AML_HAS_BYTE_LIST)) {
      // Parse the byte list if present.
      Status = AmlParseByteList (
                 ObjectNode,
                 FStream
                 );
    } else if (AmlNodeHasAttribute (ObjectNode, AML_HAS_FIELD_LIST)) {
      // Parse the field list if present.
      Status = AmlParseFieldList (
                 ObjectNode,
                 FStream,
                 NameSpaceRefList
                 );
    }

    // Check status and assert
    if (EFI_ERROR (Status)) {
      ASSERT (0);
    }
  }

  return Status;
}

/** Invoke the appropriate parsing functions based on the Node type.

  @param  [in]      Node              Node from which the children are parsed.
                                      Must be a root node or an object node.
  @param  [in]      FStream           Forward stream containing the AML
                                      bytecode to parse.
                                      The stream must not be at its end.
  @param  [in]      NameSpaceRefList  List of namespace reference nodes.

  @retval EFI_SUCCESS             The function completed successfully.
  @retval EFI_BUFFER_TOO_SMALL    No space left in the buffer.
  @retval EFI_INVALID_PARAMETER   Invalid parameter.
  @retval EFI_OUT_OF_RESOURCES    Could not allocate memory.
**/
STATIC
EFI_STATUS
EFIAPI
AmlParseStream (
  IN  AML_NODE_HEADER  *Node,
  IN  AML_STREAM       *FStream,
  IN  LIST_ENTRY       *NameSpaceRefList
  )
{
  EFI_STATUS  Status;

  if (IS_AML_ROOT_NODE (Node)) {
    Status = AmlPopulateRootNode (
               (AML_ROOT_NODE *)Node,
               FStream,
               NameSpaceRefList
               );
    if (EFI_ERROR (Status)) {
      ASSERT (0);
    }
  } else if (IS_AML_OBJECT_NODE (Node)) {
    Status = AmlPopulateObjectNode (
               (AML_OBJECT_NODE *)Node,
               FStream,
               NameSpaceRefList
               );
    if (EFI_ERROR (Status)) {
      ASSERT (0);
    }
  } else {
    // Data node or other.
    ASSERT (0);
    Status = EFI_INVALID_PARAMETER;
  }

  return Status;
}

/** Parse the definition block.

  This function parses the whole AML blob. It starts with the ACPI DSDT/SSDT
  header and then parses the AML bytestream.
  A tree structure is returned via the RootPtr.
  The tree must be deleted with the AmlDeleteTree function.

  @param  [in]  DefinitionBlock   Pointer to the definition block.
  @param  [out] RootPtr           Pointer to the root node of the tree.

  @retval EFI_SUCCESS             The function completed successfully.
  @retval EFI_BUFFER_TOO_SMALL    No space left in the buffer.
  @retval EFI_INVALID_PARAMETER   Invalid parameter.
  @retval EFI_OUT_OF_RESOURCES    Could not allocate memory.
**/
EFI_STATUS
EFIAPI
AmlParseDefinitionBlock (
  IN  CONST EFI_ACPI_DESCRIPTION_HEADER  *DefinitionBlock,
  OUT       AML_ROOT_NODE                **RootPtr
  )
{
  EFI_STATUS     Status;
  EFI_STATUS     Status1;
  AML_STREAM     Stream;
  AML_ROOT_NODE  *Root;

  LIST_ENTRY  NameSpaceRefList;

  UINT8   *Buffer;
  UINT32  MaxBufferSize;

  if ((DefinitionBlock == NULL)   ||
      (RootPtr == NULL))
  {
    ASSERT (0);
    return EFI_INVALID_PARAMETER;
  }

  Buffer = (UINT8 *)DefinitionBlock + sizeof (EFI_ACPI_DESCRIPTION_HEADER);
  if (DefinitionBlock->Length < sizeof (EFI_ACPI_DESCRIPTION_HEADER)) {
    ASSERT (0);
    return EFI_INVALID_PARAMETER;
  }

  MaxBufferSize = DefinitionBlock->Length -
                  (UINT32)sizeof (EFI_ACPI_DESCRIPTION_HEADER);

  // Create a root node.
  Status = AmlCreateRootNode (
             (EFI_ACPI_DESCRIPTION_HEADER *)DefinitionBlock,
             &Root
             );
  if (EFI_ERROR (Status)) {
    ASSERT (0);
    return Status;
  }

  *RootPtr = Root;

  if (MaxBufferSize == 0) {
    return EFI_SUCCESS;
  }

  // Initialize a stream to parse the AML bytecode.
  Status = AmlStreamInit (
             &Stream,
             Buffer,
             MaxBufferSize,
             EAmlStreamDirectionForward
             );
  if (EFI_ERROR (Status)) {
    ASSERT (0);
    goto error_handler;
  }

  // Initialize the NameSpaceRefList, holding references to nodes declaring
  // a name in the AML namespace.
  InitializeListHead (&NameSpaceRefList);

  // Parse the whole AML blob.
  Status = AmlParseStream (
             (AML_NODE_HEADER *)Root,
             &Stream,
             &NameSpaceRefList
             );
  if (EFI_ERROR (Status)) {
    ASSERT (0);
    goto error_handler;
  }

  // Check the whole AML blob has been parsed.
  if (!IS_END_OF_STREAM (&Stream)) {
    ASSERT (0);
    Status = EFI_INVALID_PARAMETER;
    goto error_handler;
  }

  // Print the list of NameSpace reference nodes.
  // AmlDbgPrintNameSpaceRefList (&NameSpaceRefList);

  // Delete the NameSpaceRefList
  goto exit_handler;

error_handler:
  if (Root != NULL) {
    AmlDeleteTree ((AML_NODE_HEADER *)Root);
  }

exit_handler:
  Status1 = AmlDeleteNameSpaceRefList (&NameSpaceRefList);
  if (EFI_ERROR (Status1)) {
    ASSERT (0);
    if (!EFI_ERROR (Status)) {
      return Status1;
    }
  }

  return Status;
}
