From cb4785542e3c41aac79381f42f86273dd4a7bf40 Mon Sep 17 00:00:00 2001 From: Sam Hurst Date: Sat, 28 Aug 2021 21:44:17 -0700 Subject: [PATCH] lib: smf: Add State Machine Framework Add an application agnostic State Machine Framework library to Zephyr that provides an easy way for developers to integrate state machines into their application. Twister passed: twister -T tests/lib/smf/ Signed-off-by: Sam Hurst --- CODEOWNERS | 1 + doc/guides/index.rst | 1 + doc/guides/smf/img/flat.svg | 60 +++ doc/guides/smf/img/hierarchical.svg | 65 +++ doc/guides/smf/index.rst | 333 ++++++++++++ include/smf.h | 154 ++++++ lib/CMakeLists.txt | 1 + lib/Kconfig | 2 + lib/smf/CMakeLists.txt | 11 + lib/smf/Kconfig | 16 + lib/smf/smf.c | 291 +++++++++++ tests/lib/smf/CMakeLists.txt | 14 + tests/lib/smf/prj.conf | 3 + tests/lib/smf/src/main.c | 25 + tests/lib/smf/src/test_lib_flat_smf.c | 291 +++++++++++ .../test_lib_hierarchical_5_ancestor_smf.c | 442 ++++++++++++++++ tests/lib/smf/src/test_lib_hierarchical_smf.c | 475 ++++++++++++++++++ tests/lib/smf/src/test_lib_smf.h | 14 + tests/lib/smf/testcase.yaml | 9 + 19 files changed, 2208 insertions(+) create mode 100644 doc/guides/smf/img/flat.svg create mode 100644 doc/guides/smf/img/hierarchical.svg create mode 100644 doc/guides/smf/index.rst create mode 100644 include/smf.h create mode 100644 lib/smf/CMakeLists.txt create mode 100644 lib/smf/Kconfig create mode 100644 lib/smf/smf.c create mode 100644 tests/lib/smf/CMakeLists.txt create mode 100644 tests/lib/smf/prj.conf create mode 100644 tests/lib/smf/src/main.c create mode 100644 tests/lib/smf/src/test_lib_flat_smf.c create mode 100644 tests/lib/smf/src/test_lib_hierarchical_5_ancestor_smf.c create mode 100644 tests/lib/smf/src/test_lib_hierarchical_smf.c create mode 100644 tests/lib/smf/src/test_lib_smf.h create mode 100644 tests/lib/smf/testcase.yaml diff --git a/CODEOWNERS b/CODEOWNERS index 0de59e03e72..291c1e8af1b 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -541,6 +541,7 @@ /include/toolchain/ @dcpleung @nashif @andyross /include/zephyr.h @dcpleung @nashif @andyross /kernel/ @dcpleung @nashif @andyross +/lib/smf/ @sambhurst /lib/util/ @carlescufi @jakub-uC /lib/util/fnmatch/ @carlescufi @jakub-uC /lib/util/getopt/ @jakub-uC diff --git a/doc/guides/index.rst b/doc/guides/index.rst index bed597a70ec..333110c6766 100644 --- a/doc/guides/index.rst +++ b/doc/guides/index.rst @@ -24,6 +24,7 @@ User and Developer Guides platformio/index.rst portability/index.rst porting/index + smf/index.rst test/index tfm/index west/index diff --git a/doc/guides/smf/img/flat.svg b/doc/guides/smf/img/flat.svg new file mode 100644 index 00000000000..c98d61afd03 --- /dev/null +++ b/doc/guides/smf/img/flat.svg @@ -0,0 +1,60 @@ + + + + + + +flat_smf + + + +ENTRY + + + + +A + +STATE S0 + + + +ENTRY->A + + + + + +B + +STATE S1 + + + +A->B + + + + + +C + +STATE S2 + + + +B->C + + + + + +C->A + + + + + diff --git a/doc/guides/smf/img/hierarchical.svg b/doc/guides/smf/img/hierarchical.svg new file mode 100644 index 00000000000..222cc0bc101 --- /dev/null +++ b/doc/guides/smf/img/hierarchical.svg @@ -0,0 +1,65 @@ + + + + + + +hierarchical_smf + + +clusterOpen + +PARENT + + + +A + +STATE S0 + + + +B + +STATE S1 + + + +A->B + + + + + +C + +STATE S2 + + + +B->C + + + + + +C->A + + + + + +ENTRY + + + + +ENTRY->A + + + + + diff --git a/doc/guides/smf/index.rst b/doc/guides/smf/index.rst new file mode 100644 index 00000000000..295beb5a4fd --- /dev/null +++ b/doc/guides/smf/index.rst @@ -0,0 +1,333 @@ +.. _smf: + +State Machine Framework +####################### + +Overview +======== + +The State Machine Framework (SMF) is an application agnostic framework that +provides an easy way for developers to integrate state machines into their +application. The framework can be added to any project by enabling the +:kconfig:`CONFIG_SMF` option. + +State Creation +============== + +A state is represented by three functions, where one function implements the +Entry actions, another function implements the Run actions, and the last +function implements the Exit actions. The prototype for these functions is as +follows: ``void funct(void *obj)``, where the ``obj`` parameter is a user +defined structure that has the state machine context, ``struct smf_ctx``, as +its first member. For example:: + + struct user_object { + struct smf_ctx ctx; + /* All User Defined Data Follows */ + }; + +The ``struct smf_ctx`` member must be first because the state machine +framework's functions casts the user defined object to the ``struct smf_ctx`` +type with the following macro: ``SMF_CTX(o)`` + +For example instead of doing this ``(struct smf_ctx *)&user_obj``, you could +use ``SMF_CTX(&user_obj)``. + +By default, a state can have no anscestor states, resulting in a flat state +machine. But to enable the creation of a hierarchical state machine, the +:kconfig:`CONFIG_SMF_ANCESTOR_SUPPORT` option must be enabled. + +The following macro can be used for easy state creation: + +* :c:macro:`SMF_CREATE_STATE` Create a state + +**NOTE:** The :c:macro:`SMF_CREATE_STATE` macro takes an additional parameter +when :kconfig:`CONFIG_SMF_ANCESTOR_SUPPORT` is enabled. + +State Machine Creation +====================== + +A state machine is created by defining a table of states that's indexed by an +enum. For example, the following creates three flat states:: + + enum demo_state { S0, S1, S2 }; + + const struct smf_state demo_states { + [S0] = SMF_CREATE_STATE(s0_entry, s0_run, s0_exit), + [S1] = SMF_CREATE_STATE(s1_entry, s1_run, s1_exit), + [S2] = SMF_CREATE_STATE(s2_entry, s2_run, s2_exit) + }; + +And this example creates three hierarchical states:: + + enum demo_state { S0, S1, S2 }; + + const struct smf_state demo_states { + [S0] = SMF_CREATE_STATE(s0_entry, s0_run, s0_exit, parent_s0), + [S1] = SMF_CREATE_STATE(s1_entry, s1_run, s1_exit, parent_s12), + [S2] = SMF_CREATE_STATE(s2_entry, s2_run, s2_exit, parent_s12) + }; + + +To set the initial state, the ``smf_set_initial`` function should be +called. It has the following prototype: +``void smf_set_initial(smf_ctx *ctx, smf_state *state)`` + +To transition from one state to another, the ``smf_set_state`` function is +used and it has the following prototype: +``void smf_set_state(smf_ctx *ctx, smf_state *state)`` + +**NOTE:** While the state machine is running, smf_set_state should only be +called from the Entry and Run functions. Calling smf_set_state from the Exit +functions doesn't make sense and will generate a warning. + +State Machine Execution +======================= + +To run the state machine, the ``smf_run_state`` function should be called in +some application dependent way. An application should cease calling +smf_run_state if it returns a non-zero value. The function has the following +prototype: ``int32_t smf_run_state(smf_ctx *ctx)`` + +State Machine Termination +========================= + +To terminate the state machine, the ``smf_terminate`` function should be +called. It can be called from the entry, run, or exit action. The function +takes a non-zero user defined value that's returned by the ``smf_run_state`` +function. The function has the following prototype: +``void smf_terminate(smf_ctx *ctx, int32_t val)`` + +Flat State Machine Example +========================== + +This example turns the following state diagram into code using the SMF, where +the initial state is S0. + +.. figure:: img/flat.svg + :align: center + :alt: Flat state machine diagram + +Code:: + + #include + + /* Forward declaration of state table */ + static const struct smf_state demo_states[]; + + /* List of demo states */ + enum demo_state { S0, S1, S2 }; + + /* User defined object */ + struct s_object { + /* This must be first */ + struct smf_ctx ctx; + + /* Other state specific data add here */ + } s_obj; + + /* State S0 */ + static void s0_entry(void *o) + { + /* Do something */ + } + static void s0_run(void *o) + { + smf_set_state(SMF_CTX(&s_obj), &demo_states[S1]); + } + static void s0_exit(void *o) + { + /* Do something */ + } + + /* State S1 */ + static void s1_run(void *o) + { + smf_set_state(SMF_CTX(&s_obj), &demo_states[S2]); + } + static void s1_exit(void *o) + { + /* Do something */ + } + + /* State S2 */ + static void s2_entry(void *o) + { + /* Do something */ + } + static void s2_run(void *o) + { + smf_set_state(SMF_CTX(&s_obj), &demo_states[S0]); + } + + /* Populate state table */ + static const struct smf_state demo_states[] = { + [S0] = SMF_CREATE_STATE(s0_entry, s0_run, s0_exit), + /* State S1 does not have an entry action */ + [S1] = SMF_CREATE_STATE(NULL, s1_run, s1_exit), + /* State S2 does not have an exit action */ + [S2] = SMF_CREATE_STATE(s2_entry, s2_run, NULL), + }; + + void main(void) + { + int32_t ret; + + /* Set initial state */ + smf_set_initial(SMF_CTX(&s_obj), &demo_states[S0]); + + /* Run the state machine */ + while(1) { + /* State machine terminates if a non-zero value is returned */ + ret = smf_run_state(SMF_CTX(&s_obj)); + if (ret) { + /* handle return code and terminate state machine */ + break; + } + k_msleep(1000); + } + } + +Hierarchical State Machine Example +================================== + +This example turns the following state diagram into code using the SMF, where +S0 and S1 share a parent state and S0 is the initial state. + +.. figure:: img/hierarchical.svg + :align: center + :alt: Hierarchial state machine diagram + +Code:: + + #include + + /* Forward declaration of state table */ + static const struct smf_state demo_states[]; + + /* List of demo states */ + enum demo_state { PARENT, S0, S1, S2 }; + + /* User defined object */ + struct s_object { + /* This must be first */ + struct smf_ctx ctx; + + /* Other state specific data add here */ + } s_obj; + + /* Parent State */ + static void parent_entry(void *o) + { + /* Do something */ + } + static void parent_exit(void *o) + { + /* Do something */ + } + + /* State S0 */ + static void s0_run(void *o) + { + smf_set_state(SMF_CTX(&s_obj), &demo_states[S1]); + } + + /* State S1 */ + static void s1_run(void *o) + { + smf_set_state(SMF_CTX(&s_obj), &demo_states[S2]); + } + + /* State S2 */ + static void s2_run(void *o) + { + smf_set_state(SMF_CTX(&s_obj), &demo_states[S0]); + } + + /* Populate state table */ + static const struct smf_state demo_states[] = { + /* Parent state does not have a run action */ + [PARENT] = SMF_CREATE_STATE(parent_entry, NULL, parent_exit, NULL), + /* Child states do not have entry or exit actions */ + [S0] = SMF_CREATE_STATE(NULL, s0_run, NULL, &demo_states[PARENT]), + [S1] = SMF_CREATE_STATE(NULL, s1_run, NULL, &demo_states[PARENT]), + /* State S2 do ot have entry or exit actions and no parent */ + [S2] = SMF_CREATE_STATE(NULL, s2_run, NULL, NULL), + }; + + void main(void) + { + int32_t ret; + + /* Set initial state */ + smf_set_initial(SMF_CTX(&s_obj), &demo_states[S0]); + + /* Run the state machine */ + while(1) { + /* State machine terminates if a non-zero value is returned */ + ret = smf_run_state(SMF_CTX(&s_obj)); + if (ret) { + /* handle return code and terminate state machine */ + break; + } + k_msleep(1000); + } + } + +When designing hierarchical state machines, the following should be considered: + - Ancestor entry actions are executed before the sibling entry actions. For + example, the parent_entry function is called before the s0_entry function. + - Transitioning from one sibling to another with a shared ancestry does not + re-execute the ancestor\'s entry action or execute the exit action. + For example, the parent_entry function is not called when transitioning + from S0 to S1, nor is the parent_exit function called. + - Ancestor exit actions are executed after the sibling exit actions. For + example, the s1_exit function is called before the parent_exit function + is called. + - The parent_run function only executes if the child_run function returns + whithout transitioning to another state, ie. calling smf_set_state. + +State Machine diagrams in this guide +==================================== + +The State Machine diagrams in this guide were generated using the graphviz dot +tool. + +The following code generated flat.svg + +.. code-block:: none + + digraph smf_flat { + node [style=rounded]; + init [shape = point]; + STATE_S0 [shape = box]; + STATE_S1 [shape = box]; + STATE_S2 [shape = box]; + + init -> STATE_S0; + STATE_S0 -> STATE_S1; + STATE_S1 -> STATE_S2; + STATE_S2 -> STATE_S0; + } + +The following code generated hierarchical.svg + +.. code-block:: none + + digraph smf_hierarchical { + node [style = rounded]; + init [shape = point]; + STATE_S0 [shape = box]; + STATE_S1 [shape = box]; + STATE_S2 [shape = box]; + + subgraph cluster_0 { + label = "PARENT"; + style = rounded; + STATE_S0 -> STATE_S1; + } + + init -> STATE_S0; + STATE_S1 -> STATE_S2; + STATE_S2 -> STATE_S0; + } diff --git a/include/smf.h b/include/smf.h new file mode 100644 index 00000000000..a59b2860dbf --- /dev/null +++ b/include/smf.h @@ -0,0 +1,154 @@ +/* + * Copyright 2021 The Chromium OS Authors + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* State Machine Framework */ + +#ifndef ZEPHYR_INCLUDE_SMF_H_ +#define ZEPHYR_INCLUDE_SMF_H_ + +#ifdef CONFIG_SMF_ANCESTOR_SUPPORT +/** + * @brief Macro to create a hierarchical state. + * + * @param _entry State entry function + * @param _run State run function + * @param _exit State exit function + * @param _parent State parent object or NULL + */ +#define SMF_CREATE_STATE(_entry, _run, _exit, _parent) \ +{ \ + .entry = _entry, \ + .run = _run, \ + .exit = _exit, \ + .parent = _parent \ +} + +#else + +/** + * @brief Macro to create a flat state. + * + * @param _entry State entry function + * @param _run State run function + * @param _exit State exit function + */ +#define SMF_CREATE_STATE(_entry, _run, _exit) \ +{ \ + .entry = _entry, \ + .run = _run, \ + .exit = _exit \ +} + +#endif /* CONFIG_SMF_ANCESTOR_SUPPORT */ + +/** + * @brief Macro to cast user defined object to state machine + * context. + * + * @param o A pointer to the user defined object + */ +#define SMF_CTX(o) ((struct smf_ctx *)o) + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +/** + * @brief Function pointer that implements a portion of a state + * + * @param obj pointer user defined object + */ +typedef void (*state_execution)(void *obj); + +/** General state that can be used in multiple state machines. */ +struct smf_state { + /** Optional method that will be run when this state is entered */ + const state_execution entry; + /** + * Optional method that will be run repeatedly during state machine + * loop. + */ + const state_execution run; + /** Optional method that will be run when this state exists */ + const state_execution exit; + /** + * Optional parent state that contains common entry/run/exit + * implementation among various child states. + * entry: Parent function executes BEFORE child function. + * run: Parent function executes AFTER child function. + * exit: Parent function executes AFTER child function. + * + * Note: When transitioning between two child states with a shared parent, + * that parent's exit and entry functions do not execute. + */ + const struct smf_state *parent; +}; + +/** Defines the current context of the state machine. */ +struct smf_ctx { + /** Current state the state machine is executing. */ + const struct smf_state *current; + /** Previous state the state machine executed */ + const struct smf_state *previous; + /** + * This value is set by the set_terminate function and + * should terminate the state machine when its set to a + * value other than zero when it's returned by the + * run_state function. + */ + int32_t terminate_val; + /** + * The state machine casts this to a "struct internal_ctx" and it's + * used to track state machine context + */ + uint32_t internal; +}; + +/** + * @brief Initializes the state machine and sets its initial state. + * + * @param ctx State machine context + * @param init_state Initial state the state machine starts in. + */ +void smf_set_initial(struct smf_ctx *ctx, const struct smf_state *init_state); + +/** + * @brief Changes a state machines state. This handles exiting the previous + * state and entering the target state. A common parent state will not + * exited nor be re-entered. + * + * @param ctx State machine context + * @param new_state State to transition to (NULL is valid and exits all states) + */ +void smf_set_state(struct smf_ctx *ctx, const struct smf_state *new_state); + +/** + * @brief Termate a state machine + * + * @param ctx State machine context + * @param val Non-Zero termination value that's returned by the smf_run_state + * function. + */ +void smf_set_terminate(struct smf_ctx *ctx, int32_t val); + +/** + * @brief Runs one iteration of a state machine (including any parent states) + * + * @param ctx State machine context + * @return A non-zero value should terminate the state machine. This + * non-zero value could represet a terminal state being reached + * or the detection of an error that should result in the + * termination of the state machine. + */ +int32_t smf_run_state(struct smf_ctx *ctx); + +#ifdef __cplusplus +} +#endif + +#endif /* ZEPHYR_INCLUDE_SMF_H_ */ diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 2077e657141..ca8c4b9440a 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -7,4 +7,5 @@ endif() add_subdirectory(gui) add_subdirectory(os) add_subdirectory(util) +add_subdirectory_ifdef(CONFIG_SMF smf) add_subdirectory_ifdef(CONFIG_OPENAMP open-amp) diff --git a/lib/Kconfig b/lib/Kconfig index 120b7735e24..5ab230463af 100644 --- a/lib/Kconfig +++ b/lib/Kconfig @@ -15,4 +15,6 @@ source "lib/open-amp/Kconfig" source "lib/util/Kconfig" +source "lib/smf/Kconfig" + endmenu diff --git a/lib/smf/CMakeLists.txt b/lib/smf/CMakeLists.txt new file mode 100644 index 00000000000..a95aa9013e0 --- /dev/null +++ b/lib/smf/CMakeLists.txt @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: Apache-2.0 + +zephyr_interface_library_named(smf) + +target_include_directories(smf INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}) + +zephyr_library() + +zephyr_library_sources(smf.c) + +zephyr_library_link_libraries(smf) diff --git a/lib/smf/Kconfig b/lib/smf/Kconfig new file mode 100644 index 00000000000..ed4be6a8b79 --- /dev/null +++ b/lib/smf/Kconfig @@ -0,0 +1,16 @@ +# Copyright 2021 The Chromium OS Authors +# SPDX-License-Identifier: Apache-2.0 + +config SMF + bool "Enable Hierarchical State Machine" + help + This option enables the Hierarchical State Machine library + +if SMF + +config SMF_ANCESTOR_SUPPORT + bool "Enable states to have 1 or more ancestors" + help + If y, then the state machine framework includes ancestor state support + +endif # SMF diff --git a/lib/smf/smf.c b/lib/smf/smf.c new file mode 100644 index 00000000000..eaea32f8ce5 --- /dev/null +++ b/lib/smf/smf.c @@ -0,0 +1,291 @@ +/* + * Copyright 2021 The Chromium OS Authors + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "smf.h" + +#include +LOG_MODULE_REGISTER(smf); + +/* + * Private structure (to this file) used to track state machine context. + * The structure is not used directly, but instead to cast the "internal" + * member of the smf_ctx structure. + */ +struct internal_ctx { + bool new_state : 1; + bool terminate : 1; + bool exit : 1; +}; + +static bool share_paren(const struct smf_state *test_state, + const struct smf_state *target_state) +{ + for (const struct smf_state *state = test_state; + state != NULL; + state = state->parent) { + if (target_state == state) { + return true; + } + } + + return false; +} + +static bool last_state_share_paren(struct smf_ctx *const ctx, + const struct smf_state *state) +{ + /* Get parent state of previous state */ + if (!ctx->previous) { + return false; + } + + return share_paren(ctx->previous->parent, state); +} + +static const struct smf_state *get_child_of(const struct smf_state *states, + const struct smf_state *parent) +{ + for (const struct smf_state *tmp = states; ; tmp = tmp->parent) { + if (tmp->parent == parent) { + return tmp; + } + + if (tmp->parent == NULL) { + return NULL; + } + } + + return NULL; +} + +static const struct smf_state *get_last_of(const struct smf_state *states) +{ + return get_child_of(states, NULL); +} + +/** + * @brief Execute all ancestor entry actions + * + * @param ctx State machine context + * @param target The entry actions of this target's ancestors are executed + * @return true if the state machine should terminate, else false + */ +__unused static bool smf_execute_ancestor_entry_actions( + struct smf_ctx *const ctx, const struct smf_state *target) +{ + struct internal_ctx * const internal = (void *) &ctx->internal; + + for (const struct smf_state *to_execute = get_last_of(target); + to_execute != NULL && to_execute != target; + to_execute = get_child_of(target, to_execute)) { + /* Execute parent state's entry */ + if (!last_state_share_paren(ctx, to_execute) && to_execute->entry) { + to_execute->entry(ctx); + + /* No need to continue if terminate was set */ + if (internal->terminate) { + return true; + } + } + } + + return false; +} + +/** + * @brief Execute all ancestor run actions + * + * @param ctx State machine context + * @param target The run actions of this target's ancestors are executed + * @return true if the state machine should terminate, else false + */ +__unused static bool smf_execute_ancestor_run_actions(struct smf_ctx *ctx) +{ + struct internal_ctx * const internal = (void *) &ctx->internal; + /* Execute all run actions in reverse order */ + + /* Return if the current state switched states */ + if (internal->new_state) { + internal->new_state = false; + return false; + } + + /* Return if the current state terminated */ + if (internal->terminate) { + return true; + } + + /* Try to run parent run actions */ + for (const struct smf_state *tmp_state = ctx->current->parent; + tmp_state != NULL; + tmp_state = tmp_state->parent) { + /* Execute parent run action */ + if (tmp_state->run) { + tmp_state->run(ctx); + /* No need to continue if terminate was set */ + if (internal->terminate) { + return true; + } + + if (internal->new_state) { + break; + } + } + } + + internal->new_state = false; + /* All done executing the run actions */ + + return false; +} + +/** + * @brief Execute all ancestor exit actions + * + * @param ctx State machine context + * @param target The exit actions of this target's ancestors are executed + * @return true if the state machine should terminate, else false + */ +__unused static bool smf_execute_ancestor_exit_actions( + struct smf_ctx *const ctx, const struct smf_state *target) +{ + struct internal_ctx * const internal = (void *) &ctx->internal; + const struct smf_state *tmp_state; + const struct smf_state *target_parent; + + /* Execute all parent exit actions in reverse order */ + + /* Get target state's parent state */ + target_parent = target->parent; + tmp_state = ctx->current; + + for (const struct smf_state *tmp_state = ctx->current->parent; + tmp_state != NULL; + tmp_state = tmp_state->parent) { + if (!share_paren(target->parent, tmp_state) && tmp_state->exit) { + tmp_state->exit(ctx); + + /* No need to continue if terminate was set */ + if (internal->terminate) { + return true; + } + } + } + return false; +} + +void smf_set_initial(struct smf_ctx *ctx, const struct smf_state *init_state) +{ + struct internal_ctx * const internal = (void *) &ctx->internal; + + internal->exit = false; + internal->terminate = false; + ctx->current = init_state; + ctx->previous = NULL; + ctx->terminate_val = 0; + + if (IS_ENABLED(CONFIG_SMF_ANCESTOR_SUPPORT)) { + internal->new_state = false; + + if (smf_execute_ancestor_entry_actions(ctx, init_state)) { + return; + } + } + + /* Now execute the initial state's entry action */ + if (init_state->entry) { + init_state->entry(ctx); + } +} + +void smf_set_state(struct smf_ctx *const ctx, const struct smf_state *target) +{ + struct internal_ctx * const internal = (void *) &ctx->internal; + + /* + * It does not make sense to call set_state in an exit phase of a state + * since we are already in a transition; we would always ignore the + * intended state to transition into. + */ + if (internal->exit) { + LOG_WRN("Calling %s from exit action", __func__); + return; + } + + internal->exit = true; + + /* Execute the current states exit action */ + if (ctx->current->exit) { + ctx->current->exit(ctx); + + /* + * No need to continue if terminate was set in the + * exit action + */ + if (internal->terminate) { + return; + } + } + + if (IS_ENABLED(CONFIG_SMF_ANCESTOR_SUPPORT)) { + internal->new_state = true; + + if (smf_execute_ancestor_exit_actions(ctx, target)) { + return; + } + } + + internal->exit = false; + + /* update the state variables */ + ctx->previous = ctx->current; + ctx->current = target; + + if (IS_ENABLED(CONFIG_SMF_ANCESTOR_SUPPORT)) { + if (smf_execute_ancestor_entry_actions(ctx, target)) { + return; + } + } + + /* Now execute the target entry action */ + if (ctx->current->entry) { + ctx->current->entry(ctx); + /* + * If terminate was set, it will be handled in the + * smf_run_state function + */ + } +} + +void smf_set_terminate(struct smf_ctx *ctx, int32_t val) +{ + struct internal_ctx * const internal = (void *) &ctx->internal; + + internal->terminate = true; + ctx->terminate_val = val; +} + +int32_t smf_run_state(struct smf_ctx *const ctx) +{ + struct internal_ctx * const internal = (void *) &ctx->internal; + + /* No need to continue if terminate was set */ + if (internal->terminate) { + return ctx->terminate_val; + } + + if (ctx->current->run) { + ctx->current->run(ctx); + } + + if (IS_ENABLED(CONFIG_SMF_ANCESTOR_SUPPORT)) { + if (smf_execute_ancestor_run_actions(ctx)) { + return ctx->terminate_val; + } + } + + return 0; +} diff --git a/tests/lib/smf/CMakeLists.txt b/tests/lib/smf/CMakeLists.txt new file mode 100644 index 00000000000..9d0cbff6d19 --- /dev/null +++ b/tests/lib/smf/CMakeLists.txt @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.13.1) +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(smf) + +target_sources(app PRIVATE src/main.c) + +if(CONFIG_SMF_ANCESTOR_SUPPORT) + target_sources(app PRIVATE src/test_lib_hierarchical_smf.c + src/test_lib_hierarchical_5_ancestor_smf.c) +else() + target_sources(app PRIVATE src/test_lib_flat_smf.c) +endif() diff --git a/tests/lib/smf/prj.conf b/tests/lib/smf/prj.conf new file mode 100644 index 00000000000..59f26ef31fe --- /dev/null +++ b/tests/lib/smf/prj.conf @@ -0,0 +1,3 @@ +CONFIG_ZTEST=y +CONFIG_LOG=y +CONFIG_SMF=y diff --git a/tests/lib/smf/src/main.c b/tests/lib/smf/src/main.c new file mode 100644 index 00000000000..40675a529ac --- /dev/null +++ b/tests/lib/smf/src/main.c @@ -0,0 +1,25 @@ +/* + * Copyright 2021 The Chromium OS Authors + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#include +#include "test_lib_smf.h" + +void test_main(void) +{ + if (IS_ENABLED(CONFIG_SMF_ANCESTOR_SUPPORT)) { + ztest_test_suite(smf_tests, + ztest_unit_test(test_smf_hierarchical), + ztest_unit_test(test_smf_hierarchical_5_ancestors)); + ztest_run_test_suite(smf_tests); + } else { + ztest_test_suite(smf_tests, + ztest_unit_test(test_smf_flat)); + ztest_run_test_suite(smf_tests); + } +} diff --git a/tests/lib/smf/src/test_lib_flat_smf.c b/tests/lib/smf/src/test_lib_flat_smf.c new file mode 100644 index 00000000000..58634ec2237 --- /dev/null +++ b/tests/lib/smf/src/test_lib_flat_smf.c @@ -0,0 +1,291 @@ +/* + * Copyright 2021 The Chromium OS Authors + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +/* + * Flat Test Transtion: + * + * A_ENTRY --> A_RUN --> A_EXIT --> B_ENTRY --> B_RUN --| + * | + * |----------------------------------------------------| + * | + * |--> B_EXIT --> C_ENTRY --> C_RUN --> C_EXIT + * + */ + + +#define TEST_OBJECT(o) ((struct test_object *)o) + +#define SMF_RUN 3 + +#define STATE_A_ENTRY_BIT (1 << 0) +#define STATE_A_RUN_BIT (1 << 1) +#define STATE_A_EXIT_BIT (1 << 2) + +#define STATE_B_ENTRY_BIT (1 << 3) +#define STATE_B_RUN_BIT (1 << 4) +#define STATE_B_EXIT_BIT (1 << 5) + +#define STATE_C_ENTRY_BIT (1 << 6) +#define STATE_C_RUN_BIT (1 << 7) +#define STATE_C_EXIT_BIT (1 << 8) + +#define TEST_ENTRY_VALUE_NUM 0 +#define TEST_RUN_VALUE_NUM 4 +#define TEST_EXIT_VALUE_NUM 8 +#define TEST_VALUE_NUM 9 + +static uint32_t test_value[] = { + 0x00, /* STATE_A_ENTRY */ + 0x01, /* STATE_A_RUN */ + 0x03, /* STATE_A_EXIT */ + 0x07, /* STATE_B_ENTRY */ + 0x0f, /* STATE_B_RUN */ + 0x1f, /* STATE_B_EXIT */ + 0x3f, /* STATE_C_ENTRY */ + 0x7f, /* STATE_C_RUN */ + 0xff, /* STATE_C_EXIT */ + 0x1ff, /* FINAL VALUE */ +}; + +/* Forward declaration of test_states */ +static const struct smf_state test_states[]; + +/* List of all TypeC-level states */ +enum test_state { + STATE_A, + STATE_B, + STATE_C, + STATE_D +}; + +enum terminate_action { + NONE, + ENTRY, + RUN, + EXIT +}; + +static struct test_object { + struct smf_ctx ctx; + uint32_t transition_bits; + uint32_t tv_idx; + enum terminate_action terminate; +} test_obj; + +static void state_a_entry(void *obj) +{ + struct test_object *o = TEST_OBJECT(obj); + + o->tv_idx = 0; + zassert_equal(o->transition_bits, test_value[o->tv_idx], + "Test State A entry failed"); + + if (o->terminate == ENTRY) { + smf_set_terminate(obj, -1); + return; + } + + o->transition_bits |= STATE_A_ENTRY_BIT; +} + +static void state_a_run(void *obj) +{ + struct test_object *o = TEST_OBJECT(obj); + + o->tv_idx++; + zassert_equal(o->transition_bits, test_value[o->tv_idx], + "Test State A run failed"); + + o->transition_bits |= STATE_A_RUN_BIT; + + smf_set_state(SMF_CTX(obj), &test_states[STATE_B]); +} + +static void state_a_exit(void *obj) +{ + struct test_object *o = TEST_OBJECT(obj); + + o->tv_idx++; + zassert_equal(o->transition_bits, test_value[o->tv_idx], + "Test State A exit failed"); + + o->transition_bits |= STATE_A_EXIT_BIT; +} + +static void state_b_entry(void *obj) +{ + struct test_object *o = TEST_OBJECT(obj); + + o->tv_idx++; + zassert_equal(o->transition_bits, test_value[o->tv_idx], + "Test State B entry failed"); + + o->transition_bits |= STATE_B_ENTRY_BIT; +} + +static void state_b_run(void *obj) +{ + struct test_object *o = TEST_OBJECT(obj); + + o->tv_idx++; + zassert_equal(o->transition_bits, test_value[o->tv_idx], + "Test State B run failed"); + + if (o->terminate == RUN) { + smf_set_terminate(obj, -1); + return; + } + + o->transition_bits |= STATE_B_RUN_BIT; + + smf_set_state(SMF_CTX(obj), &test_states[STATE_C]); +} + +static void state_b_exit(void *obj) +{ + struct test_object *o = TEST_OBJECT(obj); + + o->tv_idx++; + zassert_equal(o->transition_bits, test_value[o->tv_idx], + "Test State B exit failed"); + o->transition_bits |= STATE_B_EXIT_BIT; +} + +static void state_c_entry(void *obj) +{ + struct test_object *o = TEST_OBJECT(obj); + + o->tv_idx++; + zassert_equal(o->transition_bits, test_value[o->tv_idx], + "Test State C entry failed"); + o->transition_bits |= STATE_C_ENTRY_BIT; +} + +static void state_c_run(void *obj) +{ + struct test_object *o = TEST_OBJECT(obj); + + o->tv_idx++; + zassert_equal(o->transition_bits, test_value[o->tv_idx], + "Test State C run failed"); + o->transition_bits |= STATE_C_RUN_BIT; + + smf_set_state(SMF_CTX(obj), &test_states[STATE_D]); +} + +static void state_c_exit(void *obj) +{ + struct test_object *o = TEST_OBJECT(obj); + + o->tv_idx++; + zassert_equal(o->transition_bits, test_value[o->tv_idx], + "Test State C exit failed"); + + if (o->terminate == EXIT) { + smf_set_terminate(obj, -1); + return; + } + + o->transition_bits |= STATE_C_EXIT_BIT; +} + +static void state_d_entry(void *obj) +{ + struct test_object *o = TEST_OBJECT(obj); + + o->tv_idx++; +} + +static void state_d_run(void *obj) +{ + /* Do nothing */ +} + +static void state_d_exit(void *obj) +{ + /* Do nothing */ +} + +static const struct smf_state test_states[] = { + [STATE_A] = SMF_CREATE_STATE(state_a_entry, state_a_run, state_a_exit), + [STATE_B] = SMF_CREATE_STATE(state_b_entry, state_b_run, state_b_exit), + [STATE_C] = SMF_CREATE_STATE(state_c_entry, state_c_run, state_c_exit), + [STATE_D] = SMF_CREATE_STATE(state_d_entry, state_d_run, state_d_exit), +}; + +void test_smf_flat(void) +{ + /* A) Test transtions */ + + test_obj.transition_bits = 0; + test_obj.terminate = NONE; + smf_set_initial((struct smf_ctx *)&test_obj, &test_states[STATE_A]); + + for (int i = 0; i < SMF_RUN; i++) { + if (smf_run_state((struct smf_ctx *)&test_obj)) { + break; + } + } + + zassert_equal(TEST_VALUE_NUM, test_obj.tv_idx, + "Incorrect test value index"); + zassert_equal(test_obj.transition_bits, test_value[test_obj.tv_idx], + "Final state not reached"); + + /* B) Test termination in entry action */ + + test_obj.transition_bits = 0; + test_obj.terminate = ENTRY; + smf_set_initial((struct smf_ctx *)&test_obj, &test_states[STATE_A]); + + for (int i = 0; i < SMF_RUN; i++) { + if (smf_run_state((struct smf_ctx *)&test_obj)) { + break; + } + } + + zassert_equal(TEST_ENTRY_VALUE_NUM, test_obj.tv_idx, + "Incorrect test value index for entry termination"); + zassert_equal(test_obj.transition_bits, test_value[test_obj.tv_idx], + "Final entry termination state not reached"); + + /* C) Test termination in run action */ + + test_obj.transition_bits = 0; + test_obj.terminate = RUN; + smf_set_initial((struct smf_ctx *)&test_obj, &test_states[STATE_A]); + + for (int i = 0; i < SMF_RUN; i++) { + if (smf_run_state((struct smf_ctx *)&test_obj)) { + break; + } + } + + zassert_equal(TEST_RUN_VALUE_NUM, test_obj.tv_idx, + "Incorrect test value index for run termination"); + zassert_equal(test_obj.transition_bits, test_value[test_obj.tv_idx], + "Final run termination state not reached"); + + /* D) Test termination in exit action */ + + test_obj.transition_bits = 0; + test_obj.terminate = EXIT; + smf_set_initial((struct smf_ctx *)&test_obj, &test_states[STATE_A]); + + for (int i = 0; i < SMF_RUN; i++) { + if (smf_run_state((struct smf_ctx *)&test_obj)) { + break; + } + } + + zassert_equal(TEST_EXIT_VALUE_NUM, test_obj.tv_idx, + "Incorrect test value index for exit termination"); + zassert_equal(test_obj.transition_bits, test_value[test_obj.tv_idx], + "Final exit termination state not reached"); +} diff --git a/tests/lib/smf/src/test_lib_hierarchical_5_ancestor_smf.c b/tests/lib/smf/src/test_lib_hierarchical_5_ancestor_smf.c new file mode 100644 index 00000000000..b5e88f40785 --- /dev/null +++ b/tests/lib/smf/src/test_lib_hierarchical_5_ancestor_smf.c @@ -0,0 +1,442 @@ +/* + * Copyright 2021 The Chromium OS Authors + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +/* + * Hierarchical 5 Ancestor State Test Transistion: + * + * P05_ENTRY --> P04_ENTRY --> P03_ENTRY --> P02_ENTRY ---------| + * | + * |------------------------------------------------------------| + * | + * |--> P01_ENTRY --> A_ENTRY --> A_RUN --> A_EXIT -------------| + * | + * |------------------------------------------------------------| + * | + * |--> B_ENTRY --> B_RUN --> P01_RUN --> P02_RUN --> P03_RUN --| + * | + * |------------------------------------------------------------| + * | + * |--> P04_RUN --> P05_RUN --> B_EXIT --> P01_EXIT ------------| + * | + * |------------------------------------------------------------| + * | + * |--> P02_EXIT --> P03_EXIT --> P04_EXIT --> P05_EXIT --------| + * | + * |------------------------------------------------------------| + * | + * |--> C_ENTRY --> C_RUN --> C_EXIT --> D_ENTRY + */ + +#define TEST_OBJECT(o) ((struct test_object *)o) + +#define SMF_RUN 3 + +#define P05_ENTRY_BIT (1 << 0) +#define P04_ENTRY_BIT (1 << 1) +#define P03_ENTRY_BIT (1 << 2) +#define P02_ENTRY_BIT (1 << 3) +#define P01_ENTRY_BIT (1 << 4) +#define A_ENTRY_BIT (1 << 5) +#define A_RUN_BIT (1 << 6) +#define A_EXIT_BIT (1 << 7) +#define B_ENTRY_BIT (1 << 8) +#define B_RUN_BIT (1 << 9) +#define P01_RUN_BIT (1 << 10) +#define P02_RUN_BIT (1 << 11) +#define P03_RUN_BIT (1 << 12) +#define P04_RUN_BIT (1 << 13) +#define P05_RUN_BIT (1 << 14) +#define B_EXIT_BIT (1 << 15) +#define P01_EXIT_BIT (1 << 16) +#define P02_EXIT_BIT (1 << 17) +#define P03_EXIT_BIT (1 << 18) +#define P04_EXIT_BIT (1 << 19) +#define P05_EXIT_BIT (1 << 20) +#define C_ENTRY_BIT (1 << 21) +#define C_RUN_BIT (1 << 22) +#define C_EXIT_BIT (1 << 23) + +#define TEST_VALUE_NUM 24 +static uint32_t test_value[] = { + 0x00000000, /* P05_ENTRY */ + 0x00000001, /* P04_ENTRY */ + 0x00000003, /* P03_ENTRY */ + 0x00000007, /* P02_ENTRY */ + 0x0000000f, /* P01_ENTRY */ + 0x0000001f, /* A_ENTRY */ + 0x0000003f, /* A_RUN */ + 0x0000007f, /* A_EXIT */ + 0x000000ff, /* B_ENTRY */ + 0x000001ff, /* B_RUN */ + 0x000003ff, /* P01_RUN */ + 0x000007ff, /* P02_RUN */ + 0x00000fff, /* P03_RUN */ + 0x00001fff, /* P04_RUN */ + 0x00003fff, /* P05_RUN */ + 0x00007fff, /* B_EXIT */ + 0x0000ffff, /* P01_EXIT */ + 0x0001ffff, /* P02_EXIT */ + 0x0003ffff, /* P03_EXIT */ + 0x0007ffff, /* P04_EXIT */ + 0x000fffff, /* P05_EXIT */ + 0x001fffff, /* C_ENTRY */ + 0x003fffff, /* C_RUN */ + 0x007fffff, /* C_EXIT */ + 0x00ffffff, /* D_ENTRY */ +}; + +/* Forward declaration of test_states */ +static const struct smf_state test_states[]; + +/* List of all TypeC-level states */ +enum test_state { + P05, + P04, + P03, + P02, + P01, + A, + B, + C, + D, +}; + +static struct test_object { + struct smf_ctx ctx; + uint32_t transition_bits; + uint32_t tv_idx; +} test_obj; + +static void p05_entry(void *obj) +{ + struct test_object *o = TEST_OBJECT(obj); + + zassert_equal(o->transition_bits, test_value[o->tv_idx], + "Test Parent 05 entry failed"); + + o->transition_bits |= P05_ENTRY_BIT; +} + +static void p05_run(void *obj) +{ + struct test_object *o = TEST_OBJECT(obj); + + o->tv_idx++; + + zassert_equal(o->transition_bits, test_value[o->tv_idx], + "Test Parent 05 run failed"); + + o->transition_bits |= P05_RUN_BIT; + + smf_set_state(SMF_CTX(obj), &test_states[C]); +} + +static void p05_exit(void *obj) +{ + struct test_object *o = TEST_OBJECT(obj); + + o->tv_idx++; + + zassert_equal(o->transition_bits, test_value[o->tv_idx], + "Test Parent 05 exit failed"); + + o->transition_bits |= P05_EXIT_BIT; +} + +static void p04_entry(void *obj) +{ + struct test_object *o = TEST_OBJECT(obj); + + o->tv_idx++; + + zassert_equal(o->transition_bits, test_value[o->tv_idx], + "Test Parent 04 entry failed"); + + o->transition_bits |= P04_ENTRY_BIT; +} + +static void p04_run(void *obj) +{ + struct test_object *o = TEST_OBJECT(obj); + + o->tv_idx++; + + zassert_equal(o->transition_bits, test_value[o->tv_idx], + "Test Parent 04 run failed"); + + o->transition_bits |= P04_RUN_BIT; +} + +static void p04_exit(void *obj) +{ + struct test_object *o = TEST_OBJECT(obj); + + o->tv_idx++; + + zassert_equal(o->transition_bits, test_value[o->tv_idx], + "Test Parent 04 exit failed"); + + o->transition_bits |= P04_EXIT_BIT; +} + +static void p03_entry(void *obj) +{ + struct test_object *o = TEST_OBJECT(obj); + + o->tv_idx++; + + zassert_equal(o->transition_bits, test_value[o->tv_idx], + "Test Parent 03 entry failed"); + + o->transition_bits |= P03_ENTRY_BIT; +} + +static void p03_run(void *obj) +{ + struct test_object *o = TEST_OBJECT(obj); + + o->tv_idx++; + + zassert_equal(o->transition_bits, test_value[o->tv_idx], + "Test Parent 03 run failed"); + + o->transition_bits |= P03_RUN_BIT; +} + +static void p03_exit(void *obj) +{ + struct test_object *o = TEST_OBJECT(obj); + + o->tv_idx++; + + zassert_equal(o->transition_bits, test_value[o->tv_idx], + "Test Parent 03 exit failed"); + + o->transition_bits |= P03_EXIT_BIT; +} + +static void p02_entry(void *obj) +{ + struct test_object *o = TEST_OBJECT(obj); + + o->tv_idx++; + + zassert_equal(o->transition_bits, test_value[o->tv_idx], + "Test Parent 02 entry failed"); + + o->transition_bits |= P02_ENTRY_BIT; +} + +static void p02_run(void *obj) +{ + struct test_object *o = TEST_OBJECT(obj); + + o->tv_idx++; + + zassert_equal(o->transition_bits, test_value[o->tv_idx], + "Test Parent 02 run failed"); + + o->transition_bits |= P02_RUN_BIT; +} + +static void p02_exit(void *obj) +{ + struct test_object *o = TEST_OBJECT(obj); + + o->tv_idx++; + + zassert_equal(o->transition_bits, test_value[o->tv_idx], + "Test Parent 02 exit failed"); + + o->transition_bits |= P02_EXIT_BIT; +} + +static void p01_entry(void *obj) +{ + struct test_object *o = TEST_OBJECT(obj); + + o->tv_idx++; + + zassert_equal(o->transition_bits, test_value[o->tv_idx], + "Test Parent 01 entry failed"); + + o->transition_bits |= P01_ENTRY_BIT; +} + +static void p01_run(void *obj) +{ + struct test_object *o = TEST_OBJECT(obj); + + o->tv_idx++; + + zassert_equal(o->transition_bits, test_value[o->tv_idx], + "Test Parent 01 run failed"); + + o->transition_bits |= P01_RUN_BIT; +} + +static void p01_exit(void *obj) +{ + struct test_object *o = TEST_OBJECT(obj); + + o->tv_idx++; + + zassert_equal(o->transition_bits, test_value[o->tv_idx], + "Test Parent 01 exit failed"); + + o->transition_bits |= P01_EXIT_BIT; +} + +static void a_entry(void *obj) +{ + struct test_object *o = TEST_OBJECT(obj); + + o->tv_idx++; + + zassert_equal(o->transition_bits, test_value[o->tv_idx], + "Test State A entry failed"); + + o->transition_bits |= A_ENTRY_BIT; +} + +static void a_run(void *obj) +{ + struct test_object *o = TEST_OBJECT(obj); + + o->tv_idx++; + + zassert_equal(o->transition_bits, test_value[o->tv_idx], + "Test State A run failed"); + + o->transition_bits |= A_RUN_BIT; + + smf_set_state(SMF_CTX(obj), &test_states[B]); +} + +static void a_exit(void *obj) +{ + struct test_object *o = TEST_OBJECT(obj); + + o->tv_idx++; + + zassert_equal(o->transition_bits, test_value[o->tv_idx], + "Test State A exit failed"); + + o->transition_bits |= A_EXIT_BIT; +} + +static void b_entry(void *obj) +{ + struct test_object *o = TEST_OBJECT(obj); + + o->tv_idx++; + + zassert_equal(o->transition_bits, test_value[o->tv_idx], + "Test State B entry failed"); + + o->transition_bits |= B_ENTRY_BIT; +} + +static void b_run(void *obj) +{ + struct test_object *o = TEST_OBJECT(obj); + + o->tv_idx++; + + zassert_equal(o->transition_bits, test_value[o->tv_idx], + "Test State B run failed"); + + o->transition_bits |= B_RUN_BIT; +} + +static void b_exit(void *obj) +{ + struct test_object *o = TEST_OBJECT(obj); + + o->tv_idx++; + + zassert_equal(o->transition_bits, test_value[o->tv_idx], + "Test State B exit failed"); + + o->transition_bits |= B_EXIT_BIT; +} + +static void c_entry(void *obj) +{ + struct test_object *o = TEST_OBJECT(obj); + + o->tv_idx++; + + zassert_equal(o->transition_bits, test_value[o->tv_idx], + "Test State C entry failed"); + + o->transition_bits |= C_ENTRY_BIT; +} + +static void c_run(void *obj) +{ + struct test_object *o = TEST_OBJECT(obj); + + o->tv_idx++; + + zassert_equal(o->transition_bits, test_value[o->tv_idx], + "Test State C run failed"); + o->transition_bits |= C_RUN_BIT; + + smf_set_state(SMF_CTX(obj), &test_states[D]); +} + +static void c_exit(void *obj) +{ + struct test_object *o = TEST_OBJECT(obj); + + o->tv_idx++; + + zassert_equal(o->transition_bits, test_value[o->tv_idx], + "Test State C exit failed"); + + o->transition_bits |= C_EXIT_BIT; +} + +static void d_entry(void *obj) +{ + struct test_object *o = TEST_OBJECT(obj); + + o->tv_idx++; +} + +static const struct smf_state test_states[] = { + [P05] SMF_CREATE_STATE(p05_entry, p05_run, p05_exit, NULL), + [P04] SMF_CREATE_STATE(p04_entry, p04_run, p04_exit, &test_states[P05]), + [P03] SMF_CREATE_STATE(p03_entry, p03_run, p03_exit, &test_states[P04]), + [P02] SMF_CREATE_STATE(p02_entry, p02_run, p02_exit, &test_states[P03]), + [P01] SMF_CREATE_STATE(p01_entry, p01_run, p01_exit, &test_states[P02]), + [A] = SMF_CREATE_STATE(a_entry, a_run, a_exit, &test_states[P01]), + [B] = SMF_CREATE_STATE(b_entry, b_run, b_exit, &test_states[P01]), + [C] = SMF_CREATE_STATE(c_entry, c_run, c_exit, NULL), + [D] = SMF_CREATE_STATE(d_entry, NULL, NULL, NULL), +}; + +void test_smf_hierarchical_5_ancestors(void) +{ + test_obj.tv_idx = 0; + test_obj.transition_bits = 0; + smf_set_initial((struct smf_ctx *)&test_obj, &test_states[A]); + + for (int i = 0; i < SMF_RUN; i++) { + if (smf_run_state((struct smf_ctx *)&test_obj) < 0) { + break; + } + } + + zassert_equal(TEST_VALUE_NUM, test_obj.tv_idx, + "Incorrect test value index"); + zassert_equal(test_obj.transition_bits, test_value[test_obj.tv_idx], + "Final state not reached"); +} diff --git a/tests/lib/smf/src/test_lib_hierarchical_smf.c b/tests/lib/smf/src/test_lib_hierarchical_smf.c new file mode 100644 index 00000000000..6d6565c8af8 --- /dev/null +++ b/tests/lib/smf/src/test_lib_hierarchical_smf.c @@ -0,0 +1,475 @@ +/* + * Copyright 2021 The Chromium OS Authors + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +/* + * Hierarchical Test Transition: + * + * PARENT_AB_ENTRY --> A_ENTRY --> A_RUN --> PARENT_AB_RUN ---| + * | + * |----------------------------------------------------------| + * | + * |--> B_ENTRY --> B_RUN --> B_EXIT --> PARENT_AB_EXIT ------| + * | + * |----------------------------------------------------------| + * | + * |--> PARENT_C_ENTRY --> C_ENTRY --> C_RUN --> C_EXIT ------| + * | + * |----------------------------------------------------------| + * | + * |--> PARENT_C_EXIT + */ + +/* + * Hierarchical 10 Ancestor State Test Transistion: + * + * P10_ENTRY --> P09_ENTRY --> ... -- P02_ENTRY --> P01_ENTRY --| + * | + * |------------------------------------------------------------| + * | + * |--> A_ENTRY --> A_RUN --> P01_RUN --> P02_RUN --> P03_RUN --| + * | + * |------------------------------------------------------------| + * | + * |--> ... --> P09_RUN --> P10_RUN --> B_ENTRY --> + */ + +#define TEST_OBJECT(o) ((struct test_object *)o) + +#define SMF_RUN 3 + +#define PARENT_AB_ENTRY_BIT (1 << 0) +#define STATE_A_ENTRY_BIT (1 << 1) +#define STATE_A_RUN_BIT (1 << 2) +#define PARENT_AB_RUN_BIT (1 << 3) +#define STATE_A_EXIT_BIT (1 << 4) + +#define STATE_B_ENTRY_BIT (1 << 5) +#define STATE_B_RUN_BIT (1 << 6) +#define STATE_B_EXIT_BIT (1 << 7) +#define PARENT_AB_EXIT_BIT (1 << 8) + +#define PARENT_C_ENTRY_BIT (1 << 9) +#define STATE_C_ENTRY_BIT (1 << 10) +#define STATE_C_RUN_BIT (1 << 11) +#define STATE_C_EXIT_BIT (1 << 12) +#define PARENT_C_EXIT_BIT (1 << 13) + +#define TEST_PARENT_ENTRY_VALUE_NUM 0 +#define TEST_PARENT_RUN_VALUE_NUM 3 +#define TEST_PARENT_EXIT_VALUE_NUM 8 +#define TEST_ENTRY_VALUE_NUM 1 +#define TEST_RUN_VALUE_NUM 6 +#define TEST_EXIT_VALUE_NUM 12 +#define TEST_VALUE_NUM 14 +static uint32_t test_value[] = { + 0x00, /* PARENT_AB_ENTRY */ + 0x01, /* STATE_A_ENTRY */ + 0x03, /* STATE_A_RUN */ + 0x07, /* PARENT_AB_RUN */ + 0x0f, /* STATE_A_EXIT */ + 0x1f, /* STATE_B_ENTRY */ + 0x3f, /* STATE_B_RUN */ + 0x7f, /* STATE_B_EXIT */ + 0xff, /* STATE_AB_EXIT */ + 0x1ff, /* PARENT_C_ENTRY */ + 0x3ff, /* STATE_C_ENTRY */ + 0x7ff, /* STATE_C_RUN */ + 0xfff, /* STATE_C_EXIT */ + 0x1fff, /* PARENT_C_EXIT */ + 0x3fff, /* FINAL VALUE */ +}; + +/* Forward declaration of test_states */ +static const struct smf_state test_states[]; + +/* List of all TypeC-level states */ +enum test_state { + PARENT_AB, + PARENT_C, + STATE_A, + STATE_B, + STATE_C, + STATE_D +}; + +enum terminate_action { + NONE, + PARENT_ENTRY, + PARENT_RUN, + PARENT_EXIT, + ENTRY, + RUN, + EXIT +}; + +static struct test_object { + struct smf_ctx ctx; + uint32_t transition_bits; + uint32_t tv_idx; + enum terminate_action terminate; +} test_obj; + +static void parent_ab_entry(void *obj) +{ + struct test_object *o = TEST_OBJECT(obj); + + o->tv_idx = 0; + zassert_equal(o->transition_bits, test_value[o->tv_idx], + "Test Parent AB entry failed"); + + if (o->terminate == PARENT_ENTRY) { + smf_set_terminate(obj, -1); + return; + } + + o->transition_bits |= PARENT_AB_ENTRY_BIT; +} + +static void parent_ab_run(void *obj) +{ + struct test_object *o = TEST_OBJECT(obj); + + o->tv_idx++; + + zassert_equal(o->transition_bits, test_value[o->tv_idx], + "Test Parent AB run failed"); + + if (o->terminate == PARENT_RUN) { + smf_set_terminate(obj, -1); + return; + } + + o->transition_bits |= PARENT_AB_RUN_BIT; + + smf_set_state(SMF_CTX(obj), &test_states[STATE_B]); +} + +static void parent_ab_exit(void *obj) +{ + struct test_object *o = TEST_OBJECT(obj); + + o->tv_idx++; + + zassert_equal(o->transition_bits, test_value[o->tv_idx], + "Test Parent AB exit failed"); + + if (o->terminate == PARENT_EXIT) { + smf_set_terminate(obj, -1); + return; + } + + o->transition_bits |= PARENT_AB_EXIT_BIT; +} + +static void parent_c_entry(void *obj) +{ + struct test_object *o = TEST_OBJECT(obj); + + o->tv_idx++; + + zassert_equal(o->transition_bits, test_value[o->tv_idx], + "Test Parent C entry failed"); + o->transition_bits |= PARENT_C_ENTRY_BIT; +} + +static void parent_c_run(void *obj) +{ + /* This state should not be reached */ + zassert_true(0, "Test Parent C run failed"); +} + +static void parent_c_exit(void *obj) +{ + struct test_object *o = TEST_OBJECT(obj); + + o->tv_idx++; + + zassert_equal(o->transition_bits, test_value[o->tv_idx], + "Test Parent C exit failed"); + o->transition_bits |= PARENT_C_EXIT_BIT; +} + +static void state_a_entry(void *obj) +{ + struct test_object *o = TEST_OBJECT(obj); + + o->tv_idx++; + + zassert_equal(o->transition_bits, test_value[o->tv_idx], + "Test State A entry failed"); + + if (o->terminate == ENTRY) { + smf_set_terminate(obj, -1); + return; + } + + o->transition_bits |= STATE_A_ENTRY_BIT; +} + +static void state_a_run(void *obj) +{ + struct test_object *o = TEST_OBJECT(obj); + + o->tv_idx++; + + zassert_equal(o->transition_bits, test_value[o->tv_idx], + "Test State A run failed"); + + o->transition_bits |= STATE_A_RUN_BIT; + + /* Return to parent run state */ +} + +static void state_a_exit(void *obj) +{ + struct test_object *o = TEST_OBJECT(obj); + + o->tv_idx++; + + zassert_equal(o->transition_bits, test_value[o->tv_idx], + "Test State A exit failed"); + o->transition_bits |= STATE_A_EXIT_BIT; +} + +static void state_b_entry(void *obj) +{ + struct test_object *o = TEST_OBJECT(obj); + + o->tv_idx++; + + zassert_equal(o->transition_bits, test_value[o->tv_idx], + "Test State B entry failed"); + o->transition_bits |= STATE_B_ENTRY_BIT; +} + +static void state_b_run(void *obj) +{ + struct test_object *o = TEST_OBJECT(obj); + + o->tv_idx++; + + zassert_equal(o->transition_bits, test_value[o->tv_idx], + "Test State B run failed"); + + if (o->terminate == RUN) { + smf_set_terminate(obj, -1); + return; + } + + o->transition_bits |= STATE_B_RUN_BIT; + + smf_set_state(SMF_CTX(obj), &test_states[STATE_C]); +} + +static void state_b_exit(void *obj) +{ + struct test_object *o = TEST_OBJECT(obj); + + o->tv_idx++; + + zassert_equal(o->transition_bits, test_value[o->tv_idx], + "Test State B exit failed"); + o->transition_bits |= STATE_B_EXIT_BIT; +} + +static void state_c_entry(void *obj) +{ + struct test_object *o = TEST_OBJECT(obj); + + o->tv_idx++; + + zassert_equal(o->transition_bits, test_value[o->tv_idx], + "Test State C entry failed"); + o->transition_bits |= STATE_C_ENTRY_BIT; +} + +static void state_c_run(void *obj) +{ + struct test_object *o = TEST_OBJECT(obj); + + o->tv_idx++; + + zassert_equal(o->transition_bits, test_value[o->tv_idx], + "Test State C run failed"); + o->transition_bits |= STATE_C_RUN_BIT; + + smf_set_state(SMF_CTX(obj), &test_states[STATE_D]); +} + +static void state_c_exit(void *obj) +{ + struct test_object *o = TEST_OBJECT(obj); + + o->tv_idx++; + + zassert_equal(o->transition_bits, test_value[o->tv_idx], + "Test State C exit failed"); + + if (o->terminate == EXIT) { + smf_set_terminate(obj, -1); + return; + } + + o->transition_bits |= STATE_C_EXIT_BIT; +} + +static void state_d_entry(void *obj) +{ + struct test_object *o = TEST_OBJECT(obj); + + o->tv_idx++; +} + +static void state_d_run(void *obj) +{ + /* Do nothing */ +} + +static void state_d_exit(void *obj) +{ + /* Do nothing */ +} + +static const struct smf_state test_states[] = { + [PARENT_AB] = SMF_CREATE_STATE(parent_ab_entry, parent_ab_run, + parent_ab_exit, NULL), + [PARENT_C] = SMF_CREATE_STATE(parent_c_entry, parent_c_run, + parent_c_exit, NULL), + [STATE_A] = SMF_CREATE_STATE(state_a_entry, state_a_run, state_a_exit, + &test_states[PARENT_AB]), + [STATE_B] = SMF_CREATE_STATE(state_b_entry, state_b_run, state_b_exit, + &test_states[PARENT_AB]), + [STATE_C] = SMF_CREATE_STATE(state_c_entry, state_c_run, state_c_exit, + &test_states[PARENT_C]), + [STATE_D] = SMF_CREATE_STATE(state_d_entry, state_d_run, state_d_exit, + NULL), +}; + +void test_smf_hierarchical(void) +{ + /* A) Test state transitions */ + + test_obj.transition_bits = 0; + test_obj.terminate = NONE; + smf_set_initial((struct smf_ctx *)&test_obj, &test_states[STATE_A]); + + for (int i = 0; i < SMF_RUN; i++) { + if (smf_run_state((struct smf_ctx *)&test_obj) < 0) { + break; + } + } + + zassert_equal(TEST_VALUE_NUM, test_obj.tv_idx, + "Incorrect test value index"); + zassert_equal(test_obj.transition_bits, test_value[test_obj.tv_idx], + "Final state not reached"); + + /* B) Test termination in parent entry action */ + + test_obj.transition_bits = 0; + test_obj.terminate = PARENT_ENTRY; + smf_set_initial((struct smf_ctx *)&test_obj, &test_states[STATE_A]); + + for (int i = 0; i < SMF_RUN; i++) { + if (smf_run_state((struct smf_ctx *)&test_obj) < 0) { + break; + } + } + + zassert_equal(TEST_PARENT_ENTRY_VALUE_NUM, test_obj.tv_idx, + "Incorrect test value index for parent entry termination"); + zassert_equal(test_obj.transition_bits, test_value[test_obj.tv_idx], + "Final parent entry termination state not reached"); + + /* C) Test termination in parent run action */ + + test_obj.transition_bits = 0; + test_obj.terminate = PARENT_RUN; + smf_set_initial((struct smf_ctx *)&test_obj, &test_states[STATE_A]); + + for (int i = 0; i < SMF_RUN; i++) { + if (smf_run_state((struct smf_ctx *)&test_obj) < 0) { + break; + } + } + + zassert_equal(TEST_PARENT_RUN_VALUE_NUM, test_obj.tv_idx, + "Incorrect test value index for parent run termination"); + zassert_equal(test_obj.transition_bits, test_value[test_obj.tv_idx], + "Final parent run termination state not reached"); + + /* D) Test termination in parent exit action */ + + test_obj.transition_bits = 0; + test_obj.terminate = PARENT_EXIT; + smf_set_initial((struct smf_ctx *)&test_obj, &test_states[STATE_A]); + + for (int i = 0; i < SMF_RUN; i++) { + if (smf_run_state((struct smf_ctx *)&test_obj) < 0) { + break; + } + } + + zassert_equal(TEST_PARENT_EXIT_VALUE_NUM, test_obj.tv_idx, + "Incorrect test value index for parent exit termination"); + zassert_equal(test_obj.transition_bits, test_value[test_obj.tv_idx], + "Final parent exit termination state not reached"); + + /* E) Test termination in child entry action */ + + test_obj.transition_bits = 0; + test_obj.terminate = ENTRY; + smf_set_initial((struct smf_ctx *)&test_obj, &test_states[STATE_A]); + + for (int i = 0; i < SMF_RUN; i++) { + if (smf_run_state((struct smf_ctx *)&test_obj) < 0) { + break; + } + } + + zassert_equal(TEST_ENTRY_VALUE_NUM, test_obj.tv_idx, + "Incorrect test value index for entry termination"); + zassert_equal(test_obj.transition_bits, test_value[test_obj.tv_idx], + "Final entry termination state not reached"); + + /* F) Test termination in child run action */ + + test_obj.transition_bits = 0; + test_obj.terminate = RUN; + smf_set_initial((struct smf_ctx *)&test_obj, &test_states[STATE_A]); + + for (int i = 0; i < SMF_RUN; i++) { + if (smf_run_state((struct smf_ctx *)&test_obj) < 0) { + break; + } + } + + zassert_equal(TEST_RUN_VALUE_NUM, test_obj.tv_idx, + "Incorrect test value index for run termination"); + zassert_equal(test_obj.transition_bits, test_value[test_obj.tv_idx], + "Final run termination state not reached"); + + /* G) Test termination in child exit action */ + + test_obj.transition_bits = 0; + test_obj.terminate = EXIT; + smf_set_initial((struct smf_ctx *)&test_obj, &test_states[STATE_A]); + + for (int i = 0; i < SMF_RUN; i++) { + if (smf_run_state((struct smf_ctx *)&test_obj) < 0) { + break; + } + } + + zassert_equal(TEST_EXIT_VALUE_NUM, test_obj.tv_idx, + "Incorrect test value index for exit termination"); + zassert_equal(test_obj.transition_bits, test_value[test_obj.tv_idx], + "Final exit termination state not reached"); + +} diff --git a/tests/lib/smf/src/test_lib_smf.h b/tests/lib/smf/src/test_lib_smf.h new file mode 100644 index 00000000000..998e3321ad1 --- /dev/null +++ b/tests/lib/smf/src/test_lib_smf.h @@ -0,0 +1,14 @@ +/* + * Copyright 2021 The Chromium OS Authors + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_TEST_LIB_SMF_H_ +#define ZEPHYR_TEST_LIB_SMF_H_ + +void test_smf_flat(void); +void test_smf_hierarchical(void); +void test_smf_hierarchical_5_ancestors(void); + +#endif /* ZEPHYR_TEST_LIB_SMF_H_ */ diff --git a/tests/lib/smf/testcase.yaml b/tests/lib/smf/testcase.yaml new file mode 100644 index 00000000000..1ed38acb7f2 --- /dev/null +++ b/tests/lib/smf/testcase.yaml @@ -0,0 +1,9 @@ +tests: + libraries.smf.hierarchical: + tags: smf + platform_allow: native_posix + extra_configs: + - CONFIG_SMF_ANCESTOR_SUPPORT=y + libraries.smf.flat: + tags: smf + platform_allow: native_posix