/** @file
  AML Update Resource Data.

  Copyright (c) 2020, 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 <CodeGen/AmlResourceDataCodeGen.h>

/** Update the first interrupt of an Interrupt resource data node.

  The flags of the Interrupt resource data are left unchanged.

  The InterruptRdNode corresponds to the Resource Data created by the
  "Interrupt ()" ASL macro. It is an Extended Interrupt Resource Data.
  See ACPI 6.3 specification, s6.4.3.6 "Extended Interrupt Descriptor"
  for more information about Extended Interrupt Resource Data.

  @param  [in]  InterruptRdNode   Pointer to the an extended interrupt
                                  resource data node.
  @param  [in]  Irq               Interrupt value to update.

  @retval  EFI_SUCCESS            The function completed successfully.
  @retval  EFI_INVALID_PARAMETER  Invalid parameter.
  @retval  EFI_OUT_OF_RESOURCES   Out of resources.
**/
EFI_STATUS
EFIAPI
AmlUpdateRdInterrupt (
  IN  AML_DATA_NODE_HANDLE  InterruptRdNode,
  IN  UINT32                Irq
  )
{
  EFI_STATUS  Status;
  UINT32      *FirstInterrupt;
  UINT8       *QueryBuffer;
  UINT32      QueryBufferSize;

  if ((InterruptRdNode == NULL)                                           ||
      (AmlGetNodeType ((AML_NODE_HANDLE)InterruptRdNode) != EAmlNodeData) ||
      (!AmlNodeHasDataType (
          InterruptRdNode,
          EAmlNodeDataTypeResourceData
          ))                                  ||
      (!AmlNodeHasRdDataType (
          InterruptRdNode,
          AML_RD_BUILD_LARGE_DESC_ID (
            ACPI_LARGE_EXTENDED_IRQ_DESCRIPTOR_NAME
            )
          )))
  {
    ASSERT (0);
    return EFI_INVALID_PARAMETER;
  }

  QueryBuffer = NULL;

  // Get the size of the InterruptRdNode buffer.
  Status = AmlGetDataNodeBuffer (
             InterruptRdNode,
             NULL,
             &QueryBufferSize
             );
  if (EFI_ERROR (Status)) {
    ASSERT (0);
    return Status;
  }

  // Check the Buffer is large enough.
  if (QueryBufferSize < sizeof (EFI_ACPI_EXTENDED_INTERRUPT_DESCRIPTOR)) {
    ASSERT (0);
    return EFI_INVALID_PARAMETER;
  }

  // Allocate a buffer to fetch the data.
  QueryBuffer = AllocatePool (QueryBufferSize);
  if (QueryBuffer == NULL) {
    ASSERT (0);
    return EFI_OUT_OF_RESOURCES;
  }

  // Get the data.
  Status = AmlGetDataNodeBuffer (
             InterruptRdNode,
             QueryBuffer,
             &QueryBufferSize
             );
  if (EFI_ERROR (Status)) {
    ASSERT (0);
    goto error_handler;
  }

  // Get the address of the first interrupt field.
  FirstInterrupt =
    ((EFI_ACPI_EXTENDED_INTERRUPT_DESCRIPTOR *)QueryBuffer)->InterruptNumber;

  *FirstInterrupt = Irq;

  // Update the InterruptRdNode buffer.
  Status = AmlUpdateDataNode (
             InterruptRdNode,
             EAmlNodeDataTypeResourceData,
             QueryBuffer,
             QueryBufferSize
             );
  if (EFI_ERROR (Status)) {
    ASSERT (0);
  }

error_handler:
  if (QueryBuffer != NULL) {
    FreePool (QueryBuffer);
  }

  return Status;
}

