zephyr/scripts/dts/python-devicetree/src/devicetree/grutils.py
Gerard Marull-Paretas 7772cec6bd edtlib: always insert root node to the graph
When we have an empty Devicetree, ie,

```
/dts-v1/;

/ {

};
```

The node's dep_ordinal is never initialized because the node graph is
empty. This ends up with invalid ordinal tokens (-1) in
devicetree_generated.h which in turn produce some cryptic compiler
errors, see e.g.

```
error: pasting "dts_ord_" and "-" does not give a valid preprocessing
token
   95 | #define Z_DEVICE_DT_DEV_ID(node_id) _CONCAT(dts_ord_,
      DT_DEP_ORD(node_id))

...

include/zephyr/devicetree.h:2498:41:
note: in expansion of macro 'DT_FOREACH_OKAY_HELPER'
 2498 | #define DT_FOREACH_STATUS_OKAY_NODE(fn)
      DT_FOREACH_OKAY_HELPER(fn)
            |
	    ^~~~~~~~~~~~~~~~~~~~~~
include/zephyr/device.h:1022:1:
note: in expansion of macro 'DT_FOREACH_STATUS_OKAY_NODE'
     1022 |
	  DT_FOREACH_STATUS_OKAY_NODE(Z_MAYBE_DEVICE_DECLARE_INTERNAL)

```

(devicetree_generated.h)

```
...
 #define DT_N_ORD -1
 #define DT_N_ORD_STR_SORTABLE 000-1
...
```

This patch makes sure root node is always inserted (without any target)
so that it gets initialized later.

Discovered as part of
https://github.com/zephyrproject-rtos/zephyr/pull/63696

Signed-off-by: Gerard Marull-Paretas <gerard@teslabs.com>
2023-10-11 18:28:01 +03:00

168 lines
5.8 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
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_node(self, node):
"""
Add a node without any target to the graph.
"""
self.__nodes.add(node)
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 = sorted(self.roots(), key=node_key)
if self.__nodes and not roots:
raise Exception('TARJAN: No roots found in graph with {} nodes'.format(len(self.__nodes)))
for r in roots:
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=node_key):
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=node_key)
def required_by(self, node):
"""Get the nodes that directly depend on 'node'."""
return sorted(self.__reverse_map[node], key=node_key)
def node_key(node):
# This sort key ensures that sibling nodes with the same name will
# use unit addresses as tiebreakers. That in turn ensures ordinals
# for otherwise indistinguishable siblings are in increasing order
# by unit address, which is convenient for displaying output.
if node.parent:
parent_path = node.parent.path
else:
parent_path = '/'
if node.unit_addr is not None:
name = node.name.rsplit('@', 1)[0]
unit_addr = node.unit_addr
else:
name = node.name
unit_addr = -1
return (parent_path, name, unit_addr)