edtlib: add "hash" attribute to nodes

Add a new "hash" attribute to all Devicetree EDT nodes. The hash is
calculated on the full path of the node; this means that its value
remains stable across rebuilds.
The hash is checked for uniqueness among nodes in the same EDT.

This computed token is then added to `devicetree_generated.h` and made
accessible to Zephyr code via a new DT_NODE_HASH(node_id) macro.

Signed-off-by: Luca Burelli <l.burelli@arduino.cc>
This commit is contained in:
Luca Burelli 2025-01-09 11:45:23 +01:00 committed by Benjamin Cabé
parent d1d85fa40b
commit 16d71d0598
4 changed files with 45 additions and 0 deletions

View file

@ -242,6 +242,16 @@
*/ */
#define DT_HAS_ALIAS(alias_name) DT_NODE_EXISTS(DT_ALIAS(alias_name)) #define DT_HAS_ALIAS(alias_name) DT_NODE_EXISTS(DT_ALIAS(alias_name))
/**
* @brief Get the hash associated with a DT node
*
* Get the hash for the specified node_id. The hash is calculated on the
* full devicetree path of the node.
* @param node_id node identifier
* @return hash value as a preprocessor token
*/
#define DT_NODE_HASH(node_id) DT_CAT(node_id, _HASH)
/** /**
* @brief Get a node identifier for an instance of a compatible * @brief Get a node identifier for an instance of a compatible
* *

View file

@ -720,6 +720,9 @@ def write_dep_info(node: edtlib.Node) -> None:
else: else:
return "/* nothing */" return "/* nothing */"
out_comment("Node's hash:")
out_dt_define(f"{node.z_path_id}_HASH", node.hash)
out_comment("Node's dependency ordinal:") out_comment("Node's dependency ordinal:")
out_dt_define(f"{node.z_path_id}_ORD", node.dep_ordinal) out_dt_define(f"{node.z_path_id}_ORD", node.dep_ordinal)
out_dt_define(f"{node.z_path_id}_ORD_STR_SORTABLE", f"{node.dep_ordinal:0>5}") out_dt_define(f"{node.z_path_id}_ORD_STR_SORTABLE", f"{node.dep_ordinal:0>5}")

View file

@ -72,6 +72,8 @@ from copy import deepcopy
from dataclasses import dataclass from dataclasses import dataclass
from typing import (Any, Callable, Iterable, NoReturn, from typing import (Any, Callable, Iterable, NoReturn,
Optional, TYPE_CHECKING, Union) Optional, TYPE_CHECKING, Union)
import base64
import hashlib
import logging import logging
import os import os
import re import re
@ -90,6 +92,12 @@ from devicetree.dtlib import Property as dtlib_Property
from devicetree.grutils import Graph from devicetree.grutils import Graph
from devicetree._private import _slice_helper from devicetree._private import _slice_helper
def _compute_hash(path: str) -> str:
# Calculates the hash associated with the node's full path.
hasher = hashlib.sha256()
hasher.update(path.encode())
return base64.b64encode(hasher.digest(), altchars=b'__').decode().rstrip('=')
# #
# Public classes # Public classes
# #
@ -912,6 +920,11 @@ class Node:
The ordinal is defined for all Nodes, and is unique among nodes in its The ordinal is defined for all Nodes, and is unique among nodes in its
EDT 'nodes' list. EDT 'nodes' list.
hash:
A hashed value of the devicetree path of the node. This is defined for
all Nodes, and is checked for uniqueness among nodes in its EDT 'nodes'
list.
required_by: required_by:
A list with the nodes that directly depend on the node A list with the nodes that directly depend on the node
@ -1027,6 +1040,7 @@ class Node:
self.interrupts: list[ControllerAndData] = [] self.interrupts: list[ControllerAndData] = []
self.pinctrls: list[PinCtrl] = [] self.pinctrls: list[PinCtrl] = []
self.bus_node = self._bus_node(support_fixed_partitions_on_any_bus) self.bus_node = self._bus_node(support_fixed_partitions_on_any_bus)
self.hash: str = _compute_hash(dt_node.path)
self._init_binding() self._init_binding()
self._init_regs() self._init_regs()
@ -2268,10 +2282,18 @@ class EDT:
# Creates a list of edtlib.Node objects from the dtlib.Node objects, in # Creates a list of edtlib.Node objects from the dtlib.Node objects, in
# self.nodes # self.nodes
hash2node: dict[str, Node] = {}
for dt_node in self._dt.node_iter(): for dt_node in self._dt.node_iter():
# Warning: We depend on parent Nodes being created before their # Warning: We depend on parent Nodes being created before their
# children. This is guaranteed by node_iter(). # children. This is guaranteed by node_iter().
node = Node(dt_node, self, self._fixed_partitions_no_bus) node = Node(dt_node, self, self._fixed_partitions_no_bus)
if node.hash in hash2node:
_err(f"hash collision between '{node.path}' and "
f"'{hash2node[node.hash].path}'")
hash2node[node.hash] = node
self.nodes.append(node) self.nodes.append(node)
self._node2enode[dt_node] = node self._node2enode[dt_node] = node

View file

@ -338,6 +338,16 @@ ZTEST(devicetree_api, test_has_alias)
zassert_equal(DT_NODE_HAS_STATUS(DT_ALIAS(test_undef), okay), 0, ""); zassert_equal(DT_NODE_HAS_STATUS(DT_ALIAS(test_undef), okay), 0, "");
} }
ZTEST(devicetree_api, test_node_hashes)
{
zassert_str_equal(TO_STRING(DT_NODE_HASH(DT_ROOT)),
"il7asoJjJEMhngUeSt4tHVu8Zxx4EFG_FDeJfL3_oPE");
zassert_str_equal(TO_STRING(DT_NODE_HASH(TEST_DEADBEEF)),
"kPPqtBX5DX_QDQMO0_cOls2ebJMevAWHhAPY1JCKTyU");
zassert_str_equal(TO_STRING(DT_NODE_HASH(TEST_ABCD1234)),
"Bk4fvF6o3Mgslz_xiIZaJcuwo6_IeelozwOaxtUsSos");
}
ZTEST(devicetree_api, test_inst_checks) ZTEST(devicetree_api, test_inst_checks)
{ {
zassert_equal(DT_NODE_EXISTS(DT_INST(0, vnd_gpio_device)), 1, ""); zassert_equal(DT_NODE_EXISTS(DT_INST(0, vnd_gpio_device)), 1, "");