Device tree nodes have a dependency on their parents, as well as other nodes. To ensure driver instances are initialized in the proper we need to identify these dependencies and construct a total order on nodes that ensures no node is initialized before something it depends on. Add a Graph class that calculates a partial order on nodes, taking into account the potential for cycles in the dependency graph. When generating devicetree value headers calculate the dependency graph and document the order and dependencies in the derived files. In the future these dependencies may be expressed in binding data. Signed-off-by: Peter A. Bigot <pab@pabigot.com>
143 lines
5.2 KiB
Python
143 lines
5.2 KiB
Python
# Copyright 2009-2013, 2019 Peter A. Bigot
|
|
#
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
|
|
# This implementation is derived from the one in
|
|
# [PyXB](https://github.com/pabigot/pyxb), stripped down and modified
|
|
# specifically to manage edtlib Node instances.
|
|
|
|
import collections
|
|
|
|
from operator import attrgetter
|
|
|
|
class Graph:
|
|
"""
|
|
Represent a directed graph with edtlib Node objects as nodes.
|
|
|
|
This is used to determine order dependencies among nodes in a
|
|
devicetree. An edge from C{source} to C{target} indicates that
|
|
some aspect of C{source} requires that some aspect of C{target}
|
|
already be available.
|
|
"""
|
|
|
|
def __init__ (self, root=None):
|
|
self.__roots = None
|
|
if root is not None:
|
|
self.__roots = {root}
|
|
self.__edge_map = collections.defaultdict(set)
|
|
self.__reverse_map = collections.defaultdict(set)
|
|
self.__nodes = set()
|
|
|
|
def add_edge (self, source, target):
|
|
"""
|
|
Add a directed edge from the C{source} to the C{target}.
|
|
|
|
The nodes are added to the graph if necessary.
|
|
"""
|
|
self.__edge_map[source].add(target)
|
|
if source != target:
|
|
self.__reverse_map[target].add(source)
|
|
self.__nodes.add(source)
|
|
self.__nodes.add(target)
|
|
|
|
def roots (self):
|
|
"""
|
|
Return the set of nodes calculated to be roots (i.e., those
|
|
that have no incoming edges).
|
|
|
|
This caches the roots calculated in a previous invocation.
|
|
|
|
@rtype: C{set}
|
|
"""
|
|
if not self.__roots:
|
|
self.__roots = set()
|
|
for n in self.__nodes:
|
|
if n not in self.__reverse_map:
|
|
self.__roots.add(n)
|
|
return self.__roots
|
|
|
|
def _tarjan (self):
|
|
# Execute Tarjan's algorithm on the graph.
|
|
#
|
|
# Tarjan's algorithm
|
|
# (http://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm)
|
|
# computes the strongly-connected components
|
|
# (http://en.wikipedia.org/wiki/Strongly_connected_component)
|
|
# of the graph: i.e., the sets of nodes that form a minimal
|
|
# closed set under edge transition. In essence, the loops.
|
|
# We use this to detect groups of components that have a
|
|
# dependency cycle, and to impose a total order on components
|
|
# based on dependencies.
|
|
|
|
self.__stack = []
|
|
self.__scc_order = []
|
|
self.__index = 0
|
|
self.__tarjan_index = {}
|
|
self.__tarjan_low_link = {}
|
|
for v in self.__nodes:
|
|
self.__tarjan_index[v] = None
|
|
roots = self.roots()
|
|
if self.__nodes and not roots:
|
|
raise Exception('TARJAN: No roots found in graph with {} nodes'.format(len(self.__nodes)))
|
|
for r in sorted(roots, key=attrgetter('path')):
|
|
self._tarjan_root(r)
|
|
|
|
# Assign ordinals for edtlib
|
|
ordinal = 0
|
|
for scc in self.__scc_order:
|
|
# Zephyr customization: devicetree Node graphs should have
|
|
# no loops, so all SCCs should be singletons. That may
|
|
# change in the future, but for now we only give an
|
|
# ordinal to singletons.
|
|
if len(scc) == 1:
|
|
scc[0].dep_ordinal = ordinal
|
|
ordinal += 1
|
|
|
|
def _tarjan_root (self, v):
|
|
# Do the work of Tarjan's algorithm for a given root node.
|
|
|
|
if self.__tarjan_index.get(v) is not None:
|
|
# "Root" was already reached.
|
|
return
|
|
self.__tarjan_index[v] = self.__tarjan_low_link[v] = self.__index
|
|
self.__index += 1
|
|
self.__stack.append(v)
|
|
source = v
|
|
for target in sorted(self.__edge_map[source], key=attrgetter('path')):
|
|
if self.__tarjan_index[target] is None:
|
|
self._tarjan_root(target)
|
|
self.__tarjan_low_link[v] = min(self.__tarjan_low_link[v], self.__tarjan_low_link[target])
|
|
elif target in self.__stack:
|
|
self.__tarjan_low_link[v] = min(self.__tarjan_low_link[v], self.__tarjan_low_link[target])
|
|
|
|
if self.__tarjan_low_link[v] == self.__tarjan_index[v]:
|
|
scc = []
|
|
while True:
|
|
scc.append(self.__stack.pop())
|
|
if v == scc[-1]:
|
|
break
|
|
self.__scc_order.append(scc)
|
|
|
|
def scc_order (self):
|
|
"""Return the strongly-connected components in order.
|
|
|
|
The data structure is a list, in dependency order, of strongly
|
|
connected components (which can be single nodes). Appearance
|
|
of a node in a set earlier in the list indicates that it has
|
|
no dependencies on any node that appears in a subsequent set.
|
|
This order is preferred over a depth-first-search order for
|
|
code generation, since it detects loops.
|
|
"""
|
|
if not self.__scc_order:
|
|
self._tarjan()
|
|
return self.__scc_order
|
|
__scc_order = None
|
|
|
|
def depends_on (self, node):
|
|
"""Get the nodes that 'node' directly depends on."""
|
|
return sorted(self.__edge_map[node], key=attrgetter('path'))
|
|
|
|
def required_by (self, node):
|
|
"""Get the nodes that directly depend on 'node'."""
|
|
return sorted(self.__reverse_map[node], key=attrgetter('path'))
|