/** @file
  Arm PCI Configuration Space Parser.

  Copyright (c) 2021, ARM Limited. All rights reserved.<BR>
  SPDX-License-Identifier: BSD-2-Clause-Patent

  @par Reference(s):
  - linux/Documentation/devicetree/bindings/pci/host-generic-pci.yaml
  - PCI Firmware Specification - Revision 3.0
  - Open Firmware Recommended Practice: Interrupt Mapping, Version 0.9
  - Devicetree Specification Release v0.3
  - linux kernel code
**/

#include "CmObjectDescUtility.h"
#include <Library/DebugLib.h>

#include "FdtHwInfoParser.h"
#include "Pci/ArmPciConfigSpaceParser.h"
#include "Gic/ArmGicDispatcher.h"

/** List of "compatible" property values for host PCIe bridges nodes.

  Any other "compatible" value is not supported by this module.
*/
STATIC CONST COMPATIBILITY_STR  PciCompatibleStr[] = {
  { "pci-host-ecam-generic" }
};

/** COMPATIBILITY_INFO structure for the PCIe.
*/
STATIC CONST COMPATIBILITY_INFO  PciCompatibleInfo = {
  ARRAY_SIZE (PciCompatibleStr),
  PciCompatibleStr
};

/** Get the Segment group (also called: Domain Id) of a host-pci node.

  kernel/Documentation/devicetree/bindings/pci/pci.txt:
  "It is required to either not set this property at all or set it for all
  host bridges in the system"

  The function checks the "linux,pci-domain" property of the host-pci node.
  Either all host-pci nodes must have this property, or none of them. If the
  property is available, read it. Otherwise dynamically assign the Ids.

  @param [in]  Fdt          Pointer to a Flattened Device Tree (Fdt).
  @param [in]  HostPciNode  Offset of a host-pci node.
  @param [out] SegGroup     Segment group assigned to the host-pci controller.

  @retval EFI_SUCCESS             The function completed successfully.
  @retval EFI_ABORTED             An error occurred.
  @retval EFI_INVALID_PARAMETER   Invalid parameter.
**/
STATIC
EFI_STATUS
EFIAPI
GetPciSegGroup (
  IN  CONST VOID   *Fdt,
  IN        INT32  HostPciNode,
  OUT       INT32  *SegGroup
  )
{
  CONST UINT8   *Data;
  INT32         DataSize;
  STATIC INT32  LocalSegGroup = 0;

  if ((Fdt == NULL) ||
      (SegGroup == NULL))
  {
    ASSERT (0);
    return EFI_INVALID_PARAMETER;
  }

  Data = fdt_getprop (Fdt, HostPciNode, "linux,pci-domain", &DataSize);
  if ((Data == NULL) || (DataSize < 0)) {
    // Did not find property, assign the DomainIds ourselves.
    if (LocalSegGroup < 0) {
      // "linux,pci-domain" property was defined for another node.
      ASSERT (0);
      return EFI_ABORTED;
    }

    *SegGroup = LocalSegGroup++;
    return EFI_SUCCESS;
  }

  if ((DataSize > sizeof (UINT32))  ||
      (LocalSegGroup > 0))
  {
    // Property on more than 1 cell or
    // "linux,pci-domain" property was not defined for a node.
    ASSERT (0);
    return EFI_ABORTED;
  }

  // If one node has the "linux,pci-domain" property, then all the host-pci
  // nodes must have it.
  LocalSegGroup = -1;

  *SegGroup = fdt32_to_cpu (*(UINT32 *)Data);
  return EFI_SUCCESS;
}

