/** @file
  AML NameSpace.

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

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

/* Lexicon:

  NameSeg:
  - An ASL NameSeg is a name made of at most 4 chars.
    Cf. ACPI 6.3 specification, s19.2.2 'Name and Pathname Terms'.
  - An AML NameSeg is a name made of 4 chars.
    Cf. ACPI 6.3 specification, s20.2.2 'Name Objects Encoding'.

  NameString:
  A NameString is analogous to a pathname. It is made of 0 to 255 NameSegs.
  A NameString can be prefixed by a root char ('\') or 0 to 255 carets ('^').

  A NameString can be ASL or AML encoded.
  AML NameStrings can have a NameString prefix (dual or multi-name prefix)
  between the root/carets and the list of NameSegs. If the prefix is the
  multi-name prefix, then the number of NameSegs is encoded on one single byte.
  Cf. ACPI 6.3 specification, s19.2.2 'Name and Pathname Terms'.
  Cf. ACPI 6.3 specification, s20.2.2 'Name Objects Encoding'.

  Namespace level:
  One level in the AML Namespace level corresponds to one NameSeg. In ASL,
  objects names are NameStrings. This means a device can have a name which
  spans multiple levels.
  E.g.: The ASL code: Device (CLU0.CPU0) corresponds to 2 levels.

  Namespace node:
  A namespace node is an object node which has an associated name, and which
  changes the current scope.
  E.g.:
    1. The "Device ()" ASL statement adds a name to the AML namespace and
       changes the current scope to the device scope, this is a namespace node.
    2. The "Scope ()" ASL statement changes the current scope, this is a
       namespace node.
    3. A method invocation has a name, but does not add nor change the current
       AML scope. This is not a namespace node.

  - Object nodes with the AML_IN_NAMESPACE attribute are namespace nodes.
    Buffers (), Packages (), etc. are not part of the namespace. It is however
    possible to associate them with a name with the Name () ASL statement.
  - The root node is considered as being part of the namespace.
  - Some resource data elements can have a name when defining them in
    an ASL statement. However, this name is stripped by the ASL compiler.
    Thus, they don't have a name in the AML bytestream, and are therefore
    not part of the AML namespace.
  - Field list elements are part of the namespace.
    Fields created by an CreateXXXField () ASL statement are part of the
    namespace. The name of these node can be found in the third or fourth
    fixed argument. The exact index of the name can be found in the NameIndex
    field of the AML_BYTE_ENCODING array.
    Field are at the same level as their ASL statement in the namespace.
    E.g:
    Scope (\) {
      OperationRegion (REG0, SystemIO, 0x100, 0x100)
      Field (REG0, ByteAcc, NoLock, Preserve) {
        FIE0, 1,
        FIE1, 5
      }

      Name (BUF0, Buffer (100) {})
      CreateField (BUF0, 5, 2, MEM0)
    }

    produces this namespace:
    \ (Root)
    \-REG0
    \-FIE0
    \-FIE1
    \-BUF0
    \-MEM0

  Raw AML pathname or Raw AML NameString:
  In order to easily manipulate AML NameStrings, the non-NameSegs chars are
  removed in raw pathnames/NameStrings. Non-NameSegs chars are the
  root char ('\'), carets ('^') and NameString prefixes (Dual/Multi name char).
  E.g. The following terminology is defined in this AML Library.
  ASL absolute path:  "[RootChar]AAAA.BBBB.CCCC\0"
  AML absolute path:  "[RootChar][MultiNamePrefix][3(NameSegs)]AAAABBBBCCCC"
  Raw absolute path:  "AAAABBBBCCCC"

  Multi-name:
  A NameString with at least 2 NameSegs. A node can have a name which spans
  multiple namespace levels.
*/

#include <NameSpace/AmlNameSpace.h>

#include <AmlCoreInterface.h>
#include <AmlDbgPrint/AmlDbgPrint.h>
#include <String/AmlString.h>
#include <Tree/AmlNode.h>
#include <Tree/AmlTree.h>
#include <Tree/AmlTreeTraversal.h>

/** Context of the path search callback function.

  The function finding a node from a path and a reference node enumerates
  the namespace nodes in the tree and compares their absolute path with the
  searched path. The enumeration function uses a callback function that can
  receive a context.
  This structure is used to store the context information required in the
  callback function.
*/
typedef struct AmlPathSearchContext {
  /// Backward stream holding the raw AML absolute searched path.
  AML_STREAM         *SearchPathBStream;

  /// An empty backward stream holding a pre-allocated buffer. This prevents
  /// from having to do multiple allocations during the search.
  /// This stream is used to query the raw AML absolute path of the node
  /// currently being probed.
  AML_STREAM         *CurrNodePathBStream;

  /// If the node being visited is the node being searched,
  /// i.e. its path and the searched path match,
  /// save its reference in this pointer.
  AML_NODE_HEADER    *OutNode;
} AML_PATH_SEARCH_CONTEXT;

/** Return the first AML namespace node up in the parent hierarchy.

    Return the root node if no namespace node is found is the hierarchy.

  @param  [in]  Node      Node to look at the parents from.
                          If Node is the root node, OutNode is NULL.
  @param  [out] OutNode   If a namespace node is found, pointer to the
                          first namespace node of Node's parents.
                          Stop at the root node otherwise.

  @retval EFI_SUCCESS             The function completed successfully.
  @retval EFI_INVALID_PARAMETER   Invalid parameter.
  **/
EFI_STATUS
EFIAPI
AmlGetFirstAncestorNameSpaceNode (
  IN  CONST AML_NODE_HEADER  *Node,
  OUT       AML_NODE_HEADER  **OutNode
  )
{
  if (!IS_AML_NODE_VALID (Node) ||
      (OutNode == NULL))
  {
    ASSERT (0);
    return EFI_INVALID_PARAMETER;
  }

  // If Node is the root node, return NULL.
  if (IS_AML_ROOT_NODE (Node)) {
    *OutNode = NULL;
    return EFI_SUCCESS;
  } else {
    // Else, get the parent node.
    Node = AmlGetParent ((AML_NODE_HEADER *)Node);
    if (!IS_AML_NODE_VALID (Node)) {
      ASSERT (0);
      return EFI_INVALID_PARAMETER;
    }
  }

  // Continue getting the parent node while no namespace node is encountered.
  while (TRUE) {
    if (IS_AML_ROOT_NODE (Node)) {
      break;
    } else if (AmlNodeHasAttribute (
                 (CONST AML_OBJECT_NODE *)Node,
                 AML_IN_NAMESPACE
                 ))
    {
      break;
    } else {
      Node = AmlGetParent ((AML_NODE_HEADER *)Node);
      if (!IS_AML_NODE_VALID (Node)) {
        ASSERT (0);
        return EFI_INVALID_PARAMETER;
      }
    }
  } // while

  *OutNode = (AML_NODE_HEADER *)Node;
  return EFI_SUCCESS;
}

