Lib: SMF Modify HSM operation for UML-Style transitions
Modify the SMF such that state transitions from parent states choose the
correct Least Common Ancestor based on the transition source rather than
the current state.
SMF set as experimental.
Signed-off-by: Glenn Andrews <glenn.andrews.42@gmail.com>
diff --git a/doc/releases/migration-guide-3.7.rst b/doc/releases/migration-guide-3.7.rst
index 4c1fccd..cc9a8b1 100644
--- a/doc/releases/migration-guide-3.7.rst
+++ b/doc/releases/migration-guide-3.7.rst
@@ -493,6 +493,14 @@
now independent of the values of :kconfig:option:`CONFIG_SMF_ANCESTOR_SUPPORT` and
:kconfig:option:`CONFIG_SMF_INITIAL_TRANSITION`. If the additional arguments are not used, they
have to be set to ``NULL``. (:github:`71250`)
+* SMF now follows a more UML-like transition flow when the transition source is a parent of the
+ state called by :c:func:`smf_run_state`. Exit actions up to (but not including) the Least Common
+ Ancestor of the transition source and target state will be executed, as will entry actions from
+ (but not including) the LCA down to the target state. (:github:`71675`)
+* Previously, calling :c:func:`smf_set_state` with a ``new_state`` set to NULL would execute all
+ exit actions from the current state to the topmost parent, with the expectation the topmost exit
+ action would terminate the state machine. Passing ``NULL`` is now not allowed. Instead create a
+ 'terminate' state at the top level, and call :c:func:`smf_set_terminate` from its entry action.
ZBus
====
diff --git a/doc/releases/release-notes-3.7.rst b/doc/releases/release-notes-3.7.rst
index b460b1c..aff9996 100644
--- a/doc/releases/release-notes-3.7.rst
+++ b/doc/releases/release-notes-3.7.rst
@@ -375,6 +375,9 @@
* State Machine Framework
* The :c:macro:`SMF_CREATE_STATE` macro now always takes 5 arguments.
+ * Transition sources that are parents of the state that was run now choose the correct Least
+ Common Ancestor for executing Exit and Entry Actions.
+ * Passing ``NULL`` to :c:func:`smf_set_state` is now not allowed.
* Storage
diff --git a/doc/services/smf/index.rst b/doc/services/smf/index.rst
index 169ee50..9b5ee62 100644
--- a/doc/services/smf/index.rst
+++ b/doc/services/smf/index.rst
@@ -39,7 +39,7 @@
machine. But to enable the creation of a hierarchical state machine, the
:kconfig:option:`CONFIG_SMF_ANCESTOR_SUPPORT` option must be enabled.
-By default, the hierarchical state machine does not support initial transitions
+By default, the hierarchical state machines do not support initial transitions
to child states on entering a superstate. To enable them the
:kconfig:option:`CONFIG_SMF_INITIAL_TRANSITION` option must be enabled.
@@ -87,31 +87,29 @@
};
To set the initial state, the :c:func:`smf_set_initial` function should be
-called. It has the following prototype:
-``void smf_set_initial(smf_ctx *ctx, smf_state *state)``
+called.
To transition from one state to another, the :c:func:`smf_set_state`
-function is used and it has the following prototype:
-``void smf_set_state(smf_ctx *ctx, smf_state *state)``
+function is used.
.. note:: If :kconfig:option:`CONFIG_SMF_INITIAL_TRANSITION` is not set,
:c:func:`smf_set_initial` and :c:func:`smf_set_state` function should
not be passed a parent state as the parent state does not know which
child state to transition to. Transitioning to a parent state is OK
if an initial transition to a child state is defined. A well-formed
- HSM will have initial transitions defined for all parent states.
+ HSM should have initial transitions defined for all parent states.
-.. 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.
+.. note:: While the state machine is running, :c:func:`smf_set_state` should
+ only be called from the Entry or Run function. Calling
+ :c:func:`smf_set_state` from Exit functions will generate a warning in the
+ log and no transition will occur.
State Machine Execution
=======================
To run the state machine, the :c:func:`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)``
+smf_run_state if it returns a non-zero value.
Preventing Parent Run Actions
=============================
@@ -124,13 +122,38 @@
=========================
To terminate the state machine, the :c:func:`smf_set_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
-:c:func:`smf_run_state` function. The function has the following prototype:
-``void smf_set_terminate(smf_ctx *ctx, int32_t val)``
+should be called. It can be called from the entry, run, or exit actions. The
+function takes a non-zero user defined value that will be returned by the
+:c:func:`smf_run_state` function.
+
+UML State Machines
+==================
+
+SMF follows UML hierarchical state machine rules for transitions i.e., the
+entry and exit actions of the least common ancestor are not executed on
+transition, unless said transition is a transition to self.
+
+The UML Specification for StateMachines may be found in chapter 14 of the UML
+specification available here: https://www.omg.org/spec/UML/
+
+SMF breaks from UML rules in:
+
+1. Executing the actions associated with the transition within the context
+ of the source state, rather than after the exit actions are performed.
+2. Only allowing external transitions to self, not to sub-states. A transition
+ from a superstate to a child state is treated as a local transition.
+3. Prohibiting transitions using :c:func:`smf_set_state` in exit actions.
+
+SMF also does not provide any pseudostates except the Initial Pseudostate.
+Terminate pseudostates can be modelled by calling :c:func:`smf_set_terminate`
+from the entry action of a 'terminate' state. Orthogonal regions are modelled
+by calling :c:func:`smf_run_state` for each region.
+
+State Machine Examples
+======================
Flat State Machine Example
-==========================
+**************************
This example turns the following state diagram into code using the SMF, where
the initial state is S0.
@@ -232,7 +255,7 @@
}
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.
@@ -342,17 +365,14 @@
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.
+ - Ancestor exit actions are executed after the exit action of the current
+ state. 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 does not
call either :c:func:`smf_set_state` or :c:func:`smf_set_handled`.
- - Transitions to self in super-states containing sub-states are not supported.
- Transitions to self from the most-nested child state are supported and will
- call the exit and entry function of the child state correctly.
Event Driven State Machine Example
-==================================
+**********************************
Events are not explicitly part of the State Machine Framework but an event driven
state machine can be implemented using Zephyr :ref:`events`.
@@ -499,47 +519,55 @@
}
}
-Hierarchical State Machine Example With Initial Transitions
-===========================================================
+State Machine Example With Initial Transitions And Transition To Self
+*********************************************************************
-:zephyr_file:`tests/lib/smf/src/test_lib_initial_transitions_smf.c` defines
-a state machine for testing initial transitions and :c:func:`smf_set_handled`.
-The statechart for this test is below.
+:zephyr_file:`tests/lib/smf/src/test_lib_self_transition_smf.c` defines a state
+machine for testing the initial transitions and transitions to self in a parent
+state. The statechart for this test is below.
+
.. graphviz::
- :caption: Test state machine for initial transitions and ``smf_set_handled``
+ :caption: Test state machine for UML State Transitions
digraph smf_hierarchical_initial {
compound=true;
node [style = rounded];
- smf_set_initial [shape=plaintext];
+ "smf_set_initial()" [shape=plaintext fontname=Courier];
ab_init_state [shape = point];
STATE_A [shape = box];
STATE_B [shape = box];
STATE_C [shape = box];
STATE_D [shape = box];
+ DC[shape=point height=0 width=0 label=<>]
- subgraph cluster_ab {
- label = "PARENT_AB";
+ subgraph cluster_root {
+ label = "ROOT";
style = rounded;
- ab_init_state -> STATE_A;
- STATE_A -> STATE_B;
+
+ subgraph cluster_ab {
+ label = "PARENT_AB";
+ style = rounded;
+ ab_init_state -> STATE_A;
+ STATE_A -> STATE_B;
+ }
+
+ subgraph cluster_c {
+ label = "PARENT_C";
+ style = rounded;
+ STATE_B -> STATE_C [ltail=cluster_ab]
+ }
+
+ STATE_C -> DC [ltail=cluster_c, dir=none];
+ DC -> STATE_C [lhead=cluster_c];
+ STATE_C -> STATE_D
}
- subgraph cluster_c {
- label = "PARENT_C";
- style = rounded;
- STATE_C -> STATE_C
- }
-
- smf_set_initial -> STATE_A [lhead=cluster_ab]
- STATE_B -> STATE_C
- STATE_C -> STATE_D
+ "smf_set_initial()" -> STATE_A [lhead=cluster_ab]
}
-
API Reference
-*************
+=============
.. doxygengroup:: smf
diff --git a/include/zephyr/smf.h b/include/zephyr/smf.h
index ba01912..5b5f303 100644
--- a/include/zephyr/smf.h
+++ b/include/zephyr/smf.h
@@ -18,26 +18,27 @@
/**
* @brief State Machine Framework API
* @defgroup smf State Machine Framework API
+ * @version 0.1.0
* @ingroup os_services
* @{
*/
/**
- * @brief Macro to create a hierarchical state.
+ * @brief Macro to create a hierarchical state with initial transitions.
*
- * @param _entry State entry function
- * @param _run State run function
- * @param _exit State exit function
+ * @param _entry State entry function or NULL
+ * @param _run State run function or NULL
+ * @param _exit State exit function or NULL
* @param _parent State parent object or NULL
* @param _initial State initial transition object or NULL
*/
-#define SMF_CREATE_STATE(_entry, _run, _exit, _parent, _initial) \
-{ \
- .entry = _entry, \
- .run = _run, \
- .exit = _exit, \
- IF_ENABLED(CONFIG_SMF_ANCESTOR_SUPPORT, (.parent = _parent,)) \
- IF_ENABLED(CONFIG_SMF_INITIAL_TRANSITION, (.initial = _initial,)) \
+#define SMF_CREATE_STATE(_entry, _run, _exit, _parent, _initial) \
+{ \
+ .entry = _entry, \
+ .run = _run, \
+ .exit = _exit, \
+ IF_ENABLED(CONFIG_SMF_ANCESTOR_SUPPORT, (.parent = _parent,)) \
+ IF_ENABLED(CONFIG_SMF_INITIAL_TRANSITION, (.initial = _initial,)) \
}
/**
@@ -72,6 +73,7 @@
const state_execution run;
/** Optional method that will be run when this state exists */
const state_execution exit;
+#ifdef CONFIG_SMF_ANCESTOR_SUPPORT
/**
* Optional parent state that contains common entry/run/exit
* implementation among various child states.
@@ -79,8 +81,8 @@
* 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.
+ * 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;
@@ -89,7 +91,8 @@
* Optional initial transition state. NULL for leaf states.
*/
const struct smf_state *initial;
-#endif
+#endif /* CONFIG_SMF_INITIAL_TRANSITION */
+#endif /* CONFIG_SMF_ANCESTOR_SUPPORT */
};
/** Defines the current context of the state machine. */
@@ -98,6 +101,11 @@
const struct smf_state *current;
/** Previous state the state machine executed */
const struct smf_state *previous;
+
+#ifdef CONFIG_SMF_ANCESTOR_SUPPORT
+ /** Currently executing state (which may be a parent) */
+ const struct smf_state *executing;
+#endif /* CONFIG_SMF_ANCESTOR_SUPPORT */
/**
* This value is set by the set_terminate function and
* should terminate the state machine when its set to a
@@ -122,8 +130,8 @@
/**
* @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.
+ * state and entering the target state. For HSMs the entry and exit
+ * actions of the Least Common Ancestor will not be run.
*
* @param ctx State machine context
* @param new_state State to transition to (NULL is valid and exits all states)
diff --git a/lib/smf/smf.c b/lib/smf/smf.c
index ecc1074..fb0c95c 100644
--- a/lib/smf/smf.c
+++ b/lib/smf/smf.c
@@ -9,24 +9,22 @@
#include <zephyr/logging/log.h>
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.
+/**
+ * @brief 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;
- bool handled : 1;
+ bool new_state: 1;
+ bool terminate: 1;
+ bool is_exit: 1;
+ bool handled: 1;
};
-static bool share_paren(const struct smf_state *test_state,
- const struct smf_state *target_state)
+#ifdef CONFIG_SMF_ANCESTOR_SUPPORT
+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) {
+ for (const struct smf_state *state = test_state; state != NULL; state = state->parent) {
if (target_state == state) {
return true;
}
@@ -35,21 +33,10 @@
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) {
+ for (const struct smf_state *tmp = states;; tmp = tmp->parent) {
if (tmp->parent == parent) {
return tmp;
}
@@ -68,22 +55,55 @@
}
/**
- * @brief Execute all ancestor entry actions
+ * @brief Find the Least Common Ancestor (LCA) of two states
+ *
+ * @param source transition source
+ * @param dest transition destination
+ * @return LCA state, or NULL if states have no LCA.
+ */
+static const struct smf_state *get_lca_of(const struct smf_state *source,
+ const struct smf_state *dest)
+{
+ for (const struct smf_state *ancestor = source->parent; ancestor != NULL;
+ ancestor = ancestor->parent) {
+ if (ancestor == dest) {
+ return ancestor->parent;
+ } else if (share_paren(dest, ancestor)) {
+ return ancestor;
+ }
+ }
+
+ return NULL;
+}
+
+/**
+ * @brief Executes all entry actions from the direct child of topmost to the new state
*
* @param ctx State machine context
- * @param target The entry actions of this target's ancestors are executed
+ * @param new_state State we are transitioning to
+ * @param topmost State we are entering from. Its entry action is not 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)
+static bool smf_execute_all_entry_actions(struct smf_ctx *const ctx,
+ const struct smf_state *new_state,
+ const struct smf_state *topmost)
{
- struct internal_ctx * const internal = (void *) &ctx->internal;
+ 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) {
+ if (new_state == topmost) {
+ /* There are no child states, so do nothing */
+ return false;
+ }
+
+ for (const struct smf_state *to_execute = get_child_of(new_state, topmost);
+ to_execute != NULL && to_execute != new_state;
+ to_execute = get_child_of(new_state, to_execute)) {
+ /* Execute every entry action EXCEPT that of the topmost state */
+ if (to_execute->entry) {
+ /* Keep track of the executing entry action in case it calls
+ * smf_set_State()
+ */
+ ctx->executing = to_execute;
to_execute->entry(ctx);
/* No need to continue if terminate was set */
@@ -93,6 +113,16 @@
}
}
+ /* and execute the new state entry action */
+ if (new_state->entry) {
+ new_state->entry(ctx);
+
+ /* No need to continue if terminate was set */
+ if (internal->terminate) {
+ return true;
+ }
+ }
+
return false;
}
@@ -103,9 +133,9 @@
* @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)
+static bool smf_execute_ancestor_run_actions(struct smf_ctx *ctx)
{
- struct internal_ctx * const internal = (void *) &ctx->internal;
+ struct internal_ctx *const internal = (void *)&ctx->internal;
/* Execute all run actions in reverse order */
/* Return if the current state switched states */
@@ -126,9 +156,10 @@
}
/* Try to run parent run actions */
- for (const struct smf_state *tmp_state = ctx->current->parent;
- tmp_state != NULL;
+ for (const struct smf_state *tmp_state = ctx->current->parent; tmp_state != NULL;
tmp_state = tmp_state->parent) {
+ /* Keep track of where we are in case an ancestor calls smf_set_state() */
+ ctx->executing = tmp_state;
/* Execute parent run action */
if (tmp_state->run) {
tmp_state->run(ctx);
@@ -156,39 +187,35 @@
}
/**
- * @brief Execute all ancestor exit actions
+ * @brief Executes all exit actions from ctx->current to the direct child of topmost
*
* @param ctx State machine context
- * @param target The exit actions of this target's ancestors are executed
+ * @param topmost State we are exiting to. Its exit action is not 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)
+static bool smf_execute_all_exit_actions(struct smf_ctx *const ctx, const struct smf_state *topmost)
{
- struct internal_ctx * const internal = (void *) &ctx->internal;
+ struct internal_ctx *const internal = (void *)&ctx->internal;
- /* Execute all parent exit actions in reverse order */
+ for (const struct smf_state *to_execute = ctx->current; to_execute != topmost;
+ to_execute = to_execute->parent) {
+ if (to_execute->exit) {
+ to_execute->exit(ctx);
- for (const struct smf_state *tmp_state = ctx->current->parent;
- tmp_state != NULL;
- tmp_state = tmp_state->parent) {
- if ((target == NULL || !share_paren(target->parent, tmp_state)) &&
- tmp_state->exit) {
- tmp_state->exit(ctx);
-
- /* No need to continue if terminate was set */
+ /* No need to continue if terminate was set in the exit action */
if (internal->terminate) {
return true;
}
}
}
+
return false;
}
+#endif /* CONFIG_SMF_ANCESTOR_SUPPORT */
void smf_set_initial(struct smf_ctx *ctx, const struct smf_state *init_state)
{
- struct internal_ctx * const internal = (void *) &ctx->internal;
-
+ struct internal_ctx *const internal = (void *)&ctx->internal;
#ifdef CONFIG_SMF_INITIAL_TRANSITION
/*
@@ -199,98 +226,150 @@
init_state = init_state->initial;
}
#endif
- internal->exit = false;
+
+ internal->is_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;
+#ifdef CONFIG_SMF_ANCESTOR_SUPPORT
+ ctx->executing = init_state;
+ const struct smf_state *topmost = get_last_of(init_state);
- if (smf_execute_ancestor_entry_actions(ctx, init_state)) {
+ /* Execute topmost state entry action, since smf_execute_all_entry_actions()
+ * doesn't
+ */
+ if (topmost->entry) {
+ topmost->entry(ctx);
+ if (internal->terminate) {
+ /* No need to continue if terminate was set */
return;
}
}
- /* Now execute the initial state's entry action */
+ if (smf_execute_all_entry_actions(ctx, init_state, topmost)) {
+ /* No need to continue if terminate was set */
+ return;
+ }
+#else
+ /* execute entry action if it exists */
if (init_state->entry) {
init_state->entry(ctx);
}
+#endif
}
-void smf_set_state(struct smf_ctx *const ctx, const struct smf_state *target)
+void smf_set_state(struct smf_ctx *const ctx, const struct smf_state *new_state)
{
- struct internal_ctx * const internal = (void *) &ctx->internal;
+ 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__);
+ if (new_state == NULL) {
+ LOG_ERR("new_state cannot be NULL");
return;
}
- internal->exit = true;
+ /*
+ * It does not make sense to call smf_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->is_exit) {
+ LOG_ERR("Calling %s from exit action", __func__);
+ return;
+ }
- /* Execute the current states exit action */
- if (ctx->current->exit) {
- ctx->current->exit(ctx);
+#ifdef CONFIG_SMF_ANCESTOR_SUPPORT
+ const struct smf_state *topmost;
- /*
- * No need to continue if terminate was set in the
- * exit action
- */
+ if (share_paren(ctx->executing, new_state)) {
+ /* new state is a parent of where we are now*/
+ topmost = new_state;
+ } else if (share_paren(new_state, ctx->executing)) {
+ /* we are a parent of the new state */
+ topmost = ctx->executing;
+ } else {
+ /* not directly related, find LCA */
+ topmost = get_lca_of(ctx->executing, new_state);
+ }
+
+ internal->is_exit = true;
+ internal->new_state = true;
+
+ /* call all exit actions up to (but not including) the topmost */
+ if (smf_execute_all_exit_actions(ctx, topmost)) {
+ /* No need to continue if terminate was set in the exit action */
+ return;
+ }
+
+ /* if self-transition, call the exit action */
+ if ((ctx->executing == new_state) && (new_state->exit)) {
+ new_state->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;
+ internal->is_exit = false;
- if (smf_execute_ancestor_exit_actions(ctx, target)) {
+ /* if self transition, call the entry action */
+ if ((ctx->executing == new_state) && (new_state->entry)) {
+ new_state->entry(ctx);
+
+ /* No need to continue if terminate was set in the entry action */
+ if (internal->terminate) {
return;
}
}
-
- internal->exit = false;
-
#ifdef CONFIG_SMF_INITIAL_TRANSITION
/*
* The final target will be the deepest leaf state that
* the target contains. Set that as the real target.
*/
- while (target->initial) {
- target = target->initial;
+ while (new_state->initial) {
+ new_state = new_state->initial;
}
#endif
/* update the state variables */
ctx->previous = ctx->current;
- ctx->current = target;
+ ctx->current = new_state;
- if (IS_ENABLED(CONFIG_SMF_ANCESTOR_SUPPORT)) {
- if (smf_execute_ancestor_entry_actions(ctx, target)) {
+ /* call all entry actions (except those of topmost) */
+ if (smf_execute_all_entry_actions(ctx, new_state, topmost)) {
+ /* No need to continue if terminate was set in the entry action */
+ return;
+ }
+#else
+ /* Flat state machines have a very simple transition: */
+ if (ctx->current->exit) {
+ internal->is_exit = true;
+ ctx->current->exit(ctx);
+ /* No need to continue if terminate was set in the exit action */
+ if (internal->terminate) {
+ return;
+ }
+ internal->is_exit = false;
+ }
+ /* update the state variables */
+ ctx->previous = ctx->current;
+ ctx->current = new_state;
+
+ if (ctx->current->entry) {
+ ctx->current->entry(ctx);
+ /* No need to continue if terminate was set in the entry action */
+ if (internal->terminate) {
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
- */
- }
+#endif
}
void smf_set_terminate(struct smf_ctx *ctx, int32_t val)
{
- struct internal_ctx * const internal = (void *) &ctx->internal;
+ struct internal_ctx *const internal = (void *)&ctx->internal;
internal->terminate = true;
ctx->terminate_val = val;
@@ -305,22 +384,25 @@
int32_t smf_run_state(struct smf_ctx *const ctx)
{
- struct internal_ctx * const internal = (void *) &ctx->internal;
+ struct internal_ctx *const internal = (void *)&ctx->internal;
/* No need to continue if terminate was set */
if (internal->terminate) {
return ctx->terminate_val;
}
+#ifdef CONFIG_SMF_ANCESTOR_SUPPORT
+ ctx->executing = ctx->current;
+#endif
+
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;
- }
+#ifdef CONFIG_SMF_ANCESTOR_SUPPORT
+ if (smf_execute_ancestor_run_actions(ctx)) {
+ return ctx->terminate_val;
}
-
+#endif
return 0;
}
diff --git a/tests/lib/smf/CMakeLists.txt b/tests/lib/smf/CMakeLists.txt
index 08844e7..3e793f9 100644
--- a/tests/lib/smf/CMakeLists.txt
+++ b/tests/lib/smf/CMakeLists.txt
@@ -7,7 +7,7 @@
target_sources(app PRIVATE src/main.c)
if(CONFIG_SMF_INITIAL_TRANSITION)
- target_sources(app PRIVATE src/test_lib_initial_transitions_smf.c)
+ target_sources(app PRIVATE src/test_lib_self_transition_smf.c)
elseif(CONFIG_SMF_ANCESTOR_SUPPORT)
target_sources(app PRIVATE src/test_lib_hierarchical_smf.c
src/test_lib_hierarchical_5_ancestor_smf.c)
diff --git a/tests/lib/smf/src/test_lib_initial_transitions_smf.c b/tests/lib/smf/src/test_lib_initial_transitions_smf.c
deleted file mode 100644
index 2e00dc2..0000000
--- a/tests/lib/smf/src/test_lib_initial_transitions_smf.c
+++ /dev/null
@@ -1,501 +0,0 @@
-/*
- * Copyright 2024 Glenn Andrews
- * based on test_lib_hierarchical_smf.c
- *
- * SPDX-License-Identifier: Apache-2.0
- */
-
-#include <zephyr/ztest.h>
-#include <zephyr/smf.h>
-
-/*
- * Hierarchical Test Transition:
- *
- * PARENT_AB_ENTRY --> A_ENTRY --> A_RUN --> PARENT_AB_RUN ---|
- * |
- * |----------------------------------------------------------|
- * |
- * |--> A_EXIT --> B_ENTRY --> B_RUN --> B_EXIT --------------|
- * |
- * |----------------------------------------------------------|
- * |
- * |--> PARENT_AB_EXIT --> PARENT_C_ENTRY --> C_ENTRY --------|
- * |
- * |----------------------------------------------------------|
- * |
- * |--> C_RUN(#1) --> C_RUN(#2) --> C_EXIT --> PARENT_C_RUN --|
- * |
- * |----------------------------------------------------------|
- * |
- * |--> PARENT_C_EXIT
- */
-
-#define TEST_OBJECT(o) ((struct test_object *)o)
-
-#define SMF_RUN 4
-
-/* Initial Setup */
-#define PARENT_AB_ENTRY_BIT (1 << 0)
-#define STATE_A_ENTRY_BIT (1 << 1)
-
-/* Run 0 */
-#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)
-
-/* Run 1 */
-#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)
-
-/* Run 2 */
-#define STATE_C_1ST_RUN_BIT (1 << 11)
-
-/* Run 3 */
-#define STATE_C_2ND_RUN_BIT (1 << 12)
-#define PARENT_C_RUN_BIT (1 << 13)
-#define STATE_C_EXIT_BIT (1 << 14)
-#define PARENT_C_EXIT_BIT (1 << 15)
-
-#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 14
-#define TEST_VALUE_NUM 16
-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, /* PARENT_AB_EXIT */
- 0x1ff, /* PARENT_C_ENTRY */
- 0x3ff, /* STATE_C_ENTRY */
- 0x7ff, /* STATE_C_1ST_RUN */
- 0xfff, /* STATE_C_2ND_RUN */
- 0x1fff, /* STATE_C_EXIT */
- 0x3fff, /* PARENT_C_RUN */
- 0x7fff, /* PARENT_C_EXIT */
- 0xffff, /* 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;
- uint32_t first_time;
-} 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)
-{
- struct test_object *o = TEST_OBJECT(obj);
-
- o->tv_idx++;
-
- if (o->first_time) {
- /* This state should not be reached */
- zassert_true(0, "Test Parent C run failed");
- } else {
- o->transition_bits |= PARENT_C_RUN_BIT;
-
- smf_set_state(SMF_CTX(obj), &test_states[STATE_D]);
- }
-}
-
-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");
-
- if (o->first_time) {
- o->first_time = false;
- o->transition_bits |= STATE_C_1ST_RUN_BIT;
- smf_set_handled(SMF_CTX(obj));
- } else {
- /* Do nothing, Let parent handle it */
- o->transition_bits |= STATE_C_2ND_RUN_BIT;
- }
-}
-
-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, &test_states[STATE_A]),
- [PARENT_C] = SMF_CREATE_STATE(parent_c_entry, parent_c_run,
- parent_c_exit, NULL, &test_states[STATE_C]),
- [STATE_A] = SMF_CREATE_STATE(state_a_entry, state_a_run, state_a_exit,
- &test_states[PARENT_AB], NULL),
- [STATE_B] = SMF_CREATE_STATE(state_b_entry, state_b_run, state_b_exit,
- &test_states[PARENT_AB], NULL),
- [STATE_C] = SMF_CREATE_STATE(state_c_entry, state_c_run, state_c_exit,
- &test_states[PARENT_C], NULL),
- [STATE_D] = SMF_CREATE_STATE(state_d_entry, state_d_run, state_d_exit,
- NULL, NULL),
-};
-
-ZTEST(smf_tests, test_smf_initial_transitions)
-{
- /* A) Test state transitions */
-
- test_obj.transition_bits = 0;
- test_obj.first_time = 1;
- test_obj.terminate = NONE;
- smf_set_initial((struct smf_ctx *)&test_obj, &test_states[PARENT_AB]);
-
- 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.first_time = 1;
- test_obj.terminate = PARENT_ENTRY;
- smf_set_initial((struct smf_ctx *)&test_obj, &test_states[PARENT_AB]);
-
- 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.first_time = 1;
- test_obj.terminate = PARENT_RUN;
- smf_set_initial((struct smf_ctx *)&test_obj, &test_states[PARENT_AB]);
-
- 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.first_time = 1;
- test_obj.terminate = PARENT_EXIT;
- smf_set_initial((struct smf_ctx *)&test_obj, &test_states[PARENT_AB]);
-
- 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.first_time = 1;
- test_obj.terminate = ENTRY;
- smf_set_initial((struct smf_ctx *)&test_obj, &test_states[PARENT_AB]);
-
- 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.first_time = 1;
- test_obj.terminate = RUN;
- smf_set_initial((struct smf_ctx *)&test_obj, &test_states[PARENT_AB]);
-
- 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.first_time = 1;
- test_obj.terminate = EXIT;
- smf_set_initial((struct smf_ctx *)&test_obj, &test_states[PARENT_AB]);
-
- 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_self_transition_smf.c b/tests/lib/smf/src/test_lib_self_transition_smf.c
new file mode 100644
index 0000000..c78e6fa
--- /dev/null
+++ b/tests/lib/smf/src/test_lib_self_transition_smf.c
@@ -0,0 +1,577 @@
+/*
+ * Copyright 2024 Glenn Andrews
+ * based on test_lib_hierarchical_smf.c
+ * Copyright 2021 The Chromium OS Authors
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#include <zephyr/ztest.h>
+#include <zephyr/smf.h>
+
+/*
+ * Hierarchical Test Transition to self:
+ *
+ * This implements a hierarchical state machine using UML rules and demonstrates
+ * initial transitions, transitions to self (in PARENT_C) and smf_set_handled (in STATE_B)
+ *
+ * The order of entry, exit and run actions is given in the ordering of the test_value[] array.
+ */
+
+#define TEST_OBJECT(o) ((struct test_object *)o)
+
+#define SMF_RUN 5
+
+/* Initial Setup: Testing initial transitions */
+#define ROOT_ENTRY_BIT BIT(0)
+#define PARENT_AB_ENTRY_BIT BIT(1)
+#define STATE_A_ENTRY_BIT BIT(2)
+
+/* Run 0: normal state transition */
+#define STATE_A_RUN_BIT BIT(3)
+#define STATE_A_EXIT_BIT BIT(4)
+#define STATE_B_ENTRY_BIT BIT(5)
+
+/* Run 1: Test smf_set_handled() */
+#define STATE_B_1ST_RUN_BIT BIT(6)
+
+/* Run 2: Normal state transition via parent */
+#define STATE_B_2ND_RUN_BIT BIT(7)
+#define PARENT_AB_RUN_BIT BIT(8)
+#define STATE_B_EXIT_BIT BIT(9)
+#define PARENT_AB_EXIT_BIT BIT(10)
+#define PARENT_C_1ST_ENTRY_BIT BIT(11)
+#define STATE_C_1ST_ENTRY_BIT BIT(12)
+
+/* Run 3: PARENT_C executes transition to self */
+#define STATE_C_1ST_RUN_BIT BIT(13)
+#define PARENT_C_RUN_BIT BIT(14)
+#define STATE_C_1ST_EXIT_BIT BIT(15)
+#define PARENT_C_1ST_EXIT_BIT BIT(16)
+#define PARENT_C_2ND_ENTRY_BIT BIT(17)
+#define STATE_C_2ND_ENTRY_BIT BIT(18)
+
+/* Run 4: Test transition from parent state */
+#define STATE_C_2ND_RUN_BIT BIT(19)
+#define STATE_C_2ND_EXIT_BIT BIT(20)
+#define PARENT_C_2ND_EXIT_BIT BIT(21)
+
+/* Unused functions: Error checks if set */
+#define ROOT_RUN_BIT BIT(22)
+#define ROOT_EXIT_BIT BIT(23)
+
+/* Number of state transitions for each test: */
+#define TEST_VALUE_NUM 22
+#define TEST_PARENT_ENTRY_VALUE_NUM 1
+#define TEST_PARENT_RUN_VALUE_NUM 8
+#define TEST_PARENT_EXIT_VALUE_NUM 10
+#define TEST_ENTRY_VALUE_NUM 2
+#define TEST_RUN_VALUE_NUM 6
+#define TEST_EXIT_VALUE_NUM 15
+
+/*
+ * Note: Test values are taken before the appropriate test bit for that state is set i.e. if
+ * ROOT_ENTRY_BIT is BIT(0), test_value for root_entry() will be BIT_MASK(0) not BIT_MASK(1)
+ */
+static uint32_t test_value[] = {
+ /* Initial Setup */
+ BIT_MASK(0), /* ROOT_ENTRY_BIT */
+ BIT_MASK(1), /* PARENT_AB_ENTRY_BIT */
+ BIT_MASK(2), /* STATE_A_ENTRY_BIT */
+ /* Run 0 */
+ BIT_MASK(3), /* STATE_A_RUN_BIT */
+ BIT_MASK(4), /* STATE_A_EXIT_BIT */
+ BIT_MASK(5), /* STATE_B_ENTRY_BIT */
+ /* Run 1 */
+ BIT_MASK(6), /* STATE_B_1ST_RUN_BIT */
+ /* Run 2 */
+ BIT_MASK(7), /* STATE_B_2ND_RUN_BIT */
+ BIT_MASK(8), /* PARENT_AB_RUN_BIT */
+ BIT_MASK(9), /* STATE_B_EXIT_BIT */
+ BIT_MASK(10), /* PARENT_AB_EXIT_BIT */
+ BIT_MASK(11), /* PARENT_C_1ST_ENTRY_BIT */
+ BIT_MASK(12), /* STATE_C_1ST_ENTRY_BIT */
+ /* Run 3 */
+ BIT_MASK(13), /* STATE_C_1ST_RUN_BIT */
+ BIT_MASK(14), /* PARENT_C_RUN_BIT */
+ BIT_MASK(15), /* STATE_C_1ST_EXIT_BIT */
+ BIT_MASK(16), /* PARENT_C_1ST_EXIT_BIT */
+ BIT_MASK(17), /* PARENT_C_2ND_ENTRY_BIT */
+ BIT_MASK(18), /* STATE_C_2ND_ENTRY_BIT */
+ /* Run 4 */
+ BIT_MASK(19), /* STATE_C_2ND_RUN_BIT */
+ BIT_MASK(20), /* STATE_C_2ND_EXIT_BIT */
+ BIT_MASK(21), /* PARENT_C_2ND_EXIT_BIT */
+ /* Post-run Check */
+ BIT_MASK(22), /* FINAL_VALUE */
+};
+
+/* Forward declaration of test_states */
+static const struct smf_state test_states[];
+
+/* List of all TypeC-level states */
+enum test_state {
+ ROOT,
+ 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
+};
+
+#define B_ENTRY_FIRST_TIME BIT(0)
+#define B_RUN_FIRST_TIME BIT(1)
+#define PARENT_C_ENTRY_FIRST_TIME BIT(2)
+#define C_RUN_FIRST_TIME BIT(3)
+#define C_ENTRY_FIRST_TIME BIT(4)
+#define C_EXIT_FIRST_TIME BIT(5)
+
+#define FIRST_TIME_BITS \
+ (B_ENTRY_FIRST_TIME | B_RUN_FIRST_TIME | PARENT_C_ENTRY_FIRST_TIME | C_RUN_FIRST_TIME | \
+ C_ENTRY_FIRST_TIME | C_EXIT_FIRST_TIME)
+
+static struct test_object {
+ struct smf_ctx ctx;
+ uint32_t transition_bits;
+ uint32_t tv_idx;
+ enum terminate_action terminate;
+ uint32_t first_time;
+} test_obj;
+
+static void root_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 Root entry failed");
+
+ o->transition_bits |= ROOT_ENTRY_BIT;
+}
+
+static void root_run(void *obj)
+{
+ struct test_object *o = TEST_OBJECT(obj);
+
+ o->tv_idx++;
+
+ zassert_equal(o->transition_bits, test_value[o->tv_idx], "Test Root run failed");
+
+ o->transition_bits |= ROOT_RUN_BIT;
+
+ /* Return to parent run state */
+}
+
+static void root_exit(void *obj)
+{
+ struct test_object *o = TEST_OBJECT(obj);
+
+ o->tv_idx++;
+
+ zassert_equal(o->transition_bits, test_value[o->tv_idx], "Test Root exit failed");
+ o->transition_bits |= ROOT_EXIT_BIT;
+}
+
+static void parent_ab_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 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_C]);
+}
+
+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");
+ if (o->first_time & PARENT_C_ENTRY_FIRST_TIME) {
+ o->first_time &= ~PARENT_C_ENTRY_FIRST_TIME;
+ o->transition_bits |= PARENT_C_1ST_ENTRY_BIT;
+ } else {
+ o->transition_bits |= PARENT_C_2ND_ENTRY_BIT;
+ }
+}
+
+static void parent_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 Parent C run failed");
+
+ o->transition_bits |= PARENT_C_RUN_BIT;
+
+ smf_set_state(SMF_CTX(obj), &test_states[PARENT_C]);
+}
+
+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");
+
+ if (o->first_time & B_ENTRY_FIRST_TIME) {
+ o->first_time &= ~B_ENTRY_FIRST_TIME;
+ o->transition_bits |= PARENT_C_1ST_EXIT_BIT;
+ } else {
+ o->transition_bits |= PARENT_C_2ND_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;
+
+ 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;
+ }
+
+ if (o->first_time & B_RUN_FIRST_TIME) {
+ o->first_time &= ~B_RUN_FIRST_TIME;
+ o->transition_bits |= STATE_B_1ST_RUN_BIT;
+ smf_set_handled(SMF_CTX(obj));
+ } else {
+ o->transition_bits |= STATE_B_2ND_RUN_BIT;
+ /* bubble up to PARENT_AB */
+ }
+}
+
+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");
+ if (o->first_time & C_ENTRY_FIRST_TIME) {
+ o->first_time &= ~C_ENTRY_FIRST_TIME;
+ o->transition_bits |= STATE_C_1ST_ENTRY_BIT;
+ } else {
+ o->transition_bits |= STATE_C_2ND_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");
+
+ if (o->first_time & C_RUN_FIRST_TIME) {
+ o->first_time &= ~C_RUN_FIRST_TIME;
+ o->transition_bits |= STATE_C_1ST_RUN_BIT;
+ /* Do nothing, Let parent handle it */
+ } else {
+ o->transition_bits |= STATE_C_2ND_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;
+ }
+
+ if (o->first_time & C_EXIT_FIRST_TIME) {
+ o->first_time &= ~C_EXIT_FIRST_TIME;
+ o->transition_bits |= STATE_C_1ST_EXIT_BIT;
+ } else {
+ o->transition_bits |= STATE_C_2ND_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[] = {
+ [ROOT] = SMF_CREATE_STATE(root_entry, root_run, root_exit, NULL, &test_states[PARENT_AB]),
+ [PARENT_AB] = SMF_CREATE_STATE(parent_ab_entry, parent_ab_run, parent_ab_exit,
+ &test_states[ROOT], &test_states[STATE_A]),
+ [PARENT_C] = SMF_CREATE_STATE(parent_c_entry, parent_c_run, parent_c_exit,
+ &test_states[ROOT], &test_states[STATE_C]),
+ [STATE_A] = SMF_CREATE_STATE(state_a_entry, state_a_run, state_a_exit,
+ &test_states[PARENT_AB], NULL),
+ [STATE_B] = SMF_CREATE_STATE(state_b_entry, state_b_run, state_b_exit,
+ &test_states[PARENT_AB], NULL),
+ [STATE_C] = SMF_CREATE_STATE(state_c_entry, state_c_run, state_c_exit,
+ &test_states[PARENT_C], NULL),
+ [STATE_D] = SMF_CREATE_STATE(state_d_entry, state_d_run, state_d_exit, &test_states[ROOT],
+ NULL),
+};
+
+ZTEST(smf_tests, test_smf_self_transition)
+{
+ /* A) Test state transitions */
+
+ test_obj.transition_bits = 0;
+ test_obj.first_time = FIRST_TIME_BITS;
+ test_obj.terminate = NONE;
+ smf_set_initial((struct smf_ctx *)&test_obj, &test_states[PARENT_AB]);
+
+ 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.first_time = FIRST_TIME_BITS;
+ test_obj.terminate = PARENT_ENTRY;
+ smf_set_initial((struct smf_ctx *)&test_obj, &test_states[PARENT_AB]);
+
+ 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.first_time = FIRST_TIME_BITS;
+ test_obj.terminate = PARENT_RUN;
+ smf_set_initial((struct smf_ctx *)&test_obj, &test_states[PARENT_AB]);
+
+ 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.first_time = FIRST_TIME_BITS;
+ test_obj.terminate = PARENT_EXIT;
+ smf_set_initial((struct smf_ctx *)&test_obj, &test_states[PARENT_AB]);
+
+ 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.first_time = FIRST_TIME_BITS;
+ test_obj.terminate = ENTRY;
+ smf_set_initial((struct smf_ctx *)&test_obj, &test_states[PARENT_AB]);
+
+ 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.first_time = FIRST_TIME_BITS;
+ test_obj.terminate = RUN;
+ smf_set_initial((struct smf_ctx *)&test_obj, &test_states[PARENT_AB]);
+
+ 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.first_time = FIRST_TIME_BITS;
+ test_obj.terminate = EXIT;
+ smf_set_initial((struct smf_ctx *)&test_obj, &test_states[PARENT_AB]);
+
+ 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
index 6296352..032dec0 100644
--- a/tests/lib/smf/src/test_lib_smf.h
+++ b/tests/lib/smf/src/test_lib_smf.h
@@ -10,6 +10,6 @@
void test_smf_flat(void);
void test_smf_hierarchical(void);
void test_smf_hierarchical_5_ancestors(void);
-void test_smf_initial_transitions(void);
+void test_smf_self_transition(void);
#endif /* ZEPHYR_TEST_LIB_SMF_H_ */