blob: c5bed06a57ed538ddfbb824c8eea8d9875935741 [file] [log] [blame]
/*
* Copyright (c) 2020, The Linux Foundation. All rights reserved.
* Copyright (C) 2006 David Gibson, IBM Corporation.
*
* libfdt is dual licensed: you can use it either under the terms of
* the GPL, or the BSD license, at your option.
*
* a) This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this library; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
* MA 02110-1301 USA
*
* Alternatively,
*
* b) Redistribution and use in source and binary forms, with or
* without modification, are permitted provided that the following
* conditions are met:
*
* 1. Redistributions of source code must retain the above
* copyright notice, this list of conditions and the following
* disclaimer.
* 2. Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "AutoGen.h"
#include <Library/PrintLib.h>
#include <Library/DebugLib.h>
#include <libfdt_env.h>
#include <fdt.h>
#include <libfdt.h>
#include <Library/FdtRw.h>
STATIC FDT_FIRST_LEVEL_NODE *NodeList;
STATIC VOID FdtDeleteNodeList (VOID)
{
FDT_FIRST_LEVEL_NODE *Current = NodeList;
FDT_FIRST_LEVEL_NODE *Next;
while (Current != NULL) {
Next = Current->Next;
FreePool (Current);
Current = Next;
}
NodeList = NULL;
}
STATIC UINT32 GetNodeNameLen (CONST CHAR8 *NodeName)
{
CONST CHAR8 *Ptr = NULL;
CONST CHAR8 *End = NodeName + AsciiStrLen (NodeName);
UINT32 NameLen = 0;
if (! *NodeName) {
return NameLen;
}
Ptr = strchr (NodeName, '{');
if (! Ptr) {
Ptr = End;
}
NameLen = Ptr - NodeName;
return NameLen;
}
STATIC BOOLEAN IsNodeAdded (CONST CHAR8 *NodeName, UINT32 NameLen)
{
FDT_FIRST_LEVEL_NODE *Node = NULL;
for (Node = NodeList; Node; Node = Node->Next) {
if (!AsciiStrnCmp (Node->NodeName, NodeName, NameLen)) {
return TRUE;
}
}
return FALSE;
}
STATIC VOID FdtAppendToNodeList (CONST CHAR8 *NodeName, INT32 NodeOffset)
{
FDT_FIRST_LEVEL_NODE *Node;
UINT32 NameLen;
NameLen = GetNodeNameLen (NodeName);
if (IsNodeAdded (NodeName, NameLen)) {
return;
}
Node = AllocateZeroPool (sizeof (*Node));
if (Node) {
Node->Next = NodeList;
NodeList = Node;
Node->NodeName = AllocateZeroPool (NameLen + 1);
if (Node->NodeName) {
CopyMem ((VOID *)Node->NodeName, NodeName, NameLen);
Node->NodeOffset = NodeOffset;
} else {
FdtDeleteNodeList ();
}
} else {
FdtDeleteNodeList ();
}
}
STATIC BOOLEAN FdtFindNodeFromList (CONST CHAR8 *Name,
UINT32 NameLen,
INT32 *NodeOffset)
{
FDT_FIRST_LEVEL_NODE *Node = NULL;
*NodeOffset = 0;
if (NodeList) {
/* Put the ptr of node offset to the last one */
*NodeOffset = NodeList->NodeOffset;
for (Node = NodeList; Node; Node = Node->Next) {
if (!AsciiStrnCmp (Node->NodeName, Name, NameLen)) {
*NodeOffset = Node->NodeOffset;
return TRUE;
}
}
}
return FALSE;
}
VOID FdtUpdateNodeOffsetInList (INT32 NodeOffset, INT32 DiffLen)
{
FDT_FIRST_LEVEL_NODE *Node = NULL;
if (!DiffLen) {
return;
}
for (Node = NodeList; Node; Node = Node->Next) {
if (Node->NodeOffset > NodeOffset) {
Node->NodeOffset += DiffLen;
} else {
break;
}
}
}
/**
Compare the node's name
@param[in] Fdt A pointer to the device tree blob.
@param[in] Offset The offset of the node whose property to find.
@param[in] Source The name of the property to find.
@param[in] Len The length to compare from source.
@retval TRUE Match.
@retval FALSE Mismatch.
**/
STATIC BOOLEAN FdtNodeNameEq (CONST VOID *Fdt, INT32 Offset,
CONST CHAR8 *Source, UINT32 Len)
{
CONST CHAR8 *Ptr = fdt_offset_ptr (Fdt, Offset + FDT_TAGSIZE, Len + 1);
if (!Ptr) {
/* short match: Some error occurs */
return FALSE;
}
if (CompareMem (Ptr, Source, Len) != 0) {
return FALSE;
}
if (Ptr[Len] == '\0') {
return TRUE;
} else if (!ScanMem8 (Source, '@', Len) &&
(Ptr[Len] == '@')) {
return TRUE;
}
return FALSE;
}
/**
Find a subnode based on substring
@param[in] Fdt A pointer to the device tree blob.
@param[in] Offset The structure block offset of a node.
@param[in] Name The name of the subnode to locate.
@param[in] NameLen The number of characters of name to consider.
@param[in] Level The Level of node.
@retval > 0 Find node subnode successfully.
@retval other Some error occurs when searching this subnode.
Identical to fdt_subnode_offset_namelen(), but only examine the first
namelen characters of name for matching the subnode name. This is
useful for finding subnodes based on a portion of a larger string,
such as a full path.
**/
STATIC INT32 FdtSubnodeOffsetNamelen (CONST VOID *Fdt,
INT32 Offset,
CONST CHAR8 *Name,
UINT32 NameLen,
UINT32 Level)
{
INT32 Depth;
INT32 Value = 0, Ret;
CONST CHAR8 *Ptr = NULL;
if ((Ret = fdt_check_header (Fdt)) != 0) {
return Ret;
}
if (!Level) {
/* First find the node from the node list.
* Found: start to search from the level 0's offset
* Not Found: start to search from the last level 0's offset
*/
Ret = FdtFindNodeFromList (Name, NameLen, &Offset);
if (Ret) {
return Offset;
}
if (Offset) {
/* the depth should be 1 if find the offset from the list */
Value = 1;
}
}
for (Depth = Value;
(Offset >= 0) &&
(Depth >= 0);
Offset = fdt_next_node (Fdt, Offset, &Depth)) {
if (Depth == 1) {
/* Record the level 0's name and offset one by one */
if (!Ret &&
!Level) {
Ptr =
fdt_offset_ptr (Fdt, Offset + FDT_TAGSIZE, NameLen + 1);
if (! Ptr) {
/* short match: Some error occurs */
return 0;
}
FdtAppendToNodeList (Ptr, Offset);
}
/* Return the offset if find the node */
if (FdtNodeNameEq (Fdt, Offset, Name, NameLen)) {
return Offset;
}
}
}
if (Depth < 0) {
return -1;
}
/* Error: Could not find the node*/
return Offset;
}
/**
Find a tree node by its full path
@param[in] Fdt A pointer to the device tree blob.
@param[in] Path The full path of the node to locate.
FdtPathOffset() finds a node of a given path in the device tree.
Each path component may omit the unit address portion, but the
results of this are undefined if any such path component is
ambiguous (that is if there are multiple nodes at the relevant
level matching the given component, differentiated only by unit
address).
returns:
structure block offset of the node with the requested path (>=0), on success
-FDT_ERR_BADPATH, given path does not begin with '/' or is invalid
-FDT_ERR_NOTFOUND, if the requested node does not exist
-FDT_ERR_BADMAGIC,
-FDT_ERR_BADVERSION,
-FDT_ERR_BADSTATE,
-FDT_ERR_BADSTRUCTURE,
-FDT_ERR_TRUNCATED, standard meanings.
**/
INT32 FdtPathOffset (CONST VOID *Fdt, CONST CHAR8 *Path)
{
CONST CHAR8 *End = Path + AsciiStrLen (Path);
CONST CHAR8 *Ptr = Path;
CONST CHAR8 *Qtr = NULL;
INT32 Offset = 0;
UINT32 Level = 0;
INT32 Ret;
if (FixedPcdGetBool (EnableNewNodeSearchFuc)) {
if ((Ret = fdt_check_header (Fdt)) != 0) {
return Ret;
}
/* see if we have an alias */
if (*Path != '/') {
Qtr = strchr (Path, '/');
if (!Qtr) {
Qtr = End;
}
Ptr = fdt_get_alias_namelen (Fdt, Ptr, Qtr - Ptr);
if (!Ptr) {
return -FDT_ERR_BADPATH;
}
Offset = FdtPathOffset (Fdt, Ptr);
Ptr = Qtr;
}
while (*Ptr) {
while (*Ptr == '/') {
Ptr++;
}
if (! *Ptr) {
return Offset;
}
Qtr = strchr (Ptr, '/');
if (! Qtr) {
Qtr = End;
}
Offset = FdtSubnodeOffsetNamelen (Fdt, Offset, Ptr, Qtr - Ptr, Level);
if (Offset < 0) {
return Offset;
}
Ptr = Qtr;
Level ++;
}
} else {
Offset = fdt_path_offset (Fdt, Path);
}
return Offset;
}
INT32 FdtGetPropLen (VOID *Fdt, INT32 Offset, CONST CHAR8 *Name)
{
INT32 Len = 0;
struct fdt_property *Prop = NULL;
Prop = fdt_get_property_w (Fdt, Offset, Name, &Len);
if (!Prop ||
Len < 0) {
Len = 0;
}
return Len;
}
/**
* FdtSetProp - create or change a property
* @Fdt: pointer to the device tree blob
* @Offset: offset of the node whose property to change
* @Name: name of the property to change
* @Val: pointer to data to set the property value to
* @Len: length of the property value
*
* FdtSetProp () sets the value of the named property in the given
* node to the given value and length, creating the property if it
* does not already exist.
*
* This function may insert or delete data from the blob, and will
* therefore change the offsets of some existing nodes.
*
* returns:
* 0, on success
* -FDT_ERR_NOSPACE, there is insufficient free space in the blob to
* contain the new property value
* -FDT_ERR_BADOFFSET, nodeoffset did not point to FDT_BEGIN_NODE tag
* -FDT_ERR_BADLAYOUT,
* -FDT_ERR_BADMAGIC,
* -FDT_ERR_BADVERSION,
* -FDT_ERR_BADSTATE,
* -FDT_ERR_BADSTRUCTURE,
* -FDT_ERR_BADLAYOUT,
* -FDT_ERR_TRUNCATED, standard meanings
*/
INT32 FdtSetProp (VOID *Fdt, INT32 Offset, CONST CHAR8 *Name,
CONST VOID *Val, INT32 Len)
{
INT32 OldLen, NewLen;
INT32 Ret = 0;
if (FixedPcdGetBool (EnableNewNodeSearchFuc)) {
OldLen = FdtGetPropLen (Fdt, Offset, Name);
Ret = fdt_setprop (Fdt, Offset, Name, Val, Len);
if (Ret == 0) {
NewLen = FdtGetPropLen (Fdt, Offset, Name);
} else {
return Ret;
}
/* New prop */
if (OldLen == 0 &&
NewLen) {
NewLen = sizeof (struct fdt_property) + FDT_TAGALIGN (NewLen);
}
/* Update the node's offset in the list */
FdtUpdateNodeOffsetInList (
Offset, FDT_TAGALIGN (NewLen) - FDT_TAGALIGN (OldLen));
} else {
Ret = fdt_setprop (Fdt, Offset, Name, Val, Len);
}
return Ret;
}