/** Climb up the AML namespace hierarchy.

  This function get the ancestor namespace node in the AML namespace.
  If Levels is not zero, skip Levels namespace nodes in the AML namespace.
  If Levels is zero, return the first ancestor namespace node.
    I.e. if Levels = n, this function returns the (n + 1) ancestor.

  @param  [in] Node         Pointer to an object node.
  @param  [in, out] Levels  Pointer holding a number of AML namespace levels:
                             - At entry, the number of levels to go up in
                               the AML namespace;
                             - At exit, the number of levels that still need
                               to be climbed in case of a multi-named node.
                               Indeed, if a node with a multi-name is found,
                               and Levels is less than the number of NameSegs
                               in this name, then the function returns with
                               the number of levels that still need to be
                               climbed.
                               E.g.: If the first ancestor node's name is
                                     "AAAA.BBBB.CCCC" and
                                     Levels = 2  -> i.e go up 3 levels
                                      \
                                      ...
                                      \-"AAAA.BBBB.CCCC"    <----- OutNode
                                         \-"DDDD"           <----- Node (Input)

                                     The function should ideally return a node
                                     with the name "AAAA". However, it is not
                                     possible to split the node name
                                     "AAAA.BBBB.CCCC" to "AAAA".
                                     Thus, OutNode is set to the input node,
                                     and Levels = 2.
                               In most cases the number of levels to climb
                               correspond to non multi-name node, and therefore
                               Levels = 0 at exit.
  @param  [out] HasRoot     The returned node in OutNode has an AML absolute
                            name, starting with a root char ('\'), or if OutNode
                            is the root node.
  @param  [out] OutNode     The Levels+1 namespace ancestor of the input node in
                            the AML namespace. Must be the root node or a
                            namespace node.

  @retval EFI_SUCCESS             The function completed successfully.
  @retval EFI_INVALID_PARAMETER   Invalid parameter.
**/
STATIC
EFI_STATUS
EFIAPI
AmlGetAncestorNameSpaceNode (
  IN      CONST AML_OBJECT_NODE  *Node,
  IN OUT        UINT32           *Levels,
  OUT        UINT32              *HasRoot,
  OUT  CONST AML_NODE_HEADER     **OutNode
  )
{
  EFI_STATUS  Status;

  CONST AML_NODE_HEADER  *NameSpaceNode;
  CHAR8                  *NodeName;
  UINT32                 ParentCnt;

  UINT32  Root;
  UINT32  ParentPrefix;
  UINT32  SegCount;

  if (!IS_AML_OBJECT_NODE (Node)    ||
      (Levels == NULL)              ||
      (HasRoot == NULL)             ||
      (OutNode == NULL))
  {
    ASSERT (0);
    return EFI_INVALID_PARAMETER;
  }

  ParentCnt = *Levels;
  *HasRoot  = 0;

  // ParentCnt namespace levels need to be climbed.
  do {
    // Get the next namespace node in the hierarchy.
    Status = AmlGetFirstAncestorNameSpaceNode (
               (CONST AML_NODE_HEADER *)Node,
               (AML_NODE_HEADER **)&NameSpaceNode
               );
    if (EFI_ERROR (Status)) {
      ASSERT (0);
      return Status;
    }

    Node = (CONST AML_OBJECT_NODE *)NameSpaceNode;

    if (IS_AML_ROOT_NODE (Node)) {
      // Node is the root node. It is not possible to go beyond.
      if (ParentCnt != 0) {
        ASSERT (0);
        return EFI_INVALID_PARAMETER;
      }

      *HasRoot = 1;
      break;
    }

    NodeName = AmlNodeGetName ((CONST AML_OBJECT_NODE *)Node);
    if (NodeName == NULL) {
      ASSERT (0);
      return EFI_INVALID_PARAMETER;
    }

    // Analyze the node name.
    Status = AmlParseNameStringInfo (
               NodeName,
               &Root,
               &ParentPrefix,
               &SegCount
               );
    if (EFI_ERROR (Status)) {
      ASSERT (0);
      return Status;
    }

    if (Root != 0) {
      // NodeName is an absolute pathname.
      *HasRoot = Root;

      // If the node has Root then it cannot have ParentPrefixes (Carets).
      if (ParentPrefix != 0) {
        ASSERT (0);
        return EFI_INVALID_PARAMETER;
      }

      if (SegCount == ParentCnt) {
        // There are exactly enough AML namespace levels to consume.
        // This means the root node was the searched node.
        Node = (CONST AML_OBJECT_NODE *)AmlGetRootNode (
                                          (CONST AML_NODE_HEADER *)Node
                                          );
        if (!IS_AML_ROOT_NODE (Node)) {
          ASSERT (0);
          return EFI_INVALID_PARAMETER;
        }

        ParentCnt = 0;
        break;
      } else if (ParentCnt < SegCount) {
        // There are too many AML namespace levels in this name.
        // ParentCnt has the right value, just return.
        break;
      } else {
        // ParentCnt > SegCount
        // Return error as there must be at least ParentCnt AML namespace
        // levels left in the absolute path.
        ASSERT (0);
        return EFI_INVALID_PARAMETER;
      }
    } else {
      // Root is 0.
      if (ParentCnt < SegCount) {
        // NodeName is a relative path.
        // NodeName has enough levels to consume all the ParentCnt.
        // Exit.
        break;
      } else if (SegCount == ParentCnt) {
        // There are exactly enough AML namespace levels to consume.
        if (ParentPrefix == 0) {
          // The node name doesn't have any carets. Get the next namespace
          // node and return.
          Status = AmlGetFirstAncestorNameSpaceNode (
                     (CONST AML_NODE_HEADER *)Node,
                     (AML_NODE_HEADER **)&NameSpaceNode
                     );
          if (EFI_ERROR (Status)) {
            ASSERT (0);
            return Status;
          }

          Node      = (CONST AML_OBJECT_NODE *)NameSpaceNode;
          ParentCnt = 0;
          break;
        } else {
          // The node name has carets. Need to continue climbing the
          // AML namespace.
          ParentCnt = ParentPrefix;
        }
      } else {
        // ParentCnt > SegCount
        // NodeName doesn't have enough levels to consume all the ParentCnt.
        // Update ParentCnt: Consume SegCount levels and add ParentPrefix
        // levels. Continue climbing the tree.
        ParentCnt = ParentCnt + ParentPrefix - SegCount;
      }
    }
  } while (ParentCnt != 0);

  *OutNode = (CONST AML_NODE_HEADER *)Node;
  *Levels  = ParentCnt;

  return EFI_SUCCESS;
}