/** Update the interrupt list of an interrupt resource data node.

  The InterruptRdNode corresponds to the Resource Data created by the
  "Interrupt ()" ASL function. It is an Extended Interrupt Resource Data.
  See ACPI 6.3 specification, s6.4.3.6 "Extended Interrupt Descriptor"
  for more information about Extended Interrupt Resource Data.

  @param  [in]  InterruptRdNode   Pointer to the an extended interrupt
                                  resource data node.
  @param  [in]  ResourceConsumer    The device consumes the specified interrupt
                                    or produces it for use by a child device.
  @param  [in]  EdgeTriggered       The interrupt is edge triggered or
                                    level triggered.
  @param  [in]  ActiveLow           The interrupt is active-high or active-low.
  @param  [in]  Shared              The interrupt can be shared with other
                                    devices or not (Exclusive).
  @param  [in]  IrqList           Interrupt list. Must be non-NULL.
  @param  [in]  IrqCount          Interrupt count. Must be non-zero.


  @retval  EFI_SUCCESS            The function completed successfully.
  @retval  EFI_INVALID_PARAMETER  Invalid parameter.
  @retval  EFI_OUT_OF_RESOURCES   Out of resources.
**/
EFI_STATUS
EFIAPI
AmlUpdateRdInterruptEx (
  IN  AML_DATA_NODE_HANDLE  InterruptRdNode,
  IN  BOOLEAN               ResourceConsumer,
  IN  BOOLEAN               EdgeTriggered,
  IN  BOOLEAN               ActiveLow,
  IN  BOOLEAN               Shared,
  IN  UINT32                *IrqList,
  IN  UINT8                 IrqCount
  )
{
  EFI_STATUS  Status;

  EFI_ACPI_EXTENDED_INTERRUPT_DESCRIPTOR  *RdInterrupt;
  UINT32                                  *FirstInterrupt;
  UINT8                                   *UpdateBuffer;
  UINT16                                  UpdateBufferSize;

  if ((InterruptRdNode == NULL)                                              ||
      (AmlGetNodeType ((AML_NODE_HANDLE)InterruptRdNode) != EAmlNodeData)    ||
      (!AmlNodeHasDataType (
          InterruptRdNode,
          EAmlNodeDataTypeResourceData
          ))                                     ||
      (!AmlNodeHasRdDataType (
          InterruptRdNode,
          AML_RD_BUILD_LARGE_DESC_ID (
            ACPI_LARGE_EXTENDED_IRQ_DESCRIPTOR_NAME
            )
          ))                       ||
      (IrqList == NULL)                                                      ||
      (IrqCount == 0))
  {
    ASSERT (0);
    return EFI_INVALID_PARAMETER;
  }

  UpdateBuffer     = NULL;
  UpdateBufferSize = sizeof (EFI_ACPI_EXTENDED_INTERRUPT_DESCRIPTOR) +
                     ((IrqCount - 1) * sizeof (UINT32));

  // Allocate a buffer to update the data.
  UpdateBuffer = AllocatePool (UpdateBufferSize);
  if (UpdateBuffer == NULL) {
    ASSERT (0);
    return EFI_OUT_OF_RESOURCES;
  }

  // Update the Resource Data information (structure size, interrupt count).
  RdInterrupt                     = (EFI_ACPI_EXTENDED_INTERRUPT_DESCRIPTOR *)UpdateBuffer;
  RdInterrupt->Header.Header.Byte =
    AML_RD_BUILD_LARGE_DESC_ID (ACPI_LARGE_EXTENDED_IRQ_DESCRIPTOR_NAME);
  RdInterrupt->Header.Length =
    UpdateBufferSize - sizeof (ACPI_LARGE_RESOURCE_HEADER);
  RdInterrupt->InterruptTableLength = IrqCount;
  RdInterrupt->InterruptVectorFlags = (ResourceConsumer ? BIT0 : 0) |
                                      (EdgeTriggered ? BIT1 : 0)    |
                                      (ActiveLow ? BIT2 : 0)        |
                                      (Shared ? BIT3 : 0);

  // Get the address of the first interrupt field.
  FirstInterrupt =
    ((EFI_ACPI_EXTENDED_INTERRUPT_DESCRIPTOR *)UpdateBuffer)->InterruptNumber;

  // Copy the input list of interrupts.
  CopyMem (FirstInterrupt, IrqList, (sizeof (UINT32) * IrqCount));

  // Update the InterruptRdNode buffer.
  Status = AmlUpdateDataNode (
             InterruptRdNode,
             EAmlNodeDataTypeResourceData,
             UpdateBuffer,
             UpdateBufferSize
             );
  if (EFI_ERROR (Status)) {
    ASSERT (0);
  }

  // Cleanup
  FreePool (UpdateBuffer);

  return Status;
}

