devicetree: add DT_FOREACH_PROP_ELEM(node_id, prop, fn)

It can be convenient to "iterate" over the elements of a property, in
the same way it is convenient to "iterate" over enabled instances.

Add a new macro for doing this, along with a DT_INST_FOREACH_PROP_ELEM
variant.

This is likely to be more convenient than UTIL_LISTIFY or FOR_EACH in
some situations because:

- it handles inputs of any length
- compiler error messages will be shorter and more self-contained
- it is easier to use with phandle-array type properties, which
  require more complicated macro boilerplate when used with
  util_macro.h APIs

Signed-off-by: Martí Bolívar <marti.bolivar@nordicsemi.no>
This commit is contained in:
Martí Bolívar 2021-04-14 15:26:42 -07:00 committed by Carles Cufí
commit 9c229a417c
4 changed files with 107 additions and 2 deletions

View file

@ -44,6 +44,7 @@
* enum values are identifiers)
* _ENUM_UPPER_TOKEN: like _ENUM_TOKEN, but uppercased
* _EXISTS: property is defined
* _FOREACH_PROP_ELEM: helper for "iterating" over values in the property
* _IDX_<i>: logical index into property
* _IDX_<i>_EXISTS: logical index into property is defined
* _IDX_<i>_PH: phandle array's phandle by index (or phandle, phandles)
@ -1484,6 +1485,52 @@
#define DT_FOREACH_CHILD(node_id, fn) \
DT_CAT(node_id, _FOREACH_CHILD)(fn)
/**
* @brief Invokes "fn" for each element in the value of property "prop".
*
* The macro "fn" must take three parameters: fn(node_id, prop, idx).
* "node_id" and "prop" are the same as what is passed to
* DT_FOREACH_PROP_ELEM, and "idx" is the current index into the array.
* The "idx" values are integer literals starting from 0.
*
* Example devicetree fragment:
*
* n: node {
* my-ints = <1 2 3>;
* };
*
* Example usage:
*
* #define TIMES_TWO(node_id, prop, idx) \
* (2 * DT_PROP_BY_IDX(node_id, prop, idx)),
*
* int array[] = {
* DT_FOREACH_PROP_ELEM(DT_NODELABEL(n), my_ints, TIMES_TWO)
* };
*
* This expands to:
*
* int array[] = {
* (2 * 1), (2 * 2), (2 * 3),
* };
*
* In general, this macro expands to:
*
* fn(node_id, prop, 0) fn(node_id, prop, 1) [...] fn(node_id, prop, n-1)
*
* where "n" is the number of elements in "prop", as it would be
* returned by <tt>DT_PROP_LEN(node_id, prop)</tt>.
*
* The "prop" argument must refer to a property with type string,
* array, uint8-array, string-array, phandles, or phandle-array. It is
* an error to use this macro with properties of other types.
*
* @param node_id node identifier
* @param prop lowercase-and-underscores property name
* @param fn macro to invoke
*/
#define DT_FOREACH_PROP_ELEM(node_id, prop, fn) \
DT_CAT4(node_id, _P_, prop, _FOREACH_PROP_ELEM)(fn)
/**
* @}
@ -2151,6 +2198,19 @@
DT_DRV_COMPAT)(fn)), \
())
/**
* @brief Invokes "fn" for each element of property "prop" for
* a DT_DRV_COMPAT instance.
*
* Equivalent to DT_FOREACH_PROP_ELEM(DT_DRV_INST(inst), prop, fn).
*
* @param inst instance number
* @param prop lowercase-and-underscores property name
* @param fn macro to invoke
*/
#define DT_INST_FOREACH_PROP_ELEM(inst, prop, fn) \
DT_FOREACH_PROP_ELEM(DT_DRV_INST(inst), prop, fn)
/**
* @brief Does a DT_DRV_COMPAT instance have a property?
* @param inst instance number

View file

@ -32,6 +32,12 @@ sys.path.append(os.path.join(os.path.dirname(__file__), 'python-devicetree',
from devicetree import edtlib
# The set of binding types whose values can be iterated over with
# DT_FOREACH_PROP_ELEM(). If you change this, make sure to update the
# doxygen string for that macro.
FOREACH_PROP_ELEM_TYPES = set(['string', 'array', 'uint8-array', 'string-array',
'phandles', 'phandle-array'])
class LogFormatter(logging.Formatter):
'''A log formatter that prints the level name in lower case,
for compatibility with earlier versions of edtlib.'''
@ -490,7 +496,8 @@ def write_vanilla_props(node):
macro2val = {}
for prop_name, prop in node.props.items():
macro = f"{node.z_path_id}_P_{str2ident(prop_name)}"
prop_id = str2ident(prop_name)
macro = f"{node.z_path_id}_P_{prop_id}"
val = prop2value(prop)
if val is not None:
# DT_N_<node-id>_P_<prop-id>
@ -523,6 +530,12 @@ def write_vanilla_props(node):
macro2val[macro + f"_IDX_{i}"] = subval
macro2val[macro + f"_IDX_{i}_EXISTS"] = 1
if prop.type in FOREACH_PROP_ELEM_TYPES:
# DT_N_<node-id>_P_<prop-id>_FOREACH_PROP_ELEM
macro2val[f"{macro}_FOREACH_PROP_ELEM(fn)"] = \
' \\\n\t'.join(f'fn(DT_{node.z_path_id}, {prop_id}, {i})'
for i in range(len(prop.val)))
plen = prop_len(prop)
if plen is not None:
# DT_N_<node-id>_P_<prop-id>_LEN

View file

@ -25,7 +25,10 @@
interrupt-parent = <&test_intc>;
test_arrays: array-holder {
/* vnd,undefined-compat is for DT_NODE_HAS_COMPAT_STATUS(..,okay) */
/*
* vnd,undefined-compat is for DT_NODE_HAS_COMPAT_STATUS(..,okay).
* There should only be one vnd,array-holder in the entire DTS.
*/
compatible = "vnd,array-holder", "vnd,undefined-compat";
a = <1000 2000 3000>;
b = [aa bb cc dd];

