/** @file
  AML Utility.

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

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

#include <Utils/AmlUtility.h>

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

/** This function computes and updates the ACPI table checksum.

  @param  [in]  AcpiTable   Pointer to an Acpi table.

  @retval EFI_SUCCESS   The function completed successfully.
  @retval EFI_INVALID_PARAMETER   Invalid parameter.
**/
EFI_STATUS
EFIAPI
AcpiPlatformChecksum (
  IN  EFI_ACPI_DESCRIPTION_HEADER  * AcpiTable
  )
{
  UINT8   * Ptr;
  UINT8     Sum;
  UINT32    Size;

  if (AcpiTable == NULL) {
    ASSERT (0);
    return EFI_INVALID_PARAMETER;
  }

  Ptr = (UINT8*)AcpiTable;
  Size = AcpiTable->Length;
  Sum = 0;

  // Set the checksum field to 0 first.
  AcpiTable->Checksum = 0;

  // Compute the checksum.
  while ((Size--) != 0) {
    Sum = (UINT8)(Sum + (*Ptr++));
  }

  // Set the checksum.
  AcpiTable->Checksum = (UINT8)(0xFF - Sum + 1);

  return EFI_SUCCESS;
}

/** A callback function that computes the size of a Node and adds it to the
    Size pointer stored in the Context.
    Calling this function on the root node will compute the total size of the
    AML bytestream.

  @param  [in]      Node      Node to compute the size.
  @param  [in, out] Context   Pointer holding the computed size.
                              (UINT32 *) Context.
  @param  [in, out] Status    Pointer holding:
                               - At entry, the Status returned by the
                                 last call to this exact function during
                                 the enumeration;
                               - At exit, he 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
AmlComputeSizeCallback (
  IN      AML_NODE_HEADER  * Node,
  IN  OUT VOID             * Context,
  IN  OUT EFI_STATUS       * Status   OPTIONAL
  )
{
  UINT32                    Size;
  EAML_PARSE_INDEX          IndexPtr;
  CONST AML_OBJECT_NODE   * ParentNode;

  if (!IS_AML_NODE_VALID (Node) ||
      (Context == NULL)) {
    ASSERT (0);
    if (Status != NULL) {
      *Status = EFI_INVALID_PARAMETER;
    }
    return FALSE;
  }

  // Ignore the second fixed argument of method invocation nodes
  // as the information stored there (the argument count) is not in the
  // ACPI specification.
  ParentNode = (CONST AML_OBJECT_NODE*)AmlGetParent (Node);
  if (IS_AML_OBJECT_NODE (ParentNode)                             &&
      AmlNodeCompareOpCode (ParentNode, AML_METHOD_INVOC_OP, 0)   &&
      AmlIsNodeFixedArgument (Node, &IndexPtr)) {
    if (IndexPtr == EAmlParseIndexTerm1) {
      if (Status != NULL) {
        *Status = EFI_SUCCESS;
      }
      return TRUE;
    }
  }

  Size = *((UINT32*)Context);

  if (IS_AML_DATA_NODE (Node)) {
    Size += ((AML_DATA_NODE*)Node)->Size;
  } else if (IS_AML_OBJECT_NODE (Node)  &&
             !AmlNodeHasAttribute (
                (CONST AML_OBJECT_NODE*)Node,
                AML_IS_PSEUDO_OPCODE)) {
    // Ignore pseudo-opcodes as they are not part of the
    // ACPI specification.

    Size += (((AML_OBJECT_NODE*)Node)->AmlByteEncoding->OpCode ==
               AML_EXT_OP) ? 2 : 1;

    // Add the size of the PkgLen.
    if (AmlNodeHasAttribute (
          (AML_OBJECT_NODE*)Node,
          AML_HAS_PKG_LENGTH)) {
      Size += AmlComputePkgLengthWidth (((AML_OBJECT_NODE*)Node)->PkgLen);
    }
  }

  // Check for overflow.
  // The root node has a null size, thus the strict comparison.
  if (*((UINT32*)Context) > Size) {
    ASSERT (0);
    *Status = EFI_INVALID_PARAMETER;
    return FALSE;
  }

  *((UINT32*)Context) = Size;

  if (Status != NULL) {
    *Status = EFI_SUCCESS;
  }

  return TRUE;
}

/** Compute the size of a tree/sub-tree.

  @param  [in]      Node      Node to compute the size.
  @param  [in, out] Size      Pointer holding the computed size.

  @retval EFI_SUCCESS             The function completed successfully.
  @retval EFI_INVALID_PARAMETER   Invalid parameter.
**/
EFI_STATUS
EFIAPI
AmlComputeSize (
  IN      CONST AML_NODE_HEADER   * Node,
  IN  OUT       UINT32            * Size
  )
{
  EFI_STATUS  Status;

  if (!IS_AML_NODE_VALID (Node) ||
      (Size == NULL)) {
    ASSERT (0);
    return EFI_INVALID_PARAMETER;
  }

  *Size = 0;

  AmlEnumTree (
    (AML_NODE_HEADER*)Node,
    AmlComputeSizeCallback,
    (VOID*)Size,
    &Status
    );

  return Status;
}

