/** @file
  AML Api.

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

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

/* Even though this file has access to the internal Node definition,
   i.e. AML_ROOT_NODE, AML_OBJECT_NODE, etc. Only the external node
   handle types should be used, i.e. AML_NODE_HANDLE, AML_ROOT_NODE_HANDLE,
   etc.
   Indeed, the functions in the "Api" folder should be implemented only
   using the "safe" functions available in the "Include" folder. This
   makes the functions available in the "Api" folder easy to export.
*/
#include <AmlNodeDefines.h>

#include <AmlCoreInterface.h>
#include <AmlInclude.h>
#include <Api/AmlApiHelper.h>
#include <String/AmlString.h>

/** Update the name of a DeviceOp object node.

  @param  [in] DeviceOpNode   Object node representing a Device.
                              Must have an OpCode=AML_NAME_OP, SubOpCode=0.
                              OpCode/SubOpCode.
                              DeviceOp object nodes are defined in ASL
                              using the "Device ()" function.
  @param  [in] NewNameString  The new Device's name.
                              Must be a NULL-terminated ASL NameString
                              e.g.: "DEV0", "DV15.DEV0", etc.
                              The input string is copied.

  @retval EFI_SUCCESS             The function completed successfully.
  @retval EFI_INVALID_PARAMETER   Invalid parameter.
**/
EFI_STATUS
EFIAPI
AmlDeviceOpUpdateName (
  IN  AML_OBJECT_NODE_HANDLE  DeviceOpNode,
  IN  CHAR8                   *NewNameString
  )
{
  EFI_STATUS  Status;

  AML_DATA_NODE_HANDLE  DeviceNameDataNode;
  CHAR8                 *NewAmlNameString;
  UINT32                NewAmlNameStringSize;

  // Check the input node is an object node.
  if ((DeviceOpNode == NULL)                                              ||
      (AmlGetNodeType ((AML_NODE_HANDLE)DeviceOpNode) != EAmlNodeObject)  ||
      (!AmlNodeHasOpCode (DeviceOpNode, AML_EXT_OP, AML_EXT_DEVICE_OP))   ||
      (NewNameString == NULL))
  {
    ASSERT (0);
    return EFI_INVALID_PARAMETER;
  }

  // Get the Device's name, being a data node
  // which is the 1st fixed argument (i.e. index 0).
  DeviceNameDataNode = (AML_DATA_NODE_HANDLE)AmlGetFixedArgument (
                                               DeviceOpNode,
                                               EAmlParseIndexTerm0
                                               );
  if ((DeviceNameDataNode == NULL)                                            ||
      (AmlGetNodeType ((AML_NODE_HANDLE)DeviceNameDataNode) != EAmlNodeData)  ||
      (!AmlNodeHasDataType (DeviceNameDataNode, EAmlNodeDataTypeNameString)))
  {
    ASSERT (0);
    return EFI_INVALID_PARAMETER;
  }

  Status = ConvertAslNameToAmlName (NewNameString, &NewAmlNameString);
  if (EFI_ERROR (Status)) {
    ASSERT (0);
    return Status;
  }

  Status = AmlGetNameStringSize (NewAmlNameString, &NewAmlNameStringSize);
  if (EFI_ERROR (Status)) {
    ASSERT (0);
    goto exit_handler;
  }

  // Update the Device's name node.
  Status = AmlUpdateDataNode (
             DeviceNameDataNode,
             EAmlNodeDataTypeNameString,
             (UINT8 *)NewAmlNameString,
             NewAmlNameStringSize
             );
  ASSERT_EFI_ERROR (Status);

exit_handler:
  FreePool (NewAmlNameString);
  return Status;
}

