/** @file Flattened device tree utility. Copyright (c) 2021, ARM Limited. All rights reserved.
SPDX-License-Identifier: BSD-2-Clause-Patent @par Reference(s): - Device tree Specification - Release v0.3 - linux/Documentation/devicetree/bindings/interrupt-controller/arm%2Cgic.yaml - linux//Documentation/devicetree/bindings/interrupt-controller/arm%2Cgic.yaml **/ #include #include "FdtUtility.h" /** Get the interrupt Id of an interrupt described in a fdt. Data must describe a GIC interrupt. A GIC interrupt is on at least 3 UINT32 cells. This function DOES NOT SUPPORT extended SPI range and extended PPI range. @param [in] Data Pointer to the first cell of an "interrupts" property. @retval The interrupt id. **/ UINT32 EFIAPI FdtGetInterruptId ( UINT32 CONST *Data ) { UINT32 IrqType; UINT32 IrqId; ASSERT (Data != NULL); IrqType = fdt32_to_cpu (Data[IRQ_TYPE_OFFSET]); IrqId = fdt32_to_cpu (Data[IRQ_NUMBER_OFFSET]); switch (IrqType) { case DT_SPI_IRQ: IrqId += SPI_OFFSET; break; case DT_PPI_IRQ: IrqId += PPI_OFFSET; break; default: ASSERT (0); IrqId = 0; } return IrqId; } /** Get the ACPI interrupt flags of an interrupt described in a fdt. Data must describe a GIC interrupt. A GIC interrupt is on at least 3 UINT32 cells. PPI interrupt cpu mask on bits [15:8] are ignored. @param [in] Data Pointer to the first cell of an "interrupts" property. @retval The interrupt flags (for ACPI). **/ UINT32 EFIAPI FdtGetInterruptFlags ( UINT32 CONST *Data ) { UINT32 IrqFlags; UINT32 AcpiIrqFlags; ASSERT (Data != NULL); IrqFlags = fdt32_to_cpu (Data[IRQ_FLAGS_OFFSET]); AcpiIrqFlags = DT_IRQ_IS_EDGE_TRIGGERED (IrqFlags) ? BIT0 : 0; AcpiIrqFlags |= DT_IRQ_IS_ACTIVE_LOW (IrqFlags) ? BIT1 : 0; return AcpiIrqFlags; } /** Check whether a node has the input name. @param [in] Fdt Pointer to a Flattened Device Tree. @param [in] Node Offset of the node to check the name. @param [in] SearchName Node name to search. This is a NULL terminated string. @retval True The node has the input name. @retval FALSE Otherwise, or error. **/ STATIC BOOLEAN EFIAPI FdtNodeHasName ( IN CONST VOID *Fdt, IN INT32 Node, IN CONST VOID *SearchName ) { CONST CHAR8 *NodeName; UINT32 Length; if ((Fdt == NULL) || (SearchName == NULL)) { ASSERT (0); return FALSE; } // Always compare the whole string. Don't stop at the "@" char. Length = (UINT32)AsciiStrLen (SearchName); // Get the address of the node name. NodeName = fdt_offset_ptr (Fdt, Node + FDT_TAGSIZE, Length + 1); if (NodeName == NULL) { return FALSE; } // SearchName must be longer than the node name. if (Length > AsciiStrLen (NodeName)) { return FALSE; } if (AsciiStrnCmp (NodeName, SearchName, Length) != 0) { return FALSE; } // The name matches perfectly, or // the node name is XXX@addr and the XXX matches. if ((NodeName[Length] == '\0') || (NodeName[Length] == '@')) { return TRUE; } return FALSE; } /** Iterate through the list of strings in the Context, and check whether at least one string is matching the "compatible" property of the node. @param [in] Fdt Pointer to a Flattened Device Tree. @param [in] Node Offset of the node to operate the check on. @param [in] CompatInfo COMPATIBILITY_INFO containing the list of compatible strings to compare with the "compatible" property of the node. @retval TRUE At least one string matched, the node is compatible. @retval FALSE Otherwise, or error. **/ BOOLEAN EFIAPI FdtNodeIsCompatible ( IN CONST VOID *Fdt, IN INT32 Node, IN CONST VOID *CompatInfo ) { UINT32 Index; CONST COMPATIBILITY_STR *CompatibleTable; UINT32 Count; CONST VOID *Prop; INT32 PropLen; if ((Fdt == NULL) || (CompatInfo == NULL)) { ASSERT (0); return FALSE; } Count = ((COMPATIBILITY_INFO *)CompatInfo)->Count; CompatibleTable = ((COMPATIBILITY_INFO *)CompatInfo)->CompatTable; // Get the "compatible" property. Prop = fdt_getprop (Fdt, Node, "compatible", &PropLen); if ((Prop == NULL) || (PropLen < 0)) { return FALSE; } for (Index = 0; Index < Count; Index++) { if (fdt_stringlist_contains ( Prop, PropLen, CompatibleTable[Index].CompatStr )) { return TRUE; } } // for return FALSE; } /** Check whether a node has a property. @param [in] Fdt Pointer to a Flattened Device Tree. @param [in] Node Offset of the node to operate the check on. @param [in] PropertyName Name of the property to search. This is a NULL terminated string. @retval True The node has the property. @retval FALSE Otherwise, or error. **/ BOOLEAN EFIAPI FdtNodeHasProperty ( IN CONST VOID *Fdt, IN INT32 Node, IN CONST VOID *PropertyName ) { INT32 Size; CONST VOID *Prop; if ((Fdt == NULL) || (PropertyName == NULL)) { ASSERT (0); return FALSE; } Prop = fdt_getprop (Fdt, Node, PropertyName, &Size); if ((Prop == NULL) || (Size < 0)) { return FALSE; } return TRUE; } /** Get the next node in the whole DT fulfilling a condition. The condition to fulfill is checked by the NodeChecker function. Context is passed to NodeChecker. The Device tree is traversed in a depth-first search, starting from Node. The input Node is skipped. @param [in] Fdt Pointer to a Flattened Device Tree. @param [in, out] Node At entry: Node offset to start the search. This first node is skipped. Write (-1) to search the whole tree. At exit: If success, contains the offset of the next node fulfilling the condition. @param [in, out] Depth Depth is incremented/decremented of the depth difference between the input Node and the output Node. E.g.: If the output Node is a child node of the input Node, contains (+1). @param [in] NodeChecker Function called to check if the condition is fulfilled. @param [in] Context Context for the NodeChecker. @retval EFI_SUCCESS The function completed successfully. @retval EFI_ABORTED An error occurred. @retval EFI_INVALID_PARAMETER Invalid parameter. @retval EFI_NOT_FOUND No matching node found. **/ STATIC EFI_STATUS EFIAPI FdtGetNextCondNode ( IN CONST VOID *Fdt, IN OUT INT32 *Node, IN OUT INT32 *Depth, IN NODE_CHECKER_FUNC NodeChecker, IN CONST VOID *Context ) { INT32 CurrNode; if ((Fdt == NULL) || (Node == NULL) || (Depth == NULL) || (NodeChecker == NULL)) { ASSERT (0); return EFI_INVALID_PARAMETER; } CurrNode = *Node; do { CurrNode = fdt_next_node (Fdt, CurrNode, Depth); if ((CurrNode == -FDT_ERR_NOTFOUND) || (*Depth < 0)) { // End of the tree, no matching node found. return EFI_NOT_FOUND; } else if (CurrNode < 0) { // An error occurred. ASSERT (0); return EFI_ABORTED; } } while (!NodeChecker (Fdt, CurrNode, Context)); // Matching node found. *Node = CurrNode; return EFI_SUCCESS; } /** Get the next node in a branch fulfilling a condition. The condition to fulfill is checked by the NodeChecker function. Context is passed to NodeChecker. The Device tree is traversed in a depth-first search, starting from Node. The input Node is skipped. @param [in] Fdt Pointer to a Flattened Device Tree. @param [in] FdtBranch Only search in the sub-nodes of this branch. Write (-1) to search the whole tree. @param [in] NodeChecker Function called to check if the condition is fulfilled. @param [in] Context Context for the NodeChecker. @param [in, out] Node At entry: Node offset to start the search. This first node is skipped. Write (-1) to search the whole tree. At exit: If success, contains the offset of the next node in the branch fulfilling the condition. @retval EFI_SUCCESS The function completed successfully. @retval EFI_ABORTED An error occurred. @retval EFI_INVALID_PARAMETER Invalid parameter. @retval EFI_NOT_FOUND No matching node found. **/ STATIC EFI_STATUS EFIAPI FdtGetNextCondNodeInBranch ( IN CONST VOID *Fdt, IN INT32 FdtBranch, IN NODE_CHECKER_FUNC NodeChecker, IN CONST VOID *Context, IN OUT INT32 *Node ) { EFI_STATUS Status; INT32 CurrNode; INT32 Depth; if ((Fdt == NULL) || (Node == NULL) || (NodeChecker == NULL)) { ASSERT (0); return EFI_INVALID_PARAMETER; } CurrNode = FdtBranch; Depth = 0; // First, check the Node is in the sub-nodes of the branch. // This allows to find the relative depth of Node in the branch. if (CurrNode != *Node) { for (CurrNode = fdt_next_node (Fdt, CurrNode, &Depth); (CurrNode >= 0) && (Depth > 0); CurrNode = fdt_next_node (Fdt, CurrNode, &Depth)) { if (CurrNode == *Node) { // Node found. break; } } // for if ((CurrNode < 0) || (Depth <= 0)) { // Node is not a node in the branch, or an error occurred. ASSERT (0); return EFI_INVALID_PARAMETER; } } // Get the next node in the tree fulfilling the condition, // in any branch. Status = FdtGetNextCondNode ( Fdt, Node, &Depth, NodeChecker, Context ); if (EFI_ERROR (Status)) { ASSERT (Status == EFI_NOT_FOUND); return Status; } if (Depth <= 0) { // The node found is not in the right branch. return EFI_NOT_FOUND; } return EFI_SUCCESS; } /** Get the next node in a branch having a matching name. The Device tree is traversed in a depth-first search, starting from Node. The input Node is skipped. @param [in] Fdt Pointer to a Flattened Device Tree. @param [in] FdtBranch Only search in the sub-nodes of this branch. Write (-1) to search the whole tree. @param [in] NodeName The node name to search. This is a NULL terminated string. @param [in, out] Node At entry: Node offset to start the search. This first node is skipped. Write (-1) to search the whole tree. At exit: If success, contains the offset of the next node in the branch having a matching name. @retval EFI_SUCCESS The function completed successfully. @retval EFI_ABORTED An error occurred. @retval EFI_INVALID_PARAMETER Invalid parameter. @retval EFI_NOT_FOUND No matching node found. **/ EFI_STATUS EFIAPI FdtGetNextNamedNodeInBranch ( IN CONST VOID *Fdt, IN INT32 FdtBranch, IN CONST CHAR8 *NodeName, IN OUT INT32 *Node ) { return FdtGetNextCondNodeInBranch ( Fdt, FdtBranch, FdtNodeHasName, NodeName, Node ); } /** Get the next node in a branch with at least one compatible property. The Device tree is traversed in a depth-first search, starting from Node. The input Node is skipped. @param [in] Fdt Pointer to a Flattened Device Tree. @param [in] FdtBranch Only search in the sub-nodes of this branch. Write (-1) to search the whole tree. @param [in] CompatNamesInfo Table of compatible strings to compare with the compatible property of the node. @param [in, out] Node At entry: Node offset to start the search. This first node is skipped. Write (-1) to search the whole tree. At exit: If success, contains the offset of the next node in the branch being compatible. @retval EFI_SUCCESS The function completed successfully. @retval EFI_ABORTED An error occurred. @retval EFI_INVALID_PARAMETER Invalid parameter. @retval EFI_NOT_FOUND No matching node found. **/ EFI_STATUS EFIAPI FdtGetNextCompatNodeInBranch ( IN CONST VOID *Fdt, IN INT32 FdtBranch, IN CONST COMPATIBILITY_INFO *CompatNamesInfo, IN OUT INT32 *Node ) { return FdtGetNextCondNodeInBranch ( Fdt, FdtBranch, FdtNodeIsCompatible, (CONST VOID *)CompatNamesInfo, Node ); } /** Get the next node in a branch having the PropName property. The Device tree is traversed in a depth-first search, starting from Node. The input Node is skipped. @param [in] Fdt Pointer to a Flattened Device Tree. @param [in] FdtBranch Only search in the sub-nodes of this branch. Write (-1) to search the whole tree. @param [in] PropName Name of the property to search. This is a NULL terminated string. @param [in, out] Node At entry: Node offset to start the search. This first node is skipped. Write (-1) to search the whole tree. At exit: If success, contains the offset of the next node in the branch being compatible. @retval EFI_SUCCESS The function completed successfully. @retval EFI_ABORTED An error occurred. @retval EFI_INVALID_PARAMETER Invalid parameter. @retval EFI_NOT_FOUND No matching node found. **/ EFI_STATUS EFIAPI FdtGetNextPropNodeInBranch ( IN CONST VOID *Fdt, IN INT32 FdtBranch, IN CONST CHAR8 *PropName, IN OUT INT32 *Node ) { return FdtGetNextCondNodeInBranch ( Fdt, FdtBranch, FdtNodeHasProperty, (CONST VOID *)PropName, Node ); } /** Count the number of Device Tree nodes fulfilling a condition in a Device Tree branch. The condition to fulfill is checked by the NodeChecker function. Context is passed to NodeChecker. @param [in] Fdt Pointer to a Flattened Device Tree. @param [in] FdtBranch Only search in the sub-nodes of this branch. Write (-1) to search the whole tree. @param [in] NodeChecker Function called to check the condition is fulfilled. @param [in] Context Context for the NodeChecker. @param [out] NodeCount If success, contains the count of nodes fulfilling the condition. Can be 0. @retval EFI_SUCCESS The function completed successfully. @retval EFI_ABORTED An error occurred. @retval EFI_INVALID_PARAMETER Invalid parameter. **/ STATIC EFI_STATUS EFIAPI FdtCountCondNodeInBranch ( IN CONST VOID *Fdt, IN INT32 FdtBranch, IN NODE_CHECKER_FUNC NodeChecker, IN CONST VOID *Context, OUT UINT32 *NodeCount ) { EFI_STATUS Status; INT32 CurrNode; if ((Fdt == NULL) || (NodeChecker == NULL) || (NodeCount == NULL)) { ASSERT (0); return EFI_INVALID_PARAMETER; } *NodeCount = 0; CurrNode = FdtBranch; while (TRUE) { Status = FdtGetNextCondNodeInBranch ( Fdt, FdtBranch, NodeChecker, Context, &CurrNode ); if (EFI_ERROR (Status) && (Status != EFI_NOT_FOUND)) { ASSERT (0); return Status; } else if (Status == EFI_NOT_FOUND) { break; } (*NodeCount)++; } return EFI_SUCCESS; } /** Count the number of nodes in a branch with the input name. @param [in] Fdt Pointer to a Flattened Device Tree. @param [in] FdtBranch Only search in the sub-nodes of this branch. Write (-1) to search the whole tree. @param [in] NodeName Node name to search. This is a NULL terminated string. @param [out] NodeCount If success, contains the count of nodes fulfilling the condition. Can be 0. @retval EFI_SUCCESS The function completed successfully. @retval EFI_ABORTED An error occurred. @retval EFI_INVALID_PARAMETER Invalid parameter. **/ EFI_STATUS EFIAPI FdtCountNamedNodeInBranch ( IN CONST VOID *Fdt, IN INT32 FdtBranch, IN CONST CHAR8 *NodeName, OUT UINT32 *NodeCount ) { return FdtCountCondNodeInBranch ( Fdt, FdtBranch, FdtNodeHasName, NodeName, NodeCount ); } /** Count the number of nodes in a branch with at least one compatible property. @param [in] Fdt Pointer to a Flattened Device Tree. @param [in] FdtBranch Only search in the sub-nodes of this branch. Write (-1) to search the whole tree. @param [in] CompatNamesInfo Table of compatible strings to compare with the compatible property of the node. @param [out] NodeCount If success, contains the count of nodes fulfilling the condition. Can be 0. @retval EFI_SUCCESS The function completed successfully. @retval EFI_ABORTED An error occurred. @retval EFI_INVALID_PARAMETER Invalid parameter. **/ EFI_STATUS EFIAPI FdtCountCompatNodeInBranch ( IN CONST VOID *Fdt, IN INT32 FdtBranch, IN CONST COMPATIBILITY_INFO *CompatNamesInfo, OUT UINT32 *NodeCount ) { return FdtCountCondNodeInBranch ( Fdt, FdtBranch, FdtNodeIsCompatible, CompatNamesInfo, NodeCount ); } /** Count the number of nodes in a branch having the PropName property. @param [in] Fdt Pointer to a Flattened Device Tree. @param [in] FdtBranch Only search in the sub-nodes of this branch. Write (-1) to search the whole tree. @param [in] PropName Name of the property to search. This is a NULL terminated string. @param [out] NodeCount If success, contains the count of nodes fulfilling the condition. Can be 0. @retval EFI_SUCCESS The function completed successfully. @retval EFI_ABORTED An error occurred. @retval EFI_INVALID_PARAMETER Invalid parameter. **/ EFI_STATUS EFIAPI FdtCountPropNodeInBranch ( IN CONST VOID *Fdt, IN INT32 FdtBranch, IN CONST CHAR8 *PropName, OUT UINT32 *NodeCount ) { return FdtCountCondNodeInBranch ( Fdt, FdtBranch, FdtNodeHasProperty, PropName, NodeCount ); } /** Get the interrupt-controller node handling the interrupts of the input node. To do this, recursively search a node with either the "interrupt-controller" or the "interrupt-parent" property in the parents of Node. Devicetree Specification, Release v0.3, 2.4.1 "Properties for Interrupt Generating Devices": Because the hierarchy of the nodes in the interrupt tree might not match the devicetree, the interrupt-parent property is available to make the definition of an interrupt parent explicit. The value is the phandle to the interrupt parent. If this property is missing from a device, its interrupt parent is assumed to be its devicetree parent. @param [in] Fdt Pointer to a Flattened Device Tree. @param [in] Node Offset of the node to start the search. @param [out] IntcNode If success, contains the offset of the interrupt-controller node. @retval EFI_SUCCESS The function completed successfully. @retval EFI_NOT_FOUND No interrupt-controller node found. @retval EFI_ABORTED An error occurred. @retval EFI_INVALID_PARAMETER Invalid parameter. **/ EFI_STATUS EFIAPI FdtGetIntcParentNode ( IN CONST VOID *Fdt, IN INT32 Node, OUT INT32 *IntcNode ) { CONST UINT32 *PHandle; INT32 Size; CONST VOID *Prop; if ((Fdt == NULL) || (IntcNode == NULL)) { ASSERT (0); return EFI_INVALID_PARAMETER; } while (TRUE) { // Check whether the node has the "interrupt-controller" property. Prop = fdt_getprop (Fdt, Node, "interrupt-controller", &Size); if ((Prop != NULL) && (Size >= 0)) { // The interrupt-controller has been found. *IntcNode = Node; return EFI_SUCCESS; } else { // Check whether the node has the "interrupt-parent" property. PHandle = fdt_getprop (Fdt, Node, "interrupt-parent", &Size); if ((PHandle != NULL) && (Size == sizeof (UINT32))) { // The phandle of the interrupt-controller has been found. // Search the node having this phandle and return it. Node = fdt_node_offset_by_phandle (Fdt, fdt32_to_cpu (*PHandle)); if (Node < 0) { ASSERT (0); return EFI_ABORTED; } *IntcNode = Node; return EFI_SUCCESS; } else if (Size != -FDT_ERR_NOTFOUND) { ASSERT (0); return EFI_ABORTED; } } if (Node == 0) { // We are at the root of the tree. Not parent available. return EFI_NOT_FOUND; } // Get the parent of the node. Node = fdt_parent_offset (Fdt, Node); if (Node < 0) { // An error occurred. ASSERT (0); return EFI_ABORTED; } } // while } /** Get the "interrupt-cells" property value of the node. The "interrupts" property requires to know the number of cells used to encode an interrupt. This information is stored in the interrupt-controller of the input Node. @param [in] Fdt Pointer to a Flattened Device Tree (Fdt). @param [in] IntcNode Offset of an interrupt-controller node. @param [out] IntCells If success, contains the "interrupt-cells" property of the IntcNode. @retval EFI_SUCCESS The function completed successfully. @retval EFI_INVALID_PARAMETER Invalid parameter. @retval EFI_UNSUPPORTED Unsupported. **/ EFI_STATUS EFIAPI FdtGetInterruptCellsInfo ( IN CONST VOID *Fdt, IN INT32 IntcNode, OUT INT32 *IntCells ) { CONST UINT32 *Data; INT32 Size; if ((Fdt == NULL) || (IntCells == NULL)) { ASSERT (0); return EFI_INVALID_PARAMETER; } Data = fdt_getprop (Fdt, IntcNode, "#interrupt-cells", &Size); if ((Data == NULL) || (Size != sizeof (UINT32))) { // If error or not on one UINT32 cell. ASSERT (0); return EFI_ABORTED; } *IntCells = fdt32_to_cpu (*Data); return EFI_SUCCESS; } /** Get the "#address-cells" and/or "#size-cells" property of the node. According to the Device Tree specification, s2.3.5 "#address-cells and #size-cells": "If missing, a client program should assume a default value of 2 for #address-cells, and a value of 1 for #size-cells." @param [in] Fdt Pointer to a Flattened Device Tree. @param [in] Node Offset of the node having to get the "#address-cells" and "#size-cells" properties from. @param [out] AddressCells If success, number of address-cells. If the property is not available, default value is 2. @param [out] SizeCells If success, number of size-cells. If the property is not available, default value is 1. @retval EFI_SUCCESS The function completed successfully. @retval EFI_ABORTED An error occurred. @retval EFI_INVALID_PARAMETER Invalid parameter. **/ EFI_STATUS EFIAPI FdtGetAddressInfo ( IN CONST VOID *Fdt, IN INT32 Node, OUT INT32 *AddressCells, OPTIONAL OUT INT32 *SizeCells OPTIONAL ) { if (Fdt == NULL) { ASSERT (0); return EFI_INVALID_PARAMETER; } if (AddressCells != NULL) { *AddressCells = fdt_address_cells (Fdt, Node); if (*AddressCells < 0) { ASSERT (0); return EFI_ABORTED; } } if (SizeCells != NULL) { *SizeCells = fdt_size_cells (Fdt, Node); if (*SizeCells < 0) { ASSERT (0); return EFI_ABORTED; } } return EFI_SUCCESS; } /** Get the "#address-cells" and/or "#size-cells" property of the parent node. According to the Device Tree specification, s2.3.5 "#address-cells and #size-cells": "If missing, a client program should assume a default value of 2 for #address-cells, and a value of 1 for #size-cells." @param [in] Fdt Pointer to a Flattened Device Tree. @param [in] Node Offset of the node having to get the "#address-cells" and "#size-cells" properties from its parent. @param [out] AddressCells If success, number of address-cells. If the property is not available, default value is 2. @param [out] SizeCells If success, number of size-cells. If the property is not available, default value is 1. @retval EFI_SUCCESS The function completed successfully. @retval EFI_ABORTED An error occurred. @retval EFI_INVALID_PARAMETER Invalid parameter. **/ EFI_STATUS EFIAPI FdtGetParentAddressInfo ( IN CONST VOID *Fdt, IN INT32 Node, OUT INT32 *AddressCells, OPTIONAL OUT INT32 *SizeCells OPTIONAL ) { if (Fdt == NULL) { ASSERT (0); return EFI_INVALID_PARAMETER; } Node = fdt_parent_offset (Fdt, Node); if (Node < 0) { // End of the tree, or an error occurred. ASSERT (0); return EFI_ABORTED; } return FdtGetAddressInfo (Fdt, Node, AddressCells, SizeCells); }