/** Get the value contained in an integer node.

  @param  [in]  Node    Pointer to an integer node.
                        Must be an object node.
  @param  [out] Value   Value contained in the integer node.

  @retval EFI_SUCCESS             The function completed successfully.
  @retval EFI_INVALID_PARAMETER   Invalid parameter.
**/
STATIC
EFI_STATUS
EFIAPI
AmlNodeGetIntegerValue (
  IN  AML_OBJECT_NODE   * Node,
  OUT UINT64            * Value
  )
{
  AML_DATA_NODE  * DataNode;

  if ((!IsIntegerNode (Node)            &&
       !IsSpecialIntegerNode (Node))    ||
      (Value == NULL)) {
    ASSERT (0);
    return EFI_INVALID_PARAMETER;
  }

  // For ZeroOp and OneOp, there is no data node.
  if (IsSpecialIntegerNode (Node)) {
    if (AmlNodeCompareOpCode (Node, AML_ZERO_OP, 0)) {
      *Value = 0;
    } else if (AmlNodeCompareOpCode (Node, AML_ONE_OP, 0)) {
      *Value = 1;
    } else {
      // OnesOp cannot be handled: it represents a maximum value.
      ASSERT (0);
      return EFI_INVALID_PARAMETER;
    }
    return EFI_SUCCESS;
  }

  // For integer nodes, the value is in the first fixed argument.
  DataNode = (AML_DATA_NODE*)Node->FixedArgs[EAmlParseIndexTerm0];
  if (!IS_AML_DATA_NODE (DataNode) ||
      (DataNode->DataType != EAmlNodeDataTypeUInt)) {
    ASSERT (0);
    return EFI_INVALID_PARAMETER;
  }

  switch (DataNode->Size) {
    case 1:
    {
      *Value = *((UINT8*)(DataNode->Buffer));
      break;
    }
    case 2:
    {
      *Value = *((UINT16*)(DataNode->Buffer));
      break;
    }
    case 4:
    {
      *Value = *((UINT32*)(DataNode->Buffer));
      break;
    }
    case 8:
    {
      *Value = *((UINT64*)(DataNode->Buffer));
      break;
    }
    default:
    {
      ASSERT (0);
      return EFI_INVALID_PARAMETER;
    }
  } // switch

  return EFI_SUCCESS;
}

