From e09c8fb305125fe3ae8d02c4c1bcd3b92ffa897b Mon Sep 17 00:00:00 2001 From: TOKITA Hiroshi Date: Sat, 4 Jan 2025 21:58:52 +0900 Subject: [PATCH 1/8] sys: util: Add `GET_ARGS_FIRST_N` to get the first N of the args Gets the specified number of elements from the beginning of the list. This is the symmetircal operation as `GET_ARGS_LESS_N`. Signed-off-by: TOKITA Hiroshi --- include/zephyr/sys/util_loops.h | 382 ++++++++++++++++++++++++++++++++ include/zephyr/sys/util_macro.h | 10 + 2 files changed, 392 insertions(+) diff --git a/include/zephyr/sys/util_loops.h b/include/zephyr/sys/util_loops.h index 8c71edd16f363..afe2c004a6087 100644 --- a/include/zephyr/sys/util_loops.h +++ b/include/zephyr/sys/util_loops.h @@ -1051,6 +1051,388 @@ _47, _48, _49, _50, _51, _52, _53, _54, _55, \ _56, _57, _58, _59, _60, _61, _62, _63, ...) __VA_ARGS__ +#define Z_GET_ARGS_FIRST_0(...) + +#define Z_GET_ARGS_FIRST_1(_0, ...) _0 + +#define Z_GET_ARGS_FIRST_2(_0, _1, ...) _0, _1 + +#define Z_GET_ARGS_FIRST_3(_0, _1, _2, ...) _0, _1, _2 + +#define Z_GET_ARGS_FIRST_4(_0, _1, _2, _3, ...) _0, _1, _2, _3 + +#define Z_GET_ARGS_FIRST_5(_0, _1, _2, _3, _4, ...) _0, _1, _2, _3, _4 + +#define Z_GET_ARGS_FIRST_6(_0, _1, _2, _3, _4, _5, ...) _0, _1, _2, _3, _4, _5 + +#define Z_GET_ARGS_FIRST_7(_0, _1, _2, _3, _4, _5, _6, ...) _0, _1, _2, _3, _4, _5, _6 + +#define Z_GET_ARGS_FIRST_8(_0, _1, _2, _3, _4, _5, _6, _7, ...) _0, _1, _2, _3, _4, _5, _6, _7 + +#define Z_GET_ARGS_FIRST_9(_0, _1, _2, _3, _4, _5, _6, _7, _8, ...) \ + _0, _1, _2, _3, _4, _5, _6, _7, _8 + +#define Z_GET_ARGS_FIRST_10(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, ...) \ + _0, _1, _2, _3, _4, _5, _6, _7, _8, _9 + +#define Z_GET_ARGS_FIRST_11(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, ...) \ + _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10 + +#define Z_GET_ARGS_FIRST_12(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, ...) \ + _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11 + +#define Z_GET_ARGS_FIRST_13(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, ...) \ + _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12 + +#define Z_GET_ARGS_FIRST_14(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, ...) \ + _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13 + +#define Z_GET_ARGS_FIRST_15(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, ...) \ + _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14 + +#define Z_GET_ARGS_FIRST_16(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, \ + ...) \ + _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15 + +#define Z_GET_ARGS_FIRST_17(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, \ + _16, ...) \ + _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16 + +#define Z_GET_ARGS_FIRST_18(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, \ + _16, _17, ...) \ + _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17 + +#define Z_GET_ARGS_FIRST_19(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, \ + _16, _17, _18, ...) \ + _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18 + +#define Z_GET_ARGS_FIRST_20(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, \ + _16, _17, _18, _19, ...) \ + _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19 + +#define Z_GET_ARGS_FIRST_21(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, \ + _16, _17, _18, _19, _20, ...) \ + _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20 + +#define Z_GET_ARGS_FIRST_22(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, \ + _16, _17, _18, _19, _20, _21, ...) \ + _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21 + +#define Z_GET_ARGS_FIRST_23(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, \ + _16, _17, _18, _19, _20, _21, _22, ...) \ + _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22 + +#define Z_GET_ARGS_FIRST_24(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, \ + _16, _17, _18, _19, _20, _21, _22, _23, ...) \ + _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23 + +#define Z_GET_ARGS_FIRST_25(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, \ + _16, _17, _18, _19, _20, _21, _22, _23, _24, ...) \ + _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24 + +#define Z_GET_ARGS_FIRST_26(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, \ + _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, ...) \ + _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25 + +#define Z_GET_ARGS_FIRST_27(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, \ + _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, ...) \ + _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, _26 + +#define Z_GET_ARGS_FIRST_28(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, \ + _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, ...) \ + _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, _26, _27 + +#define Z_GET_ARGS_FIRST_29(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, \ + _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, ...) \ + _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, _26, _27, _28 + +#define Z_GET_ARGS_FIRST_30(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, \ + _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, \ + ...) \ + _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, _26, _27, _28, _29 + +#define Z_GET_ARGS_FIRST_31(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, \ + _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, \ + _30, ...) \ + _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30 + +#define Z_GET_ARGS_FIRST_32(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, \ + _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, \ + _30, _31, ...) \ + _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31 + +#define Z_GET_ARGS_FIRST_33(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, \ + _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, \ + _30, _31, _32, ...) \ + _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32 + +#define Z_GET_ARGS_FIRST_34(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, \ + _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, \ + _30, _31, _32, _33, ...) \ + _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33 + +#define Z_GET_ARGS_FIRST_35(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, \ + _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, \ + _30, _31, _32, _33, _34, ...) \ + _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34 + +#define Z_GET_ARGS_FIRST_36(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, \ + _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, \ + _30, _31, _32, _33, _34, _35, ...) \ + _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35 + +#define Z_GET_ARGS_FIRST_37(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, \ + _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, \ + _30, _31, _32, _33, _34, _35, _36, ...) \ + _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, \ + _36 + +#define Z_GET_ARGS_FIRST_38(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, \ + _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, \ + _30, _31, _32, _33, _34, _35, _36, _37, ...) \ + _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, \ + _36, _37 + +#define Z_GET_ARGS_FIRST_39(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, \ + _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, \ + _30, _31, _32, _33, _34, _35, _36, _37, _38, ...) \ + _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, \ + _36, _37, _38 + +#define Z_GET_ARGS_FIRST_40(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, \ + _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, \ + _30, _31, _32, _33, _34, _35, _36, _37, _38, _39, ...) \ + _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, \ + _36, _37, _38, _39 + +#define Z_GET_ARGS_FIRST_41(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, \ + _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, \ + _30, _31, _32, _33, _34, _35, _36, _37, _38, _39, _40, ...) \ + _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, \ + _36, _37, _38, _39, _40 + +#define Z_GET_ARGS_FIRST_42(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, \ + _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, \ + _30, _31, _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, ...) \ + _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, \ + _36, _37, _38, _39, _40, _41 + +#define Z_GET_ARGS_FIRST_43(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, \ + _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, \ + _30, _31, _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, ...) \ + _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, \ + _36, _37, _38, _39, _40, _41, _42 + +#define Z_GET_ARGS_FIRST_44(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, \ + _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, \ + _30, _31, _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, \ + ...) \ + _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, \ + _36, _37, _38, _39, _40, _41, _42, _43 + +#define Z_GET_ARGS_FIRST_45(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, \ + _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, \ + _30, _31, _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, \ + _44, ...) \ + _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, \ + _36, _37, _38, _39, _40, _41, _42, _43, _44 + +#define Z_GET_ARGS_FIRST_46(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, \ + _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, \ + _30, _31, _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, \ + _44, _45, ...) \ + _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, \ + _36, _37, _38, _39, _40, _41, _42, _43, _44, _45 + +#define Z_GET_ARGS_FIRST_47(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, \ + _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, \ + _30, _31, _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, \ + _44, _45, _46, ...) \ + _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, \ + _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46 + +#define Z_GET_ARGS_FIRST_48(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, \ + _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, \ + _30, _31, _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, \ + _44, _45, _46, _47, ...) \ + _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, \ + _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46, _47 + +#define Z_GET_ARGS_FIRST_49(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, \ + _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, \ + _30, _31, _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, \ + _44, _45, _46, _47, _48, ...) \ + _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, \ + _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46, _47, _48 + +#define Z_GET_ARGS_FIRST_50(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, \ + _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, \ + _30, _31, _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, \ + _44, _45, _46, _47, _48, _49, ...) \ + _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, \ + _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46, _47, _48, _49 + +#define Z_GET_ARGS_FIRST_51(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, \ + _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, \ + _30, _31, _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, \ + _44, _45, _46, _47, _48, _49, _50, ...) \ + _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, \ + _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46, _47, _48, _49, _50 + +#define Z_GET_ARGS_FIRST_52(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, \ + _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, \ + _30, _31, _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, \ + _44, _45, _46, _47, _48, _49, _50, _51, ...) \ + _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, \ + _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46, _47, _48, _49, _50, _51 + +#define Z_GET_ARGS_FIRST_53(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, \ + _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, \ + _30, _31, _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, \ + _44, _45, _46, _47, _48, _49, _50, _51, _52, ...) \ + _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, \ + _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46, _47, _48, _49, _50, _51, \ + _52 + +#define Z_GET_ARGS_FIRST_54(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, \ + _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, \ + _30, _31, _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, \ + _44, _45, _46, _47, _48, _49, _50, _51, _52, _53, ...) \ + _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, \ + _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46, _47, _48, _49, _50, _51, \ + _52, _53 + +#define Z_GET_ARGS_FIRST_55(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, \ + _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, \ + _30, _31, _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, \ + _44, _45, _46, _47, _48, _49, _50, _51, _52, _53, _54, ...) \ + _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, \ + _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46, _47, _48, _49, _50, _51, \ + _52, _53, _54 + +#define Z_GET_ARGS_FIRST_56(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, \ + _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, \ + _30, _31, _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, \ + _44, _45, _46, _47, _48, _49, _50, _51, _52, _53, _54, _55, ...) \ + _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, \ + _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46, _47, _48, _49, _50, _51, \ + _52, _53, _54, _55 + +#define Z_GET_ARGS_FIRST_57(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, \ + _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, \ + _30, _31, _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, \ + _44, _45, _46, _47, _48, _49, _50, _51, _52, _53, _54, _55, _56, ...) \ + _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, \ + _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46, _47, _48, _49, _50, _51, \ + _52, _53, _54, _55, _56 + +#define Z_GET_ARGS_FIRST_58(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, \ + _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, \ + _30, _31, _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, \ + _44, _45, _46, _47, _48, _49, _50, _51, _52, _53, _54, _55, _56, _57, \ + ...) \ + _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, \ + _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46, _47, _48, _49, _50, _51, \ + _52, _53, _54, _55, _56, _57 + +#define Z_GET_ARGS_FIRST_59(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, \ + _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, \ + _30, _31, _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, \ + _44, _45, _46, _47, _48, _49, _50, _51, _52, _53, _54, _55, _56, _57, \ + _58, ...) \ + _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, \ + _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46, _47, _48, _49, _50, _51, \ + _52, _53, _54, _55, _56, _57, _58 + +#define Z_GET_ARGS_FIRST_60(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, \ + _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, \ + _30, _31, _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, \ + _44, _45, _46, _47, _48, _49, _50, _51, _52, _53, _54, _55, _56, _57, \ + _58, _59, ...) \ + _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, \ + _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46, _47, _48, _49, _50, _51, \ + _52, _53, _54, _55, _56, _57, _58, _59 + +#define Z_GET_ARGS_FIRST_61(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, \ + _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, \ + _30, _31, _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, \ + _44, _45, _46, _47, _48, _49, _50, _51, _52, _53, _54, _55, _56, _57, \ + _58, _59, _60, ...) \ + _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, \ + _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46, _47, _48, _49, _50, _51, \ + _52, _53, _54, _55, _56, _57, _58, _59, _60 + +#define Z_GET_ARGS_FIRST_62(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, \ + _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, \ + _30, _31, _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, \ + _44, _45, _46, _47, _48, _49, _50, _51, _52, _53, _54, _55, _56, _57, \ + _58, _59, _60, _61, ...) \ + _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, \ + _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46, _47, _48, _49, _50, _51, \ + _52, _53, _54, _55, _56, _57, _58, _59, _60, _61 + +#define Z_GET_ARGS_FIRST_63(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, \ + _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, \ + _30, _31, _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, \ + _44, _45, _46, _47, _48, _49, _50, _51, _52, _53, _54, _55, _56, _57, \ + _58, _59, _60, _61, _62, ...) \ + _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, \ + _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46, _47, _48, _49, _50, _51, \ + _52, _53, _54, _55, _56, _57, _58, _59, _60, _61, _62 + +#define Z_GET_ARGS_FIRST_64(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, \ + _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, \ + _30, _31, _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, \ + _44, _45, _46, _47, _48, _49, _50, _51, _52, _53, _54, _55, _56, _57, \ + _58, _59, _60, _61, _62, _63, ...) \ + _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, \ + _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, \ + _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46, _47, _48, _49, _50, _51, \ + _52, _53, _54, _55, _56, _57, _58, _59, _60, _61, _62, _63 + #define Z_FOR_EACH_IDX_FIXED_ARG_EXEC(idx, x, fixed_arg0, fixed_arg1) \ fixed_arg0(idx, x, fixed_arg1) diff --git a/include/zephyr/sys/util_macro.h b/include/zephyr/sys/util_macro.h index c8901e21454ae..cba3a6140a76a 100644 --- a/include/zephyr/sys/util_macro.h +++ b/include/zephyr/sys/util_macro.h @@ -400,6 +400,16 @@ extern "C" { */ #define GET_ARGS_LESS_N(N, ...) Z_GET_ARGS_LESS_##N(__VA_ARGS__) +/** + * @brief Get the first N arguments from the argument list. + * + * @param N Number of arguments to take. + * @param ... Variable list of arguments. + * + * @return argument list only contains first N arguments. + */ +#define GET_ARGS_FIRST_N(N, ...) Z_GET_ARGS_FIRST_##N(__VA_ARGS__) + /** * @brief Like a || b, but does evaluation and * short-circuiting at C preprocessor time. From 1c6134a0f4b29b275bdc1b59da46a13de6365cad Mon Sep 17 00:00:00 2001 From: TOKITA Hiroshi Date: Sat, 18 Jan 2025 18:47:01 +0900 Subject: [PATCH 2/8] tests: unit: util: Add test for `GET_ARGS_FIRST_N` Add `GET_ARGS_FIRST_N` tests. Signed-off-by: TOKITA Hiroshi --- tests/unit/util/main.c | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/tests/unit/util/main.c b/tests/unit/util/main.c index 6da45befcabaf..d976b8a8e8d2e 100644 --- a/tests/unit/util/main.c +++ b/tests/unit/util/main.c @@ -527,6 +527,28 @@ ZTEST(util, test_GET_ARGS_LESS_N) { zassert_equal(c[0], 3); } +ZTEST(util, test_GET_ARGS_FIRST_N) +{ + uint8_t a[] = {GET_ARGS_FIRST_N(0, 1, 2, 3)}; + uint8_t b[] = {GET_ARGS_FIRST_N(1, 1, 2, 3)}; + uint8_t c[] = {GET_ARGS_FIRST_N(2, 1, 2, 3)}; + uint8_t d[] = {GET_ARGS_FIRST_N(3, 1, 2, 3)}; + + zassert_equal(sizeof(a), 0); + + zassert_equal(sizeof(b), 1); + zassert_equal(b[0], 1); + + zassert_equal(sizeof(c), 2); + zassert_equal(c[0], 1); + zassert_equal(c[1], 2); + + zassert_equal(sizeof(d), 3); + zassert_equal(d[0], 1); + zassert_equal(d[1], 2); + zassert_equal(d[2], 3); +} + ZTEST(util, test_mixing_GET_ARG_and_FOR_EACH) { #undef TEST_MACRO #define TEST_MACRO(x) x, From 1c7f32826f23cad2bdedf5ff629e14b06867f922 Mon Sep 17 00:00:00 2001 From: TOKITA Hiroshi Date: Tue, 25 Mar 2025 12:00:37 +0900 Subject: [PATCH 3/8] dts: bindings: Change `*-map-mask` and `*-map-pass-thru` as array type. They must be `array` type to match the existing `interrupt-map-mask` and `interrupt-map-pass-thru`. Signed-off-by: TOKITA Hiroshi --- dts/bindings/gpio/gpio-nexus.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dts/bindings/gpio/gpio-nexus.yaml b/dts/bindings/gpio/gpio-nexus.yaml index a1bcbc61e5ae0..de8153b0f9a89 100644 --- a/dts/bindings/gpio/gpio-nexus.yaml +++ b/dts/bindings/gpio/gpio-nexus.yaml @@ -9,10 +9,10 @@ properties: required: true gpio-map-mask: - type: compound + type: array gpio-map-pass-thru: - type: compound + type: array "#gpio-cells": type: int From f60e3017bdb15ffa771c29c60221c0ba162489dd Mon Sep 17 00:00:00 2001 From: TOKITA Hiroshi Date: Tue, 25 Mar 2025 21:12:09 +0900 Subject: [PATCH 4/8] dts: bindings: Add interrupt-nexus and io-channel-nexus base bindings Add a base binding for interrupt and io-channel nexus nodes. This makes the implicit definitions explicit. Replace existing individual map definitions with this. This file does not define a specific `compatible` on its own, So you should include this file before using it practically. Signed-off-by: TOKITA Hiroshi --- dts/bindings/adc/arduino,uno-adc.yaml | 18 +---------------- dts/bindings/iio/io-channel-nexus.yaml | 20 +++++++++++++++++++ .../interrupt-controller/interrupt-nexus.yaml | 20 +++++++++++++++++++ .../pcie/host/pci-host-ecam-generic.yaml | 8 +------- 4 files changed, 42 insertions(+), 24 deletions(-) create mode 100644 dts/bindings/iio/io-channel-nexus.yaml create mode 100644 dts/bindings/interrupt-controller/interrupt-nexus.yaml diff --git a/dts/bindings/adc/arduino,uno-adc.yaml b/dts/bindings/adc/arduino,uno-adc.yaml index 854e640914a4f..6d409b861b13f 100644 --- a/dts/bindings/adc/arduino,uno-adc.yaml +++ b/dts/bindings/adc/arduino,uno-adc.yaml @@ -13,20 +13,4 @@ description: | compatible: "arduino,uno-adc" -include: base.yaml - -properties: - io-channel-map: - type: compound - required: true - - io-channel-map-mask: - type: compound - - io-channel-map-pass-thru: - type: compound - - "#io-channel-cells": - type: int - required: true - description: Number of items to expect in an ADC specifier +include: [base.yaml, io-channel-nexus.yaml] diff --git a/dts/bindings/iio/io-channel-nexus.yaml b/dts/bindings/iio/io-channel-nexus.yaml new file mode 100644 index 0000000000000..d34ca7b5be607 --- /dev/null +++ b/dts/bindings/iio/io-channel-nexus.yaml @@ -0,0 +1,20 @@ +# Copyright (c) 2025 TOKITA Hiroshi +# SPDX-License-Identifier: Apache-2.0 + +# Common fields for io-channel nexus + +properties: + io-channel-map: + type: compound + required: true + + io-channel-map-mask: + type: array + + io-channel-map-pass-thru: + type: array + + "#io-channel-cells": + type: int + required: true + description: Number of items to expect in the io-channel specifier, such as ADC channels. diff --git a/dts/bindings/interrupt-controller/interrupt-nexus.yaml b/dts/bindings/interrupt-controller/interrupt-nexus.yaml new file mode 100644 index 0000000000000..cc7d6060fd023 --- /dev/null +++ b/dts/bindings/interrupt-controller/interrupt-nexus.yaml @@ -0,0 +1,20 @@ +# Copyright (c) 2025 TOKITA Hiroshi +# SPDX-License-Identifier: Apache-2.0 + +# Common fields for interrupt nexus nodes + +properties: + interrupt-map: + type: compound + required: true + + interrupt-map-mask: + type: array + + interrupt-map-pass-thru: + type: array + + "#interrupt-cells": + type: int + required: true + description: Number of items to expect in a interrupt specifier diff --git a/dts/bindings/pcie/host/pci-host-ecam-generic.yaml b/dts/bindings/pcie/host/pci-host-ecam-generic.yaml index fea7d35ef17a5..0a42b180665a3 100644 --- a/dts/bindings/pcie/host/pci-host-ecam-generic.yaml +++ b/dts/bindings/pcie/host/pci-host-ecam-generic.yaml @@ -5,7 +5,7 @@ description: PCIe Controller in ECAM mode compatible: "pci-host-ecam-generic" -include: pcie-controller.yaml +include: [pcie-controller.yaml, interrupt-nexus.yaml] properties: reg: @@ -22,11 +22,5 @@ properties: definition of non-prefetchable memory. One or both of prefetchable Memory and IO Space may also be provided. - interrupt-map-mask: - type: array - - interrupt-map: - type: compound - bus-range: type: array From a1b09acfe262ae469a0192f81c54624baf0b4253 Mon Sep 17 00:00:00 2001 From: TOKITA Hiroshi Date: Sun, 23 Mar 2025 20:07:55 +0900 Subject: [PATCH 5/8] scripts: dts: Add handling for `*-map` property This change introduces generating definitions corresponding to `*-map` property, which was currently discarded. For `*-map` properties are made able to be treated as a variation of phandle-array, assign sequential cell names for each group of specifiers (child_specifier_0, child_specifier_1, ..., parent_specifier_0, ...). The `*-map` data is like a two-dimensional array, so it is difficult to handle with the existing APIs, so we will also provide new APIs. Signed-off-by: TOKITA Hiroshi --- include/zephyr/devicetree.h | 1 + include/zephyr/devicetree/map.h | 331 ++++++++++++++++++ scripts/dts/gen_defines.py | 67 ++++ .../src/devicetree/edtlib.py | 91 +++++ 4 files changed, 490 insertions(+) create mode 100644 include/zephyr/devicetree/map.h diff --git a/include/zephyr/devicetree.h b/include/zephyr/devicetree.h index ded261d66cd08..c0c650e11971d 100644 --- a/include/zephyr/devicetree.h +++ b/include/zephyr/devicetree.h @@ -5570,5 +5570,6 @@ #include #include #include +#include #endif /* ZEPHYR_INCLUDE_DEVICETREE_H_ */ diff --git a/include/zephyr/devicetree/map.h b/include/zephyr/devicetree/map.h new file mode 100644 index 0000000000000..3afca063412bb --- /dev/null +++ b/include/zephyr/devicetree/map.h @@ -0,0 +1,331 @@ +/* + * Copyright (c) 2025 TOKITA Hiroshi + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_DEVICETREE_MAP_H_ +#define ZEPHYR_INCLUDE_DEVICETREE_MAP_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @defgroup devicetree-map Devicetree Map API + * + * @brief Helper macros for handling map properties. + * + * This module provides helper macros that facilitate interrupt mapping and + * specifier mapping based on DeviceTree specifications. It enables the extraction + * and interpretation of mapping data represented as phandle-arrays. + * + * In a typical DeviceTree fragment, properties ending with "-map" specify: + * - The child specifier to be mapped. + * - The parent node (phandle) to which the mapping applies. + * - The parent specifier associated with the mapping. + * + * For example, when the following DeviceTree snippet is defined: + * + * @code{.dts} + * n: node { + * gpio-map = <0 1 &gpio0 2 3>, <4 5 &gpio0 6 7>; + * }; + * @endcode + * + * In the first mapping entry: + * - `0 1` are the child specifiers. + * - &gpio0 is the parent node. + * - `2 3` are the parent specifiers. + * + * Since map properties are implemented as phandle-arrays, macros such as + * DT_PHANDLE_BY_IDX() and DT_PHA_BY_IDX() can be used to access individual elements. + * + * Both child and parent specifiers are treated as cells in a phandle-array. + * By default, each group of specifiers is given a sequential cell name + * (child_specifier_0, child_specifier_1, ..., parent_specifier_0, ...). + * + * If cell names are specified in dt-bindings, they will be used for the child specifier cell names. + * Parent specifiers always use the default naming convention. + * + * Example usage: + * + * A mapping entry is a phandle-array whose elements can be referenced as follows: + * - Child specifiers can be accessed via names such as `child_specifier_0`, + * `child_specifier_1`, ... + * - The parent node is accessed via DT_PHANDLE_BY_IDX(). + * - Parent specifiers are accessed via names such as `parent_specifier_0`, + * `parent_specifier_1`, ... + * + * @code{.c} + * int cspec_0 = DT_PHA_BY_IDX(DT_NODELABEL(n), gpio_map, 0, child_specifier_0); // 0 + * int cspec_1 = DT_PHA_BY_IDX(DT_NODELABEL(n), gpio_map, 0, child_specifier_1); // 1 + * const struct device *parent = + * device_get_binding(DT_PHANDLE_BY_IDX(DT_NODELABEL(n), gpio_map, 0)); // &gpio0 + * int pspec_0 = DT_PHA_BY_IDX(DT_NODELABEL(n), gpio_map, 0, parent_specifier_0); // 2 + * int pspec_1 = DT_PHA_BY_IDX(DT_NODELABEL(n), gpio_map, 0, parent_specifier_1); // 3 + * @endcode + * + * The map helper API also provides the following macros for convenient access to + * specific parts of a mapping entry: + * - DT_MAP_CHILD_SPECIFIER_ARGS_BY_IDX() + * - DT_MAP_PARENT_SPECIFIER_ARGS_BY_IDX() + * - DT_MAP_PARENT_ARG_BY_IDX() + * + * These macros extract, respectively, the child specifier arguments, the parent specifier + * arguments, and the parent node argument from a mapping element identified by its node ID, + * property name, and index. + * + * For instance: + * + * @code{.c} + * #define SRC_AND_DST(node_id, prop, idx) \ + * { GET_ARG_N(1, DT_MAP_CHILD_SPECIFIER_ARGS_BY_IDX(node_id, prop, idx)), \ + * GET_ARG_N(1, DT_MAP_PARENT_SPECIFIER_ARGS_BY_IDX(node_id, prop, idx)) } + * + * int src_and_dst[2][] = { + * DT_FOREACH_PROP_ELEM_SEP(DT_NODELABEL(n), gpio_map, SRC_AND_DST, (,)) + * }; + * @endcode + * + * The above expansion yields: + * + * @code{.c} + * int src_and_dst[2][] = {{0, 2}, {4, 6}}; + * @endcode + * + * @ingroup devicetree + * @{ + */ + +/** + * @brief Extracts a specified range of arguments. + * + * This helper macro first skips a given number of arguments and then selects + * the first @p len arguments from the remaining list. + * + * @param start The number of arguments to skip. + * @param len The number of arguments to extract after skipping. + * @param ... The list of input arguments. + */ +#define DT_MAP_HELPER_DO_ARGS_RANGE(start, len, ...) \ + GET_ARGS_FIRST_N(len, GET_ARGS_LESS_N(start, __VA_ARGS__)) + +/** + * @brief Extracts a range of mapping arguments for a specific field. + * + * This macro concatenates the field name with the appropriate suffixes to determine + * the starting index and length of the arguments for a map entry, and then extracts + * those arguments. + * + * @param name The mapping field name (e.g., CHILD_SPECIFIER, PARENT). + * @param node_id The node identifier. + * @param prop The property name in lowercase and underscores. + * @param idx The index of the mapping entry. + * @param ... Additional arguments corresponding to the mapping entry. + */ +#define DT_MAP_HELPER_ARGS_RANGE(name, node_id, prop, idx, ...) \ + DT_MAP_HELPER_DO_ARGS_RANGE(DT_CAT3(DT_MAP_, name, _POS_BY_IDX)(node_id, prop, idx), \ + DT_CAT3(DT_MAP_, name, _LEN_BY_IDX)(node_id, prop, idx), \ + __VA_ARGS__) + +/** + * @brief Retrieves the mapping entry at the specified index. + * + * @param node_id The node identifier. + * @param prop The property name in lowercase with underscores. + * @param idx The mapping entry index. + * @return The mapping entry as a list of comma-separated values. + */ +#define DT_MAP_BY_IDX(node_id, prop, idx) DT_CAT5(node_id, _P_, prop, _MAP_IDX_, idx) + +/** + * @brief Retrieves the first mapping entry. + * @see DT_MAP_BY_IDX + */ +#define DT_MAP(node_id, prop) DT_MAP_BY_IDX(node_id, prop, 0) + +/** + * @brief Returns the number of mapping entries for the given property. + * + * @param node_id The node identifier. + * @param prop The property name in lowercase with underscores. + * @return The total count of mapping entries. + */ +#define DT_MAP_LEN(node_id, prop) DT_CAT4(node_id, _P_, prop, _MAP_LEN) + +/** + * @brief Retrieves the starting index of the child specifier cell within a mapping entry. + * + * @param node_id The node identifier. + * @param prop The property name. + * @param idx The mapping entry index. + * @return The starting index of the child specifier cell. + */ +#define DT_MAP_CHILD_SPECIFIER_POS_BY_IDX(node_id, prop, idx) \ + DT_CAT7(node_id, _P_, prop, _MAP_IDX_, idx, _, CHILD_SPECIFIER_POS) + +/** + * @brief Retrieves the starting index of the child specifier cell within the first mapping entry. + * @see DT_MAP_CHILD_SPECIFIER_POS_BY_IDX + */ +#define DT_MAP_CHILD_SPECIFIER_POS(node_id, prop) \ + DT_MAP_CHILD_SPECIFIER_POS_BY_IDX(node_id, prop, 0) + +/** + * @brief Returns the length (number of cells) of the child specifier within a mapping entry. + * + * @param node_id The node identifier. + * @param prop The property name. + * @param idx The mapping entry index. + * @return The length (in cells) of the child specifier. + */ +#define DT_MAP_CHILD_SPECIFIER_LEN_BY_IDX(node_id, prop, idx) \ + DT_CAT7(node_id, _P_, prop, _MAP_IDX_, idx, _, CHILD_SPECIFIER_LEN) + +/** + * @brief Returns the length (number of cells) of the child specifier within the first mapping + * entry. + * @see DT_MAP_CHILD_SPECIFIER_LEN_BY_IDX + */ +#define DT_MAP_CHILD_SPECIFIER_LEN(node_id, prop) \ + DT_MAP_CHILD_SPECIFIER_LEN_BY_IDX(node_id, prop, 0) + +/** + * @brief Retrieves the starting index of the parent cell in a mapping entry. + * + * @param node_id The node identifier. + * @param prop The property name. + * @param idx The mapping entry index. + * @return The starting index of the parent cell. + */ +#define DT_MAP_PARENT_POS_BY_IDX(node_id, prop, idx) \ + DT_CAT7(node_id, _P_, prop, _MAP_IDX_, idx, _, PARENT_POS) + +/** + * @brief Retrieves the starting index of the parent cell in the first mapping entry. + * @see DT_MAP_PARENT_POS_BY_IDX + */ +#define DT_MAP_PARENT_POS(node_id, prop) DT_MAP_PARENT_POS_BY_IDX(node_id, prop, 0) + +/** + * @brief Returns the length (number of cells) of the parent cell in a mapping entry. + * + * @param node_id The node identifier. + * @param prop The property name. + * @param idx The mapping entry index. + * @return The length (in cells) of the parent cell. + */ +#define DT_MAP_PARENT_LEN_BY_IDX(node_id, prop, idx) \ + DT_CAT7(node_id, _P_, prop, _MAP_IDX_, idx, _, PARENT_LEN) + +/** + * @brief Returns the length (number of cells) of the parent cell in the first mapping entry. + * @see DT_MAP_PARENT_LEN_BY_IDX + */ +#define DT_MAP_PARENT_LEN(node_id, prop) DT_MAP_PARENT_LEN_BY_IDX(node_id, prop, 0) + +/** + * @brief Retrieves the starting index of the parent specifier cell within a mapping entry. + * + * @param node_id The node identifier. + * @param prop The property name. + * @param idx The mapping entry index. + * @return The starting index of the parent specifier cell. + */ +#define DT_MAP_PARENT_SPECIFIER_POS_BY_IDX(node_id, prop, idx) \ + DT_CAT7(node_id, _P_, prop, _MAP_IDX_, idx, _, PARENT_SPECIFIER_POS) + +/** + * @brief Retrieves the starting index of the parent specifier cell within the first mapping entry. + * @see DT_MAP_PARENT_SPECIFIER_POS_BY_IDX + */ +#define DT_MAP_PARENT_SPECIFIER_POS(node_id, prop) \ + DT_MAP_PARENT_SPECIFIER_POS_BY_IDX(node_id, prop, 0) + +/** + * @brief Returns the length (number of cells) of the parent specifier in a mapping entry. + * + * @param node_id The node identifier. + * @param prop The property name. + * @param idx The mapping entry index. + * @return The length (in cells) of the parent specifier. + */ +#define DT_MAP_PARENT_SPECIFIER_LEN_BY_IDX(node_id, prop, idx) \ + DT_CAT7(node_id, _P_, prop, _MAP_IDX_, idx, _, PARENT_SPECIFIER_LEN) + +/** + * @brief Returns the length (number of cells) of the parent specifier of the first mapping entry. + * @see DT_MAP_PARENT_SPECIFIER_LEN_BY_IDX + */ +#define DT_MAP_PARENT_SPECIFIER_LEN(node_id, prop) \ + DT_MAP_PARENT_SPECIFIER_LEN_BY_IDX(node_id, prop, 0) + +/** + * @brief Extracts the child specifier arguments from a mapping entry. + * + * This macro returns the comma-separated list of arguments for the child specifier. + * + * @param node_id The node identifier. + * @param prop The property name in lowercase with underscores. + * @param idx The mapping entry index. + * @return The child specifier arguments. + */ +#define DT_MAP_CHILD_SPECIFIER_ARGS_BY_IDX(node_id, prop, idx) \ + DT_MAP_HELPER_ARGS_RANGE(CHILD_SPECIFIER, node_id, prop, idx, \ + DT_MAP_BY_IDX(node_id, prop, idx)) + +/** + * @brief Extracts the child specifier arguments from the first mapping entry. + * @see DT_MAP_CHILD_SPECIFIER_ARGS_BY_IDX + */ +#define DT_MAP_CHILD_SPECIFIER_ARGS(node_id, prop) \ + DT_MAP_CHILD_SPECIFIER_ARGS_BY_IDX(node_id, prop, 0) + +/** + * @brief Extracts the parent node argument from a mapping entry. + * + * @param node_id The node identifier. + * @param prop The property name in lowercase with underscores. + * @param idx The mapping entry index. + * @return The parent node argument. + */ +#define DT_MAP_PARENT_ARG_BY_IDX(node_id, prop, idx) \ + DT_MAP_HELPER_ARGS_RANGE(PARENT, node_id, prop, idx, DT_MAP_BY_IDX(node_id, prop, idx)) + +/** + * @brief Extracts the parent node argument from the first mapping entry. + * @see DT_MAP_PARENT_ARG_BY_IDX + */ +#define DT_MAP_PARENT_ARG(node_id, prop) DT_MAP_PARENT_ARG_BY_IDX(node_id, prop, 0) + +/** + * @brief Extracts the parent specifier arguments from a mapping entry. + * + * This macro returns the comma-separated list of arguments for the parent specifier. + * + * @param node_id The node identifier. + * @param prop The property name in lowercase with underscores. + * @param idx The mapping entry index. + * @return The parent specifier arguments. + */ +#define DT_MAP_PARENT_SPECIFIER_ARGS_BY_IDX(node_id, prop, idx) \ + DT_MAP_HELPER_ARGS_RANGE(PARENT_SPECIFIER, node_id, prop, idx, \ + DT_MAP_BY_IDX(node_id, prop, idx)) + +/** + * @brief Extracts the parent specifier arguments of the first mapping entry. + * @see DT_MAP_PARENT_SPECIFIER_ARGS_BY_IDX + */ +#define DT_MAP_PARENT_SPECIFIER_ARGS(node_id, prop) \ + DT_MAP_PARENT_SPECIFIER_ARGS_BY_IDX(node_id, prop, 0) + +/** + * @} + */ + +#ifdef __cplusplus +} +#endif + +#endif /* ZEPHYR_INCLUDE_DEVICETREE_MAP_H_ */ diff --git a/scripts/dts/gen_defines.py b/scripts/dts/gen_defines.py index e3913dd585b79..42df47315942a 100755 --- a/scripts/dts/gen_defines.py +++ b/scripts/dts/gen_defines.py @@ -288,6 +288,7 @@ def write_special_props(node: edtlib.Node) -> None: write_pinctrls(node) write_fixed_partitions(node) write_gpio_hogs(node) + write_maps(node) def write_ranges(node: edtlib.Node) -> None: @@ -579,6 +580,72 @@ def write_gpio_hogs(node: edtlib.Node) -> None: out_dt_define(macro, val) +def write_maps(node: edtlib.Node) -> None: + if len(node.maps) == 0: + return + + out_comment("Map properties:") + + basename = str2ident(node.maps[0].basename) + macro = f"{node.z_path_id}_P_{basename}_map" + macro2val = {} + + for i, cd in enumerate(node.maps): + if basename != str2ident(cd.basename): + err(f"Map basename mismatch: {basename} != {str2ident(cd.basename)}") + + macro2val.update(controller_and_data_macros(cd, i, macro, "")) + + prop_id = f"{basename}_map" + plen = len(node.maps) + # DT_N__P__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(plen) + ) + + # DT_N__P__FOREACH_PROP_ELEM_SEP + macro2val[f"{macro}_FOREACH_PROP_ELEM_SEP(fn, sep)"] = ' DT_DEBRACKET_INTERNAL sep \\\n\t'.join( + f'fn(DT_{node.z_path_id}, {prop_id}, {i})' for i in range(plen) + ) + + # DT_N__P__FOREACH_PROP_ELEM_VARGS + macro2val[f"{macro}_FOREACH_PROP_ELEM_VARGS(fn, ...)"] = ' \\\n\t'.join( + f'fn(DT_{node.z_path_id}, {prop_id}, {i}, __VA_ARGS__)' for i in range(plen) + ) + + # DT_N__P__FOREACH_PROP_ELEM_SEP_VARGS + macro2val[f"{macro}_FOREACH_PROP_ELEM_SEP_VARGS(fn, sep, ...)"] = ( + ' DT_DEBRACKET_INTERNAL sep \\\n\t'.join( + f'fn(DT_{node.z_path_id}, {prop_id}, {i}, __VA_ARGS__)' for i in range(plen) + ) + ) + + macro2val[f"{macro}_LEN"] = plen + macro2val[f"{macro}_EXISTS"] = 1 + + for i, cd in enumerate(node.maps): + parent_specifier_len = len([k for k in cd.data if k.startswith('parent_specifier_')]) + child_specifiers = list(cd.data.values())[:-parent_specifier_len] + parent_specifiers = list(cd.data.values())[-parent_specifier_len:] + child_specifier_len = len(child_specifiers) + + args = [] + args.extend([str(v) for v in child_specifiers]) + args.extend(["DT_" + node_z_path_id(cd.controller)]) + args.extend([str(v) for v in parent_specifiers]) + + macro2val[f"{macro}_MAP_IDX_{i}"] = ", ".join(args) + macro2val[f"{macro}_MAP_IDX_{i}_CHILD_SPECIFIER_POS"] = 0 + macro2val[f"{macro}_MAP_IDX_{i}_CHILD_SPECIFIER_LEN"] = child_specifier_len + macro2val[f"{macro}_MAP_IDX_{i}_PARENT_POS"] = child_specifier_len + macro2val[f"{macro}_MAP_IDX_{i}_PARENT_LEN"] = 1 + macro2val[f"{macro}_MAP_IDX_{i}_PARENT_SPECIFIER_POS"] = child_specifier_len + 1 + macro2val[f"{macro}_MAP_IDX_{i}_PARENT_SPECIFIER_LEN"] = parent_specifier_len + + for mc, val in macro2val.items(): + out_dt_define(mc, val) + + def write_vanilla_props(node: edtlib.Node) -> None: # Writes macros for any and all properties defined in the # "properties" section of the binding for the node. diff --git a/scripts/dts/python-devicetree/src/devicetree/edtlib.py b/scripts/dts/python-devicetree/src/devicetree/edtlib.py index 916cdc230d236..e9091388312d4 100644 --- a/scripts/dts/python-devicetree/src/devicetree/edtlib.py +++ b/scripts/dts/python-devicetree/src/devicetree/edtlib.py @@ -1321,6 +1321,97 @@ def gpio_hogs(self) -> list[ControllerAndData]: return res + @property + def maps(self) -> list[ControllerAndData]: + res: list[ControllerAndData] = [] + + def count_cells_num(node: dtlib_Node, specifier: str) -> int: + """ + Calculate the number of cells in the node. + When calculating the number of interrupt cells, + add up the values of the address cells. + """ + + if node is None: + _err("node is None.") + + num = node.props[f"#{specifier}-cells"].to_num() + + if specifier == "interrupt": + parent_props = None + if node.parent: + parent_props = node.parent.props + + if "#address-cells" in node.props: + num = num + node.props["#address-cells"].to_num() + elif parent_props and "#address-cells" in parent_props: + num = num + parent_props["#address-cells"].to_num() + else: + _err("Neither the node nor its parent has `#address-cells` property") + + return num + + for prop in [v for k, v in self._node.props.items() if k.endswith("-map")]: + specifier_space = prop.name[:-4] # Strip '-map' + raw = prop.value + while raw: + if len(raw) < 4: + # Not enough room for phandle + _err("bad value for " + repr(prop)) + + child_specifier_num = count_cells_num(prop.node, specifier_space) + + child_specifiers = to_nums(raw[: 4 * child_specifier_num]) + raw = raw[4 * child_specifier_num :] + phandle = to_num(raw[:4]) + raw = raw[4:] + + controller_node = prop.node.dt.phandle2node.get(phandle) + if controller_node is None: + _err(f"controller node cannot be found from phandle:{phandle}") + + controller: Node = self.edt._node2enode[controller_node] + if controller is None: + _err("controller cannot be found from: " + repr(controller_node)) + + parent_specifier_num = count_cells_num(controller_node, specifier_space) + parent_specifiers = to_nums(raw[: 4 * parent_specifier_num]) + raw = raw[4 * parent_specifier_num :] + + # Although this is rare, if a cell-name is specified for the map node, + # it will be reflected. + # If not specified, the name of child_specifier_[i] will be set. + values: dict[str, int] = {} + for i, v in enumerate(child_specifiers): + cell_name = f"child_specifier_{i}" + if (self._binding and + self._binding.specifier2cells and + specifier_space in self._binding.specifier2cells and + i < len(self._binding.specifier2cells[specifier_space])): + cell_name = self._binding.specifier2cells[specifier_space][i] + + values[cell_name] = v + + # The cell name for parent_specifier cannot be determined. + # For convenience, we assign it the name parent_specifier_[i]. + for i, v in enumerate(parent_specifiers): + values[f"parent_specifier_{i}"] = v + + res.append( + ControllerAndData( + node=self, + controller=controller, + data=values, + name=None, + basename=specifier_space, + ) + ) + + if len(raw) != 0: + _err(f"unexpected prop.value remainings: {raw}") + + return res + @property def has_child_binding(self) -> bool: """ From 71eaaa8280c2ee4d1275d99aff12f6135955436f Mon Sep 17 00:00:00 2001 From: TOKITA Hiroshi Date: Sat, 20 Sep 2025 13:07:53 +0900 Subject: [PATCH 6/8] scripts: dts: devicetree: tests: Add a map property test Add a map property test for `test_edtlib.py`. Signed-off-by: TOKITA Hiroshi --- .../python-devicetree/tests/test_edtlib.py | 110 ++++++++++++++++++ 1 file changed, 110 insertions(+) diff --git a/scripts/dts/python-devicetree/tests/test_edtlib.py b/scripts/dts/python-devicetree/tests/test_edtlib.py index f26b5fa158150..6e9c5914889b3 100644 --- a/scripts/dts/python-devicetree/tests/test_edtlib.py +++ b/scripts/dts/python-devicetree/tests/test_edtlib.py @@ -130,6 +130,116 @@ def test_interrupts(): edtlib.ControllerAndData(node=node, controller=edt.get_node('/interrupt-map-bitops-test/controller'), data={'one': 3, 'two': 2}, name=None, basename=None) ] + +def test_maps(): + '''Tests for the maps property.''' + with from_here(): + edt = edtlib.EDT("test.dts", ["test-bindings"]) + + nexus = edt.get_node("/interrupt-map-test/nexus") + controller_0 = edt.get_node("/interrupt-map-test/controller-0") + controller_1 = edt.get_node("/interrupt-map-test/controller-1") + controller_2 = edt.get_node("/interrupt-map-test/controller-2") + + assert nexus.maps == [ + edtlib.ControllerAndData( + node=nexus, + controller=controller_0, + data={ + "child_specifier_0": 0, + "child_specifier_1": 0, + "child_specifier_2": 0, + "child_specifier_3": 0, + "parent_specifier_0": 0, + "parent_specifier_1": 0, + }, + name=None, + basename="interrupt", + ), + edtlib.ControllerAndData( + node=nexus, + controller=controller_1, + data={ + "child_specifier_0": 0, + "child_specifier_1": 0, + "child_specifier_2": 0, + "child_specifier_3": 1, + "parent_specifier_0": 0, + "parent_specifier_1": 0, + "parent_specifier_2": 0, + "parent_specifier_3": 1, + }, + name=None, + basename="interrupt", + ), + edtlib.ControllerAndData( + node=nexus, + controller=controller_2, + data={ + "child_specifier_0": 0, + "child_specifier_1": 0, + "child_specifier_2": 0, + "child_specifier_3": 2, + "parent_specifier_0": 0, + "parent_specifier_1": 0, + "parent_specifier_2": 0, + "parent_specifier_3": 0, + "parent_specifier_4": 0, + "parent_specifier_5": 2, + }, + name=None, + basename="interrupt", + ), + edtlib.ControllerAndData( + node=nexus, + controller=controller_0, + data={ + "child_specifier_0": 0, + "child_specifier_1": 1, + "child_specifier_2": 0, + "child_specifier_3": 0, + "parent_specifier_0": 0, + "parent_specifier_1": 3, + }, + name=None, + basename="interrupt", + ), + edtlib.ControllerAndData( + node=nexus, + controller=controller_1, + data={ + "child_specifier_0": 0, + "child_specifier_1": 1, + "child_specifier_2": 0, + "child_specifier_3": 1, + "parent_specifier_0": 0, + "parent_specifier_1": 0, + "parent_specifier_2": 0, + "parent_specifier_3": 4, + }, + name=None, + basename="interrupt", + ), + edtlib.ControllerAndData( + node=nexus, + controller=controller_2, + data={ + "child_specifier_0": 0, + "child_specifier_1": 1, + "child_specifier_2": 0, + "child_specifier_3": 2, + "parent_specifier_0": 0, + "parent_specifier_1": 0, + "parent_specifier_2": 0, + "parent_specifier_3": 0, + "parent_specifier_4": 0, + "parent_specifier_5": 5, + }, + name=None, + basename="interrupt", + ), + ] + def test_ranges(): '''Tests for the ranges property''' with from_here(): From 6ace2edd96883314b9738e1868a64511c21a5286 Mon Sep 17 00:00:00 2001 From: TOKITA Hiroshi Date: Mon, 14 Apr 2025 01:58:25 +0900 Subject: [PATCH 7/8] tests: lib: devicetree: api: Add map property tests Add tests for map property related macros. Add `native_sim/naive/64` to allowed platforms. Signed-off-by: TOKITA Hiroshi --- dts/bindings/test/vnd,gpio-nexus.yaml | 8 +++ dts/bindings/test/vnd,intr-nexus.yaml | 8 +++ tests/lib/devicetree/api/app.overlay | 45 ++++++++++++++ tests/lib/devicetree/api/src/main.c | 86 ++++++++++++++++++++++++++ tests/lib/devicetree/api/testcase.yaml | 1 + 5 files changed, 148 insertions(+) create mode 100644 dts/bindings/test/vnd,gpio-nexus.yaml create mode 100644 dts/bindings/test/vnd,intr-nexus.yaml diff --git a/dts/bindings/test/vnd,gpio-nexus.yaml b/dts/bindings/test/vnd,gpio-nexus.yaml new file mode 100644 index 0000000000000..2274be7451da1 --- /dev/null +++ b/dts/bindings/test/vnd,gpio-nexus.yaml @@ -0,0 +1,8 @@ +# Copyright (c) 2025, TOKITA Hiroshi +# SPDX-License-Identifier: Apache-2.0 + +description: VND GPIO nexus + +include: [gpio-nexus.yaml] + +compatible: "vnd,gpio-nexus" diff --git a/dts/bindings/test/vnd,intr-nexus.yaml b/dts/bindings/test/vnd,intr-nexus.yaml new file mode 100644 index 0000000000000..f3b194c558385 --- /dev/null +++ b/dts/bindings/test/vnd,intr-nexus.yaml @@ -0,0 +1,8 @@ +# Copyright (c) 2025, TOKITA Hiroshi +# SPDX-License-Identifier: Apache-2.0 + +description: VND interrupt nexus + +include: [interrupt-nexus.yaml] + +compatible: "vnd,intr-nexus" diff --git a/tests/lib/devicetree/api/app.overlay b/tests/lib/devicetree/api/app.overlay index 950ca9b403555..a2961f115a13d 100644 --- a/tests/lib/devicetree/api/app.overlay +++ b/tests/lib/devicetree/api/app.overlay @@ -921,4 +921,49 @@ compatible = "vnd,non-deprecated-label"; label = "FOO"; }; + + gpio-map-test { + connector { + compatible = "vnd,gpio-nexus"; + #gpio-cells = <2>; + gpio-map = <1 2 &{/gpio-map-test/parent} 3 + 4 5 &{/gpio-map-test/parent} 6>; + gpio-map-mask = <0xffffffff 0xffffffc0>; + gpio-map-pass-thru = <0x0 0x3f>; + }; + parent { + compatible = "gpio-dst"; + gpio-controller; + #gpio-cells = <1>; + }; + }; + + interrupt-map-test { + #address-cells = <2>; + #size-cells = <0>; + + controller-0@0 { + compatible = "vnd,cpu-intc"; + reg = <0x0 0x0>; + #address-cells = <1>; + #interrupt-cells = <1>; + interrupt-controller; + }; + controller-1@1 { + compatible = "vnd,intc"; + reg = <0x0 0x1>; + #address-cells = <2>; + #interrupt-cells = <2>; + interrupt-controller; + }; + nexus { + compatible = "vnd,intr-nexus"; + #interrupt-cells = <2>; + interrupt-map = < + 0 0 1 2 &{/interrupt-map-test/controller-0@0} 3 4 + 0 0 5 6 &{/interrupt-map-test/controller-1@1} 7 8 9 0 + 0 1 9 8 &{/interrupt-map-test/controller-0@0} 7 6 + 0 1 5 4 &{/interrupt-map-test/controller-1@1} 3 2 1 0>; + }; + }; }; diff --git a/tests/lib/devicetree/api/src/main.c b/tests/lib/devicetree/api/src/main.c index 98189ebe582cc..38acae5be4387 100644 --- a/tests/lib/devicetree/api/src/main.c +++ b/tests/lib/devicetree/api/src/main.c @@ -111,6 +111,9 @@ #define TEST_SUBPARTITION_1 DT_PATH(test, test_mtd_ffeeddcc, flash_20000000, partitions, \ partition_100, partition_40) +#define TEST_GPIO_CONNECTOR DT_PATH(gpio_map_test, connector) +#define TEST_INTERRUPT_NEXUS DT_PATH(interrupt_map_test, nexus) + #define ZEPHYR_USER DT_PATH(zephyr_user) #define TA_HAS_COMPAT(compat) DT_NODE_HAS_COMPAT(TEST_ARRAYS, compat) @@ -3832,4 +3835,87 @@ ZTEST(devicetree_api, test_interrupt_controller) zassert_true(DT_SAME_NODE(DT_INST_IRQ_INTC(0), TEST_INTC), ""); } +#define INTERRUPT_NEXUS_CHECK_0(n, p, i, ...) \ + zassert_equal(NUM_VA_ARGS(DT_MAP_CHILD_SPECIFIER_ARGS_BY_IDX(n, p, i)), 4); \ + zassert_equal(GET_ARG_N(1, DT_MAP_CHILD_SPECIFIER_ARGS_BY_IDX(n, p, i)), 0); \ + zassert_equal(GET_ARG_N(2, DT_MAP_CHILD_SPECIFIER_ARGS_BY_IDX(n, p, i)), 0); \ + zassert_equal(GET_ARG_N(3, DT_MAP_CHILD_SPECIFIER_ARGS_BY_IDX(n, p, i)), 1); \ + zassert_equal(GET_ARG_N(4, DT_MAP_CHILD_SPECIFIER_ARGS_BY_IDX(n, p, i)), 2); \ + zassert_equal(NUM_VA_ARGS(DT_MAP_PARENT_SPECIFIER_ARGS_BY_IDX(n, p, i)), 2); \ + zassert_equal(GET_ARG_N(1, DT_MAP_PARENT_SPECIFIER_ARGS_BY_IDX(n, p, i)), 3); \ + zassert_equal(GET_ARG_N(2, DT_MAP_PARENT_SPECIFIER_ARGS_BY_IDX(n, p, i)), 4); \ + zassert_equal(NUM_VA_ARGS(DT_MAP_PARENT_ARG_BY_IDX(n, p, i)), 1); \ + zassert_str_equal(STRINGIFY(DT_MAP_PARENT_ARG_BY_IDX(n, p, i)), \ + "DT_N_S_interrupt_map_test_S_controller_0_0"); + +#define INTERRUPT_NEXUS_CHECK_1(n, p, i, ...) \ + zassert_equal(NUM_VA_ARGS(DT_MAP_CHILD_SPECIFIER_ARGS_BY_IDX(n, p, i)), 4); \ + zassert_equal(GET_ARG_N(1, DT_MAP_CHILD_SPECIFIER_ARGS_BY_IDX(n, p, i)), 0); \ + zassert_equal(GET_ARG_N(2, DT_MAP_CHILD_SPECIFIER_ARGS_BY_IDX(n, p, i)), 0); \ + zassert_equal(GET_ARG_N(3, DT_MAP_CHILD_SPECIFIER_ARGS_BY_IDX(n, p, i)), 5); \ + zassert_equal(GET_ARG_N(4, DT_MAP_CHILD_SPECIFIER_ARGS_BY_IDX(n, p, i)), 6); \ + zassert_equal(NUM_VA_ARGS(DT_MAP_PARENT_SPECIFIER_ARGS_BY_IDX(n, p, i)), 4); \ + zassert_equal(GET_ARG_N(1, DT_MAP_PARENT_SPECIFIER_ARGS_BY_IDX(n, p, i)), 7); \ + zassert_equal(GET_ARG_N(2, DT_MAP_PARENT_SPECIFIER_ARGS_BY_IDX(n, p, i)), 8); \ + zassert_equal(GET_ARG_N(3, DT_MAP_PARENT_SPECIFIER_ARGS_BY_IDX(n, p, i)), 9); \ + zassert_equal(GET_ARG_N(4, DT_MAP_PARENT_SPECIFIER_ARGS_BY_IDX(n, p, i)), 0); \ + zassert_equal(NUM_VA_ARGS(DT_MAP_PARENT_ARG_BY_IDX(n, p, i)), 1); \ + zassert_str_equal(STRINGIFY(DT_MAP_PARENT_ARG_BY_IDX(n, p, i)), \ + "DT_N_S_interrupt_map_test_S_controller_1_1"); + +#define INTERRUPT_NEXUS_CHECK_2(n, p, i, ...) \ + zassert_equal(NUM_VA_ARGS(DT_MAP_CHILD_SPECIFIER_ARGS_BY_IDX(n, p, i)), 4); \ + zassert_equal(GET_ARG_N(1, DT_MAP_CHILD_SPECIFIER_ARGS_BY_IDX(n, p, i)), 0); \ + zassert_equal(GET_ARG_N(2, DT_MAP_CHILD_SPECIFIER_ARGS_BY_IDX(n, p, i)), 1); \ + zassert_equal(GET_ARG_N(3, DT_MAP_CHILD_SPECIFIER_ARGS_BY_IDX(n, p, i)), 9); \ + zassert_equal(GET_ARG_N(4, DT_MAP_CHILD_SPECIFIER_ARGS_BY_IDX(n, p, i)), 8); \ + zassert_equal(NUM_VA_ARGS(DT_MAP_PARENT_SPECIFIER_ARGS_BY_IDX(n, p, i)), 2); \ + zassert_equal(GET_ARG_N(1, DT_MAP_PARENT_SPECIFIER_ARGS_BY_IDX(n, p, i)), 7); \ + zassert_equal(GET_ARG_N(2, DT_MAP_PARENT_SPECIFIER_ARGS_BY_IDX(n, p, i)), 6); \ + zassert_equal(NUM_VA_ARGS(DT_MAP_PARENT_ARG_BY_IDX(n, p, i)), 1); \ + zassert_str_equal(STRINGIFY(DT_MAP_PARENT_ARG_BY_IDX(n, p, i)), \ + "DT_N_S_interrupt_map_test_S_controller_0_0"); + +#define INTERRUPT_NEXUS_CHECK_3(n, p, i, ...) \ + zassert_equal(NUM_VA_ARGS(DT_MAP_CHILD_SPECIFIER_ARGS_BY_IDX(n, p, i)), 4); \ + zassert_equal(GET_ARG_N(1, DT_MAP_CHILD_SPECIFIER_ARGS_BY_IDX(n, p, i)), 0); \ + zassert_equal(GET_ARG_N(2, DT_MAP_CHILD_SPECIFIER_ARGS_BY_IDX(n, p, i)), 1); \ + zassert_equal(GET_ARG_N(3, DT_MAP_CHILD_SPECIFIER_ARGS_BY_IDX(n, p, i)), 5); \ + zassert_equal(GET_ARG_N(4, DT_MAP_CHILD_SPECIFIER_ARGS_BY_IDX(n, p, i)), 4); \ + zassert_equal(NUM_VA_ARGS(DT_MAP_PARENT_SPECIFIER_ARGS_BY_IDX(n, p, i)), 4); \ + zassert_equal(GET_ARG_N(1, DT_MAP_PARENT_SPECIFIER_ARGS_BY_IDX(n, p, i)), 3); \ + zassert_equal(GET_ARG_N(2, DT_MAP_PARENT_SPECIFIER_ARGS_BY_IDX(n, p, i)), 2); \ + zassert_equal(GET_ARG_N(3, DT_MAP_PARENT_SPECIFIER_ARGS_BY_IDX(n, p, i)), 1); \ + zassert_equal(GET_ARG_N(4, DT_MAP_PARENT_SPECIFIER_ARGS_BY_IDX(n, p, i)), 0); \ + zassert_str_equal(STRINGIFY(DT_MAP_PARENT_ARG_BY_IDX(n, p, i)), \ + "DT_N_S_interrupt_map_test_S_controller_1_1"); + +#define INTERRUPT_NEXUS_CHECK(...) \ + UTIL_CAT(INTERRUPT_NEXUS_CHECK_, GET_ARG_N(3, __VA_ARGS__))(__VA_ARGS__) + +ZTEST(devicetree_api, test_map) +{ + zassert_equal(DT_PROP_LEN(TEST_GPIO_CONNECTOR, gpio_map), 2); + zassert_equal(DT_PHA_BY_IDX(TEST_GPIO_CONNECTOR, gpio_map, 0, child_specifier_0), 1); + zassert_equal(DT_PHA_BY_IDX(TEST_GPIO_CONNECTOR, gpio_map, 0, child_specifier_1), 2); + zassert_str_equal(STRINGIFY(DT_PHANDLE_BY_IDX(TEST_GPIO_CONNECTOR, gpio_map, 0)), + "DT_N_S_gpio_map_test_S_parent"); + zassert_equal(DT_PHA_BY_IDX(TEST_GPIO_CONNECTOR, gpio_map, 0, parent_specifier_0), 3); + zassert_equal(DT_PHA_BY_IDX(TEST_GPIO_CONNECTOR, gpio_map, 1, child_specifier_0), 4); + zassert_equal(DT_PHA_BY_IDX(TEST_GPIO_CONNECTOR, gpio_map, 1, child_specifier_1), 5); + zassert_str_equal(STRINGIFY(DT_PHANDLE_BY_IDX(TEST_GPIO_CONNECTOR, gpio_map, 1)), + "DT_N_S_gpio_map_test_S_parent"); + zassert_equal(DT_PHA_BY_IDX(TEST_GPIO_CONNECTOR, gpio_map, 1, parent_specifier_0), 6); + + zassert_equal(DT_PROP_LEN(TEST_GPIO_CONNECTOR, gpio_map_mask), 2); + zassert_equal(DT_PROP_BY_IDX(TEST_GPIO_CONNECTOR, gpio_map_mask, 0), 0xffffffff); + zassert_equal(DT_PROP_BY_IDX(TEST_GPIO_CONNECTOR, gpio_map_mask, 1), 0xffffffc0); + zassert_equal(DT_PROP_LEN(TEST_GPIO_CONNECTOR, gpio_map_pass_thru), 2); + zassert_equal(DT_PROP_BY_IDX(TEST_GPIO_CONNECTOR, gpio_map_pass_thru, 0), 0x0); + zassert_equal(DT_PROP_BY_IDX(TEST_GPIO_CONNECTOR, gpio_map_pass_thru, 1), 0x3f); + + DT_FOREACH_PROP_ELEM_VARGS(TEST_INTERRUPT_NEXUS, interrupt_map, INTERRUPT_NEXUS_CHECK, + 9999); +} + ZTEST_SUITE(devicetree_api, NULL, NULL, NULL, NULL, NULL); diff --git a/tests/lib/devicetree/api/testcase.yaml b/tests/lib/devicetree/api/testcase.yaml index ded886833ac87..2759a047ab7d6 100644 --- a/tests/lib/devicetree/api/testcase.yaml +++ b/tests/lib/devicetree/api/testcase.yaml @@ -5,6 +5,7 @@ tests: # will mostly likely be the fastest. platform_allow: - native_sim + - native_sim/native/64 - qemu_x86 - qemu_x86_64 - qemu_cortex_m3 From 814f4a9bf6639f365e1b020b4730b7f26e97d116 Mon Sep 17 00:00:00 2001 From: TOKITA Hiroshi Date: Sat, 4 Oct 2025 20:18:25 +0900 Subject: [PATCH 8/8] doc: update dt macros grammar and tests --- doc/build/dts/macros.bnf | 16 + doc/tools/check_dt_macros.py | 414 ++++++++++++++++++ .../fixtures/devicetree_generated_sample.h | 28 ++ scripts/tests/dts/test_check_dt_macros.py | 39 ++ 4 files changed, 497 insertions(+) create mode 100644 doc/tools/check_dt_macros.py create mode 100644 scripts/tests/dts/fixtures/devicetree_generated_sample.h create mode 100644 scripts/tests/dts/test_check_dt_macros.py diff --git a/doc/build/dts/macros.bnf b/doc/build/dts/macros.bnf index f5e676f8f44a3..911ed88a9ff66 100644 --- a/doc/build/dts/macros.bnf +++ b/doc/build/dts/macros.bnf @@ -106,10 +106,20 @@ node-macro =/ %s"DT_N" path-id %s"_ORD" node-macro =/ %s"DT_N" path-id %s"_PATH" ; The node's name@unit-addr, as a string literal node-macro =/ %s"DT_N" path-id %s"_FULL_NAME" +; The node's name@unit-addr without surrounding quotes. +node-macro =/ %s"DT_N" path-id %s"_FULL_NAME_UNQUOTED" +; Tokenized variants of the node's name@unit-addr. +node-macro =/ %s"DT_N" path-id %s"_FULL_NAME_TOKEN" +node-macro =/ %s"DT_N" path-id %s"_FULL_NAME_UPPER_TOKEN" ; The dependency ordinals of a node's requirements (direct dependencies). node-macro =/ %s"DT_N" path-id %s"_REQUIRES_ORDS" ; The dependency ordinals of a node supports (reverse direct dependencies). node-macro =/ %s"DT_N" path-id %s"_SUPPORTS_ORDS" +; Helper macros that iterate over the node's ancestors. +node-macro =/ %s"DT_N" path-id %s"_FOREACH_ANCESTOR" +; Node specific hash and sortable dependency strings. +node-macro =/ %s"DT_N" path-id %s"_HASH" +node-macro =/ %s"DT_N" path-id %s"_ORD_STR_SORTABLE" ; -------------------------------------------------------------------- ; pinctrl-macro: a macro related to the pinctrl properties in a node @@ -341,6 +351,7 @@ other-macro =/ %s"DT_FOREACH_OKAY_INST_" dt-name other-macro =/ %s"DT_FOREACH_OKAY_INST_VARGS_" dt-name ; E.g.: #define DT_CHOSEN_zephyr_flash other-macro =/ %s"DT_CHOSEN_" dt-name +other-macro =/ %s"DT_CHOSEN_" dt-name %s"_EXISTS" ; Declares that a compatible has at least one node on a bus. ; Example: ; @@ -355,6 +366,11 @@ other-macro =/ %s"DT_COMPAT_HAS_OKAY_" dt-name ; property to a fixed-partitions node. See the flash map API docs ; for an example. other-macro =/ %s"DT_COMPAT_" dt-name %s"_LABEL_" dt-name +; Helper macros expanded while iterating over nodes. +other-macro =/ %s"DT_FOREACH_VARGS_HELPER" +other-macro =/ %s"DT_FOREACH_OKAY_VARGS_HELPER" +; Removes brackets while expanding variadic helper macros. +other-macro =/ %s"DT_DEBRACKET_INTERNAL" ; -------------------------------------------------------------------- ; alternate-id: another way to specify a node besides a path-id diff --git a/doc/tools/check_dt_macros.py b/doc/tools/check_dt_macros.py new file mode 100644 index 0000000000000..e90e7392721ef --- /dev/null +++ b/doc/tools/check_dt_macros.py @@ -0,0 +1,414 @@ +"""Utilities for validating devicetree macros against ``macros.bnf``. + +This module provides helpers that parse the :file:`doc/build/dts/macros.bnf` +grammar and turn it into regular expressions. These expressions are then +used to validate the macros generated by :file:`scripts/dts/gen_defines.py`. + +The original tooling lives in the documentation tree of the upstream +project. Only a very small subset is required for the tests in this kata +and the implementation below purposefully keeps the feature set limited to +what ``macros.bnf`` currently requires. It is nonetheless written as a +stand-alone module so that pytest based tests can import it directly. + +The :func:`validate_macros` helper is the main entry point. It loads the +grammar, converts it to a compiled regular expression and finally checks the +macro names contained in a header file. When executed as a script the +module behaves as a small CLI that exits with a non-zero status code if any +macro fails to match the grammar. +""" + +from __future__ import annotations + +from dataclasses import dataclass +import argparse +import pathlib +import re +from typing import Dict, Iterable, Iterator, List, Optional, Sequence + + +# --------------------------------------------------------------------------- +# ABNF parsing helpers + + +def _strip_comments(text: str) -> Iterator[str]: + """Yield lines from *text* with ABNF comments removed. + + The grammar makes extensive use of ``;`` comments. Everything from the + first semicolon to the end of the line is ignored just like in standard + ABNF. + """ + + for raw_line in text.splitlines(): + line, *_ = raw_line.split(";", 1) + stripped = line.strip() + if stripped: + yield stripped + + +def _collect_rule_text(lines: Iterable[str]) -> Dict[str, List[str]]: + """Collect the textual representation of ABNF rules. + + The function understands the ``=`` and ``=/`` operators as well as + continuation lines. The returned mapping associates each rule with a + list of textual alternatives. + """ + + rules: Dict[str, List[str]] = {} + current_rule: Optional[str] = None + current_idx: Optional[int] = None + + rule_re = re.compile(r"^(?P[A-Za-z0-9_-]+)\s*=/?\s*(?P.*)$") + + for line in lines: + match = rule_re.match(line) + if match: + current_rule = match.group("name") + current_idx = None + body = match.group("body").strip() + rules.setdefault(current_rule, []) + rules[current_rule].append(body) + current_idx = len(rules[current_rule]) - 1 + continue + + if current_rule is None or current_idx is None: + raise ValueError(f"Dangling grammar text: {line!r}") + + continuation = line.strip() + rules[current_rule][current_idx] += " " + continuation + + return rules + + +# --------------------------------------------------------------------------- +# Expression tree + + +@dataclass +class Expr: + def to_regex(self) -> str: + raise NotImplementedError + + +@dataclass +class Literal(Expr): + value: str + + def to_regex(self) -> str: # pragma: no cover - trivial + return re.escape(self.value) + + +@dataclass +class Range(Expr): + lower: str + upper: str + + def to_regex(self) -> str: # pragma: no cover - trivial + return f"[{self.lower}-{self.upper}]" + + +@dataclass +class RuleRef(Expr): + name: str + cache: Dict[str, "Expr"] + + def to_regex(self) -> str: + return build_regex(self.name, self.cache) + + +@dataclass +class SequenceExpr(Expr): + parts: Sequence[Expr] + + def to_regex(self) -> str: + return "".join(part.to_regex() for part in self.parts) + + +@dataclass +class ChoiceExpr(Expr): + options: Sequence[Expr] + + def to_regex(self) -> str: + joined = "|".join(option.to_regex() for option in self.options) + return f"(?:{joined})" + + +@dataclass +class OptionalExpr(Expr): + expr: Expr + + def to_regex(self) -> str: + return f"(?:{self.expr.to_regex()})?" + + +@dataclass +class RepeatExpr(Expr): + expr: Expr + min_count: int + max_count: Optional[int] + + def to_regex(self) -> str: + pattern = f"(?:{self.expr.to_regex()})" + if self.max_count is None: + if self.min_count == 0: + suffix = "*" + elif self.min_count == 1: + suffix = "+" + else: + suffix = f"{{{self.min_count},}}" + elif self.min_count == self.max_count: + suffix = f"{{{self.min_count}}}" + else: + suffix = f"{{{self.min_count},{self.max_count}}}" + return pattern + suffix + + +class GrammarParser: + """Minimal ABNF parser tailored to ``macros.bnf``.""" + + def __init__(self, text: str, cache: Dict[str, Expr]): + self.text = text + self.pos = 0 + self.cache = cache + + def parse(self) -> Expr: + expr = self._parse_alternation() + self._skip_ws() + if self.pos != len(self.text): # pragma: no cover - defensive + raise ValueError(f"Unexpected trailing text: {self.text[self.pos:]}" ) + return expr + + # Parsing utilities ------------------------------------------------- + + def _skip_ws(self) -> None: + while self.pos < len(self.text) and self.text[self.pos].isspace(): + self.pos += 1 + + def _peek(self) -> Optional[str]: + if self.pos >= len(self.text): + return None + return self.text[self.pos] + + def _consume(self, expected: str) -> None: + if not self.text.startswith(expected, self.pos): # pragma: no cover - defensive + raise ValueError(f"Expected {expected!r}") + self.pos += len(expected) + + # Grammar ----------------------------------------------------------- + + def _parse_alternation(self) -> Expr: + options = [self._parse_concatenation()] + self._skip_ws() + while self._peek() == "/": + self.pos += 1 + options.append(self._parse_concatenation()) + self._skip_ws() + if len(options) == 1: + return options[0] + return ChoiceExpr(options) + + def _parse_concatenation(self) -> Expr: + parts: List[Expr] = [] + while True: + self._skip_ws() + token = self._peek() + if token is None or token in ")/]": + break + parts.append(self._parse_repetition()) + if not parts: + return Literal("") + if len(parts) == 1: + return parts[0] + return SequenceExpr(parts) + + def _parse_repetition(self) -> Expr: + self._skip_ws() + start = self.pos + min_count = max_count = None + + while self._peek() and self._peek().isdigit(): + self.pos += 1 + + if self._peek() == "*": + number = self.text[start:self.pos] + min_count = int(number) if number else 0 + self.pos += 1 + start = self.pos + while self._peek() and self._peek().isdigit(): + self.pos += 1 + number = self.text[start:self.pos] + max_count = int(number) if number else None + else: + self.pos = start + + element = self._parse_element() + + if min_count is None: + return element + return RepeatExpr(element, min_count, max_count) + + def _parse_element(self) -> Expr: + self._skip_ws() + char = self._peek() + if char is None: + raise ValueError("Unexpected end of expression") + + if char == "(": + self.pos += 1 + expr = self._parse_alternation() + self._skip_ws() + self._consume(")") + return expr + + if char == "[": + self.pos += 1 + expr = self._parse_alternation() + self._skip_ws() + self._consume("]") + return OptionalExpr(expr) + + if char == '"': + return self._parse_quoted() + + if char == "%": + return self._parse_percent_encoded() + + return self._parse_identifier() + + def _parse_quoted(self) -> Expr: + self.pos += 1 + start = self.pos + while self._peek() not in {'"', None}: # pragma: no branch - simple loop + self.pos += 1 + value = self.text[start:self.pos] + self._consume('"') + return Literal(value) + + def _parse_percent_encoded(self) -> Expr: + if self.text.startswith("%s\"", self.pos): + self.pos += 3 + start = self.pos + while self._peek() not in {'"', None}: # pragma: no branch - simple loop + self.pos += 1 + value = self.text[start:self.pos] + self._consume('"') + return Literal(value) + + if self.text.startswith("%x", self.pos): + self.pos += 2 + start = self.pos + while self._peek() not in {"-", None}: + self.pos += 1 + lower = chr(int(self.text[start:self.pos], 16)) + self._consume("-") + start = self.pos + while self._peek() and self._peek().isalnum(): + self.pos += 1 + upper = chr(int(self.text[start:self.pos], 16)) + return Range(lower, upper) + + raise ValueError(f"Unsupported percent-encoded literal: {self.text[self.pos:]}" ) + + def _parse_identifier(self) -> Expr: + start = self.pos + while self._peek() and re.match(r"[A-Za-z0-9_-]", self._peek()): + self.pos += 1 + identifier = self.text[start:self.pos] + if not identifier: + raise ValueError("Expected identifier") + return RuleRef(identifier, self.cache) + + +def build_regex(rule: str, cache: Dict[str, Expr]) -> str: + """Compile *rule* to a regular expression.""" + + expr = cache.get(rule) + if expr is None: + raise KeyError(f"Unknown rule: {rule}") + return expr.to_regex() + + +def _prepare_expression_tree(grammar_text: str) -> Dict[str, Expr]: + lines = _strip_comments(grammar_text) + raw_rules = _collect_rule_text(lines) + + # Inject the standard ABNF ``DIGIT`` rule which is not redefined in the + # grammar file but used liberally. + raw_rules.setdefault("DIGIT", ["%x30-39"]) + + cache: Dict[str, Expr] = {} + for name, bodies in raw_rules.items(): + # Concatenate alternatives into a single choice expression. + options = [GrammarParser(body, cache).parse() for body in bodies] + if len(options) == 1: + cache[name] = options[0] + else: + cache[name] = ChoiceExpr(options) + + return cache + + +def compile_grammar(grammar_text: str) -> re.Pattern[str]: + """Compile ``macros.bnf`` into a ``re.Pattern`` for ``dt-macro``.""" + + cache = _prepare_expression_tree(grammar_text) + regex = build_regex("dt-macro", cache) + return re.compile(f"^(?:{regex})$") + + +# --------------------------------------------------------------------------- +# Macro helpers +def iter_macro_names(header_text: str) -> Iterator[str]: + """Yield the macro identifiers from *header_text*. + + The helper ignores function like parameter lists and only yields the raw + identifier part so that it can be matched against the grammar. + """ + + for line in header_text.splitlines(): + line = line.strip() + if not line.startswith("#define DT_"): + continue + tokens = line.split() + if len(tokens) < 2: + continue + identifier = tokens[1] + # Drop function-like parameter lists. + if "(" in identifier: + identifier = identifier.split("(", 1)[0] + yield identifier + + +# --------------------------------------------------------------------------- +# Public API + + +def validate_macros(header_text: str, grammar_text: str) -> List[str]: + """Return a list of macros not covered by the grammar.""" + + pattern = compile_grammar(grammar_text) + invalid = [macro for macro in iter_macro_names(header_text) + if not pattern.fullmatch(macro)] + return invalid + + +def _cli(argv: Optional[Sequence[str]] = None) -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("header", type=pathlib.Path, help="Header to validate") + parser.add_argument("grammar", type=pathlib.Path, help="ABNF grammar file") + args = parser.parse_args(argv) + + invalid = validate_macros(args.header.read_text(encoding="utf-8"), + args.grammar.read_text(encoding="utf-8")) + if invalid: + for macro in invalid: + print(macro) + return 1 + return 0 + + +def main() -> None: # pragma: no cover - CLI thin wrapper + raise SystemExit(_cli()) + + +if __name__ == "__main__": # pragma: no cover + main() diff --git a/scripts/tests/dts/fixtures/devicetree_generated_sample.h b/scripts/tests/dts/fixtures/devicetree_generated_sample.h new file mode 100644 index 0000000000000..05498c99456ed --- /dev/null +++ b/scripts/tests/dts/fixtures/devicetree_generated_sample.h @@ -0,0 +1,28 @@ +#ifndef DEVICETREE_GENERATED_SAMPLE_H +#define DEVICETREE_GENERATED_SAMPLE_H + +/* Sample node macros exercising the grammar additions. */ +#define DT_N_S_soc_S_uart_40002000_PATH "/soc/uart@40002000" +#define DT_N_S_soc_S_uart_40002000_FULL_NAME "uart@40002000" +#define DT_N_S_soc_S_uart_40002000_FULL_NAME_UNQUOTED uart@40002000 +#define DT_N_S_soc_S_uart_40002000_FULL_NAME_TOKEN uart_40002000 +#define DT_N_S_soc_S_uart_40002000_FULL_NAME_UPPER_TOKEN UART_40002000 +#define DT_N_S_soc_S_uart_40002000_FOREACH_ANCESTOR(fn) \ +fn(DT_N_S_soc) fn(DT_N) +#define DT_N_S_soc_S_uart_40002000_HASH 0x12345678 +#define DT_N_S_soc_S_uart_40002000_ORD_STR_SORTABLE 00042 +#define DT_N_S_soc_S_uart_40002000_REQUIRES_ORDS {0, 1} +#define DT_N_S_soc_S_uart_40002000_SUPPORTS_ORDS {2} + +/* Chosen node helpers. */ +#define DT_CHOSEN_zephyr_console DT_N_S_soc_S_uart_40002000 +#define DT_CHOSEN_zephyr_console_EXISTS 1 + +/* Helper macros generated by gen_defines.py. */ +#define DT_DEBRACKET_INTERNAL(...) __VA_ARGS__ +#define DT_FOREACH_HELPER(fn) fn(DT_N) +#define DT_FOREACH_VARGS_HELPER(fn, ...) fn(DT_N, __VA_ARGS__) +#define DT_FOREACH_OKAY_HELPER(fn) fn(DT_N) +#define DT_FOREACH_OKAY_VARGS_HELPER(fn, ...) fn(DT_N, __VA_ARGS__) + +#endif /* DEVICETREE_GENERATED_SAMPLE_H */ diff --git a/scripts/tests/dts/test_check_dt_macros.py b/scripts/tests/dts/test_check_dt_macros.py new file mode 100644 index 0000000000000..911779746b84a --- /dev/null +++ b/scripts/tests/dts/test_check_dt_macros.py @@ -0,0 +1,39 @@ +"""Tests for the devicetree macro grammar checker.""" + +from importlib import util +import sys +from pathlib import Path +from types import ModuleType + +import pytest + + +FIXTURE_DIR = Path(__file__).parent / "fixtures" +REPO_ROOT = Path(__file__).resolve().parents[3] +GRAMMAR_FILE = REPO_ROOT / "doc" / "build" / "dts" / "macros.bnf" +CHECKER_PATH = REPO_ROOT / "doc" / "tools" / "check_dt_macros.py" + + +def _load_checker() -> ModuleType: + spec = util.spec_from_file_location("check_dt_macros", CHECKER_PATH) + if spec is None or spec.loader is None: # pragma: no cover - defensive + raise RuntimeError("Unable to load check_dt_macros module") + module = util.module_from_spec(spec) + sys.modules[spec.name] = module + spec.loader.exec_module(module) + return module + + +@pytest.mark.parametrize( + "header", [FIXTURE_DIR / "devicetree_generated_sample.h"], +) +def test_generated_macros_match_grammar(header: Path) -> None: + """Ensure that the sample header passes the grammar validation.""" + + checker = _load_checker() + invalid = checker.validate_macros( + header.read_text(encoding="utf-8"), + GRAMMAR_FILE.read_text(encoding="utf-8"), + ) + + assert invalid == []