/** Build the raw absolute AML pathname to Node and write it to a stream.

  A raw AML pathname is an AML pathname where the root char ('\'),
  prefix chars ('^') and NameString prefix byte (e.g.: DualNamePrefix)
  have been removed. A raw AML pathname is a list of concatenated
  NameSegs.

  E.g.:
  ASL absolute path:  "[RootChar]AAAA.BBBB.CCCC\0"
  AML absolute path:  "[RootChar][MultiNamePrefix][3(NameSegs)]AAAABBBBCCCC"
  Raw absolute path:  "AAAABBBBCCCC"

  @param  [in]   Node         Node to build the raw absolute path to
                              Must be a root node, or a namespace node.
  @param  [in]  InputParent   Skip InputParent AML namespace levels before
                              starting building the raw absolute pathname.
                              E.g.: - Node's name being "^AAAA.BBBB.CCCC";
                                    - InputParent = 2;
                                    "BBBB.CCCC" will be skipped (2
                                    levels), and "^AAAA" will remain. The
                                    first caret is not related to InputParent.
  @param  [out]  RawAbsPathBStream  Backward stream to write the raw
                                    pathname to.
                                    If Node is the root node, the Stream data
                                    Buffer will stay empty.
                                    The stream must not be at its end.

  @retval EFI_SUCCESS             The function completed successfully.
  @retval EFI_BUFFER_TOO_SMALL    No space left in the buffer.
  @retval EFI_INVALID_PARAMETER   Invalid parameter.
**/
EFI_STATUS
EFIAPI
AmlGetRawNameSpacePath (
  IN  CONST AML_NODE_HEADER  *Node,
  IN        UINT32           InputParent,
  OUT       AML_STREAM       *RawAbsPathBStream
  )
{
  EFI_STATUS  Status;

  AML_NODE_HEADER  *ParentNode;
  CHAR8            *NodeName;

  UINT32         Root;
  UINT32         ParentPrefix;
  UINT32         SegCount;
  CONST   CHAR8  *NameSeg;

  if ((!IS_AML_ROOT_NODE (Node)                 &&
       !AmlNodeHasAttribute (
          (CONST AML_OBJECT_NODE *)Node,
          AML_IN_NAMESPACE
          ))                     ||
      !IS_STREAM (RawAbsPathBStream)            ||
      IS_END_OF_STREAM (RawAbsPathBStream)      ||
      !IS_STREAM_BACKWARD (RawAbsPathBStream)   ||
      (InputParent > MAX_UINT8))
  {
    ASSERT (0);
    return EFI_INVALID_PARAMETER;
  }

  while (1) {
    if (IS_AML_ROOT_NODE (Node)) {
      break;
    }

    NodeName = AmlNodeGetName ((CONST AML_OBJECT_NODE *)Node);
    if (NodeName == NULL) {
      ASSERT (0);
      return EFI_INVALID_PARAMETER;
    }

    Status = AmlParseNameStringInfo (
               NodeName,
               &Root,
               &ParentPrefix,
               &SegCount
               );
    if (EFI_ERROR (Status)) {
      ASSERT (0);
      return Status;
    }

    if (SegCount > InputParent) {
      // 1.1. If the Node's name has enough levels to consume all the
      //      InputParent carets, write the levels that are left.
      NameSeg = AmlGetFirstNameSeg (NodeName, Root, ParentPrefix);
      Status  = AmlStreamWrite (
                  RawAbsPathBStream,
                  (CONST UINT8 *)NameSeg,
                  (SegCount - InputParent) * AML_NAME_SEG_SIZE
                  );
      if (EFI_ERROR (Status)) {
        ASSERT (0);
        return Status;
      }

      InputParent = 0;
    } else {
      // (SegCount <= InputParent)
      // 1.2. Else save the InputParent in TotalParent to climb
      //      them later.
      InputParent -= SegCount;
    }

    InputParent += ParentPrefix;

    if (Root != 0) {
      // 2. The Node's name is an absolute path.
      //    Exit, the root has been reached.
      if (InputParent != 0) {
        ASSERT (0);
        return EFI_NOT_FOUND;
      }

      break;
    }

    Status = AmlGetAncestorNameSpaceNode (
               (CONST AML_OBJECT_NODE *)Node,
               &InputParent,
               &Root,
               (CONST AML_NODE_HEADER **)&ParentNode
               );
    if (EFI_ERROR (Status)  ||
        (!IS_AML_NODE_VALID (ParentNode)))
    {
      ASSERT (0);
      return Status;
    }

    Node = ParentNode;

    if (IS_AML_ROOT_NODE (Node)) {
      // 3.1. If the root node has been found while climbing,
      //      no need to write NameSegs.
      //      Exit.
      break;
    } else if (Root != 0) {
      // 3.2. An absolute path has been found while climbing the tree.
      //      If (InputParent != 0), the raw pathname is not the root.
      //      Write the first [SegCount - InputParent] NameSegs of this
      //      absolute path.
      //      Then exit.
      if (InputParent != 0) {
        // Get the absolute pathname.
        NodeName = AmlNodeGetName ((CONST AML_OBJECT_NODE *)Node);
        if (NodeName == NULL) {
          ASSERT (0);
          return EFI_INVALID_PARAMETER;
        }

        // Analyze the absolute pathname.
        Status = AmlParseNameStringInfo (
                   NodeName,
                   &Root,
                   &ParentPrefix,
                   &SegCount
                   );
        if (EFI_ERROR (Status)) {
          ASSERT (0);
          return Status;
        }

        // Writing the n first NameSegs.
        // n = SegCount - InputParent
        NameSeg = AmlGetFirstNameSeg (NodeName, Root, ParentPrefix);
        Status  = AmlStreamWrite (
                    RawAbsPathBStream,
                    (CONST UINT8 *)NameSeg,
                    (SegCount - InputParent) * AML_NAME_SEG_SIZE
                    );
        if (EFI_ERROR (Status)) {
          ASSERT (0);
          return Status;
        }

        break;
      } // (InputParent != 0)
    }
  } // while

  return EFI_SUCCESS;
}