/** Parse the bus-range controlled by this host-pci node.

  @param [in]       Fdt           Pointer to a Flattened Device Tree (Fdt).
  @param [in]       HostPciNode   Offset of a host-pci node.
  @param [in, out]  PciInfo       PCI_PARSER_TABLE structure storing
                                  information about the current host-pci.

  @retval EFI_SUCCESS             The function completed successfully.
  @retval EFI_ABORTED             An error occurred.
  @retval EFI_INVALID_PARAMETER   Invalid parameter.
**/
STATIC
EFI_STATUS
EFIAPI
PopulateBusRange (
  IN      CONST VOID              *Fdt,
  IN            INT32             HostPciNode,
  IN OUT        PCI_PARSER_TABLE  *PciInfo
  )
{
  CONST UINT8  *Data;
  INT32        DataSize;
  UINT32       StartBus;
  UINT32       EndBus;

  if ((Fdt == NULL) ||
      (PciInfo == NULL))
  {
    ASSERT (0);
    return EFI_INVALID_PARAMETER;
  }

  Data = fdt_getprop (Fdt, HostPciNode, "bus-range", &DataSize);
  if ((Data == NULL) || (DataSize < 0)) {
    // No evidence this property is mandatory. Use default values.
    StartBus = 0;
    EndBus   = 255;
  } else if (DataSize == (2 * sizeof (UINT32))) {
    // If available, the property is on two integers.
    StartBus = fdt32_to_cpu (((UINT32 *)Data)[0]);
    EndBus   = fdt32_to_cpu (((UINT32 *)Data)[1]);
  } else {
    ASSERT (0);
    return EFI_ABORTED;
  }

  PciInfo->PciConfigSpaceInfo.StartBusNumber = StartBus;
  PciInfo->PciConfigSpaceInfo.EndBusNumber   = EndBus;

  return EFI_SUCCESS;
}

/** Parse the PCI address map.

  The PCI address map is available in the "ranges" device-tree property.

  @param [in]       Fdt           Pointer to a Flattened Device Tree (Fdt).
  @param [in]       HostPciNode   Offset of a host-pci node.
  @param [in]       AddressCells  # of cells used to encode an address on
                                  the parent bus.
  @param [in, out]  PciInfo       PCI_PARSER_TABLE structure storing
                                  information about the current host-pci.

  @retval EFI_SUCCESS             The function completed successfully.
  @retval EFI_ABORTED             An error occurred.
  @retval EFI_INVALID_PARAMETER   Invalid parameter.
  @retval EFI_OUT_OF_RESOURCES    An allocation has failed.
**/
STATIC
EFI_STATUS
EFIAPI
ParseAddressMap (
  IN      CONST VOID              *Fdt,
  IN            INT32             HostPciNode,
  IN            INT32             AddressCells,
  IN OUT        PCI_PARSER_TABLE  *PciInfo
  )
{
  CONST UINT8  *Data;
  INT32        DataSize;
  UINT32       Index;
  UINT32       Offset;
  UINT32       AddressMapSize;
  UINT32       Count;
  UINT32       PciAddressAttr;

  CM_ARM_PCI_ADDRESS_MAP_INFO  *PciAddressMapInfo;
  UINT32                       BufferSize;

  // The mapping is done on AddressMapSize bytes.
  AddressMapSize = (PCI_ADDRESS_CELLS + AddressCells + PCI_SIZE_CELLS) *
                   sizeof (UINT32);

  Data = fdt_getprop (Fdt, HostPciNode, "ranges", &DataSize);
  if ((Data == NULL) ||
      (DataSize < 0) ||
      ((DataSize % AddressMapSize) != 0))
  {
    // If error or not on AddressMapSize bytes.
    ASSERT (0);
    return EFI_ABORTED;
  }

  Count = DataSize / AddressMapSize;

  // Allocate a buffer to store each address mapping.
  BufferSize        = Count * sizeof (CM_ARM_PCI_ADDRESS_MAP_INFO);
  PciAddressMapInfo = AllocateZeroPool (BufferSize);
  if (PciAddressMapInfo == NULL) {
    ASSERT (0);
    return EFI_OUT_OF_RESOURCES;
  }

  for (Index = 0; Index < Count; Index++) {
    Offset = Index * AddressMapSize;

    // Pci address attributes
    PciAddressAttr                     = fdt32_to_cpu (*(UINT32 *)&Data[Offset]);
    PciAddressMapInfo[Index].SpaceCode = READ_PCI_SS (PciAddressAttr);
    Offset                            += sizeof (UINT32);

    // Pci address
    PciAddressMapInfo[Index].PciAddress =
      fdt64_to_cpu (*(UINT64 *)&Data[Offset]);
    Offset += (PCI_ADDRESS_CELLS - 1) * sizeof (UINT32);

    // Cpu address
    if (AddressCells == 2) {
      PciAddressMapInfo[Index].CpuAddress =
        fdt64_to_cpu (*(UINT64 *)&Data[Offset]);
    } else {
      PciAddressMapInfo[Index].CpuAddress =
        fdt32_to_cpu (*(UINT32 *)&Data[Offset]);
    }

    Offset += AddressCells * sizeof (UINT32);

    // Address size
    PciAddressMapInfo[Index].AddressSize =
      fdt64_to_cpu (*(UINT64 *)&Data[Offset]);
    Offset += PCI_SIZE_CELLS * sizeof (UINT32);
  } // for

  PciInfo->Mapping[PciMappingTableAddress].ObjectId =
    CREATE_CM_ARM_OBJECT_ID (EArmObjPciAddressMapInfo);
  PciInfo->Mapping[PciMappingTableAddress].Size =
    sizeof (CM_ARM_PCI_ADDRESS_MAP_INFO) * Count;
  PciInfo->Mapping[PciMappingTableAddress].Data  = PciAddressMapInfo;
  PciInfo->Mapping[PciMappingTableAddress].Count = Count;

  return EFI_SUCCESS;
}

