You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
684 lines
25 KiB
Python
684 lines
25 KiB
Python
# Copyright 2017 Martin Olejar
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
import os
|
|
|
|
from .header import Header, DTB_BEGIN_NODE, DTB_END_NODE, DTB_PROP, DTB_END, DTB_NOP
|
|
from .items import new_property, Property, PropBytes, PropWords, PropStrings, PropIncBin, Node
|
|
from .misc import strip_comments, split_to_lines, get_version_info, extract_string
|
|
|
|
__author__ = "Martin Olejar"
|
|
__contact__ = "martin.olejar@gmail.com"
|
|
__version__ = "0.3.3"
|
|
__license__ = "Apache 2.0"
|
|
__status__ = "Development"
|
|
__all__ = [
|
|
# FDT Classes
|
|
'FDT',
|
|
'Node',
|
|
'Header',
|
|
# properties
|
|
'Property',
|
|
'PropBytes',
|
|
'PropWords',
|
|
'PropStrings',
|
|
'PropIncBin',
|
|
# core methods
|
|
'parse_dts',
|
|
'parse_dtb',
|
|
'diff'
|
|
]
|
|
|
|
|
|
class ItemType:
|
|
NODE = 0
|
|
PROP = 1
|
|
# Specific property type
|
|
PROP_BASE = 5
|
|
PROP_WORDS = 6
|
|
PROP_BYTES = 7
|
|
PROP_STRINGS = 8
|
|
# All types
|
|
ALL = 100
|
|
|
|
|
|
class FDT:
|
|
""" Flattened Device Tree Class """
|
|
|
|
@property
|
|
def empty(self):
|
|
return self.root.empty
|
|
|
|
def __init__(self, header=None, entries=[]):
|
|
"""
|
|
FDT class constructor
|
|
|
|
:param header:
|
|
"""
|
|
self.entries = entries
|
|
self.header = Header() if header is None else header
|
|
self.root = Node('/')
|
|
self.last_handle = 0
|
|
self.label_to_handle = {}
|
|
self.handle_to_label = {}
|
|
|
|
|
|
def __str__(self):
|
|
""" String representation """
|
|
return self.info()
|
|
|
|
def info(self, props = False):
|
|
""" Return object info in human readable format """
|
|
msg = "FDT Content:\n"
|
|
for path, nodes, props in self.walk():
|
|
txt = "{} ({},{})".format(path, len(nodes), len(props))
|
|
if props:
|
|
txt += ' : ' + ", ".join(x.name for x in props)
|
|
msg += txt + "\n"
|
|
return msg
|
|
|
|
def get_node(self, path: str, create: bool = False) -> Node:
|
|
"""
|
|
Get node object from specified path
|
|
|
|
:param path: Path as string
|
|
:param create: If True, not existing nodes will be created
|
|
"""
|
|
assert isinstance(path, str), "Node path must be a string type !"
|
|
|
|
node = self.root
|
|
path = path.lstrip('/')
|
|
if path:
|
|
names = path.split('/')
|
|
for name in names:
|
|
item = node.get_subnode(name)
|
|
if item is None:
|
|
if create:
|
|
item = Node(name)
|
|
node.append(item)
|
|
else:
|
|
raise ValueError("Path \"{}\" doesn't exists".format(path))
|
|
node = item
|
|
|
|
return node
|
|
|
|
def get_property(self, name: str, path: str = '') -> Property:
|
|
"""
|
|
Get property object by name from specified path
|
|
|
|
:param name: Property name
|
|
:param path: Path to sub-node
|
|
"""
|
|
return self.get_node(path).get_property(name)
|
|
|
|
def set_property(self, name: str, value, path: str = '', create: bool = True):
|
|
"""
|
|
Set property object by name
|
|
|
|
:param name: Property name
|
|
:param value: Property value
|
|
:param path: Path to subnode
|
|
:param create: If True, not existing nodes will be created
|
|
"""
|
|
self.get_node(path, create).set_property(name, value)
|
|
|
|
def exist_node(self, path: str) -> bool:
|
|
"""
|
|
Check if <path>/node exist and return True
|
|
|
|
:param path: path/node name
|
|
:return True if <path>/node exist else False
|
|
"""
|
|
try:
|
|
self.get_node(path)
|
|
except ValueError:
|
|
return False
|
|
else:
|
|
return True
|
|
|
|
def exist_property(self, name: str, path: str = '') -> bool:
|
|
"""
|
|
Check if property exist
|
|
|
|
:param name: Property name
|
|
:param path: The path
|
|
"""
|
|
return self.get_node(path).exist_property(name) if self.exist_node(path) else False
|
|
|
|
def remove_node(self, name: str, path: str = ''):
|
|
"""
|
|
Remove node obj by path/name. Raises ValueError if path/name doesn't exist
|
|
|
|
:param name: Node name
|
|
:param path: Path to sub-node
|
|
"""
|
|
self.get_node(path).remove_subnode(name)
|
|
|
|
def remove_property(self, name: str, path: str = ''):
|
|
"""
|
|
Remove property obj by name. Raises ValueError if path/name doesn't exist
|
|
|
|
:param name: Property name
|
|
:param path: Path to subnode
|
|
"""
|
|
self.get_node(path).remove_property(name)
|
|
|
|
def add_item(self, obj, path: str = '', create: bool = True):
|
|
"""
|
|
Add sub-node or property at specified path. Raises ValueError if path doesn't exist
|
|
|
|
:param obj: The node or property object
|
|
:param path: The path to subnode
|
|
:param create: If True, not existing nodes will be created
|
|
"""
|
|
self.get_node(path, create).append(obj)
|
|
|
|
def add_label(self, label):
|
|
''' track labels/references to convert to phandles
|
|
adds label with incrmenting handle to dictionary if not alread present
|
|
returns handle for which can be used to replace the reference'''
|
|
if label in self.label_to_handle:
|
|
return self.label_to_handle[label]
|
|
self.last_handle += 1
|
|
self.label_to_handle[label] = self.last_handle
|
|
self.handle_to_label[self.last_handle] = label
|
|
return self.last_handle
|
|
|
|
def search(self, name: str, itype: int = ItemType.ALL, path: str = '', recursive: bool = True) -> list:
|
|
"""
|
|
Search properties and/or nodes with specified name. Return list of founded items
|
|
|
|
:param name: The Property or Node name. If empty "", all nodes or properties will selected
|
|
:param itype: Item type - NODE, PROP, PROP_BASE, PROP_WORDS, PROP_BYTES, PROP_STRINGS or ALL
|
|
:param path: Path to root node
|
|
:param recursive: Search in all sub-nodes (default: True)
|
|
"""
|
|
assert isinstance(name, str), "Property name must be a string type !"
|
|
|
|
node = self.get_node(path)
|
|
nodes = []
|
|
items = []
|
|
pclss = {
|
|
ItemType.PROP_BASE: Property,
|
|
ItemType.PROP_BYTES: PropBytes,
|
|
ItemType.PROP_WORDS: PropWords,
|
|
ItemType.PROP_STRINGS: PropStrings
|
|
}
|
|
while True:
|
|
nodes += node.nodes
|
|
if itype == ItemType.NODE or itype == ItemType.ALL:
|
|
if not name or node.name == name:
|
|
items.append(node)
|
|
if itype != ItemType.NODE or itype == ItemType.ALL:
|
|
for p in node.props:
|
|
if name and p.name != name:
|
|
continue
|
|
if itype in pclss and type(p) is not pclss[itype]:
|
|
continue
|
|
items.append(p)
|
|
if not recursive or not nodes:
|
|
break
|
|
node = nodes.pop()
|
|
|
|
return items
|
|
|
|
def walk(self, path: str = '', relative: bool = False) -> list:
|
|
"""
|
|
Walk trough nodes and return relative/absolute path with list of sub-nodes and properties
|
|
|
|
:param path: The path to root node
|
|
:param relative: True for relative or False for absolute return path
|
|
"""
|
|
all_nodes = []
|
|
|
|
node = self.get_node(path)
|
|
while True:
|
|
all_nodes += node.nodes
|
|
current_path = node.path
|
|
current_path = current_path.replace('///', '/')
|
|
current_path = current_path.replace('//', '/')
|
|
if path and relative:
|
|
current_path = current_path.replace(path, '').lstrip('/')
|
|
yield current_path, node.nodes, node.props
|
|
if not all_nodes:
|
|
break
|
|
node = all_nodes.pop()
|
|
|
|
def merge(self, fdt_obj, replace: bool = True):
|
|
"""
|
|
Merge external FDT object into this object.
|
|
|
|
:param fdt_obj: The FDT object which will be merged into this
|
|
:param replace: True for replace existing items or False for keep old items
|
|
"""
|
|
assert isinstance(fdt_obj, FDT)
|
|
if self.header.version is None:
|
|
self.header = fdt_obj.header
|
|
else:
|
|
if fdt_obj.header.version is not None and \
|
|
fdt_obj.header.version > self.header.version:
|
|
self.header.version = fdt_obj.header.version
|
|
if fdt_obj.entries:
|
|
for in_entry in fdt_obj.entries:
|
|
exist = False
|
|
for index in range(len(self.entries)):
|
|
if self.entries[index]['address'] == in_entry['address']:
|
|
self.entries[index]['size'] = in_entry['size']
|
|
exist = True
|
|
break
|
|
if not exist:
|
|
self.entries.append(in_entry)
|
|
|
|
self.root.merge(fdt_obj.get_node('/'), replace)
|
|
|
|
def update_phandles(self):
|
|
all_nodes = []
|
|
no_phandle_nodes = []
|
|
|
|
node = self.root
|
|
all_nodes += self.root.nodes
|
|
while all_nodes:
|
|
props = (node.get_property('phandle'), node.get_property('linux,phandle'))
|
|
value = None
|
|
for i, p in enumerate(props):
|
|
if isinstance(p, PropWords) and isinstance(p.value, int):
|
|
value = None if i == 1 and p.value != value else p.value
|
|
if value is None:
|
|
no_phandle_nodes.append(node)
|
|
# ...
|
|
node = all_nodes.pop()
|
|
all_nodes += node.nodes
|
|
|
|
for node in no_phandle_nodes:
|
|
if node.name != '/':
|
|
if node.path == '/':
|
|
phandle_value = self.add_label(node.name)
|
|
else:
|
|
phandle_value = self.add_label(node.path)
|
|
node.set_property('linux,phandle', phandle_value)
|
|
node.set_property('phandle', phandle_value)
|
|
|
|
|
|
def to_dts(self, tabsize: int = 4) -> str:
|
|
"""
|
|
Store FDT Object into string format (DTS)
|
|
|
|
:param tabsize:
|
|
"""
|
|
result = "/dts-v1/;\n"
|
|
if self.header.version is not None:
|
|
result += "// version: {}\n".format(self.header.version)
|
|
result += "// last_comp_version: {}\n".format(self.header.last_comp_version)
|
|
if self.header.version >= 2:
|
|
result += "// boot_cpuid_phys: 0x{:X}\n".format(self.header.boot_cpuid_phys)
|
|
result += '\n'
|
|
if self.entries:
|
|
for entry in self.entries:
|
|
result += "/memreserve/ "
|
|
result += "{:#x} ".format(entry['address']) if entry['address'] else "0 "
|
|
result += "{:#x}".format(entry['size']) if entry['size'] else "0"
|
|
result += ";\n"
|
|
if self.root is not None:
|
|
result += self.root.to_dts(tabsize)
|
|
return result
|
|
|
|
def to_dtb(self, version: int = None, last_comp_version: int = None, boot_cpuid_phys: int = None, strings: str = None) -> bytes:
|
|
"""
|
|
Export FDT Object into Binary Blob format (DTB)
|
|
|
|
:param version:
|
|
:param last_comp_version:
|
|
:param boot_cpuid_phys:
|
|
:param strings:
|
|
|
|
The strings param is useful (only) when manipulating a signed itb or dtb. The signature includes
|
|
the strings buffer in the dtb _in order_. C executables write the strings out in a surprising order.
|
|
The argument is used as an initial version of the strings buffer, so that all strings in the input
|
|
file are included (and in the same order) in the output file. Usage:
|
|
|
|
# Read and parse dtb
|
|
with open(input, 'rb') as file:
|
|
data = file.read()
|
|
dtree = fdt.parse_dtb(data)
|
|
|
|
# Read strings buffer (Assumes version >= 3)
|
|
strings_start = dtree.header.off_dt_strings
|
|
strings_end = strings_start + dtree.header.size_dt_strings
|
|
strings = data[strings_start:strings_end].decode("ascii")
|
|
|
|
# Serialize dtb and write to output
|
|
data = dtree.to_dtb(strings=strings)
|
|
with open(output, 'wb') as file:
|
|
file.write(data)
|
|
|
|
"""
|
|
if self.root is None:
|
|
return b''
|
|
|
|
from struct import pack
|
|
|
|
if version is not None:
|
|
self.header.version = version
|
|
if last_comp_version is not None:
|
|
self.header.last_comp_version = last_comp_version
|
|
if boot_cpuid_phys is not None:
|
|
self.header.boot_cpuid_phys = boot_cpuid_phys
|
|
if self.header.version is None:
|
|
raise Exception("DTB Version must be specified !")
|
|
if strings is None:
|
|
strings = ''
|
|
|
|
blob_entries = bytes()
|
|
if self.entries:
|
|
for entry in self.entries:
|
|
blob_entries += pack('>QQ', entry['address'], entry['size'])
|
|
blob_entries += pack('>QQ', 0, 0)
|
|
blob_data_start = self.header.size + len(blob_entries)
|
|
(blob_data, blob_strings, data_pos) = self.root.to_dtb(strings, blob_data_start, self.header.version)
|
|
blob_data += pack('>I', DTB_END)
|
|
self.header.size_dt_strings = len(blob_strings)
|
|
self.header.size_dt_struct = len(blob_data)
|
|
self.header.off_mem_rsvmap = self.header.size
|
|
self.header.off_dt_struct = blob_data_start
|
|
self.header.off_dt_strings = blob_data_start + len(blob_data)
|
|
self.header.total_size = blob_data_start + len(blob_data) + len(blob_strings)
|
|
blob_header = self.header.export()
|
|
return blob_header + blob_entries + blob_data + blob_strings.encode('ascii')
|
|
|
|
|
|
def parse_dts(text: str, root_dir: str = '') -> FDT:
|
|
"""
|
|
Parse DTS text file and create FDT Object
|
|
|
|
:param text:
|
|
:param root_dir:
|
|
"""
|
|
ver = get_version_info(text)
|
|
text = strip_comments(text)
|
|
dts_lines = split_to_lines(text)
|
|
fdt_obj = FDT()
|
|
if 'version' in ver:
|
|
fdt_obj.header.version = ver['version']
|
|
if 'last_comp_version' in ver:
|
|
fdt_obj.header.last_comp_version = ver['last_comp_version']
|
|
if 'boot_cpuid_phys' in ver:
|
|
fdt_obj.header.boot_cpuid_phys = ver['boot_cpuid_phys']
|
|
# parse entries
|
|
fdt_obj.entries = []
|
|
for line in dts_lines:
|
|
if line.endswith('{'):
|
|
break
|
|
if line.startswith('/memreserve/'):
|
|
line = line.strip(';')
|
|
line = line.split()
|
|
if len(line) != 3 :
|
|
raise Exception()
|
|
fdt_obj.entries.append({'address': int(line[1], 0), 'size': int(line[2], 0)})
|
|
# parse nodes
|
|
curnode = None
|
|
fdt_obj.root = None
|
|
for line in dts_lines:
|
|
if line.endswith('{'):
|
|
# start node
|
|
if ':' in line: #indicates the present of a label
|
|
label, rest = line.split(':')
|
|
node_name = rest.split()[0]
|
|
new_node = Node(node_name)
|
|
new_node.set_label(label)
|
|
|
|
|
|
else:
|
|
node_name = line.split()[0]
|
|
new_node = Node(node_name)
|
|
if fdt_obj.root is None:
|
|
fdt_obj.root = new_node
|
|
if curnode is not None:
|
|
curnode.append(new_node)
|
|
curnode = new_node
|
|
elif line.endswith('}'):
|
|
# end node
|
|
if curnode is not None:
|
|
if curnode.get_property('phandle') is None:
|
|
if curnode.label is not None:
|
|
handle = fdt_obj.add_label(curnode.label)
|
|
curnode.set_property('phandle', handle)
|
|
curnode = curnode.parent
|
|
else:
|
|
# properties
|
|
if line.find('=') == -1:
|
|
prop_name = line
|
|
prop_obj = Property(prop_name)
|
|
else:
|
|
line = line.split('=', maxsplit=1)
|
|
prop_name = line[0].rstrip(' ')
|
|
prop_value = line[1].lstrip(' ')
|
|
if prop_value.startswith('<'):
|
|
prop_obj = PropWords(prop_name)
|
|
prop_value = prop_value.replace('<', '').replace('>', '')
|
|
# ['interrupts ' = ' <0 5 4>, <0 6 4>']
|
|
# just change ',' to ' ' -- to concatenate the values into single array
|
|
if ',' in prop_value:
|
|
prop_value = prop_value.replace(',', ' ')
|
|
|
|
# keep the orginal references for phandles as a phantom
|
|
# property
|
|
if "&" in prop_value:
|
|
phantom_obj = PropStrings(prop_name+'_with_references')
|
|
phantom_obj.append(line[1].lstrip(' '))
|
|
if curnode is not None:
|
|
curnode.append(phantom_obj)
|
|
for prop in prop_value.split():
|
|
if prop.startswith('0x'):
|
|
prop_obj.append(int(prop, 16))
|
|
elif prop.startswith('0b'):
|
|
prop_obj.append(int(prop, 2))
|
|
elif prop.startswith('0'):
|
|
prop_obj.append(int(prop, 8))
|
|
elif prop.startswith('&'):
|
|
prop_obj.append(fdt_obj.add_label(prop[1:]))
|
|
else:
|
|
prop_obj.append(int(prop))
|
|
elif prop_value.startswith('['):
|
|
prop_obj = PropBytes(prop_name)
|
|
prop_value = prop_value.replace('[', '').replace(']', '')
|
|
for prop in prop_value.split():
|
|
prop_obj.append(int(prop, 16))
|
|
elif prop_value.startswith('/incbin/'):
|
|
prop_value = prop_value.replace('/incbin/("', '').replace('")', '')
|
|
prop_value = prop_value.split(',')
|
|
file_path = os.path.join(root_dir, prop_value[0].strip())
|
|
file_offset = int(prop_value.strip(), 0) if len(prop_value) > 1 else 0
|
|
file_size = int(prop_value.strip(), 0) if len(prop_value) > 2 else 0
|
|
if file_path is None or not os.path.exists(file_path):
|
|
raise Exception("File path doesn't exist: {}".format(file_path))
|
|
with open(file_path, "rb") as f:
|
|
f.seek(file_offset)
|
|
prop_data = f.read(file_size) if file_size > 0 else f.read()
|
|
prop_obj = PropIncBin(prop_name, prop_data, os.path.split(file_path)[1])
|
|
elif prop_value.startswith('/plugin/'):
|
|
raise NotImplementedError("Not implemented property value: /plugin/")
|
|
elif prop_value.startswith('/bits/'):
|
|
raise NotImplementedError("Not implemented property value: /bits/")
|
|
else:
|
|
prop_obj = PropStrings(prop_name)
|
|
expect_open = True
|
|
in_prop = False
|
|
prop = ''
|
|
for c in prop_value:
|
|
if c == '"' and not in_prop and expect_open:
|
|
prop = ''
|
|
in_prop = True
|
|
elif c == '"' and in_prop:
|
|
if not len(prop) > 0:
|
|
raise ValueError('Empty string')
|
|
prop_obj.append(prop)
|
|
in_prop = False
|
|
expect_open = False
|
|
elif in_prop:
|
|
prop += c
|
|
elif c == ',' and not expect_open:
|
|
expect_open = True
|
|
elif c == ' ':
|
|
continue
|
|
else:
|
|
raise ValueError(f'Invalid char: {c}')
|
|
|
|
if expect_open:
|
|
raise ValueError('Expected string after ,')
|
|
if curnode is not None:
|
|
curnode.append(prop_obj)
|
|
|
|
return fdt_obj
|
|
|
|
|
|
def parse_dtb(data: bytes, offset: int = 0) -> FDT:
|
|
"""
|
|
Parse FDT Binary Blob and create FDT Object
|
|
|
|
:param data: FDT Binary Blob in bytes
|
|
:param offset: The offset of input data
|
|
"""
|
|
assert isinstance(data, (bytes, bytearray)), "Invalid argument type"
|
|
|
|
from struct import unpack_from
|
|
|
|
fdt_obj = FDT()
|
|
# parse header
|
|
fdt_obj.header = Header.parse(data)
|
|
# parse entries
|
|
index = fdt_obj.header.off_mem_rsvmap
|
|
while True:
|
|
entrie = dict(zip(('address', 'size'), unpack_from(">QQ", data, offset + index)))
|
|
index += 16
|
|
if entrie['address'] == 0 and entrie['size'] == 0:
|
|
break
|
|
fdt_obj.entries.append(entrie)
|
|
# parse nodes
|
|
current_node = None
|
|
fdt_obj.root = None
|
|
index = fdt_obj.header.off_dt_struct
|
|
while True:
|
|
if len(data) < (offset + index + 4):
|
|
raise Exception("Index out of range !")
|
|
tag = unpack_from(">I", data, offset + index)[0]
|
|
index += 4
|
|
if tag == DTB_BEGIN_NODE:
|
|
node_name = extract_string(data, offset + index)
|
|
index = ((index + len(node_name) + 4) & ~3)
|
|
if not node_name: node_name = '/'
|
|
new_node = Node(node_name)
|
|
if fdt_obj.root is None:
|
|
fdt_obj.root = new_node
|
|
if current_node is not None:
|
|
current_node.append(new_node)
|
|
current_node = new_node
|
|
elif tag == DTB_END_NODE:
|
|
if current_node is not None:
|
|
current_node = current_node.parent
|
|
elif tag == DTB_PROP:
|
|
prop_size, prop_string_pos, = unpack_from(">II", data, offset + index)
|
|
prop_start = index + 8
|
|
if fdt_obj.header.version < 16 and prop_size >= 8:
|
|
prop_start = ((prop_start + 7) & ~0x7)
|
|
prop_name = extract_string(data, fdt_obj.header.off_dt_strings + prop_string_pos)
|
|
prop_raw_value = data[offset + prop_start : offset + prop_start + prop_size]
|
|
index = prop_start + prop_size
|
|
index = ((index + 3) & ~0x3)
|
|
if current_node is not None:
|
|
current_node.append(new_property(prop_name, prop_raw_value))
|
|
elif tag == DTB_END:
|
|
break
|
|
else:
|
|
raise Exception("Unknown Tag: {}".format(tag))
|
|
|
|
return fdt_obj
|
|
|
|
|
|
def diff(fdt1: FDT, fdt2: FDT) -> tuple:
|
|
"""
|
|
Compare two flattened device tree objects and return list of 3 objects (same in 1 and 2, specific for 1, specific for 2)
|
|
|
|
:param fdt1: The object 1 of FDT
|
|
:param fdt2: The object 2 of FDT
|
|
"""
|
|
assert isinstance(fdt1, FDT), "Invalid argument type"
|
|
assert isinstance(fdt2, FDT), "Invalid argument type"
|
|
|
|
fdt_a = FDT(fdt1.header)
|
|
fdt_b = FDT(fdt2.header)
|
|
|
|
if fdt1.header.version is not None and fdt2.header.version is not None:
|
|
fdt_same = FDT(fdt1.header if fdt1.header.version > fdt2.header.version else fdt2.header)
|
|
else:
|
|
fdt_same = FDT(fdt1.header)
|
|
|
|
if fdt1.entries and fdt2.entries:
|
|
for entry_a in fdt1.entries:
|
|
for entry_b in fdt2.entries:
|
|
if entry_a['address'] == entry_b['address'] and entry_a['size'] == entry_b['size']:
|
|
fdt_same.entries.append(entry_a)
|
|
break
|
|
|
|
for entry_a in fdt1.entries:
|
|
found = False
|
|
for entry_s in fdt_same.entries:
|
|
if entry_a['address'] == entry_s['address'] and entry_a['size'] == entry_s['size']:
|
|
found = True
|
|
break
|
|
if not found:
|
|
fdt_a.entries.append(entry_a)
|
|
|
|
for entry_b in fdt2.entries:
|
|
found = False
|
|
for entry_s in fdt_same.entries:
|
|
if entry_b['address'] == entry_s['address'] and entry_b['size'] == entry_s['size']:
|
|
found = True
|
|
break
|
|
if not found:
|
|
fdt_b.entries.append(entry_b)
|
|
|
|
for path, nodes, props in fdt1.walk():
|
|
try:
|
|
rnode = fdt2.get_node(path)
|
|
except:
|
|
rnode = None
|
|
|
|
for node_b in nodes:
|
|
if rnode is None or rnode.get_subnode(node_b.name) is None:
|
|
fdt_a.add_item(Node(node_b.name), path)
|
|
else:
|
|
fdt_same.add_item(Node(node_b.name), path)
|
|
|
|
for prop_a in props:
|
|
if rnode is not None and prop_a == rnode.get_property(prop_a.name):
|
|
fdt_same.add_item(prop_a.copy(), path)
|
|
else:
|
|
fdt_a.add_item(prop_a.copy(), path)
|
|
|
|
for path, nodes, props in fdt2.walk():
|
|
try:
|
|
rnode = fdt_same.get_node(path)
|
|
except:
|
|
rnode = None
|
|
|
|
for node_b in nodes:
|
|
if rnode is None or rnode.get_subnode(node_b.name) is None:
|
|
fdt_b.add_item(Node(node_b.name), path)
|
|
|
|
for prop_b in props:
|
|
if rnode is None or prop_b != rnode.get_property(prop_b.name):
|
|
fdt_b.add_item(prop_b.copy(), path)
|
|
|
|
return fdt_same, fdt_a, fdt_b
|