/** Add the RootChar and prefix byte to the raw AML NameString in the
    input Stream to create a valid absolute path.

  The prefix byte can be AML_DUAL_NAME_PREFIX, AML_MULTI_NAME_PREFIX
  or nothing.

  @param  [in, out] AmlPathBStream  The Stream initially contains a raw
                                    NameString (i.e. a list of NameSegs).
                                    The Stream can be empty (e.g.: for the
                                    root path).
                                    The stream must not be at its end.

  @retval EFI_SUCCESS             The function completed successfully.
  @retval EFI_BUFFER_TOO_SMALL    No space left in the buffer.
  @retval EFI_INVALID_PARAMETER   Invalid parameter.
**/
STATIC
EFI_STATUS
EFIAPI
AmlAddPrefix (
  IN  OUT AML_STREAM  *AmlPathBStream
  )
{
  EFI_STATUS  Status;
  UINT32      NameSegCount;
  UINT32      NameSegSize;

  // At most 3 bytes are needed for: RootChar + MultiNamePrefix + SegCount.
  CHAR8   Prefix[3];
  UINT32  PrefixSize;

  // The Stream contains concatenated NameSegs.
  if (!IS_STREAM (AmlPathBStream)       ||
      IS_END_OF_STREAM (AmlPathBStream) ||
      !IS_STREAM_BACKWARD (AmlPathBStream))
  {
    ASSERT (0);
    return EFI_INVALID_PARAMETER;
  }

  // Its size should be a multiple of AML_NAME_SEG_SIZE.
  // AML_NAME_SEG_SIZE = 4. Check the 2 lowest bits.
  NameSegSize = AmlStreamGetIndex (AmlPathBStream);
  if ((NameSegSize & (AML_NAME_SEG_SIZE - 1)) != 0) {
    ASSERT (0);
    return EFI_INVALID_PARAMETER;
  }

  // Each NameSeg is 4 bytes so divide the NameSegSize by 4.
  NameSegCount = NameSegSize >> 2;
  if (NameSegCount > MAX_UINT8) {
    // There can be at most 255 NameSegs.
    ASSERT (0);
    return EFI_INVALID_PARAMETER;
  }

  Prefix[0] = AML_ROOT_CHAR;

  switch (NameSegCount) {
    case 0:
    {
      // Root and parents only NameString (no NameSeg(s)) end with '\0'.
      Prefix[1]  = AML_ZERO_OP;
      PrefixSize = 2;
      break;
    }
    case 1:
    {
      PrefixSize = 1;
      break;
    }
    case 2:
    {
      Prefix[1]  = AML_DUAL_NAME_PREFIX;
      PrefixSize = 2;
      break;
    }
    default:
    {
      Prefix[1]  = AML_MULTI_NAME_PREFIX;
      Prefix[2]  = (UINT8)NameSegCount;
      PrefixSize = 3;
      break;
    }
  }

  // Add the RootChar + prefix (if needed) at the beginning of the pathname.
  Status = AmlStreamWrite (AmlPathBStream, (CONST UINT8 *)Prefix, PrefixSize);
  if (EFI_ERROR (Status)) {
    ASSERT (0);
    return Status;
  }

  return Status;
}

/** Remove the prefix bytes of an AML NameString stored in a backward stream
    to get a raw NameString.

  The AML encoding for '\', '^', Dual name or multi-name prefix are
  stripped off.
  E.g: If the ASL path was "\AAAA.BBBB", the AML equivalent would be
       "{RootChar}{DualNamePrefix}AAAABBBB". So resultant raw NameString
       is "AAAABBBB".

  @param  [in, out] AmlPathBStream    Backward stream containing an AML
                                      NameString.
                                      The stream must not be at its end.

  @retval EFI_SUCCESS             The function completed successfully.
  @retval EFI_INVALID_PARAMETER   Invalid parameter.
**/
STATIC
EFI_STATUS
EFIAPI
AmlRemovePrefix (
  IN  OUT AML_STREAM  *AmlPathBStream
  )
{
  EFI_STATUS  Status;

  UINT32  TotalSize;
  UINT32  RewindSize;

  UINT32  Root;
  UINT32  ParentPrefix;
  UINT32  SegCount;

  if (!IS_STREAM (AmlPathBStream)         ||
      IS_END_OF_STREAM (AmlPathBStream)   ||
      !IS_STREAM_BACKWARD (AmlPathBStream))
  {
    ASSERT (0);
    return EFI_INVALID_PARAMETER;
  }

  Status = AmlParseNameStringInfo (
             (CHAR8 *)AmlStreamGetCurrPos (AmlPathBStream),
             &Root,
             &ParentPrefix,
             &SegCount
             );
  if (EFI_ERROR (Status)) {
    ASSERT (0);
    return Status;
  }

  TotalSize = AmlComputeNameStringSize (Root, ParentPrefix, SegCount);
  if (TotalSize == 0) {
    ASSERT (0);
    return EFI_INVALID_PARAMETER;
  }

  // Rewind the stream of all the bytes that are not SegCounts
  // to drop the prefix.
  RewindSize = TotalSize - (SegCount * AML_NAME_SEG_SIZE);
  if (RewindSize != 0) {
    Status = AmlStreamRewind (AmlPathBStream, RewindSize);
    if (EFI_ERROR (Status)) {
      ASSERT (0);
      return Status;
    }
  }

  return EFI_SUCCESS;
}