/** Parse the PCI interrupt map.

  The PCI interrupt map is available in the "interrupt-map"
  and "interrupt-map-mask" device-tree properties.

  Cf Devicetree Specification Release v0.3,
  s2.4.3 Interrupt Nexus Properties

  An interrupt-map must be as:
  interrupt-map = < [child unit address] [child interrupt specifier]
                    [interrupt-parent]
                    [parent unit address] [parent interrupt specifier] >

  @param [in]       Fdt           Pointer to a Flattened Device Tree (Fdt).
  @param [in]       HostPciNode   Offset of a host-pci node.
  @param [in, out]  PciInfo       PCI_PARSER_TABLE structure storing
                                  information about the current host-pci.

  @retval EFI_SUCCESS             The function completed successfully.
  @retval EFI_ABORTED             An error occurred.
  @retval EFI_INVALID_PARAMETER   Invalid parameter.
  @retval EFI_NOT_FOUND           Not found.
  @retval EFI_OUT_OF_RESOURCES    An allocation has failed.
**/
STATIC
EFI_STATUS
EFIAPI
ParseIrqMap (
  IN      CONST VOID              *Fdt,
  IN            INT32             HostPciNode,
  IN OUT        PCI_PARSER_TABLE  *PciInfo
  )
{
  EFI_STATUS   Status;
  CONST UINT8  *Data;
  INT32        DataSize;
  UINT32       Index;
  UINT32       Offset;

  INT32  IntcNode;
  INT32  IntcAddressCells;
  INT32  IntcCells;

  INT32  PciIntCells;
  INT32  IntcPhandle;

  INT32        IrqMapSize;
  UINT32       IrqMapCount;
  CONST UINT8  *IrqMapMask;
  INT32        IrqMapMaskSize;

  INT32   PHandleOffset;
  UINT32  GicVersion;

  UINT32  PciAddressAttr;

  CM_ARM_PCI_INTERRUPT_MAP_INFO  *PciInterruptMapInfo;
  UINT32                         BufferSize;

  Data = fdt_getprop (Fdt, HostPciNode, "interrupt-map", &DataSize);
  if ((Data == NULL) || (DataSize <= 0)) {
    DEBUG ((
      DEBUG_WARN,
      "Fdt parser: No Legacy interrupts found for PCI configuration space at "
      "address: 0x%lx, group segment: %d\n",
      PciInfo->PciConfigSpaceInfo.BaseAddress,
      PciInfo->PciConfigSpaceInfo.PciSegmentGroupNumber
      ));
    return EFI_NOT_FOUND;
  }

  // PCI interrupts are expected to be on 1 cell. Check it.
  Status = FdtGetInterruptCellsInfo (Fdt, HostPciNode, &PciIntCells);
  if (EFI_ERROR (Status)) {
    ASSERT (0);
    return Status;
  }

  if (PciIntCells != PCI_INTERRUPTS_CELLS) {
    ASSERT (0);
    return EFI_ABORTED;
  }

  IrqMapMask = fdt_getprop (
                 Fdt,
                 HostPciNode,
                 "interrupt-map-mask",
                 &IrqMapMaskSize
                 );
  if ((IrqMapMask == NULL) ||
      (IrqMapMaskSize !=
       (PCI_ADDRESS_CELLS + PCI_INTERRUPTS_CELLS) * sizeof (UINT32)))
  {
    ASSERT (0);
    return EFI_ABORTED;
  }

  // Get the interrupt-controller of the first irq mapping.
  PHandleOffset = (PCI_ADDRESS_CELLS + PciIntCells) * sizeof (UINT32);
  if (PHandleOffset > DataSize) {
    ASSERT (0);
    return EFI_ABORTED;
  }

  IntcPhandle = fdt32_to_cpu (*(UINT32 *)&Data[PHandleOffset]);
  IntcNode    = fdt_node_offset_by_phandle (Fdt, IntcPhandle);
  if (IntcNode < 0) {
    ASSERT (0);
    return EFI_ABORTED;
  }

  // Only support Gic(s) for now.
  Status = GetGicVersion (Fdt, IntcNode, &GicVersion);
  if (EFI_ERROR (Status)) {
    ASSERT (0);
    return Status;
  }

  // Get the "address-cells" property of the IntcNode.
  Status = FdtGetAddressInfo (Fdt, IntcNode, &IntcAddressCells, NULL);
  if (EFI_ERROR (Status)) {
    ASSERT (0);
    return Status;
  }

  // Get the "interrupt-cells" property of the IntcNode.
  Status = FdtGetInterruptCellsInfo (Fdt, IntcNode, &IntcCells);
  if (EFI_ERROR (Status)) {
    ASSERT (0);
    return Status;
  }

  // An irq mapping is done on IrqMapSize bytes
  // (which includes 1 cell for the PHandle).
  IrqMapSize = (PCI_ADDRESS_CELLS + PciIntCells + 1
                + IntcAddressCells + IntcCells) * sizeof (UINT32);
  if ((DataSize % IrqMapSize) != 0) {
    // The mapping is not done on IrqMapSize bytes.
    ASSERT (0);
    return EFI_ABORTED;
  }

  IrqMapCount = DataSize / IrqMapSize;

  // We assume the same interrupt-controller is used for all the mappings.
  // Check this is correct.
  for (Index = 0; Index < IrqMapCount; Index++) {
    if (IntcPhandle != fdt32_to_cpu (
                         *(UINT32 *)&Data[(Index * IrqMapSize) + PHandleOffset]
                         ))
    {
      ASSERT (0);
      return EFI_ABORTED;
    }
  }

  // Allocate a buffer to store each interrupt mapping.
  IrqMapCount         = DataSize / IrqMapSize;
  BufferSize          = IrqMapCount * sizeof (CM_ARM_PCI_ADDRESS_MAP_INFO);
  PciInterruptMapInfo = AllocateZeroPool (BufferSize);
  if (PciInterruptMapInfo == NULL) {
    ASSERT (0);
    return EFI_OUT_OF_RESOURCES;
  }

  for (Index = 0; Index < IrqMapCount; Index++) {
    Offset = Index * IrqMapSize;

    // Pci address attributes
    PciAddressAttr = fdt32_to_cpu (
                       (*(UINT32 *)&Data[Offset]) &
                       (*(UINT32 *)&IrqMapMask[0])
                       );
    PciInterruptMapInfo[Index].PciBus    = READ_PCI_BBBBBBBB (PciAddressAttr);
    PciInterruptMapInfo[Index].PciDevice = READ_PCI_DDDDD (PciAddressAttr);
    Offset                              += PCI_ADDRESS_CELLS * sizeof (UINT32);

    // Pci irq
    PciInterruptMapInfo[Index].PciInterrupt = fdt32_to_cpu (
                                                (*(UINT32 *)&Data[Offset]) &
                                                (*(UINT32 *)&IrqMapMask[3 * sizeof (UINT32)])
                                                );
    // -1 to translate from device-tree (INTA=1) to ACPI (INTA=0) irq IDs.
    PciInterruptMapInfo[Index].PciInterrupt -= 1;
    Offset                                  += PCI_INTERRUPTS_CELLS * sizeof (UINT32);

    // PHandle (skip it)
    Offset += sizeof (UINT32);

    // "Parent unit address" (skip it)
    Offset += IntcAddressCells * sizeof (UINT32);

    // Interrupt controller interrupt and flags
    PciInterruptMapInfo[Index].IntcInterrupt.Interrupt =
      FdtGetInterruptId ((UINT32 *)&Data[Offset]);
    PciInterruptMapInfo[Index].IntcInterrupt.Flags =
      FdtGetInterruptFlags ((UINT32 *)&Data[Offset]);
  } // for

  PciInfo->Mapping[PciMappingTableInterrupt].ObjectId =
    CREATE_CM_ARM_OBJECT_ID (EArmObjPciInterruptMapInfo);
  PciInfo->Mapping[PciMappingTableInterrupt].Size =
    sizeof (CM_ARM_PCI_INTERRUPT_MAP_INFO) * IrqMapCount;
  PciInfo->Mapping[PciMappingTableInterrupt].Data  = PciInterruptMapInfo;
  PciInfo->Mapping[PciMappingTableInterrupt].Count = IrqMapCount;

  return Status;
}