/** Replace a Zero (AML_ZERO_OP) or One (AML_ONE_OP) object node
    with a byte integer (AML_BYTE_PREFIX) object node having the same value.

  @param  [in]  Node    Pointer to an integer node.
                        Must be an object node having ZeroOp or OneOp.

  @retval EFI_SUCCESS             The function completed successfully.
  @retval EFI_INVALID_PARAMETER   Invalid parameter.
**/
STATIC
EFI_STATUS
EFIAPI
AmlUnwindSpecialInteger (
  IN  AML_OBJECT_NODE   * Node
  )
{
  EFI_STATUS                  Status;

  AML_DATA_NODE             * NewDataNode;
  UINT8                       Value;
  CONST AML_BYTE_ENCODING   * ByteEncoding;

  if (!IsSpecialIntegerNode (Node)) {
    ASSERT (0);
    return EFI_INVALID_PARAMETER;
  }

  // Find the value.
  if (AmlNodeCompareOpCode (Node, AML_ZERO_OP, 0)) {
    Value = 0;
  } else if (AmlNodeCompareOpCode (Node, AML_ONE_OP, 0)) {
    Value = 1;
  } else {
    // OnesOp cannot be handled: it represents a maximum value.
    ASSERT (0);
    return EFI_INVALID_PARAMETER;
  }

  Status = AmlCreateDataNode (
              EAmlNodeDataTypeUInt,
              &Value,
              sizeof (UINT8),
              (AML_DATA_NODE**)&NewDataNode
              );
  if (EFI_ERROR (Status)) {
    ASSERT (0);
    return Status;
  }

  // Change the encoding of the special node to a ByteOp encoding.
  ByteEncoding = AmlGetByteEncodingByOpCode (AML_BYTE_PREFIX, 0);
  if (ByteEncoding == NULL) {
    ASSERT (0);
    Status = EFI_INVALID_PARAMETER;
    goto error_handler;
  }

  // Update the ByteEncoding from ZERO_OP/ONE_OP to AML_BYTE_PREFIX.
  Node->AmlByteEncoding = ByteEncoding;

  // Add the data node as the first fixed argument of the ByteOp object.
  Status = AmlSetFixedArgument (
              (AML_OBJECT_NODE*)Node,
              EAmlParseIndexTerm0,
              (AML_NODE_HEADER*)NewDataNode
              );
  if (EFI_ERROR (Status)) {
    ASSERT (0);
    goto error_handler;
  }

  return Status;

error_handler:
  AmlDeleteTree ((AML_NODE_HEADER*)NewDataNode);
  return Status;
}

