/** @file
  RISC-V Interrupt Controllers parser.

  Copyright (c) 2025, Ventana Micro Systems Inc. All rights reserved.<BR>

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

**/

#include <Library/BaseLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/DebugLib.h>
#include <Library/MemoryAllocationLib.h>

typedef struct {
  UINT32        Signature;
  LIST_ENTRY    Link;
  INT32         ExtIntcNode;
  UINT32        GsiBase;
} RISCV_EXT_INTC_DATA;

//
// Unique signature for private data structure.
//
#define RISCV_EXT_INTC_DATA_SIGNATURE  SIGNATURE_32 ('R','S','I','C')

#define RISCV_EXT_INTC_INFO_FROM_THIS(a)  \
  CR (a,                                       \
      RISCV_EXT_INTC_DATA,                \
      Link,                                    \
      RISCV_EXT_INTC_DATA_SIGNATURE       \
      );

STATIC LIST_ENTRY  mExtIntcList;

/** Create list of external interrupt controllers.

  In RISC-V, GSI space is divided among PLIC/APLICs. To convert
  the IRQ number in DT of a device to appropriate GSI number,
  create a list of external interrupt controllers.

  @param [in]  ExtIntcNode      External interrupt controller node.
  @param [in]  GsiBase          GSI base of the interrupt controller.

  @retval EFI_SUCCESS           External interrupt controller node is added in the list.
  @retval EFI_OUT_OF_RESOURCES  Failed to allocate memory for the list.
**/
EFI_STATUS
FdtCreateExtIntcList (
  INT32   ExtIntcNode,
  UINT32  GsiBase
  )
{
  RISCV_EXT_INTC_DATA  *Node;

  Node = AllocateZeroPool (sizeof (RISCV_EXT_INTC_DATA));
  if (Node == NULL) {
    ASSERT (0);
    return EFI_OUT_OF_RESOURCES;
  }

  // Initialise the list head.
  InitializeListHead (&mExtIntcList);
  Node->Signature   = RISCV_EXT_INTC_DATA_SIGNATURE;
  Node->ExtIntcNode = ExtIntcNode;
  Node->GsiBase     = GsiBase;
  InsertTailList (&mExtIntcList, &Node->Link);
  return EFI_SUCCESS;
}

/** Convert the IRQ number in DT to ACPI GSI number

  In RISC-V, GSI space is divided among PLIC/APLICs. Hence, based
  on the parent interrupt controller, the IRQ number need to be
  converted into appropriate GSI number.

  @param [in]  ExtIntcNode    Parent interrupt controller node.
  @param [in]  Irq            Irq number to convert.

**/
UINT32
FdtConvertToGsi (
  INT32   ExtIntcNode,
  UINT32  Irq
  )
{
  LIST_ENTRY           *Node;
  RISCV_EXT_INTC_DATA  *Data;

  Node = GetFirstNode (&mExtIntcList);
  while (!IsNull (&mExtIntcList, Node)) {
    Data = RISCV_EXT_INTC_INFO_FROM_THIS (Node);
    if (Data->ExtIntcNode == ExtIntcNode) {
      return Data->GsiBase + Irq;
    }

    Node = GetNextNode (&mExtIntcList, Node);
  }

  return Irq;
}

/** Free the external interrupt controller/GSI base list

    @param [in]  ImageHandle  The handle to the image.
    @param [in]  SystemTable  Pointer to the System Table.

    @retval EFI_SUCCESS       The list is freed up.
**/
EFI_STATUS
EFIAPI
RiscVExtIntcDataDestructor (
  IN  EFI_HANDLE        ImageHandle,
  IN  EFI_SYSTEM_TABLE  *SystemTable
  )
{
  RISCV_EXT_INTC_DATA  *Data;
  LIST_ENTRY           *NextNode;
  LIST_ENTRY           *Node;

  Node = GetFirstNode (&mExtIntcList);
  while (!IsNull (&mExtIntcList, Node)) {
    Data     = RISCV_EXT_INTC_INFO_FROM_THIS (Node);
    NextNode = GetNextNode (&mExtIntcList, Node);
    RemoveEntryList (&Data->Link);
    FreePool (Data);

    Node = NextNode;
  }

  return EFI_SUCCESS;
}