/** Build the absolute ASL pathname to Node.

  BufferSize is always updated to the size of the pathname.

  If:
   - the content of BufferSize is >= to the size of the pathname AND;
   - Buffer is not NULL.
  then copy the pathname in the Buffer. A buffer of the size
  MAX_ASL_NAMESTRING_SIZE is big enough to receive any ASL pathname.

  @param  [in]      Node            Node to build the absolute path to.
                                    Must be a root node, or a namespace node.
  @param  [out]     Buffer          Buffer to write the path to.
                                    If NULL, only update *BufferSize.
  @param  [in, out] BufferSize      Pointer holding:
                                    - At entry, the size of the Buffer;
                                    - At exit, the size of the pathname.

  @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    Out of memory.
**/
EFI_STATUS
EFIAPI
AmlGetAslPathName (
  IN      AML_NODE_HEADER  *Node,
  OUT CHAR8                *Buffer,
  IN  OUT UINT32           *BufferSize
  )
{
  EFI_STATUS  Status;

  // Backward stream used to build the raw AML absolute path to the node.
  AML_STREAM  RawAmlAbsPathBStream;
  CHAR8       *RawAmlAbsPathBuffer;
  UINT32      RawAmlAbsPathBufferSize;

  CHAR8   *AmlPathName;
  CHAR8   *AslPathName;
  UINT32  AslPathNameSize;

  UINT32  Root;
  UINT32  ParentPrefix;
  UINT32  SegCount;

  if ((!IS_AML_ROOT_NODE (Node)         &&
       !AmlNodeHasAttribute (
          (CONST AML_OBJECT_NODE *)Node,
          AML_IN_NAMESPACE
          ))             ||
      (BufferSize == NULL))
  {
    ASSERT (0);
    return EFI_INVALID_PARAMETER;
  }

  AslPathName = NULL;

  // Allocate a Stream to get the raw AML absolute pathname.
  RawAmlAbsPathBufferSize = MAX_AML_NAMESTRING_SIZE;
  RawAmlAbsPathBuffer     = AllocateZeroPool (RawAmlAbsPathBufferSize);
  if (RawAmlAbsPathBuffer == NULL) {
    ASSERT (0);
    return EFI_OUT_OF_RESOURCES;
  }

  Status = AmlStreamInit (
             &RawAmlAbsPathBStream,
             (UINT8 *)RawAmlAbsPathBuffer,
             RawAmlAbsPathBufferSize,
             EAmlStreamDirectionBackward
             );
  if (EFI_ERROR (Status)) {
    ASSERT (0);
    goto exit_handler;
  }

  // Get the raw pathname of the Node. The raw pathname being an
  // AML NameString without the RootChar and prefix byte.
  // It is a list of concatenated NameSegs.
  Status = AmlGetRawNameSpacePath (Node, 0, &RawAmlAbsPathBStream);
  if (EFI_ERROR (Status)) {
    ASSERT (0);
    goto exit_handler;
  }

  // Add the RootChar and prefix byte.
  Status = AmlAddPrefix (&RawAmlAbsPathBStream);
  if (EFI_ERROR (Status)) {
    ASSERT (0);
    goto exit_handler;
  }

  AmlPathName = (CHAR8 *)AmlStreamGetCurrPos (&RawAmlAbsPathBStream);

  // Analyze the NameString.
  Status = AmlParseNameStringInfo (
             (CONST CHAR8 *)AmlPathName,
             &Root,
             &ParentPrefix,
             &SegCount
             );
  if (EFI_ERROR (Status)) {
    ASSERT (0);
    goto exit_handler;
  }

  // Compute the size the ASL pathname will take.
  AslPathNameSize = AslComputeNameStringSize (Root, ParentPrefix, SegCount);
  if (AslPathNameSize == 0) {
    ASSERT (0);
    Status = EFI_INVALID_PARAMETER;
    goto exit_handler;
  }

  // Input Buffer is large enough. Copy the pathname if the Buffer is valid.
  if ((Buffer != NULL) && (AslPathNameSize <= *BufferSize)) {
    Status = ConvertAmlNameToAslName (AmlPathName, &AslPathName);
    if (EFI_ERROR (Status)) {
      ASSERT (0);
      Status = EFI_OUT_OF_RESOURCES;
      goto exit_handler;
    }

    CopyMem (Buffer, AslPathName, AslPathNameSize);
  }

  *BufferSize = AslPathNameSize;

exit_handler:
  // Free allocated memory.
  FreePool (RawAmlAbsPathBuffer);
  if (AslPathName != NULL) {
    FreePool (AslPathName);
  }

  return Status;
}

#if !defined (MDEPKG_NDEBUG)

/** Recursively print the pathnames in the AML namespace in Node's branch.

  @param  [in]  Node          Pointer to a node.
  @param  [in]  Context       An empty forward stream holding a pre-allocated
                              buffer. This prevents from having to do multiple
                              allocations during the enumeration.
  @param  [in, out] Status    At entry, contains the status returned by the
                              last call to this exact function during the
                              enumeration.
                              As exit, contains the returned status of the
                              call to this function.
                              Optional, can be NULL.

  @retval TRUE if the enumeration can continue or has finished without
          interruption.
  @retval FALSE if the enumeration needs to stopped or has stopped.
**/
STATIC
BOOLEAN
EFIAPI
AmlDbgPrintNameSpaceCallback (
  IN      AML_NODE_HEADER  *Node,
  IN      VOID             *Context,
  IN  OUT EFI_STATUS       *Status   OPTIONAL
  )
{
  BOOLEAN     ContinueEnum;
  EFI_STATUS  Status1;

  AML_STREAM  *CurrNodePathFStream;
  CHAR8       *CurrNodePathBuffer;
  UINT32      CurrNodePathBufferSize;

  ContinueEnum = TRUE;
  Status1      = EFI_SUCCESS;

  if (!IS_AML_NODE_VALID (Node) ||
      (Context == NULL))
  {
    ASSERT (0);
    Status1      = EFI_INVALID_PARAMETER;
    ContinueEnum = FALSE;
    goto exit_handler;
  }

  if (!IS_AML_ROOT_NODE (Node)  &&
      !AmlNodeHasAttribute (
         (CONST AML_OBJECT_NODE *)Node,
         AML_IN_NAMESPACE
         ))
  {
    // Skip this node and continue enumeration.
    goto exit_handler;
  }

  if (IS_AML_ROOT_NODE (Node)) {
    DEBUG ((DEBUG_INFO, "\\\n"));
  } else if (AmlNodeHasAttribute (
               (CONST AML_OBJECT_NODE *)Node,
               AML_IN_NAMESPACE
               ))
  {
    CurrNodePathFStream = (AML_STREAM *)Context;

    // Check the Context's content.
    if (!IS_STREAM (CurrNodePathFStream)           ||
        IS_END_OF_STREAM (CurrNodePathFStream)     ||
        !IS_STREAM_FORWARD (CurrNodePathFStream))
    {
      ASSERT (0);
      Status1      = EFI_INVALID_PARAMETER;
      ContinueEnum = FALSE;
      goto exit_handler;
    }

    CurrNodePathBuffer     = (CHAR8 *)AmlStreamGetBuffer (CurrNodePathFStream);
    CurrNodePathBufferSize = AmlStreamGetMaxBufferSize (CurrNodePathFStream);

    Status1 = AmlGetAslPathName (
                (AML_NODE_HEADER *)Node,
                CurrNodePathBuffer,
                &CurrNodePathBufferSize
                );
    if (EFI_ERROR (Status1)) {
      ASSERT (0);
      ContinueEnum = FALSE;
      goto exit_handler;
    }

    DEBUG ((DEBUG_INFO, "%a\n", CurrNodePathBuffer));
  } else {
    ASSERT (0);
    Status1      = EFI_INVALID_PARAMETER;
    ContinueEnum = FALSE;
  }

exit_handler:
  if (Status != NULL) {
    *Status = Status1;
  }

  return ContinueEnum;
}

