/** @file Arm Gic cpu parser. Copyright (c) 2021, ARM Limited. All rights reserved.
SPDX-License-Identifier: BSD-2-Clause-Patent @par Reference(s): - linux/Documentation/devicetree/bindings/arm/cpus.yaml - linux/Documentation/devicetree/bindings/interrupt-controller/arm,gic.yaml - linux/Documentation/devicetree/bindings/interrupt-controller/arm,gic-v3.yaml **/ #include "FdtHwInfoParser.h" #include "CmObjectDescUtility.h" #include "Gic/ArmGicCParser.h" #include "Gic/ArmGicDispatcher.h" /** List of "compatible" property values for CPU nodes. Any other "compatible" value is not supported by this module. */ STATIC CONST COMPATIBILITY_STR CpuCompatibleStr[] = { { "arm,arm-v7" }, { "arm,arm-v8" }, { "arm,cortex-a15" }, { "arm,cortex-a7" }, { "arm,cortex-a57" } }; /** COMPATIBILITY_INFO structure for CPU nodes. */ STATIC CONST COMPATIBILITY_INFO CpuCompatibleInfo = { ARRAY_SIZE (CpuCompatibleStr), CpuCompatibleStr }; /** Parse a "cpu" node. @param [in] Fdt Pointer to a Flattened Device Tree (Fdt). @param [in] CpuNode Offset of a cpu node. @param [in] GicVersion Version of the GIC. @param [in] AddressCells Number of address cells used for the reg property. @param [out] GicCInfo CM_ARM_GICC_INFO structure to populate. @retval EFI_SUCCESS The function completed successfully. @retval EFI_ABORTED An error occurred. @retval EFI_INVALID_PARAMETER Invalid parameter. @retval EFI_UNSUPPORTED Unsupported. **/ STATIC EFI_STATUS EFIAPI CpuNodeParser ( IN CONST VOID *Fdt, IN INT32 CpuNode, IN UINT32 GicVersion, IN UINT32 AddressCells, OUT CM_ARM_GICC_INFO *GicCInfo ) { CONST UINT8 *Data; INT32 DataSize; UINT32 ProcUid; UINT64 MpIdr; UINT64 CheckAffMask; MpIdr = 0; CheckAffMask = ARM_CORE_AFF0 | ARM_CORE_AFF1 | ARM_CORE_AFF2; if (GicCInfo == NULL) { ASSERT (0); return EFI_INVALID_PARAMETER; } Data = fdt_getprop (Fdt, CpuNode, "reg", &DataSize); if ((Data == NULL) || ((DataSize != sizeof (UINT32)) && (DataSize != sizeof (UINT64)))) { ASSERT (0); return EFI_ABORTED; } /* If cpus node's #address-cells property is set to 2 The first reg cell bits [7:0] must be set to bits [39:32] of MPIDR_EL1. The second reg cell bits [23:0] must be set to bits [23:0] of MPIDR_EL1. */ if (AddressCells == 2) { MpIdr = fdt64_to_cpu (*((UINT64 *)Data)); CheckAffMask |= ARM_CORE_AFF3; } else { MpIdr = fdt32_to_cpu (*((UINT32 *)Data)); } if ((MpIdr & ~CheckAffMask) != 0) { ASSERT (0); return EFI_INVALID_PARAMETER; } // To fit the Affinity [0-3] a 32bits value, place the Aff3 on bits // [31:24] instead of their original place ([39:32]). ProcUid = MpIdr | ((MpIdr & ARM_CORE_AFF3) >> 8); /* ACPI 6.3, s5.2.12.14 GIC CPU Interface (GICC) Structure: GIC 's CPU Interface Number. In GICv1/v2 implementations, this value matches the bit index of the associated processor in the GIC distributor's GICD_ITARGETSR register. For GICv3/4 implementations this field must be provided by the platform, if compatibility mode is supported. If it is not supported by the implementation, then this field must be zero. Note: We do not support compatibility mode for GicV3 */ if (GicVersion == 2) { GicCInfo->CPUInterfaceNumber = ProcUid; } else { GicCInfo->CPUInterfaceNumber = 0; } GicCInfo->AcpiProcessorUid = ProcUid; GicCInfo->Flags = EFI_ACPI_6_3_GIC_ENABLED; GicCInfo->MPIDR = MpIdr; return EFI_SUCCESS; } /** Parse a "cpus" node and its children "cpu" nodes. Create as many CM_ARM_GICC_INFO structures as "cpu" nodes. @param [in] Fdt Pointer to a Flattened Device Tree (Fdt). @param [in] CpusNode Offset of a cpus node. @param [in] GicVersion Version of the GIC. @param [out] NewGicCmObjDesc If success, CM_OBJ_DESCRIPTOR containing all the created CM_ARM_GICC_INFO. @retval EFI_SUCCESS The function completed successfully. @retval EFI_ABORTED An error occurred. @retval EFI_INVALID_PARAMETER Invalid parameter. @retval EFI_UNSUPPORTED Unsupported. **/ STATIC EFI_STATUS EFIAPI CpusNodeParser ( IN CONST VOID *Fdt, IN INT32 CpusNode, IN UINT32 GicVersion, OUT CM_OBJ_DESCRIPTOR **NewGicCmObjDesc ) { EFI_STATUS Status; INT32 CpuNode; UINT32 CpuNodeCount; INT32 AddressCells; UINT32 Index; CM_ARM_GICC_INFO *GicCInfoBuffer; UINT32 GicCInfoBufferSize; if (NewGicCmObjDesc == NULL) { ASSERT (0); return EFI_INVALID_PARAMETER; } AddressCells = fdt_address_cells (Fdt, CpusNode); if (AddressCells < 0) { ASSERT (0); return EFI_ABORTED; } // Count the number of "cpu" nodes under the "cpus" node. Status = FdtCountNamedNodeInBranch (Fdt, CpusNode, "cpu", &CpuNodeCount); if (EFI_ERROR (Status)) { ASSERT (0); return Status; } if (CpuNodeCount == 0) { ASSERT (0); return EFI_NOT_FOUND; } // Allocate memory for CpuNodeCount CM_ARM_GICC_INFO structures. GicCInfoBufferSize = CpuNodeCount * sizeof (CM_ARM_GICC_INFO); GicCInfoBuffer = AllocateZeroPool (GicCInfoBufferSize); if (GicCInfoBuffer == NULL) { ASSERT (0); return EFI_OUT_OF_RESOURCES; } CpuNode = CpusNode; for (Index = 0; Index < CpuNodeCount; Index++) { Status = FdtGetNextNamedNodeInBranch (Fdt, CpusNode, "cpu", &CpuNode); if (EFI_ERROR (Status)) { ASSERT (0); if (Status == EFI_NOT_FOUND) { // Should have found the node. Status = EFI_ABORTED; } goto exit_handler; } // Parse the "cpu" node. if (!FdtNodeIsCompatible (Fdt, CpuNode, &CpuCompatibleInfo)) { ASSERT (0); Status = EFI_UNSUPPORTED; goto exit_handler; } Status = CpuNodeParser ( Fdt, CpuNode, GicVersion, AddressCells, &GicCInfoBuffer[Index] ); if (EFI_ERROR (Status)) { ASSERT (0); goto exit_handler; } } // for Status = CreateCmObjDesc ( CREATE_CM_ARM_OBJECT_ID (EArmObjGicCInfo), CpuNodeCount, GicCInfoBuffer, GicCInfoBufferSize, NewGicCmObjDesc ); ASSERT_EFI_ERROR (Status); exit_handler: FreePool (GicCInfoBuffer); return Status; } /** Parse a Gic compatible interrupt-controller node, extracting GicC information generic to Gic v2 and v3. This function modifies a CM_OBJ_DESCRIPTOR object. The following CM_ARM_GICC_INFO fields are patched: - VGICMaintenanceInterrupt; - Flags; @param [in] Fdt Pointer to a Flattened Device Tree (Fdt). @param [in] GicIntcNode Offset of a Gic compatible interrupt-controller node. @param [in, out] GicCCmObjDesc The CM_ARM_GICC_INFO to patch. @retval EFI_SUCCESS The function completed successfully. @retval EFI_ABORTED An error occurred. @retval EFI_INVALID_PARAMETER Invalid parameter. **/ STATIC EFI_STATUS EFIAPI GicCIntcNodeParser ( IN CONST VOID *Fdt, IN INT32 GicIntcNode, IN OUT CM_OBJ_DESCRIPTOR *GicCCmObjDesc ) { EFI_STATUS Status; INT32 IntCells; CM_ARM_GICC_INFO *GicCInfo; CONST UINT8 *Data; INT32 DataSize; if (GicCCmObjDesc == NULL) { ASSERT (0); return EFI_INVALID_PARAMETER; } // Get the number of cells used to encode an interrupt. Status = FdtGetInterruptCellsInfo (Fdt, GicIntcNode, &IntCells); if (EFI_ERROR (Status)) { ASSERT (0); return Status; } // Get the GSIV maintenance interrupt. // According to the DT bindings, this could be the: // "Interrupt source of the parent interrupt controller on secondary GICs" // but it is assumed that only one Gic is available. Data = fdt_getprop (Fdt, GicIntcNode, "interrupts", &DataSize); if ((Data != NULL) && (DataSize == (IntCells * sizeof (UINT32)))) { GicCInfo = (CM_ARM_GICC_INFO *)GicCCmObjDesc->Data; GicCInfo->VGICMaintenanceInterrupt = FdtGetInterruptId ((CONST UINT32 *)Data); GicCInfo->Flags = DT_IRQ_IS_EDGE_TRIGGERED ( fdt32_to_cpu (((UINT32 *)Data)[IRQ_FLAGS_OFFSET]) ) ? EFI_ACPI_6_3_VGIC_MAINTENANCE_INTERRUPT_MODE_FLAGS : 0; return Status; } else if (DataSize < 0) { // This property is optional and was not found. Just return. return Status; } // The property exists and its size doesn't match for one interrupt. ASSERT (0); return EFI_ABORTED; } /** Parse a Gic compatible interrupt-controller node, extracting GicCv2 information. This function modifies a CM_OBJ_DESCRIPTOR object. The following CM_ARM_GICC_INFO fields are patched: - PhysicalAddress; - GICH; - GICV; @param [in] Fdt Pointer to a Flattened Device Tree (Fdt). @param [in] Gicv2IntcNode Offset of a Gicv2 compatible interrupt-controller node. @param [in, out] GicCCmObjDesc The CM_ARM_GICC_INFO to patch. @retval EFI_SUCCESS The function completed successfully. @retval EFI_ABORTED An error occurred. @retval EFI_INVALID_PARAMETER Invalid parameter. **/ STATIC EFI_STATUS EFIAPI GicCv2IntcNodeParser ( IN CONST VOID *Fdt, IN INT32 Gicv2IntcNode, IN OUT CM_OBJ_DESCRIPTOR *GicCCmObjDesc ) { EFI_STATUS Status; UINT32 Index; CM_ARM_GICC_INFO *GicCInfo; INT32 AddressCells; INT32 SizeCells; CONST UINT8 *GicCValue; CONST UINT8 *GicVValue; CONST UINT8 *GicHValue; CONST UINT8 *Data; INT32 DataSize; UINT32 RegSize; UINT32 RegCount; if (GicCCmObjDesc == NULL) { ASSERT (0); return EFI_INVALID_PARAMETER; } GicCInfo = (CM_ARM_GICC_INFO *)GicCCmObjDesc->Data; GicVValue = NULL; GicHValue = NULL; // Get the #address-cells and #size-cells property values. Status = FdtGetParentAddressInfo ( Fdt, Gicv2IntcNode, &AddressCells, &SizeCells ); if (EFI_ERROR (Status)) { ASSERT (0); return Status; } // Don't support more than 64 bits and less than 32 bits addresses. if ((AddressCells < 1) || (AddressCells > 2) || (SizeCells < 1) || (SizeCells > 2)) { ASSERT (0); return EFI_ABORTED; } RegSize = (AddressCells + SizeCells) * sizeof (UINT32); Data = fdt_getprop (Fdt, Gicv2IntcNode, "reg", &DataSize); if ((Data == NULL) || (DataSize < 0) || ((DataSize % RegSize) != 0)) { // If error or wrong size. ASSERT (0); return EFI_ABORTED; } RegCount = DataSize/RegSize; switch (RegCount) { case 4: { // GicV is at index 3 in the reg property. GicV is optional. GicVValue = Data + (sizeof (UINT32) * GET_DT_REG_ADDRESS_OFFSET (3, AddressCells, SizeCells)); // fall-through. } case 3: { // GicH is at index 2 in the reg property. GicH is optional. GicHValue = Data + (sizeof (UINT32) * GET_DT_REG_ADDRESS_OFFSET (2, AddressCells, SizeCells)); // fall-through. } case 2: { // GicC is at index 1 in the reg property. GicC is mandatory. GicCValue = Data + (sizeof (UINT32) * GET_DT_REG_ADDRESS_OFFSET (1, AddressCells, SizeCells)); break; } default: { // Not enough or too much information. ASSERT (0); return EFI_ABORTED; } } // Patch the relevant fields of the CM_ARM_GICC_INFO objects. for (Index = 0; Index < GicCCmObjDesc->Count; Index++) { if (AddressCells == 2) { GicCInfo[Index].PhysicalBaseAddress = fdt64_to_cpu (*(UINT64 *)GicCValue); GicCInfo[Index].GICH = (GicHValue == NULL) ? 0 : fdt64_to_cpu (*(UINT64 *)GicHValue); GicCInfo[Index].GICV = (GicVValue == NULL) ? 0 : fdt64_to_cpu (*(UINT64 *)GicVValue); } else { GicCInfo[Index].PhysicalBaseAddress = fdt32_to_cpu (*(UINT32 *)GicCValue); GicCInfo[Index].GICH = (GicHValue == NULL) ? 0 : fdt32_to_cpu (*(UINT32 *)GicHValue); GicCInfo[Index].GICV = (GicVValue == NULL) ? 0 : fdt32_to_cpu (*(UINT32 *)GicVValue); } } // for return EFI_SUCCESS; } /** Parse a Gic compatible interrupt-controller node, extracting GicCv3 information. This function modifies a CM_OBJ_DESCRIPTOR object. The following CM_ARM_GICC_INFO fields are patched: - PhysicalAddress; - GICH; - GICV; @param [in] Fdt Pointer to a Flattened Device Tree (Fdt). @param [in] Gicv3IntcNode Offset of a Gicv3 compatible interrupt-controller node. @param [in, out] GicCCmObjDesc The CM_ARM_GICC_INFO to patch. @retval EFI_SUCCESS The function completed successfully. @retval EFI_ABORTED An error occurred. @retval EFI_INVALID_PARAMETER Invalid parameter. **/ STATIC EFI_STATUS EFIAPI GicCv3IntcNodeParser ( IN CONST VOID *Fdt, IN INT32 Gicv3IntcNode, IN OUT CM_OBJ_DESCRIPTOR *GicCCmObjDesc ) { EFI_STATUS Status; UINT32 Index; CM_ARM_GICC_INFO *GicCInfo; INT32 AddressCells; INT32 SizeCells; UINT32 AdditionalRedistReg; CONST UINT8 *GicCValue; CONST UINT8 *GicVValue; CONST UINT8 *GicHValue; CONST UINT8 *Data; INT32 DataSize; UINT32 RegSize; UINT32 RegCount; if (GicCCmObjDesc == NULL) { ASSERT (0); return EFI_INVALID_PARAMETER; } GicCInfo = (CM_ARM_GICC_INFO *)GicCCmObjDesc->Data; GicCValue = NULL; GicVValue = NULL; GicHValue = NULL; // Get the #address-cells and #size-cells property values. Status = FdtGetParentAddressInfo ( Fdt, Gicv3IntcNode, &AddressCells, &SizeCells ); if (EFI_ERROR (Status)) { ASSERT (0); return Status; } // Don't support more than 64 bits and less than 32 bits addresses. if ((AddressCells < 1) || (AddressCells > 2) || (SizeCells < 1) || (SizeCells > 2)) { ASSERT (0); return EFI_ABORTED; } // The "#redistributor-regions" property is optional. Data = fdt_getprop (Fdt, Gicv3IntcNode, "#redistributor-regions", &DataSize); if ((Data != NULL) && (DataSize == sizeof (UINT32))) { ASSERT (fdt32_to_cpu (*(UINT32 *)Data) > 1); AdditionalRedistReg = fdt32_to_cpu (*(UINT32 *)Data) - 1; } else { AdditionalRedistReg = 0; } RegSize = (AddressCells + SizeCells) * sizeof (UINT32); /* Ref: linux/blob/master/Documentation/devicetree/bindings/ interrupt-controller/arm%2Cgic-v3.yaml reg: description: | Specifies base physical address(s) and size of the GIC registers, in the following order: - GIC Distributor interface (GICD) - GIC Redistributors (GICR), one range per redistributor region - GIC CPU interface (GICC) - GIC Hypervisor interface (GICH) - GIC Virtual CPU interface (GICV) GICC, GICH and GICV are optional. minItems: 2 maxItems: 4096 */ Data = fdt_getprop (Fdt, Gicv3IntcNode, "reg", &DataSize); if ((Data == NULL) || (DataSize < 0) || ((DataSize % RegSize) != 0)) { // If error or wrong size. ASSERT (0); return EFI_ABORTED; } RegCount = (DataSize / RegSize) - AdditionalRedistReg; // The GicD and GicR info is mandatory. switch (RegCount) { case 5: { // GicV is at index 4 in the reg property. GicV is optional. GicVValue = Data + (sizeof (UINT32) * GET_DT_REG_ADDRESS_OFFSET ( 4 + AdditionalRedistReg, AddressCells, SizeCells )); // fall-through. } case 4: { // GicH is at index 3 in the reg property. GicH is optional. GicHValue = Data + (sizeof (UINT32) * GET_DT_REG_ADDRESS_OFFSET ( 3 + AdditionalRedistReg, AddressCells, SizeCells )); // fall-through. } case 3: { // GicC is at index 2 in the reg property. GicC is optional. // Even though GicC is optional, it is made mandatory in this parser. GicCValue = Data + (sizeof (UINT32) * GET_DT_REG_ADDRESS_OFFSET ( 2 + AdditionalRedistReg, AddressCells, SizeCells )); // fall-through } case 2: { // GicR is discribed by the CM_ARM_GIC_REDIST_INFO object. // GicD is described by the CM_ARM_GICD_INFO object. break; } default: { // Not enough or too much information. ASSERT (0); return EFI_ABORTED; } } // Patch the relevant fields of the CM_ARM_GICC_INFO objects. if (AddressCells == 2) { for (Index = 0; Index < GicCCmObjDesc->Count; Index++) { // GicR is discribed by the CM_ARM_GIC_REDIST_INFO object. GicCInfo[Index].GICRBaseAddress = 0; GicCInfo[Index].PhysicalBaseAddress = (GicCValue == NULL) ? 0 : fdt64_to_cpu (*(UINT64 *)GicCValue); GicCInfo[Index].GICH = (GicHValue == NULL) ? 0 : fdt64_to_cpu (*(UINT64 *)GicHValue); GicCInfo[Index].GICV = (GicVValue == NULL) ? 0 : fdt64_to_cpu (*(UINT64 *)GicVValue); } } else { for (Index = 0; Index < GicCCmObjDesc->Count; Index++) { // GicR is discribed by the CM_ARM_GIC_REDIST_INFO object. GicCInfo[Index].GICRBaseAddress = 0; GicCInfo[Index].PhysicalBaseAddress = (GicCValue == NULL) ? 0 : fdt32_to_cpu (*(UINT32 *)GicCValue); GicCInfo[Index].GICH = (GicHValue == NULL) ? 0 : fdt32_to_cpu (*(UINT32 *)GicHValue); GicCInfo[Index].GICV = (GicVValue == NULL) ? 0 : fdt32_to_cpu (*(UINT32 *)GicVValue); } } return EFI_SUCCESS; } /** CM_ARM_GICC_INFO parser function. This parser expects FdtBranch to be the "\cpus" node node. At most one CmObj is created. The following structure is populated: typedef struct CmArmGicCInfo { UINT32 CPUInterfaceNumber; // {Populated} UINT32 AcpiProcessorUid; // {Populated} UINT32 Flags; // {Populated} UINT32 ParkingProtocolVersion; // {default = 0} UINT32 PerformanceInterruptGsiv; // {default = 0} UINT64 ParkedAddress; // {default = 0} UINT64 PhysicalBaseAddress; // {Populated} UINT64 GICV; // {Populated} UINT64 GICH; // {Populated} UINT32 VGICMaintenanceInterrupt; // {Populated} UINT64 GICRBaseAddress; // {default = 0} UINT64 MPIDR; // {Populated} UINT8 ProcessorPowerEfficiencyClass; // {default = 0} UINT16 SpeOverflowInterrupt; // {default = 0} UINT32 ProximityDomain; // {default = 0} UINT32 ClockDomain; // {default = 0} UINT32 AffinityFlags; // {default = 0} } CM_ARM_GICC_INFO; The pmu information can be found in the pmu node. There is no support for now. 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 ArmGicCInfoParser ( IN CONST FDT_HW_INFO_PARSER_HANDLE FdtParserHandle, IN INT32 FdtBranch ) { EFI_STATUS Status; INT32 IntcNode; UINT32 GicVersion; CM_OBJ_DESCRIPTOR *NewCmObjDesc; VOID *Fdt; if (FdtParserHandle == NULL) { ASSERT (0); return EFI_INVALID_PARAMETER; } Fdt = FdtParserHandle->Fdt; NewCmObjDesc = NULL; // The FdtBranch points to the Cpus Node. // Get the interrupt-controller node associated to the "cpus" node. Status = FdtGetIntcParentNode (Fdt, FdtBranch, &IntcNode); if (EFI_ERROR (Status)) { ASSERT (0); if (Status == EFI_NOT_FOUND) { // Should have found the node. Status = EFI_ABORTED; } return Status; } Status = GetGicVersion (Fdt, IntcNode, &GicVersion); if (EFI_ERROR (Status)) { ASSERT (0); return Status; } // Parse the "cpus" nodes and its children "cpu" nodes, // and create a CM_OBJ_DESCRIPTOR. Status = CpusNodeParser (Fdt, FdtBranch, GicVersion, &NewCmObjDesc); if (EFI_ERROR (Status)) { ASSERT (0); return Status; } // Parse the interrupt-controller node according to the Gic version. switch (GicVersion) { case 2: { Status = GicCv2IntcNodeParser (Fdt, IntcNode, NewCmObjDesc); break; } case 3: { Status = GicCv3IntcNodeParser (Fdt, IntcNode, NewCmObjDesc); break; } default: { // Unsupported Gic version. ASSERT (0); Status = EFI_UNSUPPORTED; } } if (EFI_ERROR (Status)) { ASSERT (0); goto exit_handler; } // Parse the Gic information common to Gic v2 and v3. Status = GicCIntcNodeParser (Fdt, IntcNode, NewCmObjDesc); if (EFI_ERROR (Status)) { ASSERT (0); goto exit_handler; } // Add all the CmObjs to the Configuration Manager. Status = AddMultipleCmObj (FdtParserHandle, NewCmObjDesc, 0, NULL); if (EFI_ERROR (Status)) { ASSERT (0); goto exit_handler; } exit_handler: FreeCmObjDesc (NewCmObjDesc); return Status; }