zephyr/scripts/devicetree.py

294 lines
7.1 KiB
Python
Executable File

#!/usr/bin/env python3
#
# Copyright (c) 2017 Intel Corporation
#
# 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 sys
import pprint
def read_until(line, fd, end):
out = [line]
while True:
idx = line.find(end)
if idx < 0:
line = clean_line(fd.readline(), fd)
out.append(line)
else:
out.append(line[idx + len(end):])
return out
def remove_comment(line, fd):
out = []
while True:
idx = line.find('/*')
if idx < 0:
idx = line.find('//')
if idx < 0:
out.append(line)
else:
out.append(line[:idx])
return ' '.join(out)
out.append(line[:idx])
line = read_until(line[idx:], fd, '*/')[-1]
def clean_line(line, fd):
return remove_comment(line, fd).strip()
def parse_node_name(line):
line = line[:-1]
if '@' in line:
line, addr = line.split('@')
else:
addr = None
if ':' in line:
label, name = line.split(':')
else:
name = line
label = None
if addr is None:
return label, name.strip(), None, None
return label, name.strip(), addr, int(addr,16)
def parse_values_internal(value, start, end, separator):
out = []
inside = False
accum = []
for ch in value:
if not inside:
if ch == start:
inside = True
accum = []
else:
if ch == end:
inside = False
out.append(''.join(accum))
accum = []
else:
accum.append(ch)
if separator == ' ':
out = [v.split() for v in out]
if len(out) == 1:
return parse_value(out[0])
return [parse_value(v) for v in out]
def parse_values(value, start, end, separator):
out = parse_values_internal(value, start, end, separator)
if isinstance(out, list) and all(isinstance(v, str) and len(v) == 1 and not v.isalpha() for v in out):
return bytearray(out)
return out
def parse_value(value):
if value == '':
return value
if isinstance(value, list):
out = [parse_value(v) for v in value]
return out[0] if len(out) == 1 else out
if value[0] == '<':
return parse_values(value, '<', '>', ' ')
if value[0] == '"':
return parse_values(value, '"', '"', ',')
if value[0] == '[':
return parse_values(value, '[', ']', ' ')
if value[0] == '&':
return {'ref': value[1:]}
if value[0].isdigit():
if value.startswith("0x"):
return int(value, 16)
if value[0] == '0':
return int(value, 8)
return int(value, 10)
return value
def parse_property(property, fd):
if '=' in property:
key, value = property.split('=', 1)
value = ' '.join(read_until(value, fd, ';')).strip()
if not value.endswith(';'):
raise SyntaxError("parse_property: missing semicolon: %s" % value)
return key.strip(), parse_value(value[:-1])
property = property.strip()
if not property.endswith(';'):
raise SyntaxError("parse_property: missing semicolon: %s" % property)
return property[:-1].strip(), True
def build_node_name(name, addr):
if addr is None:
return name
elif isinstance(addr, int):
return '%s@%x' % (name, addr)
return '%s@%s' % (name, addr.strip())
def parse_node(line, fd):
label, name, addr, numeric_addr = parse_node_name(line)
node = {
'label': label,
'type': type,
'addr': numeric_addr,
'children': {},
'props': {},
'name': build_node_name(name, addr)
}
while True:
line = fd.readline()
if not line:
raise SyntaxError("parse_node: Missing } while parsing node")
line = clean_line(line, fd)
if not line:
continue
if line == "};":
break
if line.endswith('{'):
new_node = parse_node(line, fd)
node['children'][new_node['name']] = new_node
else:
key, value = parse_property(line, fd)
node['props'][key] = value
return node
def parse_file(fd, ignore_dts_version=False):
nodes = {}
has_v1_tag = False
while True:
line = fd.readline()
if not line:
break
line = clean_line(line, fd)
if not line:
continue
if line.startswith('/include/ '):
tag, filename = line.split()
with open(filename.strip()[1:-1], "r") as new_fd:
nodes.update(parse_file(new_fd, True))
elif line == '/dts-v1/;':
has_v1_tag = True
elif line.startswith('/memreserve/ ') and line.endswith(';'):
tag, start, end = line.split()
start = int(start, 16)
end = int(end[:-1], 16)
label = "reserved_memory_0x%x_0x%x" % (start, end)
nodes[label] = {
'type': 'memory',
'reg': [start, end],
'label': label,
'addr': start,
'name': build_node_name(name, start)
}
elif line.endswith('{'):
if not has_v1_tag and not ignore_dts_version:
raise SyntaxError("parse_file: Missing /dts-v1/ tag")
new_node = parse_node(line, fd)
nodes[new_node['name']] = new_node
else:
raise SyntaxError("parse_file: Couldn't understand the line: %s" % line)
return nodes
def dump_refs(name, value, indent=0):
spaces = ' ' * indent
out = []
if isinstance(value, dict) and 'ref' in value:
out.append('%s\"%s\" -> \"%s\";' % (spaces, name, value['ref']))
elif isinstance(value, list):
for elem in value:
out.extend(dump_refs(name, elem, indent))
return out
def dump_all_refs(name, props, indent=0):
out = []
for key, value in props.items():
out.extend(dump_refs(name, value, indent))
return out
def next_subgraph(count=[0]):
count[0] += 1
return 'subgraph cluster_%d' % count[0]
def get_dot_node_name(node):
name = node['name']
return name[1:] if name[0] == '&' else name
def dump_to_dot(nodes, indent=0, start_string='digraph devicetree', name=None):
spaces = ' ' * indent
print("%s%s {" % (spaces, start_string))
if name is not None:
print("%slabel = \"%s\";" % (spaces, name))
print("%s\"%s\";" % (spaces, name))
ref_list = []
for key, value in nodes.items():
if value.get('children'):
refs = dump_to_dot(value['children'], indent + 1, next_subgraph(), get_dot_node_name(value))
ref_list.extend(refs)
else:
print("%s\"%s\";" % (spaces, get_dot_node_name(value)))
for key, value in nodes.items():
refs = dump_all_refs(get_dot_node_name(value), value.get('props', {}), indent)
ref_list.extend(refs)
if start_string.startswith("digraph"):
print("%s%s" % (spaces, '\n'.join(ref_list)))
print("%s}" % spaces)
return ref_list
def main(args):
if len(args) == 1:
print('Usage: %s filename.dts' % args[0])
return 1
if '--dot' in args:
formatter = dump_to_dot
args.remove('--dot')
else:
formatter = lambda nodes: pprint.pprint(nodes, indent=2)
with open(args[1], "r") as fd:
formatter(parse_file(fd))
return 0
if __name__ == '__main__':
sys.exit(main(sys.argv))