summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLeandro Pereira <leandro.pereira@intel.com>2016-12-08 12:55:45 -0800
committerAnas Nashif <nashif@linux.intel.com>2017-01-30 20:51:21 +0000
commitca42e85bcd9b0311058923a2c3b74d778e79d8b9 (patch)
treef5cb24e2509dd9820d83f0b248b152757d346f9d
parentd159c8e4763813c304324ad9843a4d4d0f08bc9b (diff)
scripts: Add device tree parser script
As decided during the Zephyr Mini Summit in December 2016, we're going to use the same Device Tree format as used by the Linux kernel to store device configuration from vendors. This is a parser written during the summit that will parse *.dts files, and will be used as part of the solution to read in the files provided by manufacturers and generate board configuration files. The script will also optionally generate files in the DOT language so that DTS files can be graphed using the Graphviz suite. Change-Id: I6e2b7a64a6dcc349b2888332a660b4700af6cd63 Signed-off-by: Leandro Pereira <leandro.pereira@intel.com>
-rwxr-xr-xscripts/devicetree.py290
1 files changed, 290 insertions, 0 deletions
diff --git a/scripts/devicetree.py b/scripts/devicetree.py
new file mode 100755
index 000000000..c74dc90ff
--- /dev/null
+++ b/scripts/devicetree.py
@@ -0,0 +1,290 @@
+#!/usr/bin/python
+#
+# 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
+
+ return label, name.strip(), 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
+ return '%s@%x' % (name, addr)
+
+def parse_node(line, fd):
+ label, name, addr = parse_node_name(line)
+
+ node = {
+ 'label': label,
+ 'type': type,
+ 'addr': 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))