View file

@ -1252,6 +1252,34 @@ static void test_arrays(void)
zassert_equal(DT_PROP_LEN(TEST_ARRAYS, c), 2, "");
}
static void test_foreach_prop_elem(void)
{
#define TIMES_TWO(node_id, prop, idx) \
(2 * DT_PROP_BY_IDX(node_id, prop, idx)),
int array[] = {
DT_FOREACH_PROP_ELEM(TEST_ARRAYS, a, TIMES_TWO)
};
zassert_equal(ARRAY_SIZE(array), 3, "");
zassert_equal(array[0], 2000, "");
zassert_equal(array[1], 4000, "");
zassert_equal(array[2], 6000, "");
#undef DT_DRV_COMPAT
#define DT_DRV_COMPAT vnd_array_holder
int inst_array[] = {
DT_INST_FOREACH_PROP_ELEM(0, a, TIMES_TWO)
};
zassert_equal(ARRAY_SIZE(inst_array), ARRAY_SIZE(array), "");
zassert_equal(inst_array[0], array[0], "");
zassert_equal(inst_array[1], array[1], "");
zassert_equal(inst_array[2], array[2], "");
#undef TIMES_TWO
}
struct test_gpio_info {
uint32_t reg_addr;
uint32_t reg_len;
@ -1782,6 +1810,7 @@ void test_main(void)
ztest_unit_test(test_pwms),
ztest_unit_test(test_macro_names),
ztest_unit_test(test_arrays),
ztest_unit_test(test_foreach_prop_elem),
ztest_unit_test(test_devices),
ztest_unit_test(test_cs_gpios),
ztest_unit_test(test_chosen),