Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

devicetree: use stable identifiers for LLEXT-exported DT object names #77799

Merged
merged 3 commits into from
Jan 22, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 30 additions & 4 deletions include/zephyr/device.h
Original file line number Diff line number Diff line change
Expand Up @@ -101,9 +101,35 @@ typedef int16_t device_handle_t;
* The ordinal used in this name can be mapped to the path by
* examining zephyr/include/generated/zephyr/devicetree_generated.h.
*/
#define Z_DEVICE_DT_DEV_ID(node_id) _CONCAT(dts_ord_, DT_DEP_ORD(node_id))
#define Z_DEVICE_DT_DEP_ORD(node_id) _CONCAT(dts_ord_, DT_DEP_ORD(node_id))

#if defined(CONFIG_LLEXT_EXPORT_DEVICES)
/* Same as above, but uses the hash of the node path instead of the ordinal.
*
* The hash used in this name can be mapped to the path by
* examining zephyr/include/generated/zephyr/devicetree_generated.h.
*/
#define Z_DEVICE_DT_HASH(node_id) _CONCAT(dts_, DT_NODE_HASH(node_id))

/* By default, device identifiers are obtained using the dependency ordinal.
* When LLEXT_EXPORT_DEV_IDS_BY_HASH is defined, the main Zephyr binary exports
* DT identifiers via EXPORT_SYMBOL_NAMED as hashed versions of their paths.
* When matching extensions are built, that is what they need to look for.
*
* The ordinal or hash used in this name can be mapped to the path by
* examining zephyr/include/generated/zephyr/devicetree_generated.h.
*/
#if defined(LL_EXTENSION_BUILD) && defined(CONFIG_LLEXT_EXPORT_DEV_IDS_BY_HASH)
#define Z_DEVICE_DT_DEV_ID(node_id) Z_DEVICE_DT_HASH(node_id)
#else
#define Z_DEVICE_DT_DEV_ID(node_id) Z_DEVICE_DT_DEP_ORD(node_id)
#endif

#if defined(CONFIG_LLEXT_EXPORT_DEV_IDS_BY_HASH)
/* Export device identifiers by hash */
#define Z_DEVICE_EXPORT(node_id) \
EXPORT_SYMBOL_NAMED(DEVICE_DT_NAME_GET(node_id), \
DEVICE_NAME_GET(Z_DEVICE_DT_HASH(node_id)))
#elif defined(CONFIG_LLEXT_EXPORT_DEVICES)
/* Export device identifiers using the builtin name */
#define Z_DEVICE_EXPORT(node_id) EXPORT_SYMBOL(DEVICE_DT_NAME_GET(node_id))
#endif
Expand Down Expand Up @@ -175,8 +201,8 @@ typedef int16_t device_handle_t;
*
* This macro defines a @ref device that is automatically configured by the
* kernel during system initialization. The global device object's name as a C
* identifier is derived from the node's dependency ordinal. @ref device.name is
* set to `DEVICE_DT_NAME(node_id)`.
* identifier is derived from the node's dependency ordinal or hash.
* @ref device.name is set to `DEVICE_DT_NAME(node_id)`.
*
* The device is declared with extern visibility, so a pointer to a global
* device object can be obtained with `DEVICE_DT_GET(node_id)` from any source
Expand Down
10 changes: 10 additions & 0 deletions include/zephyr/devicetree.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
*
Expand Down
62 changes: 45 additions & 17 deletions include/zephyr/llext/symbol.h
Original file line number Diff line number Diff line change
Expand Up @@ -90,18 +90,30 @@ struct llext_symtable {
/** @cond ignore */
#ifdef LL_EXTENSION_BUILD
/* Extension build: add exported symbols to llext table */
#define Z_LL_EXTENSION_SYMBOL(x) \
#define Z_LL_EXTENSION_SYMBOL_NAMED(sym_ident, sym_name) \
static const struct llext_const_symbol \
Z_GENERIC_SECTION(".exported_sym") __used \
__llext_sym_ ## x = { \
.name = STRINGIFY(x), .addr = (const void *)&x, \
__llext_sym_ ## sym_name = { \
.name = STRINGIFY(sym_name), .addr = (const void *)&sym_ident, \
}
#else
/* No-op when not building an extension */
#define Z_LL_EXTENSION_SYMBOL(x)
#define Z_LL_EXTENSION_SYMBOL_NAMED(sym_ident, sym_name)
#endif
/** @endcond */

/**
* @brief Exports a symbol from an extension to the base image with a custom name
*
* Version of @ref LL_EXTENSION_SYMBOL that allows the user to specify a custom
* name for the exported symbol.
*
* @param sym_ident Extension symbol to export to the base image
* @param sym_name Name associated with the symbol
*/
#define LL_EXTENSION_SYMBOL_NAMED(sym_ident, sym_name) \
Z_LL_EXTENSION_SYMBOL_NAMED(sym_ident, sym_name)

/**
* @brief Exports a symbol from an extension to the base image
*
Expand All @@ -113,34 +125,50 @@ struct llext_symtable {
*
* @param x Extension symbol to export to the base image
*/
#define LL_EXTENSION_SYMBOL(x) Z_LL_EXTENSION_SYMBOL(x)
#define LL_EXTENSION_SYMBOL(x) Z_LL_EXTENSION_SYMBOL_NAMED(x, x)

