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 @@
+
+
+
+
+
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 @@
+
+
+
+
+
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