/** Set the value contained in an integer node.

  The OpCode is updated accordingly to the new value
  (e.g.: If the original value was a UINT8 value, then the OpCode
         would be AML_BYTE_PREFIX. If it the new value is a UINT16
         value then the OpCode will be updated to AML_WORD_PREFIX).

  @param  [in]  Node            Pointer to an integer node.
                                Must be an object node.
  @param  [in]  NewValue        New value to write in the integer node.
  @param  [out] ValueWidthDiff  Difference in number of bytes used to store
                                the new value.
                                Can be negative.

  @retval EFI_SUCCESS             The function completed successfully.
  @retval EFI_INVALID_PARAMETER   Invalid parameter.
  @retval EFI_OUT_OF_RESOURCES    Could not allocate memory.
**/
EFI_STATUS
EFIAPI
AmlNodeSetIntegerValue (
  IN  AML_OBJECT_NODE   * Node,
  IN  UINT64              NewValue,
  OUT INT8              * ValueWidthDiff
  )
{
  EFI_STATUS        Status;
  AML_DATA_NODE   * DataNode;

  UINT8             NewOpCode;
  UINT8             NumberOfBytes;

  if ((!IsIntegerNode (Node)            &&
       !IsSpecialIntegerNode (Node))    ||
      (ValueWidthDiff == NULL)) {
    ASSERT (0);
    return EFI_INVALID_PARAMETER;
  }

  *ValueWidthDiff = 0;
  // For ZeroOp and OneOp, there is no data node.
  // Thus the object node is converted to a byte object node holding 0 or 1.
  if (IsSpecialIntegerNode (Node)) {
    switch (NewValue) {
      case AML_ZERO_OP:
        Node->AmlByteEncoding = AmlGetByteEncodingByOpCode (AML_ZERO_OP, 0);
        return EFI_SUCCESS;
      case AML_ONE_OP:
        Node->AmlByteEncoding = AmlGetByteEncodingByOpCode (AML_ONE_OP, 0);
        return EFI_SUCCESS;
      default:
      {
        Status = AmlUnwindSpecialInteger (Node);
        if (EFI_ERROR (Status)) {
          ASSERT (0);
          return Status;
        }
        // The AmlUnwindSpecialInteger functions converts a special integer
        // node to a UInt8/Byte data node. Thus, the size increments by one:
        // special integer are encoded as one byte (the opcode only) while byte
        // integers are encoded as two bytes (the opcode + the value).
        *ValueWidthDiff += sizeof (UINT8);
      }
    } // switch
  } // IsSpecialIntegerNode (Node)

  // For integer nodes, the value is in the first fixed argument.
  DataNode = (AML_DATA_NODE*)Node->FixedArgs[EAmlParseIndexTerm0];
  if (!IS_AML_DATA_NODE (DataNode) ||
      (DataNode->DataType != EAmlNodeDataTypeUInt)) {
    ASSERT (0);
    return EFI_INVALID_PARAMETER;
  }

  // The value can be encoded with a special 0 or 1 OpCode.
  // The AML_ONES_OP is not handled.
  if (NewValue <= 1) {
    NewOpCode = (NewValue == 0) ? AML_ZERO_OP : AML_ONE_OP;
    Node->AmlByteEncoding = AmlGetByteEncodingByOpCode (NewOpCode, 0);

    // The value is encoded with a AML_ZERO_OP or AML_ONE_OP.
    // This means there is no need for a DataNode containing the value.
    // The change in size is equal to the size of the DataNode's buffer.
    *ValueWidthDiff = -((INT8)DataNode->Size);

    // Detach and free the DataNode containing the integer value.
    DataNode->NodeHeader.Parent = NULL;
    Node->FixedArgs[EAmlParseIndexTerm0] = NULL;
    Status = AmlDeleteNode ((AML_NODE_HEADER*)DataNode);
    if (EFI_ERROR (Status)) {
      ASSERT (0);
      return Status;
    }

    return EFI_SUCCESS;
  }

  // Check the number of bits needed to represent the value.
  if (NewValue > MAX_UINT32) {
    // Value is 64 bits.
    NewOpCode = AML_QWORD_PREFIX;
    NumberOfBytes = 8;
  } else if (NewValue > MAX_UINT16) {
    // Value is 32 bits.
    NewOpCode = AML_DWORD_PREFIX;
    NumberOfBytes = 4;
  } else if (NewValue > MAX_UINT8) {
    // Value is 16 bits.
    NewOpCode = AML_WORD_PREFIX;
    NumberOfBytes = 2;
  } else {
    // Value is 8 bits.
    NewOpCode = AML_BYTE_PREFIX;
    NumberOfBytes = 1;
  }

  *ValueWidthDiff += (INT8)(NumberOfBytes - DataNode->Size);

  // Update the ByteEncoding as it may have changed between [8 .. 64] bits.
  Node->AmlByteEncoding = AmlGetByteEncodingByOpCode (NewOpCode, 0);
  if (Node->AmlByteEncoding == NULL) {
    ASSERT (0);
    return EFI_INVALID_PARAMETER;
  }

  // Free the old DataNode buffer and allocate a buffer with the right size
  // to store the new data.
  if (*ValueWidthDiff != 0) {
    FreePool (DataNode->Buffer);
    DataNode->Buffer = AllocateZeroPool (NumberOfBytes);
    if (DataNode->Buffer == NULL) {
      ASSERT (0);
      return EFI_OUT_OF_RESOURCES;
    }
    DataNode->Size = NumberOfBytes;
  }

  // Write the new value.
  CopyMem (DataNode->Buffer, &NewValue, NumberOfBytes);

  return EFI_SUCCESS;
}