/** @cond ignore */
#if defined(LL_EXTENSION_BUILD)
/* Extension build: EXPORT_SYMBOL maps to LL_EXTENSION_SYMBOL */
#define Z_EXPORT_SYMBOL(x) Z_LL_EXTENSION_SYMBOL(x)
#define Z_EXPORT_SYMBOL_NAMED(sym_ident, sym_name) \
Z_LL_EXTENSION_SYMBOL_NAMED(sym_ident, sym_name)
#elif defined(CONFIG_LLEXT_EXPORT_BUILTINS_BY_SLID)
/* SLID-enabled LLEXT application: export symbols, names in separate section */
#define Z_EXPORT_SYMBOL(x) \
#define Z_EXPORT_SYMBOL_NAMED(sym_ident, sym_name) \
static const char Z_GENERIC_SECTION("llext_exports_strtab") __used \
__llext_sym_name_ ## x[] = STRINGIFY(x); \
static const STRUCT_SECTION_ITERABLE(llext_const_symbol, \
__llext_sym_ ## x) = { \
.name = __llext_sym_name_ ## x, .addr = (const void *)&x, \
__llext_sym_name_ ## sym_name[] = STRINGIFY(sym_name); \
static const STRUCT_SECTION_ITERABLE(llext_const_symbol, \
__llext_sym_ ## sym_name) = { \
.name = __llext_sym_name_ ## sym_name, \
.addr = (const void *)&sym_ident, \
}
#elif defined(CONFIG_LLEXT)
/* LLEXT application: export symbols */
#define Z_EXPORT_SYMBOL(x) \
static const STRUCT_SECTION_ITERABLE(llext_const_symbol, \
__llext_sym_ ## x) = { \
.name = STRINGIFY(x), .addr = (const void *)&x, \
#define Z_EXPORT_SYMBOL_NAMED(sym_ident, sym_name) \
static const STRUCT_SECTION_ITERABLE(llext_const_symbol, \
__llext_sym_ ## sym_name) = { \
.name = STRINGIFY(sym_name), .addr = (const void *)&sym_ident, \
}
#else
/* No extension support in this build */
#define Z_EXPORT_SYMBOL(x)
#define Z_EXPORT_SYMBOL_NAMED(sym_ident, sym_name)
#endif
/** @endcond */

/**
* @brief Export a constant symbol from the current build with a custom name
*
* Version of @ref EXPORT_SYMBOL that allows the user to specify a custom name
* for the exported symbol.
*
* When @c CONFIG_LLEXT is not enabled, this macro is a no-op.
*
* @param sym_ident Symbol to export
* @param sym_name Name associated with the symbol
*/
#define EXPORT_SYMBOL_NAMED(sym_ident, sym_name) \
Z_EXPORT_SYMBOL_NAMED(sym_ident, sym_name)

/**
* @brief Export a constant symbol from the current build
*
Expand All @@ -152,7 +180,7 @@ struct llext_symtable {
*
* @param x Symbol to export
*/
#define EXPORT_SYMBOL(x) Z_EXPORT_SYMBOL(x)
#define EXPORT_SYMBOL(x) EXPORT_SYMBOL_NAMED(x, x)

/**
* @}
Expand Down
3 changes: 3 additions & 0 deletions scripts/dts/gen_defines.py
Original file line number Diff line number Diff line change
Expand Up @@ -720,6 +720,9 @@ def fmt_dep_list(dep_list):
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}")
Expand Down
22 changes: 22 additions & 0 deletions scripts/dts/python-devicetree/src/devicetree/edtlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@
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
Expand All @@ -90,6 +92,12 @@
from devicetree.grutils import Graph
from devicetree._private import _slice_helper

def _compute_hash(path: str) -> str:
pillo79 marked this conversation as resolved.
Show resolved Hide resolved
# 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
#
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -1027,6 +1040,7 @@ def __init__(
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()
Expand Down Expand Up @@ -2270,10 +2284,18 @@ def _init_nodes(self) -> None:
# 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

Expand Down
8 changes: 8 additions & 0 deletions subsys/llext/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,14 @@ config LLEXT_EXPORT_DEVICES
When enabled, all Zephyr devices defined in the device tree are
made available to llexts via the standard DT_ / DEVICE_* macros.

config LLEXT_EXPORT_DEV_IDS_BY_HASH
bool "Use hash of device path in device name"
depends on LLEXT_EXPORT_DEVICES
help
When enabled, exported device names are generated from a hash of the
node path instead of an ordinal number. Identifiers generated this
way are stable across rebuilds.

config LLEXT_EXPORT_BUILTINS_BY_SLID
bool "Export built-in symbols to llexts via SLIDs"
help
Expand Down
10 changes: 10 additions & 0 deletions tests/lib/devicetree/api/src/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)),
rruuaanng marked this conversation as resolved.
Show resolved Hide resolved
"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, "");
Expand Down
18 changes: 18 additions & 0 deletions tests/subsys/llext/testcase.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -123,3 +123,21 @@ tests:
- CONFIG_LLEXT_STORAGE_WRITABLE=y
- CONFIG_LLEXT_TYPE_ELF_RELOCATABLE=y
- CONFIG_LLEXT_EXPORT_BUILTINS_BY_SLID=y

# Test the export device IDs by hash feature on a single architecture in
# both normal and SLID mode.
llext.devices_by_hash:
arch_allow: arm
filter: not CONFIG_MPU and not CONFIG_MMU
extra_conf_files: ['no_mem_protection.conf']
extra_configs:
- CONFIG_LLEXT_STORAGE_WRITABLE=n
- CONFIG_LLEXT_EXPORT_DEV_IDS_BY_HASH=y
llext.devices_by_hash_slid_linking:
arch_allow: arm
filter: not CONFIG_MPU and not CONFIG_MMU
extra_conf_files: ['no_mem_protection.conf']
extra_configs:
- CONFIG_LLEXT_STORAGE_WRITABLE=n
- CONFIG_LLEXT_EXPORT_DEV_IDS_BY_HASH=y
- CONFIG_LLEXT_EXPORT_BUILTINS_BY_SLID=y
Loading