From fff9ecbc7ff2ea67af5c28ed3263d60668d10c46 Mon Sep 17 00:00:00 2001 From: Gerard Marull-Paretas Date: Tue, 5 Jul 2022 16:52:36 +0200 Subject: [PATCH] devicetree: add DT_(INST_)FOREACH_CHILD(_STATUS_OKAY)_SEP(_VARGS) It is frequent to see in Devicetree code constructs like: ```c #define NAME_AND_COMMA(node_id) DT_NODE_FULL_NAME(node_id), const char *child_names[] = { DT_FOREACH_CHILD(DT_NODELABEL(n), NAME_AND_COMMA) }; ``` That is, an auxiliary macro to append a separator character in DT_FOREACH* macros. Non-DT API, e.g. FOR_EACH(), takes a separator argument to avoid such intermediate macros. This patch adds DT_FOREACH_CHILD_SEP (and instance/status okay/vargs versions of it). They all take an extra argument: a separator. With this change, the example above can be simplified to: ```c const char *child_labels[] = { DT_FOREACH_CHILD(DT_NODELABEL(n), DT_NODE_FULL_NAME, (,)) }; ``` Notes: - Other DT_FOREACH* macros could/should be extended as well Signed-off-by: Gerard Marull-Paretas --- doc/build/dts/macros.bnf | 4 + include/zephyr/devicetree.h | 128 ++++++++++++++++++++++++++++ scripts/dts/gen_defines.py | 34 +++++--- tests/lib/devicetree/api/src/main.c | 66 ++++++++++++-- 4 files changed, 213 insertions(+), 19 deletions(-) diff --git a/doc/build/dts/macros.bnf b/doc/build/dts/macros.bnf index ce61c5360aa..e48510f18d4 100644 --- a/doc/build/dts/macros.bnf +++ b/doc/build/dts/macros.bnf @@ -61,11 +61,15 @@ node-macro =/ %s"DT_N" path-id %s"_PARENT" ; These are used internally by DT_FOREACH_CHILD, which iterates over ; each child node. node-macro =/ %s"DT_N" path-id %s"_FOREACH_CHILD" +node-macro =/ %s"DT_N" path-id %s"_FOREACH_CHILD_SEP" node-macro =/ %s"DT_N" path-id %s"_FOREACH_CHILD_VARGS" +node-macro =/ %s"DT_N" path-id %s"_FOREACH_CHILD_SEP_VARGS" ; These are used internally by DT_FOREACH_CHILD_STATUS_OKAY, which iterates ; over each child node with status "okay". node-macro =/ %s"DT_N" path-id %s"_FOREACH_CHILD_STATUS_OKAY" +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" ; 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 0b8c3b12234..50e2f1f7478 100644 --- a/include/zephyr/devicetree.h +++ b/include/zephyr/devicetree.h @@ -2044,6 +2044,43 @@ #define DT_FOREACH_CHILD(node_id, fn) \ DT_CAT(node_id, _FOREACH_CHILD)(fn) +/** + * @brief Invokes "fn" for each child of "node_id" with a separator + * + * The macro "fn" must take one parameter, which will be the node + * identifier of a child node of "node_id". + * + * Example devicetree fragment: + * + * n: node { + * child-1 { + * ... + * }; + * child-2 { + * ... + * }; + * }; + * + * Example usage: + * + * const char *child_names[] = { + * DT_FOREACH_CHILD_SEP(DT_NODELABEL(n), DT_NODE_FULL_NAME, (,)) + * }; + * + * This expands to: + * + * const char *child_names[] = { + * "child-1", "child-2" + * }; + * + * @param node_id node identifier + * @param fn macro to invoke + * @param sep Separator (e.g. comma or semicolon). Must be in parentheses; + * this is required to enable providing a comma as separator. + */ +#define DT_FOREACH_CHILD_SEP(node_id, fn, sep) \ + DT_CAT(node_id, _FOREACH_CHILD_SEP)(fn, sep) + /** * @brief Invokes "fn" for each child of "node_id" with multiple arguments * @@ -2062,6 +2099,24 @@ #define DT_FOREACH_CHILD_VARGS(node_id, fn, ...) \ DT_CAT(node_id, _FOREACH_CHILD_VARGS)(fn, __VA_ARGS__) +/** + * @brief Invokes "fn" for each child of "node_id" with separator and multiple + * arguments. + * + * The macro "fn" takes multiple arguments. The first should be the node + * identifier for the child node. The remaining are passed-in by the caller. + * + * @param node_id node identifier + * @param fn macro to invoke + * @param sep Separator (e.g. comma or semicolon). Must be in parentheses; + * this is required to enable providing a comma as separator. + * @param ... variable number of arguments to pass to fn + * + * @see DT_FOREACH_CHILD_VARGS + */ +#define DT_FOREACH_CHILD_SEP_VARGS(node_id, fn, sep, ...) \ + DT_CAT(node_id, _FOREACH_CHILD_SEP_VARGS)(fn, sep, __VA_ARGS__) + /** * @brief Call "fn" on the child nodes with status "okay" * @@ -2080,6 +2135,25 @@ #define DT_FOREACH_CHILD_STATUS_OKAY(node_id, fn) \ DT_CAT(node_id, _FOREACH_CHILD_STATUS_OKAY)(fn) +/** + * @brief Call "fn" on the child nodes with status "okay" with separator + * + * The macro "fn" should take one argument, which is the node + * identifier for the child node. + * + * As usual, both a missing status and an "ok" status are + * treated as "okay". + * + * @param node_id node identifier + * @param fn macro to invoke + * @param sep Separator (e.g. comma or semicolon). Must be in parentheses; + * this is required to enable providing a comma as separator. + * + * @see DT_FOREACH_CHILD_STATUS_OKAY + */ +#define DT_FOREACH_CHILD_STATUS_OKAY_SEP(node_id, fn, sep) \ + DT_CAT(node_id, _FOREACH_CHILD_STATUS_OKAY_SEP)(fn, sep) + /** * @brief Call "fn" on the child nodes with status "okay" with multiple * arguments @@ -2102,6 +2176,27 @@ #define DT_FOREACH_CHILD_STATUS_OKAY_VARGS(node_id, fn, ...) \ DT_CAT(node_id, _FOREACH_CHILD_STATUS_OKAY_VARGS)(fn, __VA_ARGS__) +/** + * @brief Call "fn" on the child nodes with status "okay" with separator and + * multiple arguments + * + * The macro "fn" takes multiple arguments. The first should be the node + * identifier for the child node. The remaining are passed-in by the caller. + * + * As usual, both a missing status and an "ok" status are + * treated as "okay". + * + * @param node_id node identifier + * @param fn macro to invoke + * @param sep Separator (e.g. comma or semicolon). Must be in parentheses; + * this is required to enable providing a comma as separator. + * @param ... variable number of arguments to pass to fn + * + * @see DT_FOREACH_CHILD_SEP_STATUS_OKAY + */ +#define DT_FOREACH_CHILD_STATUS_OKAY_SEP_VARGS(node_id, fn, sep, ...) \ + DT_CAT(node_id, _FOREACH_CHILD_STATUS_OKAY_SEP_VARGS)(fn, sep, __VA_ARGS__) + /** * @brief Invokes "fn" for each element in the value of property "prop". * @@ -2545,6 +2640,22 @@ #define DT_INST_FOREACH_CHILD(inst, fn) \ DT_FOREACH_CHILD(DT_DRV_INST(inst), fn) +/** + * @brief Call "fn" on all child nodes of DT_DRV_INST(inst) with a separator + * + * The macro "fn" should take one argument, which is the node + * identifier for the child node. + * + * @param inst instance number + * @param fn macro to invoke on each child node identifier + * @param sep Separator (e.g. comma or semicolon). Must be in parentheses; + * this is required to enable providing a comma as separator. + * + * @see DT_FOREACH_CHILD_SEP + */ +#define DT_INST_FOREACH_CHILD_SEP(inst, fn, sep) \ + DT_FOREACH_CHILD_SEP(DT_DRV_INST(inst), fn, sep) + /** * @brief Call "fn" on all child nodes of DT_DRV_INST(inst). * @@ -2563,6 +2674,23 @@ #define DT_INST_FOREACH_CHILD_VARGS(inst, fn, ...) \ DT_FOREACH_CHILD_VARGS(DT_DRV_INST(inst), fn, __VA_ARGS__) +/** + * @brief Call "fn" on all child nodes of DT_DRV_INST(inst) with separator. + * + * The macro "fn" takes multiple arguments. The first should be the node + * identifier for the child node. The remaining are passed-in by the caller. + * + * @param inst instance number + * @param fn macro to invoke on each child node identifier + * @param sep Separator (e.g. comma or semicolon). Must be in parentheses; + * this is required to enable providing a comma as separator. + * @param ... variable number of arguments to pass to fn + * + * @see DT_FOREACH_CHILD_SEP_VARGS + */ +#define DT_INST_FOREACH_CHILD_SEP_VARGS(inst, fn, sep, ...) \ + DT_FOREACH_CHILD_SEP_VARGS(DT_DRV_INST(inst), fn, sep, __VA_ARGS__) + /** * @brief Get a DT_DRV_COMPAT value's index into its enumeration values * @param inst instance number diff --git a/scripts/dts/gen_defines.py b/scripts/dts/gen_defines.py index 88fcf1c531d..ea80802d8f3 100755 --- a/scripts/dts/gen_defines.py +++ b/scripts/dts/gen_defines.py @@ -521,21 +521,33 @@ def write_children(node): " ".join(f"fn(DT_{child.z_path_id})" for child in node.children.values())) + out_dt_define(f"{node.z_path_id}_FOREACH_CHILD_SEP(fn, sep)", + " DT_DEBRACKET_INTERNAL sep ".join(f"fn(DT_{child.z_path_id})" + for child in node.children.values())) + out_dt_define(f"{node.z_path_id}_FOREACH_CHILD_VARGS(fn, ...)", - " ".join(f"fn(DT_{child.z_path_id}, __VA_ARGS__)" for child in - node.children.values())) + " ".join(f"fn(DT_{child.z_path_id}, __VA_ARGS__)" + for child in node.children.values())) - functions = '' - functions_args = '' - for child in node.children.values(): - if child.status == "okay": - functions = functions + f"fn(DT_{child.z_path_id}) " - functions_args = functions_args + f"fn(DT_{child.z_path_id}, " \ - "__VA_ARGS__) " + out_dt_define(f"{node.z_path_id}_FOREACH_CHILD_SEP_VARGS(fn, sep, ...)", + " DT_DEBRACKET_INTERNAL sep ".join(f"fn(DT_{child.z_path_id}, __VA_ARGS__)" + for child in node.children.values())) + + out_dt_define(f"{node.z_path_id}_FOREACH_CHILD_STATUS_OKAY(fn)", + " ".join(f"fn(DT_{child.z_path_id})" + for child in node.children.values() if child.status == "okay")) + + out_dt_define(f"{node.z_path_id}_FOREACH_CHILD_STATUS_OKAY_SEP(fn, sep)", + " DT_DEBRACKET_INTERNAL sep ".join(f"fn(DT_{child.z_path_id})" + for child in node.children.values() if child.status == "okay")) - out_dt_define(f"{node.z_path_id}_FOREACH_CHILD_STATUS_OKAY(fn)", functions) out_dt_define(f"{node.z_path_id}_FOREACH_CHILD_STATUS_OKAY_VARGS(fn, ...)", - functions_args) + " ".join(f"fn(DT_{child.z_path_id}, __VA_ARGS__)" + for child in node.children.values() if child.status == "okay")) + + out_dt_define(f"{node.z_path_id}_FOREACH_CHILD_STATUS_OKAY_SEP_VARGS(fn, sep, ...)", + " DT_DEBRACKET_INTERNAL sep ".join(f"fn(DT_{child.z_path_id}, __VA_ARGS__)" + for child in node.children.values() if child.status == "okay")) def write_status(node): diff --git a/tests/lib/devicetree/api/src/main.c b/tests/lib/devicetree/api/src/main.c index ba1d138f17b..43620237e7d 100644 --- a/tests/lib/devicetree/api/src/main.c +++ b/tests/lib/devicetree/api/src/main.c @@ -1630,7 +1630,8 @@ ZTEST(devicetree_api, test_parent) #define DT_DRV_COMPAT vnd_child_bindings ZTEST(devicetree_api, test_child_nodes_list) { - #define TEST_FUNC(child) { DT_PROP(child, val) }, + #define TEST_FUNC(child) { DT_PROP(child, val) } + #define TEST_FUNC_AND_COMMA(child) TEST_FUNC(child), #define TEST_PARENT DT_PARENT(DT_NODELABEL(test_child_a)) struct vnd_child_binding { @@ -1638,31 +1639,55 @@ ZTEST(devicetree_api, test_child_nodes_list) }; struct vnd_child_binding vals[] = { - DT_FOREACH_CHILD(TEST_PARENT, TEST_FUNC) + DT_FOREACH_CHILD(TEST_PARENT, TEST_FUNC_AND_COMMA) + }; + + struct vnd_child_binding vals_sep[] = { + DT_FOREACH_CHILD_SEP(TEST_PARENT, TEST_FUNC, (,)) }; struct vnd_child_binding vals_inst[] = { - DT_INST_FOREACH_CHILD(0, TEST_FUNC) + DT_INST_FOREACH_CHILD(0, TEST_FUNC_AND_COMMA) + }; + + struct vnd_child_binding vals_inst_sep[] = { + DT_INST_FOREACH_CHILD_SEP(0, TEST_FUNC, (,)) }; struct vnd_child_binding vals_status_okay[] = { - DT_FOREACH_CHILD_STATUS_OKAY(TEST_PARENT, TEST_FUNC) + DT_FOREACH_CHILD_STATUS_OKAY(TEST_PARENT, TEST_FUNC_AND_COMMA) + }; + + struct vnd_child_binding vals_status_okay_sep[] = { + DT_FOREACH_CHILD_STATUS_OKAY_SEP(TEST_PARENT, TEST_FUNC, (,)) }; zassert_equal(ARRAY_SIZE(vals), 3, ""); + zassert_equal(ARRAY_SIZE(vals_sep), 3, ""); zassert_equal(ARRAY_SIZE(vals_inst), 3, ""); + zassert_equal(ARRAY_SIZE(vals_inst_sep), 3, ""); zassert_equal(ARRAY_SIZE(vals_status_okay), 2, ""); + zassert_equal(ARRAY_SIZE(vals_status_okay_sep), 2, ""); zassert_equal(vals[0].val, 0, ""); zassert_equal(vals[1].val, 1, ""); zassert_equal(vals[2].val, 2, ""); + zassert_equal(vals_sep[0].val, 0, ""); + zassert_equal(vals_sep[1].val, 1, ""); + zassert_equal(vals_sep[2].val, 2, ""); zassert_equal(vals_inst[0].val, 0, ""); zassert_equal(vals_inst[1].val, 1, ""); zassert_equal(vals_inst[2].val, 2, ""); + zassert_equal(vals_inst_sep[0].val, 0, ""); + zassert_equal(vals_inst_sep[1].val, 1, ""); + zassert_equal(vals_inst_sep[2].val, 2, ""); zassert_equal(vals_status_okay[0].val, 0, ""); zassert_equal(vals_status_okay[1].val, 1, ""); + zassert_equal(vals_status_okay_sep[0].val, 0, ""); + zassert_equal(vals_status_okay_sep[1].val, 1, ""); #undef TEST_PARENT + #undef TEST_FUNC_AND_COMMA #undef TEST_FUNC } @@ -1670,7 +1695,8 @@ ZTEST(devicetree_api, test_child_nodes_list) #define DT_DRV_COMPAT vnd_child_bindings ZTEST(devicetree_api, test_child_nodes_list_varg) { - #define TEST_FUNC(child, arg) { DT_PROP(child, val) + arg }, + #define TEST_FUNC(child, arg) { DT_PROP(child, val) + arg } + #define TEST_FUNC_AND_COMMA(child, arg) TEST_FUNC(child, arg), #define TEST_PARENT DT_PARENT(DT_NODELABEL(test_child_a)) struct vnd_child_binding { @@ -1678,31 +1704,55 @@ ZTEST(devicetree_api, test_child_nodes_list_varg) }; struct vnd_child_binding vals[] = { - DT_FOREACH_CHILD_VARGS(TEST_PARENT, TEST_FUNC, 1) + DT_FOREACH_CHILD_VARGS(TEST_PARENT, TEST_FUNC_AND_COMMA, 1) + }; + + struct vnd_child_binding vals_sep[] = { + DT_FOREACH_CHILD_SEP_VARGS(TEST_PARENT, TEST_FUNC, (,), 1) }; struct vnd_child_binding vals_inst[] = { - DT_INST_FOREACH_CHILD_VARGS(0, TEST_FUNC, 1) + DT_INST_FOREACH_CHILD_VARGS(0, TEST_FUNC_AND_COMMA, 1) + }; + + struct vnd_child_binding vals_inst_sep[] = { + DT_INST_FOREACH_CHILD_SEP_VARGS(0, TEST_FUNC, (,), 1) }; struct vnd_child_binding vals_status_okay[] = { - DT_FOREACH_CHILD_STATUS_OKAY_VARGS(TEST_PARENT, TEST_FUNC, 1) + DT_FOREACH_CHILD_STATUS_OKAY_VARGS(TEST_PARENT, TEST_FUNC_AND_COMMA, 1) + }; + + struct vnd_child_binding vals_status_okay_sep[] = { + DT_FOREACH_CHILD_STATUS_OKAY_SEP_VARGS(TEST_PARENT, TEST_FUNC, (,), 1) }; zassert_equal(ARRAY_SIZE(vals), 3, ""); + zassert_equal(ARRAY_SIZE(vals_sep), 3, ""); zassert_equal(ARRAY_SIZE(vals_inst), 3, ""); + zassert_equal(ARRAY_SIZE(vals_inst_sep), 3, ""); zassert_equal(ARRAY_SIZE(vals_status_okay), 2, ""); + zassert_equal(ARRAY_SIZE(vals_status_okay_sep), 2, ""); zassert_equal(vals[0].val, 1, ""); zassert_equal(vals[1].val, 2, ""); zassert_equal(vals[2].val, 3, ""); + zassert_equal(vals_sep[0].val, 1, ""); + zassert_equal(vals_sep[1].val, 2, ""); + zassert_equal(vals_sep[2].val, 3, ""); zassert_equal(vals_inst[0].val, 1, ""); zassert_equal(vals_inst[1].val, 2, ""); zassert_equal(vals_inst[2].val, 3, ""); + zassert_equal(vals_inst_sep[0].val, 1, ""); + zassert_equal(vals_inst_sep[1].val, 2, ""); + zassert_equal(vals_inst_sep[2].val, 3, ""); zassert_equal(vals_status_okay[0].val, 1, ""); zassert_equal(vals_status_okay[1].val, 2, ""); + zassert_equal(vals_status_okay_sep[0].val, 1, ""); + zassert_equal(vals_status_okay_sep[1].val, 2, ""); #undef TEST_PARENT + #undef TEST_FUNC_AND_COMMA #undef TEST_FUNC }