/** @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; }