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))
/**
* @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
*

View File

@ -720,6 +720,9 @@ def write_dep_info(node: edtlib.Node) -> None:
else:
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_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}")

View File

@ -72,6 +72,8 @@ from copy import deepcopy
from dataclasses import dataclass
from typing import (Any, Callable, Iterable, NoReturn,
Optional, TYPE_CHECKING, Union)
import base64
import hashlib
import logging
import os
import re
@ -90,6 +92,12 @@ from devicetree.dtlib import Property as dtlib_Property
from devicetree.grutils import Graph
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
#
@ -912,6 +920,11 @@ class Node:
The ordinal is defined for all Nodes, and is unique among nodes in its
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:
A list with the nodes that directly depend on the node
@ -1027,6 +1040,7 @@ class Node:
self.interrupts: list[ControllerAndData] = []
self.pinctrls: list[PinCtrl] = []
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_regs()
@ -2268,10 +2282,18 @@ class EDT:
# Creates a list of edtlib.Node objects from the dtlib.Node objects, in
# self.nodes
hash2node: dict[str, Node] = {}
for dt_node in self._dt.node_iter():
# Warning: We depend on parent Nodes being created before their
# children. This is guaranteed by node_iter().
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._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, "");
}
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)
{
zassert_equal(DT_NODE_EXISTS(DT_INST(0, vnd_gpio_device)), 1, "");