/** Parse a Host-pci node.

  @param [in]       Fdt          Pointer to a Flattened Device Tree (Fdt).
  @param [in]       HostPciNode  Offset of a host-pci node.
  @param [in, out]  PciInfo      The CM_ARM_PCI_CONFIG_SPACE_INFO to populate.

  @retval EFI_SUCCESS             The function completed successfully.
  @retval EFI_ABORTED             An error occurred.
  @retval EFI_INVALID_PARAMETER   Invalid parameter.
  @retval EFI_OUT_OF_RESOURCES    An allocation has failed.
**/
STATIC
EFI_STATUS
EFIAPI
PciNodeParser (
  IN      CONST VOID              *Fdt,
  IN            INT32             HostPciNode,
  IN OUT        PCI_PARSER_TABLE  *PciInfo
  )
{
  EFI_STATUS   Status;
  INT32        AddressCells;
  INT32        SizeCells;
  CONST UINT8  *Data;
  INT32        DataSize;
  INT32        SegGroup;

  if ((Fdt == NULL) ||
      (PciInfo == NULL))
  {
    ASSERT (0);
    return EFI_INVALID_PARAMETER;
  }

  // Segment Group / DomainId
  Status = GetPciSegGroup (Fdt, HostPciNode, &SegGroup);
  if (EFI_ERROR (Status)) {
    ASSERT (0);
    return Status;
  }

  PciInfo->PciConfigSpaceInfo.PciSegmentGroupNumber = SegGroup;

  // Bus range
  Status = PopulateBusRange (Fdt, HostPciNode, PciInfo);
  if (EFI_ERROR (Status)) {
    ASSERT (0);
    return Status;
  }

  Status = FdtGetParentAddressInfo (
             Fdt,
             HostPciNode,
             &AddressCells,
             &SizeCells
             );
  if (EFI_ERROR (Status)) {
    ASSERT (0);
    return Status;
  }

  // Only support 32/64 bits addresses.
  if ((AddressCells < 1)  ||
      (AddressCells > 2)  ||
      (SizeCells < 1)     ||
      (SizeCells > 2))
  {
    ASSERT (0);
    return EFI_ABORTED;
  }

  Data = fdt_getprop (Fdt, HostPciNode, "reg", &DataSize);
  if ((Data == NULL) ||
      (DataSize != ((AddressCells + SizeCells) * sizeof (UINT32))))
  {
    // If error or wrong size.
    ASSERT (0);
    return EFI_ABORTED;
  }

  // Base address
  if (AddressCells == 2) {
    PciInfo->PciConfigSpaceInfo.BaseAddress = fdt64_to_cpu (*(UINT64 *)Data);
  } else {
    PciInfo->PciConfigSpaceInfo.BaseAddress = fdt32_to_cpu (*(UINT32 *)Data);
  }

  // Address map
  Status = ParseAddressMap (
             Fdt,
             HostPciNode,
             AddressCells,
             PciInfo
             );
  if (EFI_ERROR (Status)) {
    ASSERT (0);
    return Status;
  }

  // Irq map
  Status = ParseIrqMap (
             Fdt,
             HostPciNode,
             PciInfo
             );
  if (EFI_ERROR (Status) && (Status != EFI_NOT_FOUND)) {
    ASSERT (0);
  }

  return EFI_SUCCESS;
}

