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:
parent
a6dcf333a1
commit
cb4785542e
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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->A -->
|
||||
<g id="edge1" class="edge">
|
||||
<title>ENTRY->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->B -->
|
||||
<g id="edge2" class="edge">
|
||||
<title>A->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->C -->
|
||||
<g id="edge3" class="edge">
|
||||
<title>B->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->A -->
|
||||
<g id="edge4" class="edge">
|
||||
<title>C->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 |
|
@ -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->B -->
|
||||
<g id="edge2" class="edge">
|
||||
<title>A->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->C -->
|
||||
<g id="edge3" class="edge">
|
||||
<title>B->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->A -->
|
||||
<g id="edge4" class="edge">
|
||||
<title>C->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->A -->
|
||||
<g id="edge1" class="edge">
|
||||
<title>ENTRY->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 |
|
@ -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;
|
||||
}
|
|
@ -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_ */
|
|
@ -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)
|
||||
|
|
|
@ -15,4 +15,6 @@ source "lib/open-amp/Kconfig"
|
|||
|
||||
source "lib/util/Kconfig"
|
||||
|
||||
source "lib/smf/Kconfig"
|
||||
|
||||
endmenu
|
||||
|
|
|
@ -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)
|
|
@ -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
|
|
@ -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;
|
||||
}
|
|
@ -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()
|
|
@ -0,0 +1,3 @@
|
|||
CONFIG_ZTEST=y
|
||||
CONFIG_LOG=y
|
||||
CONFIG_SMF=y
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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");
|
||||
}
|
|
@ -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");
|
||||
}
|
|
@ -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");
|
||||
|
||||
}
|
|
@ -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_ */
|
|
@ -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
|
Loading…
Reference in New Issue