/** Print the absolute pathnames in the AML namespace of
    all the nodes in the tree starting from the Root node.

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

  @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    Out of memory.
**/
EFI_STATUS
EFIAPI
AmlDbgPrintNameSpace (
  IN  AML_ROOT_NODE  *RootNode
  )
{
  EFI_STATUS  Status;

  AML_STREAM  CurrNodePathFStream;
  CHAR8       *CurrNodePathBuffer;
  UINT32      CurrNodePathBufferSize;

  if (!IS_AML_ROOT_NODE (RootNode)) {
    ASSERT (0);
    return EFI_INVALID_PARAMETER;
  }

  DEBUG ((DEBUG_INFO, "AmlNameSpace: AML namespace:\n"));

  // Allocate memory to build the absolute ASL path to each node.
  CurrNodePathBufferSize = MAX_AML_NAMESTRING_SIZE;
  CurrNodePathBuffer     = AllocateZeroPool (CurrNodePathBufferSize);
  if (CurrNodePathBuffer == NULL) {
    ASSERT (0);
    return EFI_OUT_OF_RESOURCES;
  }

  // An empty forward stream holding a pre-allocated buffer is used
  // to avoid multiple allocations during the enumeration.
  Status = AmlStreamInit (
             &CurrNodePathFStream,
             (UINT8 *)CurrNodePathBuffer,
             CurrNodePathBufferSize,
             EAmlStreamDirectionForward
             );
  if (EFI_ERROR (Status)) {
    ASSERT (0);
    goto exit_handler;
  }

  AmlEnumTree (
    (AML_NODE_HEADER *)RootNode,
    AmlDbgPrintNameSpaceCallback,
    (VOID *)&CurrNodePathFStream,
    &Status
    );
  ASSERT_EFI_ERROR (Status);

exit_handler:
  FreePool (CurrNodePathBuffer);

  return Status;
}

#endif // MDEPKG_NDEBUG

/** Callback function to find the node corresponding to an absolute pathname.

  For each namespace node, build its raw AML absolute path. Then compare this
  path with the raw AML absolute path of the search node available in the
  Context.

  @param  [in]      Node      Pointer to the node to whose pathname is being
                              tested.
  @param  [in, out] Context   A pointer to AML_PATH_SEARCH_CONTEXT that has:
                               - The searched path stored in a stream;
                               - An empty stream to query the pathname of the
                                 probed node;
                               - A node pointer to store the searched node
                                 if found.
  @param  [in, out] Status    At entry, contains the status returned by the
                              last call to this exact function during the
                              enumeration.
                              As exit, contains the returned status of the
                              call to this function.
                              Optional, can be NULL.

  @retval TRUE if the enumeration can continue or has finished without
          interruption.
  @retval FALSE if the enumeration needs to stopped or has stopped.
**/
STATIC
BOOLEAN
EFIAPI
AmlEnumeratePathCallback (
  IN      AML_NODE_HEADER  *Node,
  IN  OUT VOID             *Context,
  IN  OUT EFI_STATUS       *Status   OPTIONAL
  )
{
  BOOLEAN     ContinueEnum;
  EFI_STATUS  Status1;

  AML_PATH_SEARCH_CONTEXT  *PathSearchContext;

  AML_STREAM  *SearchPathBStream;

  AML_STREAM  *CurrNodePathBStream;
  UINT32      CurrNodePathSize;

  ContinueEnum = TRUE;
  Status1      = EFI_SUCCESS;

  if (!IS_AML_NODE_VALID (Node) ||
      (Context == NULL))
  {
    ASSERT (0);
    Status1      = EFI_INVALID_PARAMETER;
    ContinueEnum = FALSE;
    goto exit_handler;
  }

  if (!AmlNodeHasAttribute (
         (CONST AML_OBJECT_NODE *)Node,
         AML_IN_NAMESPACE
         ))
  {
    goto exit_handler;
  }

  PathSearchContext   = (AML_PATH_SEARCH_CONTEXT *)Context;
  SearchPathBStream   = PathSearchContext->SearchPathBStream;
  CurrNodePathBStream = PathSearchContext->CurrNodePathBStream;

  // Check the Context's content.
  if (!IS_STREAM (SearchPathBStream)            ||
      IS_END_OF_STREAM (SearchPathBStream)      ||
      !IS_STREAM_BACKWARD (SearchPathBStream)   ||
      !IS_STREAM (CurrNodePathBStream)          ||
      IS_END_OF_STREAM (CurrNodePathBStream)    ||
      !IS_STREAM_BACKWARD (CurrNodePathBStream))
  {
    ASSERT (0);
    Status1      = EFI_INVALID_PARAMETER;
    ContinueEnum = FALSE;
    goto exit_handler;
  }

  CurrNodePathSize = AmlStreamGetMaxBufferSize (CurrNodePathBStream);
  if (CurrNodePathSize == 0) {
    ASSERT (0);
    Status1      = EFI_INVALID_PARAMETER;
    ContinueEnum = FALSE;
    goto exit_handler;
  }

  // Get the raw AML absolute pathname of the current node.
  Status1 = AmlGetRawNameSpacePath (Node, 0, CurrNodePathBStream);
  if (EFI_ERROR (Status1)) {
    ASSERT (0);
    ContinueEnum = FALSE;
    goto exit_handler;
  }

  DEBUG ((
    DEBUG_VERBOSE,
    "AmlNameSpace: "
    "Comparing search path with current node path.\n"
    ));
  DEBUG ((DEBUG_VERBOSE, "Search path:"));
  AMLDBG_PRINT_CHARS (
    DEBUG_VERBOSE,
    (CHAR8 *)AmlStreamGetCurrPos (SearchPathBStream),
    AmlStreamGetIndex (SearchPathBStream)
    );
  DEBUG ((DEBUG_VERBOSE, "\nPath of the current node: "));
  AMLDBG_PRINT_CHARS (
    DEBUG_VERBOSE,
    (CHAR8 *)AmlStreamGetCurrPos (CurrNodePathBStream),
    AmlStreamGetIndex (CurrNodePathBStream)
    );
  DEBUG ((DEBUG_VERBOSE, "\n"));

  // Compare the searched path and Node's path.
  if ((AmlStreamGetIndex (CurrNodePathBStream)  ==
       AmlStreamGetIndex (SearchPathBStream))     &&
      (CompareMem (
         AmlStreamGetCurrPos (CurrNodePathBStream),
         AmlStreamGetCurrPos (SearchPathBStream),
         AmlStreamGetIndex (SearchPathBStream)
         ) == 0))
  {
    Status1                    = EFI_SUCCESS;
    ContinueEnum               = FALSE;
    PathSearchContext->OutNode = Node;
  } else {
    // If the paths don't match, reset the CurrNodePathStream's content.
    Status1 = AmlStreamReset (CurrNodePathBStream);
    if (EFI_ERROR (Status1)) {
      ASSERT (0);
      ContinueEnum = FALSE;
    }
  }

exit_handler:
  if (Status != NULL) {
    *Status = Status1;
  }

  return ContinueEnum;
}