/** Add the parsed Pci information to the Configuration Manager.

  CmObj of the following types are concerned:
   - EArmObjPciConfigSpaceInfo
   - EArmObjPciAddressMapInfo
   - EArmObjPciInterruptMapInfo

  @param [in]  FdtParserHandle  A handle to the parser instance.
  @param [in]  PciTableInfo     PCI_PARSER_TABLE structure containing the
                                CmObjs to add.

  @retval EFI_SUCCESS             The function completed successfully.
  @retval EFI_INVALID_PARAMETER   Invalid parameter.
  @retval EFI_OUT_OF_RESOURCES    An allocation has failed.
**/
STATIC
EFI_STATUS
EFIAPI
PciInfoAdd (
  IN  CONST FDT_HW_INFO_PARSER_HANDLE  FdtParserHandle,
  IN        PCI_PARSER_TABLE           *PciTableInfo
  )
{
  EFI_STATUS                    Status;
  CM_ARM_PCI_CONFIG_SPACE_INFO  *PciConfigSpaceInfo;

  if ((FdtParserHandle == NULL) ||
      (PciTableInfo == NULL))
  {
    ASSERT (0);
    return EFI_INVALID_PARAMETER;
  }

  PciConfigSpaceInfo = &PciTableInfo->PciConfigSpaceInfo;

  // Add the address map space CmObj to the Configuration Manager.
  Status = AddMultipleCmObjWithCmObjRef (
             FdtParserHandle,
             &PciTableInfo->Mapping[PciMappingTableAddress],
             &PciConfigSpaceInfo->AddressMapToken
             );
  if (EFI_ERROR (Status)) {
    ASSERT (0);
    return Status;
  }

  // Add the interrupt map space CmObj to the Configuration Manager.
  // Possible to have no legacy interrupts, or no device described and
  // thus no interrupt-mapping.
  if (PciTableInfo->Mapping[PciMappingTableInterrupt].Count != 0) {
    Status = AddMultipleCmObjWithCmObjRef (
               FdtParserHandle,
               &PciTableInfo->Mapping[PciMappingTableInterrupt],
               &PciConfigSpaceInfo->InterruptMapToken
               );
    if (EFI_ERROR (Status)) {
      ASSERT (0);
      return Status;
    }
  }

  // Add the configuration space CmObj to the Configuration Manager.
  Status = AddSingleCmObj (
             FdtParserHandle,
             CREATE_CM_ARM_OBJECT_ID (EArmObjPciConfigSpaceInfo),
             &PciTableInfo->PciConfigSpaceInfo,
             sizeof (CM_ARM_PCI_CONFIG_SPACE_INFO),
             NULL
             );
  ASSERT_EFI_ERROR (Status);
  return Status;
}