/** Update an integer value defined by a NameOp object node.

  For compatibility reasons, the NameOpNode must initially
  contain an integer.

  @param  [in] NameOpNode   NameOp object node.
                            Must have an OpCode=AML_NAME_OP, SubOpCode=0.
                            NameOp object nodes are defined in ASL
                            using the "Name ()" function.
  @param  [in] NewInt       New Integer value to assign.
                            Must be a UINT64.

  @retval EFI_SUCCESS             The function completed successfully.
  @retval EFI_INVALID_PARAMETER   Invalid parameter.
**/
EFI_STATUS
EFIAPI
AmlNameOpUpdateInteger (
  IN  AML_OBJECT_NODE_HANDLE  NameOpNode,
  IN  UINT64                  NewInt
  )
{
  EFI_STATUS              Status;
  AML_OBJECT_NODE_HANDLE  IntegerOpNode;

  if ((NameOpNode == NULL)                                             ||
      (AmlGetNodeType ((AML_NODE_HANDLE)NameOpNode) != EAmlNodeObject) ||
      (!AmlNodeHasOpCode (NameOpNode, AML_NAME_OP, 0)))
  {
    ASSERT (0);
    return EFI_INVALID_PARAMETER;
  }

  // Get the Integer object node defined by the "Name ()" function:
  // it must have an Integer OpCode (Byte/Word/DWord/QWord).
  // It is the 2nd fixed argument (i.e. index 1) of the NameOp node.
  // This can also be a ZeroOp or OneOp node.
  IntegerOpNode = (AML_OBJECT_NODE_HANDLE)AmlGetFixedArgument (
                                            NameOpNode,
                                            EAmlParseIndexTerm1
                                            );
  if ((IntegerOpNode == NULL)  ||
      (AmlGetNodeType ((AML_NODE_HANDLE)IntegerOpNode) != EAmlNodeObject))
  {
    ASSERT (0);
    return EFI_INVALID_PARAMETER;
  }

  // Update the Integer value.
  Status = AmlUpdateInteger (IntegerOpNode, NewInt);
  ASSERT_EFI_ERROR (Status);

  return Status;
}

/** Update a string value defined by a NameOp object node.

  The NameOpNode must initially contain a string.
  The EISAID ASL macro converts a string to an integer. This, it is
  not accepted.

  @param  [in] NameOpNode   NameOp object node.
                            Must have an OpCode=AML_NAME_OP, SubOpCode=0.
                            NameOp object nodes are defined in ASL
                            using the "Name ()" function.
  @param  [in] NewName      New NULL terminated string to assign to
                            the NameOpNode.
                            The input string is copied.

  @retval EFI_SUCCESS             The function completed successfully.
  @retval EFI_INVALID_PARAMETER   Invalid parameter.
**/
EFI_STATUS
EFIAPI
AmlNameOpUpdateString (
  IN        AML_OBJECT_NODE_HANDLE  NameOpNode,
  IN  CONST CHAR8                   *NewName
  )
{
  EFI_STATUS              Status;
  AML_OBJECT_NODE_HANDLE  StringOpNode;
  AML_DATA_NODE_HANDLE    StringDataNode;

  if ((NameOpNode == NULL)                                             ||
      (AmlGetNodeType ((AML_NODE_HANDLE)NameOpNode) != EAmlNodeObject) ||
      (!AmlNodeHasOpCode (NameOpNode, AML_NAME_OP, 0)))
  {
    ASSERT (0);
    return EFI_INVALID_PARAMETER;
  }

  // Get the String object node defined by the "Name ()" function:
  // it must have a string OpCode.
  // It is the 2nd fixed argument (i.e. index 1) of the NameOp node.
  StringOpNode = (AML_OBJECT_NODE_HANDLE)AmlGetFixedArgument (
                                           NameOpNode,
                                           EAmlParseIndexTerm1
                                           );
  if ((StringOpNode == NULL)  ||
      (AmlGetNodeType ((AML_NODE_HANDLE)StringOpNode) != EAmlNodeObject))
  {
    ASSERT (0);
    return EFI_INVALID_PARAMETER;
  }

  // Get the string data node.
  // It is the 1st fixed argument (i.e. index 0) of the StringOpNode node.
  StringDataNode = (AML_DATA_NODE_HANDLE)AmlGetFixedArgument (
                                           StringOpNode,
                                           EAmlParseIndexTerm0
                                           );
  if ((StringDataNode == NULL)  ||
      (AmlGetNodeType ((AML_NODE_HANDLE)StringDataNode) != EAmlNodeData))
  {
    ASSERT (0);
    return EFI_INVALID_PARAMETER;
  }

  // Update the string value.
  Status = AmlUpdateDataNode (
             StringDataNode,
             EAmlNodeDataTypeString,
             (UINT8 *)NewName,
             (UINT32)AsciiStrLen (NewName) + 1
             );
  ASSERT_EFI_ERROR (Status);

  return Status;
}

