lib: smf: Add State Machine Framework

Add an application agnostic State Machine Framework library to
Zephyr that provides an easy way for developers to integrate
state machines into their application.

Twister passed:
twister -T tests/lib/smf/

Signed-off-by: Sam Hurst <sbh1187@gmail.com>
This commit is contained in:
Sam Hurst 2021-08-28 21:44:17 -07:00 committed by Christopher Friedt
parent a6dcf333a1
commit cb4785542e
19 changed files with 2208 additions and 0 deletions

View File

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

View File

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

View File

@ -0,0 +1,60 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Generated by graphviz version 2.43.0 (0)
-->
<!-- Title: flat_smf Pages: 1 -->
<svg width="129pt" height="231pt"
viewBox="0.00 0.00 129.00 230.60" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 226.6)">
<title>flat_smf</title>
<polygon fill="white" stroke="transparent" points="-4,4 -4,-226.6 125,-226.6 125,4 -4,4"/>
<!-- ENTRY -->
<g id="node1" class="node">
<title>ENTRY</title>
<ellipse fill="black" stroke="black" cx="78" cy="-220.8" rx="1.8" ry="1.8"/>
</g>
<!-- A -->
<g id="node2" class="node">
<title>A</title>
<path fill="none" stroke="black" d="M47,-146.5C47,-146.5 109,-146.5 109,-146.5 115,-146.5 121,-152.5 121,-158.5 121,-158.5 121,-170.5 121,-170.5 121,-176.5 115,-182.5 109,-182.5 109,-182.5 47,-182.5 47,-182.5 41,-182.5 35,-176.5 35,-170.5 35,-170.5 35,-158.5 35,-158.5 35,-152.5 41,-146.5 47,-146.5"/>
<text text-anchor="middle" x="78" y="-160.8" font-family="Times,serif" font-size="14.00">STATE S0</text>
</g>
<!-- ENTRY&#45;&gt;A -->
<g id="edge1" class="edge">
<title>ENTRY&#45;&gt;A</title>
<path fill="none" stroke="black" d="M78,-218.86C78,-215.58 78,-204.26 78,-192.88"/>
<polygon fill="black" stroke="black" points="81.5,-192.64 78,-182.64 74.5,-192.64 81.5,-192.64"/>
</g>
<!-- B -->
<g id="node3" class="node">
<title>B</title>
<path fill="none" stroke="black" d="M12,-73.5C12,-73.5 74,-73.5 74,-73.5 80,-73.5 86,-79.5 86,-85.5 86,-85.5 86,-97.5 86,-97.5 86,-103.5 80,-109.5 74,-109.5 74,-109.5 12,-109.5 12,-109.5 6,-109.5 0,-103.5 0,-97.5 0,-97.5 0,-85.5 0,-85.5 0,-79.5 6,-73.5 12,-73.5"/>
<text text-anchor="middle" x="43" y="-87.8" font-family="Times,serif" font-size="14.00">STATE S1</text>
</g>
<!-- A&#45;&gt;B -->
<g id="edge2" class="edge">
<title>A&#45;&gt;B</title>
<path fill="none" stroke="black" d="M69.53,-146.31C65.44,-138.03 60.46,-127.91 55.91,-118.69"/>
<polygon fill="black" stroke="black" points="58.96,-116.95 51.39,-109.53 52.68,-120.05 58.96,-116.95"/>
</g>
<!-- C -->
<g id="node4" class="node">
<title>C</title>
<path fill="none" stroke="black" d="M47,-0.5C47,-0.5 109,-0.5 109,-0.5 115,-0.5 121,-6.5 121,-12.5 121,-12.5 121,-24.5 121,-24.5 121,-30.5 115,-36.5 109,-36.5 109,-36.5 47,-36.5 47,-36.5 41,-36.5 35,-30.5 35,-24.5 35,-24.5 35,-12.5 35,-12.5 35,-6.5 41,-0.5 47,-0.5"/>
<text text-anchor="middle" x="78" y="-14.8" font-family="Times,serif" font-size="14.00">STATE S2</text>
</g>
<!-- B&#45;&gt;C -->
<g id="edge3" class="edge">
<title>B&#45;&gt;C</title>
<path fill="none" stroke="black" d="M51.47,-73.31C55.56,-65.03 60.54,-54.91 65.09,-45.69"/>
<polygon fill="black" stroke="black" points="68.32,-47.05 69.61,-36.53 62.04,-43.95 68.32,-47.05"/>
</g>
<!-- C&#45;&gt;A -->
<g id="edge4" class="edge">
<title>C&#45;&gt;A</title>
<path fill="none" stroke="black" d="M84.89,-36.52C88.71,-46.88 93.06,-60.48 95,-73 97.52,-89.25 97.52,-93.75 95,-110 93.62,-118.9 91.02,-128.35 88.26,-136.79"/>
<polygon fill="black" stroke="black" points="84.87,-135.88 84.89,-146.48 91.48,-138.18 84.87,-135.88"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