/** Increment/decrement the value contained in the IntegerNode.

  @param  [in]  IntegerNode     Pointer to an object node containing
                                an integer.
  @param  [in]  IsIncrement     Choose the operation to do:
                                 - TRUE:  Increment the Node's size and
                                          the Node's count;
                                 - FALSE: Decrement the Node's size and
                                          the Node's count.
  @param  [in]  Diff            Value to add/subtract to the integer.
  @param  [out] ValueWidthDiff  When modifying the integer, it can be
                                promoted/demoted, e.g. from UINT8 to UINT16.
                                Stores the change in width.
                                Can be negative.

  @retval EFI_SUCCESS             The function completed successfully.
  @retval EFI_INVALID_PARAMETER   Invalid parameter.
**/
STATIC
EFI_STATUS
EFIAPI
AmlNodeUpdateIntegerValue (
  IN  AML_OBJECT_NODE       * IntegerNode,
  IN  BOOLEAN                 IsIncrement,
  IN  UINT64                  Diff,
  OUT INT8                  * ValueWidthDiff
  )
{
  EFI_STATUS       Status;
  UINT64           Value;

  if (ValueWidthDiff == NULL) {
    ASSERT (0);
    return EFI_INVALID_PARAMETER;
  }

  // Get the current value.
  // Checks on the IntegerNode are done in the call.
  Status = AmlNodeGetIntegerValue (IntegerNode, &Value);
  if (EFI_ERROR (Status)) {
    ASSERT (0);
    return Status;
  }

  // Check for UINT64 over/underflow.
  if ((IsIncrement && (Value > (MAX_UINT64 - Diff))) ||
      (!IsIncrement && (Value < Diff))) {
    ASSERT (0);
    return EFI_INVALID_PARAMETER;
  }

  // Compute the new value.
  if (IsIncrement) {
    Value += Diff;
  } else {
    Value -= Diff;
  }

  Status = AmlNodeSetIntegerValue (
             IntegerNode,
             Value,
             ValueWidthDiff
             );
  ASSERT_EFI_ERROR (Status);
  return Status;
}

