devicetree: add first-class node label helpers
Add the following new macros:
- DT_FOREACH_NODELABEL
- DT_FOREACH_NODELABEL_VARGS
- DT_INST_FOREACH_NODELABEL
- DT_INST_FOREACH_NODELABEL_VARGS
These are for-each helpers for iterating over the node labels of a
devicetree node. Since node labels are unique in the entire
devicetree, their token representations can be useful as unique IDs in
code as well.
As a first user of these, add:
- DT_NODELABEL_STRING_ARRAY
- DT_INST_NODELABEL_STRING_ARRAY
The motivating use case for these macros is to allow looking up a
struct device by devicetree node label in Zephyr shell utilities.
The work on the shells themselves is deferred to other patches.
To make working with the string array helpers easier, add:
- DT_NUM_NODELABELS
- DT_INST_NUM_NODELABELS
Signed-off-by: Martí Bolívar <mbolivar@amperecomputing.com>
diff --git a/doc/build/dts/macros.bnf b/doc/build/dts/macros.bnf
index 88ed70f..e04ab31 100644
--- a/doc/build/dts/macros.bnf
+++ b/doc/build/dts/macros.bnf
@@ -88,6 +88,11 @@
node-macro =/ %s"DT_N" path-id %s"_FOREACH_CHILD_STATUS_OKAY_SEP"
node-macro =/ %s"DT_N" path-id %s"_FOREACH_CHILD_STATUS_OKAY_VARGS"
node-macro =/ %s"DT_N" path-id %s"_FOREACH_CHILD_STATUS_OKAY_SEP_VARGS"
+; These are used internally by DT_FOREACH_NODELABEL and
+; DT_FOREACH_NODELABEL_VARGS, which iterate over a node's node labels.
+node-macro =/ %s"DT_N" path-id %s"_FOREACH_NODELABEL" [ %s"_VARGS" ]
+; These are used internally by DT_NUM_NODELABELS
+node-macro =/ %s"DT_N" path-id %s"_NODELABEL_NUM"
; The node's zero-based index in the list of it's parent's child nodes.
node-macro =/ %s"DT_N" path-id %s"_CHILD_IDX"
; The node's status macro; dt-name in this case is something like "okay"
diff --git a/include/zephyr/devicetree.h b/include/zephyr/devicetree.h
index 58f924e..237373e 100644
--- a/include/zephyr/devicetree.h
+++ b/include/zephyr/devicetree.h
@@ -29,7 +29,7 @@
* @brief devicetree.h API
* @defgroup devicetree Devicetree
* @since 2.2
- * @version 1.0.0
+ * @version 1.1.0
* @{
* @}
*/
@@ -594,6 +594,33 @@
(DT_DEP_ORD(node_id1) == (DT_DEP_ORD(node_id2)))
/**
+ * @brief Get a devicetree node's node labels as an array of strings
+ *
+ * Example devicetree fragment:
+ *
+ * @code{.dts}
+ * foo: bar: node@deadbeef {};
+ * @endcode
+ *
+ * Example usage:
+ *
+ * @code{.c}
+ * DT_NODELABEL_STRING_ARRAY(DT_NODELABEL(foo))
+ * @endcode
+ *
+ * This expands to:
+ *
+ * @code{.c}
+ * { "foo", "bar", }
+ * @endcode
+ *
+ * @param node_id node identifier
+ * @return an array initializer for an array of the node's node labels as strings
+ */
+#define DT_NODELABEL_STRING_ARRAY(node_id) \
+ { DT_FOREACH_NODELABEL(node_id, DT_NODELABEL_STRING_ARRAY_ENTRY_INTERNAL) }
+
+/**
* @}
*/
@@ -2309,6 +2336,32 @@
#define DT_NUM_IRQS(node_id) DT_CAT(node_id, _IRQ_NUM)
/**
+ * @brief Get the number of node labels that a node has
+ *
+ * Example devicetree fragment:
+ *
+ * @code{.dts}
+ * / {
+ * foo {};
+ * bar: bar@1000 {};
+ * baz: baz2: baz@2000 {};
+ * };
+ * @endcode
+ *
+ * Example usage:
+ *
+ * @code{.c}
+ * DT_NUM_NODELABELS(DT_PATH(foo)) // 0
+ * DT_NUM_NODELABELS(DT_NODELABEL(bar)) // 1
+ * DT_NUM_NODELABELS(DT_NODELABEL(baz)) // 2
+ * @endcode
+ *
+ * @param node_id node identifier
+ * @return number of node labels that the node has
+ */
+#define DT_NUM_NODELABELS(node_id) DT_CAT(node_id, _NODELABEL_NUM)
+
+/**
* @brief Get the interrupt level for the node
*
* @param node_id node identifier
@@ -3163,6 +3216,86 @@
())
/**
+ * @brief Invokes @p fn for each node label of a given node
+ *
+ * The order of the node labels in this macro's expansion matches
+ * the order in the final devicetree, with duplicates removed.
+ *
+ * Node labels are passed to @p fn as tokens. Note that devicetree
+ * node labels are always valid C tokens (see "6.2 Labels" in
+ * Devicetree Specification v0.4 for details). The node labels are
+ * passed as tokens to @p fn as-is, without any lowercasing or
+ * conversion of special characters to underscores.
+ *
+ * Example devicetree fragment:
+ *
+ * @code{.dts}
+ * foo: bar: FOO: node@deadbeef {};
+ * @endcode
+ *
+ * Example usage:
+ *
+ * @code{.c}
+ * int foo = 1;
+ * int bar = 2;
+ * int FOO = 3;
+ *
+ * #define FN(nodelabel) + nodelabel
+ * int sum = 0 DT_FOREACH_NODELABEL(DT_NODELABEL(foo), FN)
+ * @endcode
+ *
+ * This expands to:
+ *
+ * @code{.c}
+ * int sum = 0 + 1 + 2 + 3;
+ * @endcode
+ *
+ * @param node_id node identifier whose node labels to use
+ * @param fn macro which will be passed each node label in order
+ */
+#define DT_FOREACH_NODELABEL(node_id, fn) DT_CAT(node_id, _FOREACH_NODELABEL)(fn)
+
+/**
+ * @brief Invokes @p fn for each node label of a given node with
+ * multiple arguments.
+ *
+ * This is like DT_FOREACH_NODELABEL() except you can also pass
+ * additional arguments to @p fn.
+ *
+ * Example devicetree fragment:
+ *
+ * @code{.dts}
+ * foo: bar: node@deadbeef {};
+ * @endcode
+ *
+ * Example usage:
+ *
+ * @code{.c}
+ * int foo = 0;
+ * int bar = 1;
+ *
+ * #define VAR_PLUS(nodelabel, to_add) int nodelabel ## _added = nodelabel + to_add;
+ *
+ * DT_FOREACH_NODELABEL_VARGS(DT_NODELABEL(foo), VAR_PLUS, 1)
+ * @endcode
+ *
+ * This expands to:
+ *
+ * @code{.c}
+ * int foo = 0;
+ * int bar = 1;
+ * int foo_added = foo + 1;
+ * int bar_added = bar + 1;
+ * @endcode
+ *
+ * @param node_id node identifier whose node labels to use
+ * @param fn macro which will be passed each node label in order
+ * @param ... additional arguments to pass to @p fn
+ */
+#define DT_FOREACH_NODELABEL_VARGS(node_id, fn, ...) \
+ DT_CAT(node_id, _FOREACH_NODELABEL_VARGS)(fn, __VA_ARGS__)
+
+/**
* @}
*/
@@ -3485,6 +3618,26 @@
DT_CHILD_NUM_STATUS_OKAY(DT_DRV_INST(inst))
/**
+ * @brief Get a string array of DT_DRV_INST(inst)'s node labels
+ *
+ * Equivalent to DT_NODELABEL_STRING_ARRAY(DT_DRV_INST(inst)).
+ *
+ * @param inst instance number
+ * @return an array initializer for an array of the instance's node labels as strings
+ */
+#define DT_INST_NODELABEL_STRING_ARRAY(inst) DT_NODELABEL_STRING_ARRAY(DT_DRV_INST(inst))
+
+/**
+ * @brief Get the number of node labels by instance number
+ *
+ * Equivalent to DT_NUM_NODELABELS(DT_DRV_INST(inst)).
+ *
+ * @param inst instance number
+ * @return the number of node labels that the node with that instance number has
+ */
+#define DT_INST_NUM_NODELABELS(inst) DT_NUM_NODELABELS(DT_DRV_INST(inst))
+
+/**
* @brief Call @p fn on all child nodes of DT_DRV_INST(inst).
*
* The macro @p fn should take one argument, which is the node
@@ -4325,6 +4478,32 @@
())
/**
+ * @brief Call @p fn on all node labels for a given `DT_DRV_COMPAT` instance
+ *
+ * Equivalent to DT_FOREACH_NODELABEL(DT_DRV_INST(inst), fn).
+ *
+ * @param inst instance number
+ * @param fn macro which will be passed each node label for the node
+ * with that instance number
+ */
+#define DT_INST_FOREACH_NODELABEL(inst, fn) \
+ DT_FOREACH_NODELABEL(DT_DRV_INST(inst), fn)
+
+/**
+ * @brief Call @p fn on all node labels for a given `DT_DRV_COMPAT` instance
+ * with multiple arguments
+ *
+ * Equivalent to DT_FOREACH_NODELABEL_VARGS(DT_DRV_INST(inst), fn, ...).
+ *
+ * @param inst instance number
+ * @param fn macro which will be passed each node label for the node
+ * with that instance number
+ * @param ... additional arguments to pass to @p fn
+ */
+#define DT_INST_FOREACH_NODELABEL_VARGS(inst, fn, ...) \
+ DT_FOREACH_NODELABEL_VARGS(DT_DRV_INST(inst), fn, __VA_ARGS__)
+
+/**
* @brief Invokes @p fn for each element of property @p prop for
* a `DT_DRV_COMPAT` instance.
*
@@ -4567,6 +4746,13 @@
#define DT_U64_C(_v) UINT64_C(_v)
#endif
+/* Helpers for DT_NODELABEL_STRING_ARRAY. We define our own stringify
+ * in order to avoid adding a dependency on toolchain.h..
+ */
+#define DT_NODELABEL_STRING_ARRAY_ENTRY_INTERNAL(nodelabel) DT_STRINGIFY_INTERNAL(nodelabel),
+#define DT_STRINGIFY_INTERNAL(arg) DT_STRINGIFY_INTERNAL_HELPER(arg)
+#define DT_STRINGIFY_INTERNAL_HELPER(arg) #arg
+
/** @endcond */
/* have these last so they have access to all previously defined macros */
diff --git a/scripts/dts/gen_defines.py b/scripts/dts/gen_defines.py
index eb350b0..b2da629 100755
--- a/scripts/dts/gen_defines.py
+++ b/scripts/dts/gen_defines.py
@@ -132,6 +132,13 @@
out_dt_define(f"{node.z_path_id}_CHILD_IDX",
node.parent.child_index(node))
+ out_comment("Helpers for dealing with node labels:")
+ out_dt_define(f"{node.z_path_id}_NODELABEL_NUM", len(node.labels))
+ out_dt_define(f"{node.z_path_id}_FOREACH_NODELABEL(fn)",
+ " ".join(f"fn({nodelabel})" for nodelabel in node.labels))
+ out_dt_define(f"{node.z_path_id}_FOREACH_NODELABEL_VARGS(fn, ...)",
+ " ".join(f"fn({nodelabel}, __VA_ARGS__)" for nodelabel in node.labels))
+
write_children(node)
write_dep_info(node)
write_idents_and_existence(node)
diff --git a/tests/lib/devicetree/api/src/main.c b/tests/lib/devicetree/api/src/main.c
index c57a280..1cd5134 100644
--- a/tests/lib/devicetree/api/src/main.c
+++ b/tests/lib/devicetree/api/src/main.c
@@ -1600,6 +1600,64 @@
#undef BUILD_BUG_ON_EXPANSION
}
+ZTEST(devicetree_api, test_foreach_nodelabel)
+{
+#undef DT_DRV_COMPAT
+#define DT_DRV_COMPAT vnd_adc_temp_sensor
+#define ENTRY(nodelabel) enum_ ## nodelabel,
+#define VAR_PLUS(nodelabel, to_add) int nodelabel ## _added = enum_ ## nodelabel + to_add;
+
+ /* DT_FOREACH_NODELABEL */
+ enum {
+ DT_FOREACH_NODELABEL(DT_NODELABEL(test_nodelabel), ENTRY)
+ };
+ zassert_equal(enum_test_nodelabel, 0, "");
+ zassert_equal(enum_TEST_NODELABEL_ALLCAPS, 1, "");
+ zassert_equal(enum_test_gpio_1, 2, "");
+
+ /* DT_FOREACH_NODELABEL_VARGS */
+ DT_FOREACH_NODELABEL_VARGS(DT_NODELABEL(test_nodelabel), VAR_PLUS, 1);
+ zassert_equal(test_nodelabel_added, 1, "");
+ zassert_equal(TEST_NODELABEL_ALLCAPS_added, 2, "");
+ zassert_equal(test_gpio_1_added, 3, "");
+
+ /* DT_NODELABEL_STRING_ARRAY is tested here since it's closely related */
+ const char *nodelabels[] = DT_NODELABEL_STRING_ARRAY(DT_NODELABEL(test_nodelabel));
+
+ zassert_equal(ARRAY_SIZE(nodelabels), 3);
+ zassert_true(!strcmp(nodelabels[0], "test_nodelabel"), "");
+ zassert_true(!strcmp(nodelabels[1], "TEST_NODELABEL_ALLCAPS"), "");
+ zassert_true(!strcmp(nodelabels[2], "test_gpio_1"), "");
+
+ /* DT_NUM_NODELABELS */
+ zassert_equal(DT_NUM_NODELABELS(DT_NODELABEL(test_nodelabel)), 3, "");
+ zassert_equal(DT_NUM_NODELABELS(DT_PATH(chosen)), 0, "");
+ zassert_equal(DT_NUM_NODELABELS(DT_ROOT), 0, "");
+
+ /* DT_INST_FOREACH_NODELABEL */
+ enum {
+ DT_INST_FOREACH_NODELABEL(0, ENTRY)
+ };
+ zassert_equal(enum_test_temp_sensor, 0, "");
+
+ /* DT_INST_FOREACH_NODELABEL_VARGS */
+ DT_INST_FOREACH_NODELABEL_VARGS(0, VAR_PLUS, 1);
+ zassert_equal(test_temp_sensor_added, 1, "");
+
+ /* DT_INST_NODELABEL_STRING_ARRAY */
+ const char *inst_nodelabels[] = DT_INST_NODELABEL_STRING_ARRAY(0);
+
+ zassert_equal(ARRAY_SIZE(inst_nodelabels), 1);
+ zassert_true(!strcmp(inst_nodelabels[0], "test_temp_sensor"), "");
+
+ /* DT_INST_NUM_NODELABELS */
+ zassert_equal(DT_INST_NUM_NODELABELS(0), 1, "");
+
+#undef VAR_PLUS
+#undef ENTRY
+#undef DT_DRV_COMPAT
+}
+
ZTEST(devicetree_api, test_foreach_prop_elem)
{
#define TIMES_TWO(node_id, prop, idx) \