/** Build a raw AML absolute path from a reference node and a relative
    ASL path.

  The AslPath can be a relative path or an absolute path.
  Node must be a root node or a namespace node.
  A root node is expected to be at the top of the tree.

  @param  [in]  ReferenceNode               Reference node.
                                            If a relative path is given, the
                                            search is done from this node. If
                                            an absolute path is given, the
                                            search is done from the root node.
                                            Must be a root node or an object
                                            node which is part of the
                                            namespace.
  @param  [in]  AslPath                     ASL path to the searched node in
                                            the namespace. An ASL path name is
                                            NULL terminated. Can be a relative
                                            or absolute path.
                                            E.g.: "\\_SB.CLU0.CPU0".
  @param  [in, out] RawAmlAbsSearchPathBStream  Backward stream to write the
                                                raw absolute AML path of the
                                                searched node.
                                                The stream must not be at
                                                its end.

  @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    Out of memory.
**/
STATIC
EFI_STATUS
EFIAPI
AmlBuildAbsoluteAmlPath (
  IN      AML_NODE_HEADER  *ReferenceNode,
  IN      CHAR8            *AslPath,
  IN  OUT AML_STREAM       *RawAmlAbsSearchPathBStream
  )
{
  EFI_STATUS  Status;
  CHAR8       *AmlPath;

  UINT32  AmlNameStringSize;
  UINT32  Root;
  UINT32  ParentPrefix;
  UINT32  SegCount;

  if ((!IS_AML_ROOT_NODE (ReferenceNode)              &&
       !AmlNodeHasAttribute (
          (CONST AML_OBJECT_NODE *)ReferenceNode,
          AML_IN_NAMESPACE
          ))                           ||
      (AslPath == NULL)                               ||
      !IS_STREAM (RawAmlAbsSearchPathBStream)         ||
      IS_END_OF_STREAM (RawAmlAbsSearchPathBStream)   ||
      !IS_STREAM_BACKWARD (RawAmlAbsSearchPathBStream))
  {
    ASSERT (0);
    return EFI_INVALID_PARAMETER;
  }

  // 1. Validate, analyze and convert the AslPath to an AmlPath.
  Status = ConvertAslNameToAmlName (AslPath, &AmlPath);
  if (EFI_ERROR (Status)) {
    ASSERT (0);
    return Status;
  }

  Status = AmlParseNameStringInfo (AmlPath, &Root, &ParentPrefix, &SegCount);
  if (EFI_ERROR (Status)) {
    ASSERT (0);
    goto exit_handler;
  }

  // Not possible to go beyond the root.
  if (IS_AML_ROOT_NODE (ReferenceNode) && (ParentPrefix != 0)) {
    Status = EFI_INVALID_PARAMETER;
    ASSERT (0);
    goto exit_handler;
  }

  AmlNameStringSize = AmlComputeNameStringSize (Root, ParentPrefix, SegCount);
  if (AmlNameStringSize == 0) {
    Status = EFI_INVALID_PARAMETER;
    ASSERT (0);
    goto exit_handler;
  }

  // 2.1. Write the AML path to the stream.
  Status = AmlStreamWrite (
             RawAmlAbsSearchPathBStream,
             (CONST UINT8 *)AmlPath,
             AmlNameStringSize
             );
  if (EFI_ERROR (Status)) {
    ASSERT (0);
    goto exit_handler;
  }

  // 2.2. Then remove the AML prefix (root char, parent prefix, etc.)
  //      to obtain a raw AML NameString. Raw AML NameString are easier
  //      to manipulate.
  Status = AmlRemovePrefix (RawAmlAbsSearchPathBStream);
  if (EFI_ERROR (Status)) {
    ASSERT (0);
    goto exit_handler;
  }

  // 3. If AslPath is a relative path and the reference Node is not
  //    the root node, fill the Stream with the absolute path to the
  //    reference node.
  if ((Root == 0) && !IS_AML_ROOT_NODE (ReferenceNode)) {
    Status = AmlGetRawNameSpacePath (
               ReferenceNode,
               ParentPrefix,
               RawAmlAbsSearchPathBStream
               );
    if (EFI_ERROR (Status)) {
      ASSERT (0);
    }
  }

exit_handler:
  // Free allocated memory.
  FreePool (AmlPath);

  return Status;
}