/** Free the CmObjDesc of the ParserTable.

  @param [in]  PciTableInfo     PCI_PARSER_TABLE structure containing the
                                CmObjs to free.

  @retval EFI_SUCCESS             The function completed successfully.
  @retval EFI_INVALID_PARAMETER   Invalid parameter.
**/
STATIC
EFI_STATUS
EFIAPI
FreeParserTable (
  IN  PCI_PARSER_TABLE  *PciTableInfo
  )
{
  UINT32  Index;
  VOID    *Data;

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

  for (Index = 0; Index < PciMappingTableMax; Index++) {
    Data = PciTableInfo->Mapping[Index].Data;
    if (Data != NULL) {
      FreePool (Data);
    }
  }

  return EFI_SUCCESS;
}

/** CM_ARM_PCI_CONFIG_SPACE_INFO parser function.

  The following structure is populated:
  typedef struct CmArmPciConfigSpaceInfo {
    UINT64  BaseAddress;                          // {Populated}
    UINT16  PciSegmentGroupNumber;                // {Populated}
    UINT8   StartBusNumber;                       // {Populated}
    UINT8   EndBusNumber;                         // {Populated}
  } CM_ARM_PCI_CONFIG_SPACE_INFO;

  typedef struct CmArmPciAddressMapInfo {
    UINT8                     SpaceCode;          // {Populated}
    UINT64                    PciAddress;         // {Populated}
    UINT64                    CpuAddress;         // {Populated}
    UINT64                    AddressSize;        // {Populated}
  } CM_ARM_PCI_ADDRESS_MAP_INFO;

  typedef struct CmArmPciInterruptMapInfo {
    UINT8                       PciBus;           // {Populated}
    UINT8                       PciDevice;        // {Populated}
    UINT8                       PciInterrupt;     // {Populated}
    CM_ARM_GENERIC_INTERRUPT    IntcInterrupt;    // {Populated}
  } CM_ARM_PCI_INTERRUPT_MAP_INFO;

  A parser parses a Device Tree to populate a specific CmObj type. None,
  one or many CmObj can be created by the parser.
  The created CmObj are then handed to the parser's caller through the
  HW_INFO_ADD_OBJECT interface.
  This can also be a dispatcher. I.e. a function that not parsing a
  Device Tree but calling other parsers.

  @param [in]  FdtParserHandle A handle to the parser instance.
  @param [in]  FdtBranch       When searching for DT node name, restrict
                               the search to this Device Tree branch.

  @retval EFI_SUCCESS             The function completed successfully.
  @retval EFI_ABORTED             An error occurred.
  @retval EFI_INVALID_PARAMETER   Invalid parameter.
  @retval EFI_NOT_FOUND           Not found.
  @retval EFI_UNSUPPORTED         Unsupported.
**/
EFI_STATUS
EFIAPI
ArmPciConfigInfoParser (
  IN  CONST FDT_HW_INFO_PARSER_HANDLE  FdtParserHandle,
  IN        INT32                      FdtBranch
  )
{
  EFI_STATUS        Status;
  UINT32            Index;
  INT32             PciNode;
  UINT32            PciNodeCount;
  PCI_PARSER_TABLE  PciTableInfo;
  VOID              *Fdt;

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

  Fdt = FdtParserHandle->Fdt;

  // Only search host-pci devices.
  // PCI Firmware Specification Revision 3.0, s4.1.2. "MCFG Table Description":
  // "This table directly refers to PCI Segment Groups defined in the system
  // via the _SEG object in the ACPI name space for the applicable host bridge
  // device."
  Status = FdtCountCompatNodeInBranch (
             Fdt,
             FdtBranch,
             &PciCompatibleInfo,
             &PciNodeCount
             );
  if (EFI_ERROR (Status)) {
    ASSERT (0);
    return Status;
  }

  if (PciNodeCount == 0) {
    return EFI_NOT_FOUND;
  }

  // Parse each host-pci node in the branch.
  PciNode = FdtBranch;
  for (Index = 0; Index < PciNodeCount; Index++) {
    ZeroMem (&PciTableInfo, sizeof (PCI_PARSER_TABLE));

    Status = FdtGetNextCompatNodeInBranch (
               Fdt,
               FdtBranch,
               &PciCompatibleInfo,
               &PciNode
               );
    if (EFI_ERROR (Status)) {
      ASSERT (0);
      if (Status == EFI_NOT_FOUND) {
        // Should have found the node.
        Status = EFI_ABORTED;
      }

      return Status;
    }

    Status = PciNodeParser (Fdt, PciNode, &PciTableInfo);
    if (EFI_ERROR (Status)) {
      ASSERT (0);
      goto error_handler;
    }

    // Add Pci information to the Configuration Manager.
    Status = PciInfoAdd (FdtParserHandle, &PciTableInfo);
    if (EFI_ERROR (Status)) {
      ASSERT (0);
      goto error_handler;
    }

    Status = FreeParserTable (&PciTableInfo);
    if (EFI_ERROR (Status)) {
      ASSERT (0);
      return Status;
    }
  } // for

  return Status;

error_handler:
  FreeParserTable (&PciTableInfo);
  return Status;
}