@ -0,0 +1,65 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Generated by graphviz version 2.43.0 (0)
-->
<!-- Title: hierarchical_smf Pages: 1 -->
<svg width="145pt" height="234pt"
viewBox="0.00 0.00 145.00 233.60" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 229.6)">
<title>hierarchical_smf</title>
<polygon fill="white" stroke="transparent" points="-4,4 -4,-229.6 141,-229.6 141,4 -4,4"/>
<g id="clust1" class="cluster">
<title>clusterOpen</title>
<path fill="none" stroke="black" d="M20,-65C20,-65 98,-65 98,-65 104,-65 110,-71 110,-77 110,-77 110,-202 110,-202 110,-208 104,-214 98,-214 98,-214 20,-214 20,-214 14,-214 8,-208 8,-202 8,-202 8,-77 8,-77 8,-71 14,-65 20,-65"/>
<text text-anchor="middle" x="59" y="-198.8" font-family="Times,serif" font-size="14.00">PARENT</text>
</g>
<!-- A -->
<g id="node1" class="node">
<title>A</title>
<path fill="none" stroke="black" d="M28,-146.5C28,-146.5 90,-146.5 90,-146.5 96,-146.5 102,-152.5 102,-158.5 102,-158.5 102,-170.5 102,-170.5 102,-176.5 96,-182.5 90,-182.5 90,-182.5 28,-182.5 28,-182.5 22,-182.5 16,-176.5 16,-170.5 16,-170.5 16,-158.5 16,-158.5 16,-152.5 22,-146.5 28,-146.5"/>
<text text-anchor="middle" x="59" y="-160.8" font-family="Times,serif" font-size="14.00">STATE S0</text>
</g>
<!-- B -->
<g id="node2" class="node">
<title>B</title>
<path fill="none" stroke="black" d="M28,-73.5C28,-73.5 90,-73.5 90,-73.5 96,-73.5 102,-79.5 102,-85.5 102,-85.5 102,-97.5 102,-97.5 102,-103.5 96,-109.5 90,-109.5 90,-109.5 28,-109.5 28,-109.5 22,-109.5 16,-103.5 16,-97.5 16,-97.5 16,-85.5 16,-85.5 16,-79.5 22,-73.5 28,-73.5"/>
<text text-anchor="middle" x="59" y="-87.8" font-family="Times,serif" font-size="14.00">STATE S1</text>
</g>
<!-- A&#45;&gt;B -->
<g id="edge2" class="edge">
<title>A&#45;&gt;B</title>
<path fill="none" stroke="black" d="M59,-146.31C59,-138.29 59,-128.55 59,-119.57"/>
<polygon fill="black" stroke="black" points="62.5,-119.53 59,-109.53 55.5,-119.53 62.5,-119.53"/>
</g>
<!-- C -->
<g id="node3" class="node">
<title>C</title>
<path fill="none" stroke="black" d="M63,-0.5C63,-0.5 125,-0.5 125,-0.5 131,-0.5 137,-6.5 137,-12.5 137,-12.5 137,-24.5 137,-24.5 137,-30.5 131,-36.5 125,-36.5 125,-36.5 63,-36.5 63,-36.5 57,-36.5 51,-30.5 51,-24.5 51,-24.5 51,-12.5 51,-12.5 51,-6.5 57,-0.5 63,-0.5"/>
<text text-anchor="middle" x="94" y="-14.8" font-family="Times,serif" font-size="14.00">STATE S2</text>
</g>
<!-- B&#45;&gt;C -->
<g id="edge3" class="edge">
<title>B&#45;&gt;C</title>
<path fill="none" stroke="black" d="M67.47,-73.31C71.56,-65.03 76.54,-54.91 81.09,-45.69"/>
<polygon fill="black" stroke="black" points="84.32,-47.05 85.61,-36.53 78.04,-43.95 84.32,-47.05"/>
</g>
<!-- C&#45;&gt;A -->
<g id="edge4" class="edge">
<title>C&#45;&gt;A</title>
<path fill="none" stroke="black" d="M102.83,-36.75C111.11,-55.4 120.86,-85.71 111,-110 106.48,-121.14 98.4,-131.23 89.94,-139.58"/>
<polygon fill="black" stroke="black" points="87.47,-137.09 82.48,-146.44 92.21,-142.24 87.47,-137.09"/>
</g>
<!-- ENTRY -->
<g id="node4" class="node">
<title>ENTRY</title>
<ellipse fill="black" stroke="black" cx="59" cy="-223.8" rx="1.8" ry="1.8"/>
</g>
<!-- ENTRY&#45;&gt;A -->
<g id="edge1" class="edge">
<title>ENTRY&#45;&gt;A</title>
<path fill="none" stroke="black" d="M59,-221.81C59,-218.21 59,-205.41 59,-192.93"/>
<polygon fill="black" stroke="black" points="62.5,-192.73 59,-182.73 55.5,-192.73 62.5,-192.73"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.5 KiB

