Boot Linux faster!

Check our new training course

Boot Linux faster!

Check our new training course
and Creative Commons CC-BY-SA
lecture and lab materials

Bootlin logo

Elixir Cross Referencer

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