/** Get the first Resource Data element contained in a named object.

  In the following ASL code, the function will return the Resource Data
  node corresponding to the "QWordMemory ()" ASL macro.
  Name (_CRS, ResourceTemplate() {
      QWordMemory (...) {...},
      Interrupt (...) {...}
    }
  )

  Note:
  "_CRS" names defined as methods are not handled by this function.
  They must be defined as names, using the "Name ()" statement.

  @param  [in] NameOpNode   NameOp object node defining a named object.
                            Must have an OpCode=AML_NAME_OP, SubOpCode=0.
                            NameOp object nodes are defined in ASL
                            using the "Name ()" function.
  @param  [out] OutRdNode   Pointer to the first Resource Data element of
                            the named object. A Resource Data element
                            is stored in a data node.

  @retval EFI_SUCCESS             The function completed successfully.
  @retval EFI_INVALID_PARAMETER   Invalid parameter.
**/
EFI_STATUS
EFIAPI
AmlNameOpGetFirstRdNode (
  IN  AML_OBJECT_NODE_HANDLE  NameOpNode,
  OUT AML_DATA_NODE_HANDLE    *OutRdNode
  )
{
  AML_OBJECT_NODE_HANDLE  BufferOpNode;
  AML_DATA_NODE_HANDLE    FirstRdNode;

  if ((NameOpNode == NULL)                                              ||
      (AmlGetNodeType ((AML_NODE_HANDLE)NameOpNode) != EAmlNodeObject)  ||
      (!AmlNodeHasOpCode (NameOpNode, AML_NAME_OP, 0))                  ||
      (OutRdNode == NULL))
  {
    ASSERT (0);
    return EFI_INVALID_PARAMETER;
  }

  *OutRdNode = NULL;

  // Get the value of the variable which is represented as a BufferOp object
  // node which is the 2nd fixed argument (i.e. index 1).
  BufferOpNode = (AML_OBJECT_NODE_HANDLE)AmlGetFixedArgument (
                                           NameOpNode,
                                           EAmlParseIndexTerm1
                                           );
  if ((BufferOpNode == NULL)                                             ||
      (AmlGetNodeType ((AML_NODE_HANDLE)BufferOpNode) != EAmlNodeObject) ||
      (!AmlNodeHasOpCode (BufferOpNode, AML_BUFFER_OP, 0)))
  {
    ASSERT (0);
    return EFI_INVALID_PARAMETER;
  }

  // Get the first Resource data node in the variable list of
  // argument of the BufferOp node.
  FirstRdNode = (AML_DATA_NODE_HANDLE)AmlGetNextVariableArgument (
                                        (AML_NODE_HANDLE)BufferOpNode,
                                        NULL
                                        );
  if ((FirstRdNode == NULL)                                            ||
      (AmlGetNodeType ((AML_NODE_HANDLE)FirstRdNode) != EAmlNodeData)  ||
      (!AmlNodeHasDataType (FirstRdNode, EAmlNodeDataTypeResourceData)))
  {
    ASSERT (0);
    return EFI_INVALID_PARAMETER;
  }

  *OutRdNode = FirstRdNode;
  return EFI_SUCCESS;
}