333
doc/guides/smf/index.rst Normal file
View File

@ -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 <smf.h>
/* 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 <smf.h>
/* 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;
}

154
include/smf.h Normal file
View File

@ -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 <zephyr.h>
/**
* @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_ */

View File

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

View File

@ -15,4 +15,6 @@ source "lib/open-amp/Kconfig"
source "lib/util/Kconfig"
source "lib/smf/Kconfig"
endmenu

11
lib/smf/CMakeLists.txt Normal file
View File

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

16
lib/smf/Kconfig Normal file
View File

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

291
lib/smf/smf.c Normal file
View File

@ -0,0 +1,291 @@
/*
* Copyright 2021 The Chromium OS Authors
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "smf.h"
#include <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.
*/
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;
}

View File

@ -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()

3
tests/lib/smf/prj.conf Normal file
View File

@ -0,0 +1,3 @@
CONFIG_ZTEST=y
CONFIG_LOG=y
CONFIG_SMF=y

25
tests/lib/smf/src/main.c Normal file
View File

@ -0,0 +1,25 @@
/*
* Copyright 2021 The Chromium OS Authors
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr.h>
#include <ztest.h>
#include <smf.h>
#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);
}
}

View File

@ -0,0 +1,291 @@
/*
* Copyright 2021 The Chromium OS Authors
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <ztest.h>
#include <smf.h>
/*
* 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");
}

View File

@ -0,0 +1,442 @@
/*
* Copyright 2021 The Chromium OS Authors
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <ztest.h>
#include <smf.h>
/*
* 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");
}

View File

@ -0,0 +1,475 @@
/*
* Copyright 2021 The Chromium OS Authors
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <ztest.h>
#include <smf.h>
/*
* 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");
}

View File

@ -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_ */

View File

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