From c67bf628c8b63b83cab05c878d79bc0e00c72c45 Mon Sep 17 00:00:00 2001 From: Pierre Gondois Date: Thu, 9 Dec 2021 10:32:06 +0100 Subject: [PATCH] DynamicTablesPkg: FdtHwInfoParser: Add PCI config parser On platforms that implement PCIe, the PCIe configuration space information must be described to a standards-based operating system in the Memory mapped configuration space base address Description (MCFG) table. The PCIe information is described in the platform Device Tree, the bindings for which can be found at: - linux/Documentation/devicetree/bindings/pci/ host-generic-pci.yaml The FdtHwInfoParser implements a PCI configuration space Parser that parses the platform Device Tree to create CM_ARM_PCI_CONFIG_SPACE_INFO objects which are encapsulated in a Configuration Manager descriptor object and added to the platform information repository. The platform Configuration Manager can then utilise this information when generating the MCFG table. Signed-off-by: Pierre Gondois Reviewed-by: Sami Mujawar --- .../Pci/ArmPciConfigSpaceParser.c | 813 ++++++++++++++++++ .../Pci/ArmPciConfigSpaceParser.h | 143 +++ 2 files changed, 956 insertions(+) create mode 100644 DynamicTablesPkg/Library/FdtHwInfoParserLib/Pci/ArmPciConfigSpaceParser.c create mode 100644 DynamicTablesPkg/Library/FdtHwInfoParserLib/Pci/ArmPciConfigSpaceParser.h diff --git a/DynamicTablesPkg/Library/FdtHwInfoParserLib/Pci/ArmPciConfigSpaceParser.c b/DynamicTablesPkg/Library/FdtHwInfoParserLib/Pci/ArmPciConfigSpaceParser.c new file mode 100644 index 0000000000..2ffff1ccd2 --- /dev/null +++ b/DynamicTablesPkg/Library/FdtHwInfoParserLib/Pci/ArmPciConfigSpaceParser.c @@ -0,0 +1,813 @@ +/** @file + Arm PCI Configuration Space Parser. + + Copyright (c) 2021, ARM Limited. All rights reserved.
+ 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 + +#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; +} diff --git a/DynamicTablesPkg/Library/FdtHwInfoParserLib/Pci/ArmPciConfigSpaceParser.h b/DynamicTablesPkg/Library/FdtHwInfoParserLib/Pci/ArmPciConfigSpaceParser.h new file mode 100644 index 0000000000..6e0027abea --- /dev/null +++ b/DynamicTablesPkg/Library/FdtHwInfoParserLib/Pci/ArmPciConfigSpaceParser.h @@ -0,0 +1,143 @@ +/** @file + Arm PCI Configuration Space Parser. + + Copyright (c) 2021, ARM Limited. All rights reserved.
+ 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 +**/ + +#ifndef ARM_PCI_CONFIG_SPACE_PARSER_H_ +#define ARM_PCI_CONFIG_SPACE_PARSER_H_ + +/** Read LEN bits at OFF offsets bits of the ADDR. + + @param [in] ADDR Address to read the bits from. + @param [in] OFF Offset of the bits to read. + @param [in] LEN Number of bits to read. + + @return The bits read. +**/ +#define READ_BITS(ADDR, OFF, LEN) (((ADDR) >> (OFF)) & ((1<<(LEN))-1)) + +/* Pci address attributes. +*/ +/// 0 if relocatable. +#define READ_PCI_N(ADDR) READ_BITS((ADDR), 31, 1) +/// 1 if prefetchable. +#define READ_PCI_P(ADDR) READ_BITS((ADDR), 30, 1) +/// 1 if aliased. +#define READ_PCI_T(ADDR) READ_BITS((ADDR), 29, 1) + +/** Space code. + + 00: Configuration Space + 01: I/O Space + 10: 32-bit-address Memory Space + 11: 64-bit-address Memory Space +*/ +#define READ_PCI_SS(ADDR) READ_BITS((ADDR), 24, 2) +/// Bus number. +#define READ_PCI_BBBBBBBB(ADDR) READ_BITS((ADDR), 16, 8) +/// Device number. +#define READ_PCI_DDDDD(ADDR) READ_BITS((ADDR), 11, 5) + +/** Number of device-tree cells used for PCI nodes properties. + + Values are well defined, except the "#interrupt-cells" which + is assumed to be 1. +*/ +#define PCI_ADDRESS_CELLS 3U +#define PCI_SIZE_CELLS 2U +#define PCI_INTERRUPTS_CELLS 1U + +/** PCI interrupt flags for device-tree. + + Local Bus Specification Revision 3.0, s2.2.6., Interrupt Pins: + - 'Interrupts on PCI are optional and defined as "level sensitive," + asserted low (negative true)' +*/ +#define DT_PCI_IRQ_FLAGS(x) (((x) & 0xF) == BIT0) + +/** Indexes in the mapping table. +*/ +typedef enum PciMappingTable { + PciMappingTableAddress, ///< 0 - Address mapping + PciMappingTableInterrupt, ///< 1 - Interrupt mapping + PciMappingTableMax, ///< 2 - Max +} PCI_MAPPING_TABLE; + +#pragma pack(1) + +/** PCI parser table + + Multiple address-map and interrupt map can correspond to + one host-pci device. This structure allows to temporarily + store the CmObjects created and generate tokens once + the whole device tree is parsed. +*/ +typedef struct PciParserTable { + /// PCI Configuration Space Info + CM_ARM_PCI_CONFIG_SPACE_INFO PciConfigSpaceInfo; + + /// Store the address mapping and interrupt mapping as CmObjDesc + /// before adding them to the Configuration Manager. + CM_OBJ_DESCRIPTOR Mapping[PciMappingTableMax]; +} PCI_PARSER_TABLE; + +#pragma pack() + +/** 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 + ); + +#endif // ARM_PCI_CONFIG_SPACE_PARSER_H_