/** Get the Resource Data element following the CurrRdNode Resource Data.

  In the following ASL code, if CurrRdNode corresponds to the first
  "QWordMemory ()" ASL macro, the function will return the Resource Data
  node corresponding to the "Interrupt ()" ASL macro.
  Name (_CRS, ResourceTemplate() {
      QwordMemory (...) {...},
      Interrupt (...) {...}
    }
  )

  Note:
  "_CRS" names defined as methods are not handled by this function.
  They must be defined as names, using the "Name ()" statement.

  @param  [in]  CurrRdNode   Pointer to the current Resource Data element of
                             the named object.
  @param  [out] OutRdNode    Pointer to the Resource Data element following
                             the CurrRdNode.
                             Contain a NULL pointer if CurrRdNode is the
                             last Resource Data element in the list.
                             The "End Tag" is not considered as a resource
                             data element and is not returned.

  @retval EFI_SUCCESS             The function completed successfully.
  @retval EFI_INVALID_PARAMETER   Invalid parameter.
**/
EFI_STATUS
EFIAPI
AmlNameOpGetNextRdNode (
  IN  AML_DATA_NODE_HANDLE  CurrRdNode,
  OUT AML_DATA_NODE_HANDLE  *OutRdNode
  )
{
  AML_OBJECT_NODE_HANDLE  NameOpNode;
  AML_OBJECT_NODE_HANDLE  BufferOpNode;

  if ((CurrRdNode == NULL)                                              ||
      (AmlGetNodeType ((AML_NODE_HANDLE)CurrRdNode) != EAmlNodeData)    ||
      (!AmlNodeHasDataType (CurrRdNode, EAmlNodeDataTypeResourceData))  ||
      (OutRdNode == NULL))
  {
    ASSERT (0);
    return EFI_INVALID_PARAMETER;
  }

  *OutRdNode = NULL;

  // The parent of the CurrRdNode must be a BufferOp node.
  BufferOpNode = (AML_OBJECT_NODE_HANDLE)AmlGetParent (
                                           (AML_NODE_HANDLE)CurrRdNode
                                           );
  if ((BufferOpNode == NULL)  ||
      (!AmlNodeHasOpCode (BufferOpNode, AML_BUFFER_OP, 0)))
  {
    ASSERT (0);
    return EFI_INVALID_PARAMETER;
  }

  // The parent of the BufferOpNode must be a NameOp node.
  NameOpNode = (AML_OBJECT_NODE_HANDLE)AmlGetParent (
                                         (AML_NODE_HANDLE)BufferOpNode
                                         );
  if ((NameOpNode == NULL)  ||
      (!AmlNodeHasOpCode (NameOpNode, AML_NAME_OP, 0)))
  {
    ASSERT (0);
    return EFI_INVALID_PARAMETER;
  }

  *OutRdNode = (AML_DATA_NODE_HANDLE)AmlGetNextVariableArgument (
                                       (AML_NODE_HANDLE)BufferOpNode,
                                       (AML_NODE_HANDLE)CurrRdNode
                                       );

  // If the Resource Data is an End Tag, return NULL.
  if (AmlNodeHasRdDataType (
        *OutRdNode,
        AML_RD_BUILD_SMALL_DESC_ID (ACPI_SMALL_END_TAG_DESCRIPTOR_NAME)
        ))
  {
    *OutRdNode = NULL;
  }

  return EFI_SUCCESS;
}

/** Attach a node in an AML tree.

  The node will be added as the last statement of the ParentNode.
  E.g.:
  ASL code corresponding to NewNode:
  Name (_UID, 0)

  ASL code corresponding to ParentNode:
  Device (PCI0) {
    Name(_HID, EISAID("PNP0A08"))
  }

  "AmlAttachNode (ParentNode, NewNode)" will result in:
  ASL code:
  Device (PCI0) {
    Name(_HID, EISAID("PNP0A08"))
    Name (_UID, 0)
  }

  @param  [in]  ParentNode  Pointer to the parent node.
                            Must be a root or an object node.
  @param  [in]  NewNode     Pointer to the node to add.

  @retval EFI_SUCCESS             The function completed successfully.
  @retval EFI_INVALID_PARAMETER   Invalid parameter.
**/
EFI_STATUS
EFIAPI
AmlAttachNode (
  IN  AML_NODE_HANDLE  ParentNode,
  IN  AML_NODE_HANDLE  NewNode
  )
{
  return AmlVarListAddTail (ParentNode, NewNode);
}