/** Update the base address and length of a QWord resource data node.

  @param  [in] QWordRdNode         Pointer a QWord resource data
                                   node.
  @param  [in] BaseAddress         Base address.
  @param  [in] BaseAddressLength   Base address length.

  @retval  EFI_SUCCESS            The function completed successfully.
  @retval  EFI_INVALID_PARAMETER  Invalid parameter.
  @retval  EFI_OUT_OF_RESOURCES   Out of resources.
**/
EFI_STATUS
EFIAPI
AmlUpdateRdQWord (
  IN  AML_DATA_NODE_HANDLE  QWordRdNode,
  IN  UINT64                BaseAddress,
  IN  UINT64                BaseAddressLength
  )
{
  EFI_STATUS                               Status;
  EFI_ACPI_QWORD_ADDRESS_SPACE_DESCRIPTOR  *RdQWord;

  UINT8   *QueryBuffer;
  UINT32  QueryBufferSize;

  if ((QWordRdNode == NULL)                                             ||
      (AmlGetNodeType ((AML_NODE_HANDLE)QWordRdNode) != EAmlNodeData)   ||
      (!AmlNodeHasDataType (QWordRdNode, EAmlNodeDataTypeResourceData)) ||
      (!AmlNodeHasRdDataType (
          QWordRdNode,
          AML_RD_BUILD_LARGE_DESC_ID (
            ACPI_LARGE_QWORD_ADDRESS_SPACE_DESCRIPTOR_NAME
            )
          )))
  {
    ASSERT (0);
    return EFI_INVALID_PARAMETER;
  }

  // Get the size of the QWordRdNode's buffer.
  Status = AmlGetDataNodeBuffer (
             QWordRdNode,
             NULL,
             &QueryBufferSize
             );
  if (EFI_ERROR (Status)) {
    ASSERT (0);
    return Status;
  }

  // Allocate a buffer to fetch the data.
  QueryBuffer = AllocatePool (QueryBufferSize);
  if (QueryBuffer == NULL) {
    ASSERT (0);
    return EFI_OUT_OF_RESOURCES;
  }

  // Get the data.
  Status = AmlGetDataNodeBuffer (
             QWordRdNode,
             QueryBuffer,
             &QueryBufferSize
             );
  if (EFI_ERROR (Status)) {
    ASSERT (0);
    goto error_handler;
  }

  RdQWord = (EFI_ACPI_QWORD_ADDRESS_SPACE_DESCRIPTOR *)QueryBuffer;

  // Update the Base Address and Length.
  RdQWord->AddrRangeMin = BaseAddress;
  RdQWord->AddrRangeMax = BaseAddress + BaseAddressLength - 1;
  RdQWord->AddrLen      = BaseAddressLength;

  // Update Base Address Resource Data node.
  Status = AmlUpdateDataNode (
             QWordRdNode,
             EAmlNodeDataTypeResourceData,
             QueryBuffer,
             QueryBufferSize
             );
  if (EFI_ERROR (Status)) {
    ASSERT (0);
  }

error_handler:
  if (QueryBuffer != NULL) {
    FreePool (QueryBuffer);
  }

  return Status;
}