/** Find a node in the AML namespace, given an ASL path and a reference Node.

   - The AslPath can be an absolute path, or a relative path from the
     reference Node;
   - Node must be a root node or a namespace node;
   - A root node is expected to be at the top of the tree.

  E.g.:
  For the following AML namespace, with the ReferenceNode being the node with
  the name "AAAA":
   - the node with the name "BBBB" can be found by looking for the ASL
     path "BBBB";
   - the root node can be found by looking for the ASL relative path "^",
     or the absolute path "\\".

  AML namespace:
  \
  \-AAAA      <- ReferenceNode
    \-BBBB

  @param  [in]  ReferenceNode   Reference node.
                                If a relative path is given, the
                                search is done from this node. If
                                an absolute path is given, the
                                search is done from the root node.
                                Must be a root node or an object
                                node which is part of the
                                namespace.
  @param  [in]  AslPath         ASL path to the searched node in
                                the namespace. An ASL path name is
                                NULL terminated. Can be a relative
                                or absolute path.
                                E.g.: "\\_SB.CLU0.CPU0" or "^CPU0"
  @param  [out] OutNode         Pointer to the found node.
                                Contains NULL if not found.

  @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    Out of memory.
**/
EFI_STATUS
EFIAPI
AmlFindNode (
  IN  AML_NODE_HEADER  *ReferenceNode,
  IN  CHAR8            *AslPath,
  OUT AML_NODE_HEADER  **OutNode
  )
{
  EFI_STATUS  Status;

  AML_PATH_SEARCH_CONTEXT  PathSearchContext;
  AML_ROOT_NODE            *RootNode;

  // Backward stream used to build the raw AML absolute path to the searched
  // node.
  AML_STREAM  RawAmlAbsSearchPathBStream;
  CHAR8       *RawAmlAbsSearchPathBuffer;
  UINT32      RawAmlAbsSearchPathBufferSize;

  // Backward stream used to store the raw AML absolute path of the node
  // currently enumerated in the tree. This path can then be compared to the
  // RawAmlAbsSearchPath.
  AML_STREAM  RawAmlAbsCurrNodePathBStream;
  CHAR8       *RawAmlAbsCurrNodePathBuffer;
  UINT32      RawAmlAbsCurrNodePathBufferSize;

  if ((!IS_AML_ROOT_NODE (ReferenceNode)        &&
       !AmlNodeHasAttribute (
          (CONST AML_OBJECT_NODE *)ReferenceNode,
          AML_IN_NAMESPACE
          ))                     ||
      (AslPath == NULL)                         ||
      (OutNode == NULL))
  {
    ASSERT (0);
    return EFI_INVALID_PARAMETER;
  }

  *OutNode                    = NULL;
  RawAmlAbsCurrNodePathBuffer = NULL;

  // 1. Build a raw absolute AML path from the reference node and the ASL
  //    path. For this:
  // 1.1. First initialize a backward stream.
  RawAmlAbsSearchPathBufferSize = MAX_AML_NAMESTRING_SIZE;
  RawAmlAbsSearchPathBuffer     = AllocateZeroPool (RawAmlAbsSearchPathBufferSize);
  if (RawAmlAbsSearchPathBuffer == NULL) {
    ASSERT (0);
    return EFI_OUT_OF_RESOURCES;
  }

  Status = AmlStreamInit (
             &RawAmlAbsSearchPathBStream,
             (UINT8 *)RawAmlAbsSearchPathBuffer,
             RawAmlAbsSearchPathBufferSize,
             EAmlStreamDirectionBackward
             );
  if (EFI_ERROR (Status)) {
    ASSERT (0);
    goto exit_handler;
  }

  // 1.2. Then build the raw AML absolute path.
  Status = AmlBuildAbsoluteAmlPath (
             ReferenceNode,
             AslPath,
             &RawAmlAbsSearchPathBStream
             );
  if (EFI_ERROR (Status)) {
    ASSERT (0);
    goto exit_handler;
  }

  // 2. Find the root node by climbing up the tree from the reference node.
  RootNode = AmlGetRootNode (ReferenceNode);
  if (RootNode == NULL) {
    ASSERT (0);
    Status = EFI_INVALID_PARAMETER;
    goto exit_handler;
  }

  // 3. If the searched node is the root node, return.
  //    For the Root Node there is no NameSegs so the length of
  //     the stream will be zero.
  if (AmlStreamGetIndex (&RawAmlAbsSearchPathBStream) == 0) {
    *OutNode = (AML_NODE_HEADER *)RootNode;
    Status   = EFI_SUCCESS;
    goto exit_handler;
  }

  // 4. Create a backward stream large enough to hold the current node path
  //    during enumeration. This prevents from doing multiple allocation/free
  //    operations.
  RawAmlAbsCurrNodePathBufferSize = MAX_ASL_NAMESTRING_SIZE;
  RawAmlAbsCurrNodePathBuffer     = AllocateZeroPool (
                                      RawAmlAbsCurrNodePathBufferSize
                                      );
  if (RawAmlAbsCurrNodePathBuffer == NULL) {
    ASSERT (0);
    Status = EFI_OUT_OF_RESOURCES;
    goto exit_handler;
  }

  Status = AmlStreamInit (
             &RawAmlAbsCurrNodePathBStream,
             (UINT8 *)RawAmlAbsCurrNodePathBuffer,
             RawAmlAbsCurrNodePathBufferSize,
             EAmlStreamDirectionBackward
             );
  if (EFI_ERROR (Status)) {
    ASSERT (0);
    goto exit_handler;
  }

  // 5. Fill a path search context structure with:
  //     - SearchPathStream: backward stream containing the raw absolute AML
  //       path to the searched node;
  //     - CurrNodePathStream: backward stream containing the raw absolute AML
  //       of the node currently being enumerated;
  //     - OutNode: node pointer to the store the potentially found node.
  PathSearchContext.SearchPathBStream   = &RawAmlAbsSearchPathBStream;
  PathSearchContext.CurrNodePathBStream = &RawAmlAbsCurrNodePathBStream;
  PathSearchContext.OutNode             = NULL;

  // 6. Iterate through the namespace nodes of the tree.
  //    For each namespace node, build its raw AML absolute path. Then compare
  //    it with the search path.
  AmlEnumTree (
    (AML_NODE_HEADER *)RootNode,
    AmlEnumeratePathCallback,
    (VOID *)&PathSearchContext,
    &Status
    );
  if (EFI_ERROR (Status)) {
    ASSERT (0);
    goto exit_handler;
  }

  *OutNode = PathSearchContext.OutNode;
  if (*OutNode == NULL) {
    Status = EFI_NOT_FOUND;
  }

exit_handler:
  // Free allocated memory.
  FreePool (RawAmlAbsSearchPathBuffer);
  if (RawAmlAbsCurrNodePathBuffer != NULL) {
    FreePool (RawAmlAbsCurrNodePathBuffer);
  }

  return Status;
}