/** Propagate the size information up the tree.

  The length of the ACPI table is updated in the RootNode,
  but not the checksum.

  @param  [in]  Node          Pointer to a node.
                              Must be a root node or an object node.
  @param  [in]  IsIncrement   Choose the operation to do:
                               - TRUE:  Increment the Node's size and
                                        the Node's count;
                               - FALSE: Decrement the Node's size and
                                        the Node's count.
  @param  [in]  Diff          Value to add/subtract to the Node's size.

  @retval EFI_SUCCESS             The function completed successfully.
  @retval EFI_INVALID_PARAMETER   Invalid parameter.
**/
STATIC
EFI_STATUS
EFIAPI
AmlPropagateSize (
  IN  AML_NODE_HEADER   * Node,
  IN  BOOLEAN             IsIncrement,
  IN  UINT32            * Diff
  )
{
  EFI_STATUS         Status;
  AML_OBJECT_NODE  * ObjectNode;
  AML_NODE_HEADER  * ParentNode;

  UINT32             Value;
  UINT32             InitialPkgLenWidth;
  UINT32             NewPkgLenWidth;
  UINT32             ReComputedPkgLenWidth;
  INT8               FieldWidthChange;

  if (!IS_AML_OBJECT_NODE (Node) &&
      !IS_AML_ROOT_NODE (Node)) {
    ASSERT (0);
    return EFI_INVALID_PARAMETER;
  }

  if (IS_AML_OBJECT_NODE (Node)) {
    ObjectNode = (AML_OBJECT_NODE*)Node;

    // For BufferOp, the buffer size is stored in BufferSize. Therefore,
    // BufferOp needs special handling to update the BufferSize.
    // BufferSize must be updated before the PkgLen to accommodate any
    // increment resulting from the update of the BufferSize.
    // DefBuffer := BufferOp PkgLength BufferSize ByteList
    // BufferOp := 0x11
    // BufferSize := TermArg => Integer
    if (AmlNodeCompareOpCode (ObjectNode, AML_BUFFER_OP, 0)) {
      // First fixed argument of BufferOp is an integer (BufferSize)
      // (can be a BYTE, WORD, DWORD or QWORD).
      // BufferSize is an object node.
      Status = AmlNodeUpdateIntegerValue (
                 (AML_OBJECT_NODE*)AmlGetFixedArgument (
                                     ObjectNode,
                                     EAmlParseIndexTerm0
                                     ),
                 IsIncrement,
                 (UINT64)(*Diff),
                 &FieldWidthChange
                 );
      if (EFI_ERROR (Status)) {
        ASSERT (0);
        return Status;
      }

      // FieldWidthChange is an integer.
      // It must be positive if IsIncrement is TRUE, negative otherwise.
      if ((IsIncrement              &&
           (FieldWidthChange < 0))  ||
          (!IsIncrement             &&
           (FieldWidthChange > 0))) {
        ASSERT (0);
        return EFI_INVALID_PARAMETER;
      }

      // Check for UINT32 overflow.
      if (*Diff > (MAX_UINT32 - (UINT32)ABS (FieldWidthChange))) {
        ASSERT (0);
        return EFI_INVALID_PARAMETER;
      }

      // Update Diff if the field width changed.
      *Diff = (UINT32)(*Diff + ABS (FieldWidthChange));
    } // AML_BUFFER_OP node.

    // Update the PgkLen.
    // Needs to be done at last to reflect the potential field width changes.
    if (AmlNodeHasAttribute (ObjectNode, AML_HAS_PKG_LENGTH)) {
      Value = ObjectNode->PkgLen;

      // Subtract the size of the PkgLen encoding. The size of the PkgLen
      // encoding must be computed after having updated Value.
      InitialPkgLenWidth = AmlComputePkgLengthWidth (Value);
      Value -= InitialPkgLenWidth;

      // Check for an over/underflows.
      // PkgLen is a 28 bit value, cf 20.2.4 Package Length Encoding
      // i.e. the maximum value is (2^28 - 1) = ((BIT0 << 28) - 1).
      if ((IsIncrement && ((((BIT0 << 28) - 1) - Value) < *Diff))   ||
          (!IsIncrement && (Value < *Diff))) {
        ASSERT (0);
        return EFI_INVALID_PARAMETER;
      }

      // Update the size.
      if (IsIncrement) {
        Value += *Diff;
      } else {
        Value -= *Diff;
      }

      // Compute the new PkgLenWidth.
      NewPkgLenWidth = AmlComputePkgLengthWidth (Value);
      if (NewPkgLenWidth == 0) {
        ASSERT (0);
        return EFI_INVALID_PARAMETER;
      }

      // Add it to the Value.
      Value += NewPkgLenWidth;

      // Check that adding the PkgLenWidth didn't trigger a domino effect,
      // increasing the encoding width of the PkgLen again.
      // The PkgLen is encoded on at most 4 bytes. It is possible to increase
      // the PkgLen width if its encoding is on less than 3 bytes.
      ReComputedPkgLenWidth = AmlComputePkgLengthWidth (Value);
      if (ReComputedPkgLenWidth != NewPkgLenWidth) {
        if ((ReComputedPkgLenWidth != 0)   &&
            (ReComputedPkgLenWidth < 4)) {
          // No need to recompute the PkgLen since a new threshold cannot
          // be reached by incrementing the value by one.
          Value += 1;
        } else {
          ASSERT (0);
          return EFI_INVALID_PARAMETER;
        }
      }

      *Diff += (InitialPkgLenWidth > ReComputedPkgLenWidth) ?
                 (InitialPkgLenWidth - ReComputedPkgLenWidth) :
                 (ReComputedPkgLenWidth - InitialPkgLenWidth);
      ObjectNode->PkgLen = Value;
    } // PkgLen update.

    // During CodeGeneration, the tree is incomplete and
    // there is no root node at the top of the tree. Stop
    // propagating the new size when finding a root node
    // OR when a NULL parent is found.
    ParentNode = AmlGetParent ((AML_NODE_HEADER*)Node);
    if (ParentNode != NULL) {
      // Propagate the size up the tree.
      Status = AmlPropagateSize (
                 Node->Parent,
                 IsIncrement,
                 Diff
                 );
      if (EFI_ERROR (Status)) {
        ASSERT (0);
        return Status;
      }
    }

  } else if (IS_AML_ROOT_NODE (Node)) {
    // Update the length field in the SDT header.
    Value = ((AML_ROOT_NODE*)Node)->SdtHeader->Length;

    // Check for an over/underflows.
    if ((IsIncrement && (Value > (MAX_UINT32 - *Diff))) ||
        (!IsIncrement && (Value < *Diff))) {
      ASSERT (0);
      return EFI_INVALID_PARAMETER;
    }

    // Update the size.
    if (IsIncrement) {
      Value += *Diff;
    } else {
      Value -= *Diff;
    }

    ((AML_ROOT_NODE*)Node)->SdtHeader->Length = Value;
  }

  return EFI_SUCCESS;
}

