#!/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))