blob: 97c8a0c1022a3636454ecf3e218c148e13328204 [file]
/** @file
Arm SMMUv3 IORT parser.
Copyright (c) 2025, ARM Limited. All rights reserved.<BR>
SPDX-License-Identifier: BSD-2-Clause-Patent
@par Reference(s):
- linux/Documentation/devicetree/bindings/iommu/arm,smmu-v3.yaml
**/
#include <Library/BaseLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/FdtLib.h>
#include <IndustryStandard/IoRemappingTable.h>
#include "FdtHwInfoParser.h"
#include "CmObjectDescUtility.h"
#include "Arm/Iort/ArmIortParser.h"
#include "Arm/Iort/SmmuV3Parser.h"
#define IOMMU_MAP_CELL_COUNT 4
#define MSI_MAP_CELL_COUNT 4
/** Find the interrupt ID specified by the given interrupt name
@param [in] InterruptNames Pointer to the FDT interrupt-names field
@param [in] InterruptNamesSize Size of the FDT interrupt-names field, in bytes
@param [in] Interrupts Pointer to the FDT interrupts field
@param [in] IntCells Number of cells per interrupt
@param [in] Name Pointer to the name of the interrupt to find
@return Interrupt ID, or 0 if no matching interrupt found
**/
STATIC
UINT32
FdtGetInterruptFromName (
IN CONST CHAR8 *InterruptNames,
IN CONST UINT32 InterruptNamesSize,
IN CONST UINT32 *Interrupts,
IN CONST UINTN IntCells,
IN CONST CHAR8 *Name
)
{
INTN StrIndex;
INTN Index;
INTN StringLength;
StrIndex = 0;
Index = 0;
while (Index < InterruptNamesSize) {
StringLength = MIN (AsciiStrSize (Name), InterruptNamesSize - Index);
if (!AsciiStrnCmp (&InterruptNames[Index], Name, StringLength)) {
break;
}
StrIndex++;
Index += AsciiStrSize (&InterruptNames[Index]);
}
if (Index >= InterruptNamesSize) {
return 0;
}
return FdtGetInterruptId (&Interrupts[StrIndex * IntCells]);
}
/** List of "compatible" property values for SmmuV3 nodes.
Other "compatible" values are not supported by this module.
*/
STATIC CONST COMPATIBILITY_STR SmmuV3CompatibleStr[] = {
{ "arm,smmu-v3" }
};
/** SmmuV3 compatiblity information.
*/
STATIC CONST COMPATIBILITY_INFO SmmuV3CompatibleInfo = {
ARRAY_SIZE (SmmuV3CompatibleStr),
SmmuV3CompatibleStr
};
/** List of "compatible" property values for PciRootComplex nodes.
*/
STATIC CONST COMPATIBILITY_STR RootComplexCompatibleStr[] = {
{ "pci-host-ecam-generic" }
};
/** PciRootComplex compatiblity information.
*/
STATIC CONST COMPATIBILITY_INFO RootComplexCompatibleInfo = {
ARRAY_SIZE (RootComplexCompatibleStr),
RootComplexCompatibleStr
};
/** Find the appropriate msi-map
@param [in] FdtParserHandle A handle to the parser instance.
@param [in] SmmuV3Node Offset of an SmmuV3 node.
@param [out] DataOut Location to store pointer to msi-map property.
@param [out] MapSizeOut Location to store number of entries in msi-map property.
@retval EFI_SUCCESS The function completed successfully.
@retval EFI_ABORTED An error occurred.
@retval EFI_INVALID_PARAMETER Invalid parameter.
@retval EFI_NOT_FOUND msi-map not found for this SmmuV3 node.
**/
STATIC
EFI_STATUS
FindIommuMsiMapForSmmuV3 (
IN CONST VOID *Fdt,
IN INT32 SmmuV3Node,
OUT CONST UINT32 **IommuMapDataOut,
OUT UINT32 *IommuMapSizeOut,
OUT CONST UINT32 **MsiMapDataOut,
OUT UINT32 *MsiMapSizeOut
)
{
EFI_STATUS Status;
UINT32 Index;
UINT32 MapIndex;
UINT32 IommuMapSize;
INT32 RootComplexNode;
UINT32 RootComplexNodeCount;
INT32 IommuMapDataSize;
INT32 MsiMapDataSize;
INT32 Node;
CONST UINT32 *IommuMapData;
CONST UINT32 *MsiMapData;
Status = FdtCountCompatNodeInBranch (
Fdt,
-1, // FdtBranch,
&RootComplexCompatibleInfo,
&RootComplexNodeCount
);
if (EFI_ERROR (Status)) {
ASSERT_EFI_ERROR (Status);
return EFI_INVALID_PARAMETER;
}
RootComplexNode = -1;
for (Index = 0; Index < RootComplexNodeCount; Index++) {
Status = FdtGetNextCompatNodeInBranch (
Fdt,
-1,
&RootComplexCompatibleInfo,
&RootComplexNode
);
if (EFI_ERROR (Status)) {
ASSERT_EFI_ERROR (Status);
if (Status == EFI_NOT_FOUND) {
// Should have found the node.
Status = EFI_ABORTED;
}
return Status;
}
IommuMapData = FdtGetProp (Fdt, RootComplexNode, "iommu-map", &IommuMapDataSize);
if ((IommuMapData == NULL) || ((IommuMapDataSize % (IOMMU_MAP_CELL_COUNT * sizeof (UINT32))) != 0)) {
// If error or invalid number of cells (not multiple of IOMMU_MAP_CELL_COUNT).
ASSERT ((IommuMapData != NULL) && ((IommuMapDataSize % (IOMMU_MAP_CELL_COUNT * sizeof (UINT32))) == 0));
return EFI_ABORTED;
}
IommuMapSize = IommuMapDataSize / (IOMMU_MAP_CELL_COUNT * sizeof (UINT32));
for (MapIndex = 0; MapIndex < IommuMapSize; MapIndex++) {
Node = FdtNodeOffsetByPhandle (Fdt, Fdt32ToCpu (((UINT32 *)IommuMapData)[MapIndex * IOMMU_MAP_CELL_COUNT + 1]));
if (Node == SmmuV3Node) {
break; // Found node for this SMMU
}
}
if (MapIndex == IommuMapSize) {
// Not this root complex
continue;
}
MsiMapData = FdtGetProp (Fdt, RootComplexNode, "msi-map", &MsiMapDataSize);
if ((MsiMapData == NULL) || ((MsiMapDataSize % (MSI_MAP_CELL_COUNT * sizeof (UINT32))) != 0)) {
// If error or invalid number of cells (not multiple of MSI_MAP_CELL_COUNT).
ASSERT ((MsiMapData != NULL) && ((MsiMapDataSize % (MSI_MAP_CELL_COUNT * sizeof (UINT32))) == 0));
return EFI_ABORTED;
}
*IommuMapDataOut = IommuMapData;
*IommuMapSizeOut = IommuMapDataSize;
*MsiMapDataOut = MsiMapData;
*MsiMapSizeOut = MsiMapDataSize;
return EFI_SUCCESS;
} // for
ASSERT (0);
return EFI_NOT_FOUND;
}
/** Generate ID mappings for SMMUv3 from iommu-map and msi-map arrays
@param [in] Fdt Pointer to a Flattened Device Tree (Fdt).
@param [in] SmmuV3Node Offset of an SmmuV3 node.
@param [in] IommuMapData Pointer to iommu-map array.
@param [in] IommuMapSize Size of the iommu-map array, in entries.
@param [in] MsiMapData Pointer to msi-map array.
@param [in] MsiMapSize Size of the msi-map array, in entries.
@param [out] IdMappings Location to store ID mappings.
@retval The number of ID mappings generated
**/
STATIC
UINT32
GenerateSmmuV3IdMappings (
IN CONST VOID *Fdt,
IN CONST UINT32 SmmuV3Node,
IN CONST UINT32 *IommuMapData,
IN CONST UINT32 IommuMapSize,
IN CONST UINT32 *MsiMapData,
IN CONST UINT32 MsiMapSize,
OUT CM_ARM_ID_MAPPING *IdMappings
)
{
UINT32 *IommuMapEntry;
UINT32 IommuMapIndex;
UINT32 IommuInputId;
UINT32 IommuOutputId;
UINT32 IommuNumIds;
UINT32 IommuNode;
UINT32 *MsiMapEntry;
UINT32 MsiMapIndex;
UINT32 MsiInputId;
UINT32 MsiOutputId;
UINT32 MsiNumIds;
UINT32 MapIndex;
MapIndex = 0;
// Parse msi-map (pci ID -> device ID) and iommu-map (pci ID -> stream ID)
// to construct stream ID -> device ID mapping array
for (MsiMapIndex = 0; MsiMapIndex < MsiMapSize; MsiMapIndex++) {
MsiMapEntry = &((UINT32 *)MsiMapData)[MsiMapIndex * MSI_MAP_CELL_COUNT];
MsiInputId = Fdt32ToCpu (MsiMapEntry[0]);
MsiOutputId = Fdt32ToCpu (MsiMapEntry[2]);
MsiNumIds = Fdt32ToCpu (MsiMapEntry[3]);
for (IommuMapIndex = 0; IommuMapIndex < IommuMapSize && MsiNumIds > 0; IommuMapIndex++) {
IommuMapEntry = &((UINT32 *)IommuMapData)[IommuMapIndex * IOMMU_MAP_CELL_COUNT];
IommuNode = FdtNodeOffsetByPhandle (Fdt, Fdt32ToCpu (IommuMapEntry[1]));
IommuInputId = Fdt32ToCpu (IommuMapEntry[0]);
IommuOutputId = Fdt32ToCpu (IommuMapEntry[2]);
IommuNumIds = Fdt32ToCpu (IommuMapEntry[3]);
if ((IommuNode == SmmuV3Node) && (MsiInputId < (IommuInputId + IommuNumIds)) && ((MsiInputId + MsiNumIds) > IommuInputId)) {
if (MsiInputId < IommuInputId) {
IdMappings[MapIndex].InputBase = IommuOutputId;
IdMappings[MapIndex].NumIds = MIN ((MsiInputId + MsiNumIds) - IommuInputId, IommuNumIds);
IdMappings[MapIndex].OutputBase = MsiOutputId + (IommuInputId - MsiInputId);
} else {
IdMappings[MapIndex].InputBase = IommuOutputId + (MsiInputId - IommuInputId);
IdMappings[MapIndex].NumIds = MIN ((IommuInputId + IommuNumIds) - MsiInputId, MsiNumIds);
IdMappings[MapIndex].OutputBase = MsiOutputId;
}
IdMappings[MapIndex].OutputReferenceToken = CM_ABSTRACT_TOKEN_MAKE (ETokenNameSpaceFdtHwInfo, EFdtHwInfoIortObject, Fdt32ToCpu (MsiMapEntry[1]));
MapIndex++;
}
}
}
return MapIndex;
}
/** Parse an SmmuV3 node.
@param [in] FdtParserHandle A handle to the parser instance.
@param [in] Fdt Pointer to a Flattened Device Tree (Fdt).
@param [in] SmmuV3Node Offset of an SmmuV3 node.
@param [in] SmmuV3Info The CM_ARM_SMMUV3_INFO to populate.
@retval EFI_SUCCESS The function completed successfully.
@retval EFI_ABORTED An error occurred.
@retval EFI_INVALID_PARAMETER Invalid parameter.
**/
STATIC
EFI_STATUS
EFIAPI
SmmuV3NodeParser (
IN CONST FDT_HW_INFO_PARSER_HANDLE FdtParserHandle,
IN CONST VOID *Fdt,
IN INT32 SmmuV3Node,
IN CM_ARM_SMMUV3_NODE *SmmuV3Info
)
{
EFI_STATUS Status;
CONST UINT32 *Data;
INT32 IntcNode;
INT32 DataSize;
INT32 IntCells;
INT32 AddressCells;
CONST UINT8 *InterruptNames;
INT32 InterruptNamesSize;
CM_ARM_ID_MAPPING *IdMappings;
CONST UINT32 *IommuMapData;
UINT32 IommuMapSize;
CONST UINT32 *MsiMapData;
UINT32 MsiMapSize;
if ((Fdt == NULL) || (SmmuV3Info == NULL)) {
ASSERT ((Fdt != NULL) && (SmmuV3Info != NULL));
return EFI_INVALID_PARAMETER;
}
AddressCells = FdtAddressCells (Fdt, SmmuV3Node);
if (AddressCells < 0) {
ASSERT (AddressCells >= 0);
return EFI_ABORTED;
}
// Get the associated interrupt-controller.
Status = FdtGetIntcParentNode (Fdt, SmmuV3Node, &IntcNode);
if (EFI_ERROR (Status)) {
ASSERT_EFI_ERROR (Status);
return Status;
}
// Get the number of cells used to encode an interrupt.
Status = FdtGetInterruptCellsInfo (Fdt, IntcNode, &IntCells);
if (EFI_ERROR (Status)) {
ASSERT_EFI_ERROR (Status);
if (Status == EFI_NOT_FOUND) {
// Should have found the node.
Status = EFI_ABORTED;
}
return Status;
}
InterruptNames = FdtGetProp (Fdt, SmmuV3Node, "interrupt-names", &InterruptNamesSize);
Data = FdtGetProp (Fdt, SmmuV3Node, "interrupts", &DataSize);
if (Data == NULL) {
// If error.
ASSERT (Data != NULL);
return EFI_ABORTED;
}
/// GSIV of the Event interrupt if SPI based
SmmuV3Info->EventInterrupt = FdtGetInterruptFromName ((CONST CHAR8 *)InterruptNames, InterruptNamesSize, Data, IntCells, "eventq");
/// PRI Interrupt if SPI based
SmmuV3Info->PriInterrupt = FdtGetInterruptFromName ((CONST CHAR8 *)InterruptNames, InterruptNamesSize, Data, IntCells, "priq");
/// GERR interrupt if GSIV based
SmmuV3Info->GerrInterrupt = FdtGetInterruptFromName ((CONST CHAR8 *)InterruptNames, InterruptNamesSize, Data, IntCells, "gerror");
/// Sync interrupt if GSIV based
SmmuV3Info->SyncInterrupt = FdtGetInterruptFromName ((CONST CHAR8 *)InterruptNames, InterruptNamesSize, Data, IntCells, "cmdq-sync");
/// SMMU flags
SmmuV3Info->Flags = 0;
/// VATOS address
SmmuV3Info->VatosAddress = 0;
/// Model
SmmuV3Info->Model = EFI_ACPI_IORT_SMMUv3_MODEL_GENERIC;
Data = FdtGetProp (Fdt, SmmuV3Node, "hisilicon,broken-prefetch-cmd", &DataSize);
if ((Data != NULL) && (DataSize >= 0)) {
SmmuV3Info->Model = EFI_ACPI_IORT_SMMUv3_MODEL_HISILICON_HI161X;
}
Data = FdtGetProp (Fdt, SmmuV3Node, "cavium,cn9900-broken-page1-regspace", &DataSize);
if ((Data != NULL) && (DataSize >= 0)) {
SmmuV3Info->Model = EFI_ACPI_IORT_SMMUv3_MODEL_CAVIUM_CN99XX;
}
Data = FdtGetProp (Fdt, SmmuV3Node, "reg", &DataSize);
if (Data == NULL) {
ASSERT (Data != NULL);
return EFI_ABORTED;
}
if (AddressCells == 2) {
SmmuV3Info->BaseAddress = Fdt64ToCpu (*((UINT64 *)Data));
} else {
SmmuV3Info->BaseAddress = Fdt32ToCpu (*((UINT32 *)Data));
}
Status = FindIommuMsiMapForSmmuV3 (Fdt, SmmuV3Node, &IommuMapData, &IommuMapSize, &MsiMapData, &MsiMapSize);
if (EFI_ERROR (Status)) {
ASSERT_EFI_ERROR (Status);
return Status;
}
IommuMapSize /= IOMMU_MAP_CELL_COUNT * sizeof (UINT32);
MsiMapSize /= MSI_MAP_CELL_COUNT * sizeof (UINT32);
// Allocate for worst-case scenario
IdMappings = AllocateZeroPool ((IommuMapSize * MsiMapSize + 1) * sizeof (CM_ARM_ID_MAPPING));
if (IdMappings == NULL) {
ASSERT (IdMappings != NULL);
return EFI_OUT_OF_RESOURCES;
}
SmmuV3Info->IdMappingCount = GenerateSmmuV3IdMappings (
Fdt,
SmmuV3Node,
IommuMapData,
IommuMapSize,
MsiMapData,
MsiMapSize,
IdMappings
);
if ((SmmuV3Info->EventInterrupt == 0) || (SmmuV3Info->PriInterrupt == 0) ||
(SmmuV3Info->GerrInterrupt == 0) || (SmmuV3Info->SyncInterrupt == 0))
{
Data = FdtGetProp (Fdt, SmmuV3Node, "msi-parent", &DataSize);
if ((Data == NULL) || (DataSize != 2 * sizeof (UINT32))) {
ASSERT ((Data != NULL) && (DataSize == 2 * sizeof (UINT32)));
FreePool (IdMappings);
return EFI_ABORTED;
}
IdMappings[SmmuV3Info->IdMappingCount].InputBase = 0;
IdMappings[SmmuV3Info->IdMappingCount].OutputBase = Fdt32ToCpu (((UINT32 *)Data)[1]);
IdMappings[SmmuV3Info->IdMappingCount].OutputReferenceToken = CM_ABSTRACT_TOKEN_MAKE (
ETokenNameSpaceFdtHwInfo,
EFdtHwInfoIortObject,
Fdt32ToCpu (((UINT32 *)Data)[0])
);
IdMappings[SmmuV3Info->IdMappingCount].NumIds = 1;
IdMappings[SmmuV3Info->IdMappingCount].Flags = EFI_ACPI_IORT_ID_MAPPING_FLAGS_SINGLE;
SmmuV3Info->DeviceIdMappingIndex = SmmuV3Info->IdMappingCount;
SmmuV3Info->IdMappingCount++;
SmmuV3Info->Flags |= EFI_ACPI_IORT_SMMUv3_FLAG_DEVICEID_VALID;
}
// Add the CmObj to the Configuration Manager.
Status = AddSingleCmObjArray (
FdtParserHandle,
CREATE_CM_ARM_OBJECT_ID (EArmObjIdMappingArray),
&IdMappings[0],
sizeof (CM_ARM_ID_MAPPING) * SmmuV3Info->IdMappingCount,
SmmuV3Info->IdMappingCount,
&SmmuV3Info->IdMappingToken
);
if (EFI_ERROR (Status)) {
ASSERT_EFI_ERROR (Status);
FreePool (IdMappings);
return Status;
}
/// Proximity domain flag
SmmuV3Info->ProximityDomain = 0;
/// Unique identifier for this node.
SmmuV3Info->Identifier = GetNextIortIdentifier ();
FreePool (IdMappings);
return EFI_SUCCESS;
}
/** CM_ARM_SMMUV3_NODE parser function.
The following structure is populated:
typedef struct CmArmSmmuV3Node {
CM_OBJECT_TOKEN Token;
UINT32 IdMappingCount; // {Populated}
CM_OBJECT_TOKEN IdMappingToken; // {Populated}
UINT64 BaseAddress; // {Populated}
UINT32 Flags; // {default = 0}
UINT64 VatosAddress; // {default = 0}
UINT32 Model; // {Populated}
UINT32 EventInterrupt; // {Populated}
UINT32 PriInterrupt; // {Populated}
UINT32 GerrInterrupt; // {Populated}
UINT32 SyncInterrupt; // {Populated}
UINT32 ProximityDomain; // {default = 0}
UINT32 DeviceIdMappingIndex; // {Populated}
UINT32 Identifier; // {Populated}
CM_OBJECT_TOKEN ProximityDomainToken; // {default = 0}
} CM_ARM_SMMUV3_NODE;
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
ArmSmmuV3Parser (
IN CONST FDT_HW_INFO_PARSER_HANDLE FdtParserHandle,
IN INT32 FdtBranch
)
{
EFI_STATUS Status;
UINT32 Index;
INT32 SmmuV3Node;
UINT32 SmmuV3NodeCount;
CM_ARM_SMMUV3_NODE SmmuV3Info;
VOID *Fdt;
if (FdtParserHandle == NULL) {
ASSERT (FdtParserHandle != NULL);
return EFI_INVALID_PARAMETER;
}
Fdt = FdtParserHandle->Fdt;
Status = FdtCountCompatNodeInBranch (
Fdt,
FdtBranch,
&SmmuV3CompatibleInfo,
&SmmuV3NodeCount
);
if (EFI_ERROR (Status)) {
ASSERT_EFI_ERROR (Status);
return Status;
}
if (SmmuV3NodeCount == 0) {
return EFI_NOT_FOUND;
}
// Parse each SmmuV3 node in the branch.
SmmuV3Node = FdtBranch;
for (Index = 0; Index < SmmuV3NodeCount; Index++) {
ZeroMem (&SmmuV3Info, sizeof (CM_ARM_SMMUV3_NODE));
Status = FdtGetNextCompatNodeInBranch (
Fdt,
FdtBranch,
&SmmuV3CompatibleInfo,
&SmmuV3Node
);
if (EFI_ERROR (Status)) {
ASSERT_EFI_ERROR (Status);
if (Status == EFI_NOT_FOUND) {
// Should have found the node.
Status = EFI_ABORTED;
}
return Status;
}
Status = SmmuV3NodeParser (FdtParserHandle, Fdt, SmmuV3Node, &SmmuV3Info);
if (EFI_ERROR (Status)) {
ASSERT_EFI_ERROR (Status);
return Status;
}
// Add the CmObj to the Configuration Manager.
Status = AddSingleCmObjWithToken (
FdtParserHandle,
CREATE_CM_ARM_OBJECT_ID (EArmObjSmmuV3),
&SmmuV3Info,
sizeof (CM_ARM_SMMUV3_NODE),
CM_ABSTRACT_TOKEN_MAKE (ETokenNameSpaceFdtHwInfo, EFdtHwInfoIortObject, FdtGetPhandle (Fdt, SmmuV3Node))
);
if (EFI_ERROR (Status)) {
ASSERT_EFI_ERROR (Status);
return Status;
}
} // for
return Status;
}