/** Propagate the node count information up the tree.

  @param  [in]  ObjectNode        Pointer to an object node.
  @param  [in]  IsIncrement       Choose the operation to do:
                                   - TRUE:  Increment the Node's size and
                                            the Node's count;
                                   - FALSE: Decrement the Node's size and
                                           the Node's count.
  @param  [in]  NodeCount         Number of nodes added/removed (depends on the
                                  value of Operation).
  @param  [out] FieldWidthChange  When modifying the integer, it can be
                                  promoted/demoted, e.g. from UINT8 to UINT16.
                                  Stores the change in width.
                                  Can be negative.

  @retval EFI_SUCCESS             The function completed successfully.
  @retval EFI_INVALID_PARAMETER   Invalid parameter.
**/
STATIC
EFI_STATUS
EFIAPI
AmlPropagateNodeCount (
  IN  AML_OBJECT_NODE   * ObjectNode,
  IN  BOOLEAN             IsIncrement,
  IN  UINT8               NodeCount,
  OUT INT8              * FieldWidthChange
  )
{
  EFI_STATUS         Status;

  AML_NODE_HEADER  * NodeCountArg;
  UINT8              CurrNodeCount;

  // Currently there is no use case where (NodeCount > 1).
  if (!IS_AML_OBJECT_NODE (ObjectNode)  ||
      (FieldWidthChange == NULL)        ||
      (NodeCount > 1)) {
    ASSERT (0);
    return EFI_INVALID_PARAMETER;
  }

  *FieldWidthChange = 0;

  // Update the number of elements stored in PackageOp and VarPackageOp.
  // The number of elements is stored as the first fixed argument.
  // DefPackage := PackageOp PkgLength NumElements PackageElementList
  // PackageOp := 0x12
  // DefVarPackage := VarPackageOp PkgLength VarNumElements PackageElementList
  // VarPackageOp := 0x13
  // NumElements := ByteData
  // VarNumElements := TermArg => Integer
  NodeCountArg = AmlGetFixedArgument (ObjectNode, EAmlParseIndexTerm0);
  if (AmlNodeCompareOpCode (ObjectNode, AML_PACKAGE_OP, 0)) {
    // First fixed argument of PackageOp stores the number of elements
    // in the package. It is an UINT8.

    // Check for over/underflow.
    CurrNodeCount = *(((AML_DATA_NODE*)NodeCountArg)->Buffer);
    if ((IsIncrement && (CurrNodeCount == MAX_UINT8)) ||
        (!IsIncrement && (CurrNodeCount == 0))) {
      ASSERT (0);
      return EFI_INVALID_PARAMETER;
    }

    // Update the node count in the DataNode.
    CurrNodeCount = IsIncrement ? (CurrNodeCount + 1) : (CurrNodeCount - 1);
    *(((AML_DATA_NODE*)NodeCountArg)->Buffer) =  CurrNodeCount;
  } else if (AmlNodeCompareOpCode (ObjectNode, AML_VAR_PACKAGE_OP, 0)) {
    // First fixed argument of PackageOp stores the number of elements
    // in the package. It is an integer (can be a BYTE, WORD, DWORD, QWORD).
    Status = AmlNodeUpdateIntegerValue (
                (AML_OBJECT_NODE*)NodeCountArg,
                IsIncrement,
                NodeCount,
                FieldWidthChange
                );
    if (EFI_ERROR (Status)) {
      ASSERT (0);
      return Status;
    }
  }

  return EFI_SUCCESS;
}

/** Propagate information up the tree.

  The information can be a new size, a new number of arguments.

  @param  [in]  Node          Pointer to a node.
                              Must be a root node or an object node.
  @param  [in]  IsIncrement   Choose the operation to do:
                               - TRUE:  Increment the Node's size and
                                        the Node's count;
                               - FALSE: Decrement the Node's size and
                                        the Node's count.
  @param  [in]  Diff          Value to add/subtract to the Node's size.
  @param  [in]  NodeCount     Number of nodes added/removed.

  @retval EFI_SUCCESS             The function completed successfully.
  @retval EFI_INVALID_PARAMETER   Invalid parameter.
**/
EFI_STATUS
EFIAPI
AmlPropagateInformation (
  IN  AML_NODE_HEADER   * Node,
  IN  BOOLEAN             IsIncrement,
  IN  UINT32              Diff,
  IN  UINT8               NodeCount
  )
{
  EFI_STATUS  Status;
  INT8        FieldWidthChange;

  // Currently there is no use case where (NodeCount > 1).
  if ((!IS_AML_ROOT_NODE (Node)     &&
       !IS_AML_OBJECT_NODE (Node))  ||
      (NodeCount > 1)) {
    ASSERT (0);
    return EFI_INVALID_PARAMETER;
  }

  // Propagate the node count first as it may change the number of bytes
  // needed to store the node count, and then impact FieldWidthChange.
  if ((NodeCount != 0) &&
      IS_AML_OBJECT_NODE (Node)) {
    Status = AmlPropagateNodeCount (
               (AML_OBJECT_NODE*)Node,
               IsIncrement,
               NodeCount,
               &FieldWidthChange
               );
    if (EFI_ERROR (Status)) {
      ASSERT (0);
      return Status;
    }

    // Propagate the potential field width change.
    // Maximum change is between UINT8/UINT64: 8 bytes.
    if ((ABS (FieldWidthChange) > 8)                          ||
        (IsIncrement                                          &&
          ((FieldWidthChange < 0)                             ||
           ((Diff + (UINT8)FieldWidthChange) > MAX_UINT32)))  ||
        (!IsIncrement                                         &&
          ((FieldWidthChange > 0)                             ||
           (Diff < (UINT32)ABS (FieldWidthChange))))) {
      ASSERT (0);
      return EFI_INVALID_PARAMETER;
    }
    Diff = (UINT32)(Diff + (UINT8)ABS (FieldWidthChange));
  }

  // Diff can be zero if some data is updated without modifying the data size.
  if (Diff != 0) {
    Status = AmlPropagateSize (Node, IsIncrement, &Diff);
    if (EFI_ERROR (Status)) {
      ASSERT (0);
      return Status;
    }
  }

  return EFI_SUCCESS;
}
