Lib: SMF Modify HSM operation for UML-Style transitions

Modify the SMF such that state transitions from parent states choose the
correct Least Common Ancestor based on the transition source rather than
the current state.

SMF set as experimental.

Signed-off-by: Glenn Andrews <glenn.andrews.42@gmail.com>
This commit is contained in:
Glenn Andrews 2024-04-20 20:43:43 -07:00 committed by Anas Nashif
commit 531c457550
8 changed files with 501 additions and 296 deletions

View file

@ -7,7 +7,7 @@ project(smf)
target_sources(app PRIVATE src/main.c)
if(CONFIG_SMF_INITIAL_TRANSITION)
target_sources(app PRIVATE src/test_lib_initial_transitions_smf.c)
target_sources(app PRIVATE src/test_lib_self_transition_smf.c)
elseif(CONFIG_SMF_ANCESTOR_SUPPORT)
target_sources(app PRIVATE src/test_lib_hierarchical_smf.c
src/test_lib_hierarchical_5_ancestor_smf.c)

View file

@ -1,6 +1,7 @@
/*
* Copyright 2024 Glenn Andrews
* based on test_lib_hierarchical_smf.c
* Copyright 2021 The Chromium OS Authors
*
* SPDX-License-Identifier: Apache-2.0
*/
@ -9,82 +10,100 @@
#include <zephyr/smf.h>
/*
* Hierarchical Test Transition:
* Hierarchical Test Transition to self:
*
* PARENT_AB_ENTRY --> A_ENTRY --> A_RUN --> PARENT_AB_RUN ---|
* |
* |----------------------------------------------------------|
* |
* |--> A_EXIT --> B_ENTRY --> B_RUN --> B_EXIT --------------|
* |
* |----------------------------------------------------------|
* |
* |--> PARENT_AB_EXIT --> PARENT_C_ENTRY --> C_ENTRY --------|
* |
* |----------------------------------------------------------|
* |
* |--> C_RUN(#1) --> C_RUN(#2) --> C_EXIT --> PARENT_C_RUN --|
* |
* |----------------------------------------------------------|
* |
* |--> PARENT_C_EXIT
* This implements a hierarchical state machine using UML rules and demonstrates
* initial transitions, transitions to self (in PARENT_C) and smf_set_handled (in STATE_B)
*
* The order of entry, exit and run actions is given in the ordering of the test_value[] array.
*/
#define TEST_OBJECT(o) ((struct test_object *)o)
#define SMF_RUN 4
#define SMF_RUN 5
/* Initial Setup */
#define PARENT_AB_ENTRY_BIT (1 << 0)
#define STATE_A_ENTRY_BIT (1 << 1)
/* Initial Setup: Testing initial transitions */
#define ROOT_ENTRY_BIT BIT(0)
#define PARENT_AB_ENTRY_BIT BIT(1)
#define STATE_A_ENTRY_BIT BIT(2)
/* Run 0 */
#define STATE_A_RUN_BIT (1 << 2)
#define PARENT_AB_RUN_BIT (1 << 3)
#define STATE_A_EXIT_BIT (1 << 4)
#define STATE_B_ENTRY_BIT (1 << 5)
/* Run 0: normal state transition */
#define STATE_A_RUN_BIT BIT(3)
#define STATE_A_EXIT_BIT BIT(4)
#define STATE_B_ENTRY_BIT BIT(5)
/* Run 1 */
#define STATE_B_RUN_BIT (1 << 6)
#define STATE_B_EXIT_BIT (1 << 7)
#define PARENT_AB_EXIT_BIT (1 << 8)
#define PARENT_C_ENTRY_BIT (1 << 9)
#define STATE_C_ENTRY_BIT (1 << 10)
/* Run 1: Test smf_set_handled() */
#define STATE_B_1ST_RUN_BIT BIT(6)
/* Run 2 */
#define STATE_C_1ST_RUN_BIT (1 << 11)
/* Run 2: Normal state transition via parent */
#define STATE_B_2ND_RUN_BIT BIT(7)
#define PARENT_AB_RUN_BIT BIT(8)
#define STATE_B_EXIT_BIT BIT(9)
#define PARENT_AB_EXIT_BIT BIT(10)
#define PARENT_C_1ST_ENTRY_BIT BIT(11)
#define STATE_C_1ST_ENTRY_BIT BIT(12)
/* Run 3 */
#define STATE_C_2ND_RUN_BIT (1 << 12)
#define PARENT_C_RUN_BIT (1 << 13)
#define STATE_C_EXIT_BIT (1 << 14)
#define PARENT_C_EXIT_BIT (1 << 15)
/* Run 3: PARENT_C executes transition to self */
#define STATE_C_1ST_RUN_BIT BIT(13)
#define PARENT_C_RUN_BIT BIT(14)
#define STATE_C_1ST_EXIT_BIT BIT(15)
#define PARENT_C_1ST_EXIT_BIT BIT(16)
#define PARENT_C_2ND_ENTRY_BIT BIT(17)
#define STATE_C_2ND_ENTRY_BIT BIT(18)
#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
/* Run 4: Test transition from parent state */
#define STATE_C_2ND_RUN_BIT BIT(19)
#define STATE_C_2ND_EXIT_BIT BIT(20)
#define PARENT_C_2ND_EXIT_BIT BIT(21)
/* Unused functions: Error checks if set */
#define ROOT_RUN_BIT BIT(22)
#define ROOT_EXIT_BIT BIT(23)
/* Number of state transitions for each test: */
#define TEST_VALUE_NUM 22
#define TEST_PARENT_ENTRY_VALUE_NUM 1
#define TEST_PARENT_RUN_VALUE_NUM 8
#define TEST_PARENT_EXIT_VALUE_NUM 10
#define TEST_ENTRY_VALUE_NUM 2
#define TEST_RUN_VALUE_NUM 6
#define TEST_EXIT_VALUE_NUM 14
#define TEST_VALUE_NUM 16
#define TEST_EXIT_VALUE_NUM 15
/*
* Note: Test values are taken before the appropriate test bit for that state is set i.e. if
* ROOT_ENTRY_BIT is BIT(0), test_value for root_entry() will be BIT_MASK(0) not BIT_MASK(1)
*/
static uint32_t test_value[] = {
0x00, /* PARENT_AB_ENTRY */
0x01, /* STATE_A_ENTRY */
0x03, /* STATE_A_RUN */
0x07, /* PARENT_AB_RUN */
0x0f, /* STATE_A_EXIT */
0x1f, /* STATE_B_ENTRY */
0x3f, /* STATE_B_RUN */
0x7f, /* STATE_B_EXIT */
0xff, /* PARENT_AB_EXIT */
0x1ff, /* PARENT_C_ENTRY */
0x3ff, /* STATE_C_ENTRY */
0x7ff, /* STATE_C_1ST_RUN */
0xfff, /* STATE_C_2ND_RUN */
0x1fff, /* STATE_C_EXIT */
0x3fff, /* PARENT_C_RUN */
0x7fff, /* PARENT_C_EXIT */
0xffff, /* FINAL VALUE */
/* Initial Setup */
BIT_MASK(0), /* ROOT_ENTRY_BIT */
BIT_MASK(1), /* PARENT_AB_ENTRY_BIT */
BIT_MASK(2), /* STATE_A_ENTRY_BIT */
/* Run 0 */
BIT_MASK(3), /* STATE_A_RUN_BIT */
BIT_MASK(4), /* STATE_A_EXIT_BIT */
BIT_MASK(5), /* STATE_B_ENTRY_BIT */
/* Run 1 */
BIT_MASK(6), /* STATE_B_1ST_RUN_BIT */
/* Run 2 */
BIT_MASK(7), /* STATE_B_2ND_RUN_BIT */
BIT_MASK(8), /* PARENT_AB_RUN_BIT */
BIT_MASK(9), /* STATE_B_EXIT_BIT */
BIT_MASK(10), /* PARENT_AB_EXIT_BIT */
BIT_MASK(11), /* PARENT_C_1ST_ENTRY_BIT */
BIT_MASK(12), /* STATE_C_1ST_ENTRY_BIT */
/* Run 3 */
BIT_MASK(13), /* STATE_C_1ST_RUN_BIT */
BIT_MASK(14), /* PARENT_C_RUN_BIT */
BIT_MASK(15), /* STATE_C_1ST_EXIT_BIT */
BIT_MASK(16), /* PARENT_C_1ST_EXIT_BIT */
BIT_MASK(17), /* PARENT_C_2ND_ENTRY_BIT */
BIT_MASK(18), /* STATE_C_2ND_ENTRY_BIT */
/* Run 4 */
BIT_MASK(19), /* STATE_C_2ND_RUN_BIT */
BIT_MASK(20), /* STATE_C_2ND_EXIT_BIT */
BIT_MASK(21), /* PARENT_C_2ND_EXIT_BIT */
/* Post-run Check */
BIT_MASK(22), /* FINAL_VALUE */
};
/* Forward declaration of test_states */
@ -92,6 +111,7 @@ static const struct smf_state test_states[];
/* List of all TypeC-level states */
enum test_state {
ROOT,
PARENT_AB,
PARENT_C,
STATE_A,
@ -110,6 +130,17 @@ enum terminate_action {
EXIT
};
#define B_ENTRY_FIRST_TIME BIT(0)
#define B_RUN_FIRST_TIME BIT(1)
#define PARENT_C_ENTRY_FIRST_TIME BIT(2)
#define C_RUN_FIRST_TIME BIT(3)
#define C_ENTRY_FIRST_TIME BIT(4)
#define C_EXIT_FIRST_TIME BIT(5)
#define FIRST_TIME_BITS \
(B_ENTRY_FIRST_TIME | B_RUN_FIRST_TIME | PARENT_C_ENTRY_FIRST_TIME | C_RUN_FIRST_TIME | \
C_ENTRY_FIRST_TIME | C_EXIT_FIRST_TIME)
static struct test_object {
struct smf_ctx ctx;
uint32_t transition_bits;
@ -118,14 +149,47 @@ static struct test_object {
uint32_t first_time;
} test_obj;
static void parent_ab_entry(void *obj)
static void root_entry(void *obj)
{
struct test_object *o = TEST_OBJECT(obj);
o->tv_idx = 0;
zassert_equal(o->transition_bits, test_value[o->tv_idx],
"Test Parent AB entry failed");
zassert_equal(o->transition_bits, test_value[o->tv_idx], "Test Root entry failed");
o->transition_bits |= ROOT_ENTRY_BIT;
}
static void root_run(void *obj)
{
struct test_object *o = TEST_OBJECT(obj);
o->tv_idx++;
zassert_equal(o->transition_bits, test_value[o->tv_idx], "Test Root run failed");
o->transition_bits |= ROOT_RUN_BIT;
/* Return to parent run state */
}
static void root_exit(void *obj)
{
struct test_object *o = TEST_OBJECT(obj);
o->tv_idx++;
zassert_equal(o->transition_bits, test_value[o->tv_idx], "Test Root exit failed");
o->transition_bits |= ROOT_EXIT_BIT;
}
static void parent_ab_entry(void *obj)
{
struct test_object *o = TEST_OBJECT(obj);
o->tv_idx++;
zassert_equal(o->transition_bits, test_value[o->tv_idx], "Test Parent AB entry failed");
if (o->terminate == PARENT_ENTRY) {
smf_set_terminate(obj, -1);
@ -141,8 +205,7 @@ static void parent_ab_run(void *obj)
o->tv_idx++;
zassert_equal(o->transition_bits, test_value[o->tv_idx],
"Test Parent AB run failed");
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);
@ -151,7 +214,7 @@ static void parent_ab_run(void *obj)
o->transition_bits |= PARENT_AB_RUN_BIT;
smf_set_state(SMF_CTX(obj), &test_states[STATE_B]);
smf_set_state(SMF_CTX(obj), &test_states[STATE_C]);
}
static void parent_ab_exit(void *obj)
@ -160,8 +223,7 @@ static void parent_ab_exit(void *obj)
o->tv_idx++;
zassert_equal(o->transition_bits, test_value[o->tv_idx],
"Test Parent AB exit failed");
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);
@ -177,9 +239,13 @@ static void parent_c_entry(void *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;
zassert_equal(o->transition_bits, test_value[o->tv_idx], "Test Parent C entry failed");
if (o->first_time & PARENT_C_ENTRY_FIRST_TIME) {
o->first_time &= ~PARENT_C_ENTRY_FIRST_TIME;
o->transition_bits |= PARENT_C_1ST_ENTRY_BIT;
} else {
o->transition_bits |= PARENT_C_2ND_ENTRY_BIT;
}
}
static void parent_c_run(void *obj)
@ -188,14 +254,11 @@ static void parent_c_run(void *obj)
o->tv_idx++;
if (o->first_time) {
/* This state should not be reached */
zassert_true(0, "Test Parent C run failed");
} else {
o->transition_bits |= PARENT_C_RUN_BIT;
zassert_equal(o->transition_bits, test_value[o->tv_idx], "Test Parent C run failed");
smf_set_state(SMF_CTX(obj), &test_states[STATE_D]);
}
o->transition_bits |= PARENT_C_RUN_BIT;
smf_set_state(SMF_CTX(obj), &test_states[PARENT_C]);
}
static void parent_c_exit(void *obj)
@ -204,9 +267,14 @@ static void parent_c_exit(void *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;
zassert_equal(o->transition_bits, test_value[o->tv_idx], "Test Parent C exit failed");
if (o->first_time & B_ENTRY_FIRST_TIME) {
o->first_time &= ~B_ENTRY_FIRST_TIME;
o->transition_bits |= PARENT_C_1ST_EXIT_BIT;
} else {
o->transition_bits |= PARENT_C_2ND_EXIT_BIT;
}
}
static void state_a_entry(void *obj)
@ -215,8 +283,7 @@ static void state_a_entry(void *obj)
o->tv_idx++;
zassert_equal(o->transition_bits, test_value[o->tv_idx],
"Test State A entry failed");
zassert_equal(o->transition_bits, test_value[o->tv_idx], "Test State A entry failed");
if (o->terminate == ENTRY) {
smf_set_terminate(obj, -1);
@ -232,12 +299,11 @@ static void state_a_run(void *obj)
o->tv_idx++;
zassert_equal(o->transition_bits, test_value[o->tv_idx],
"Test State A run failed");
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 */
smf_set_state(SMF_CTX(obj), &test_states[STATE_B]);
}
static void state_a_exit(void *obj)
@ -246,8 +312,7 @@ static void state_a_exit(void *obj)
o->tv_idx++;
zassert_equal(o->transition_bits, test_value[o->tv_idx],
"Test State A exit failed");
zassert_equal(o->transition_bits, test_value[o->tv_idx], "Test State A exit failed");
o->transition_bits |= STATE_A_EXIT_BIT;
}
@ -257,8 +322,8 @@ static void state_b_entry(void *obj)
o->tv_idx++;
zassert_equal(o->transition_bits, test_value[o->tv_idx],
"Test State B entry failed");
zassert_equal(o->transition_bits, test_value[o->tv_idx], "Test State B entry failed");
o->transition_bits |= STATE_B_ENTRY_BIT;
}
@ -268,17 +333,21 @@ static void state_b_run(void *obj)
o->tv_idx++;
zassert_equal(o->transition_bits, test_value[o->tv_idx],
"Test State B run failed");
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]);
if (o->first_time & B_RUN_FIRST_TIME) {
o->first_time &= ~B_RUN_FIRST_TIME;
o->transition_bits |= STATE_B_1ST_RUN_BIT;
smf_set_handled(SMF_CTX(obj));
} else {
o->transition_bits |= STATE_B_2ND_RUN_BIT;
/* bubble up to PARENT_AB */
}
}
static void state_b_exit(void *obj)
@ -287,8 +356,8 @@ static void state_b_exit(void *obj)
o->tv_idx++;
zassert_equal(o->transition_bits, test_value[o->tv_idx],
"Test State B exit failed");
zassert_equal(o->transition_bits, test_value[o->tv_idx], "Test State B exit failed");
o->transition_bits |= STATE_B_EXIT_BIT;
}
@ -298,9 +367,13 @@ static void state_c_entry(void *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;
zassert_equal(o->transition_bits, test_value[o->tv_idx], "Test State C entry failed");
if (o->first_time & C_ENTRY_FIRST_TIME) {
o->first_time &= ~C_ENTRY_FIRST_TIME;
o->transition_bits |= STATE_C_1ST_ENTRY_BIT;
} else {
o->transition_bits |= STATE_C_2ND_ENTRY_BIT;
}
}
static void state_c_run(void *obj)
@ -309,16 +382,15 @@ static void state_c_run(void *obj)
o->tv_idx++;
zassert_equal(o->transition_bits, test_value[o->tv_idx],
"Test State C run failed");
zassert_equal(o->transition_bits, test_value[o->tv_idx], "Test State C run failed");
if (o->first_time) {
o->first_time = false;
if (o->first_time & C_RUN_FIRST_TIME) {
o->first_time &= ~C_RUN_FIRST_TIME;
o->transition_bits |= STATE_C_1ST_RUN_BIT;
smf_set_handled(SMF_CTX(obj));
} else {
/* Do nothing, Let parent handle it */
} else {
o->transition_bits |= STATE_C_2ND_RUN_BIT;
smf_set_state(SMF_CTX(obj), &test_states[STATE_D]);
}
}
@ -328,15 +400,19 @@ static void state_c_exit(void *obj)
o->tv_idx++;
zassert_equal(o->transition_bits, test_value[o->tv_idx],
"Test State C exit failed");
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;
if (o->first_time & C_EXIT_FIRST_TIME) {
o->first_time &= ~C_EXIT_FIRST_TIME;
o->transition_bits |= STATE_C_1ST_EXIT_BIT;
} else {
o->transition_bits |= STATE_C_2ND_EXIT_BIT;
}
}
static void state_d_entry(void *obj)
@ -357,26 +433,27 @@ static void state_d_exit(void *obj)
}
static const struct smf_state test_states[] = {
[PARENT_AB] = SMF_CREATE_STATE(parent_ab_entry, parent_ab_run,
parent_ab_exit, NULL, &test_states[STATE_A]),
[PARENT_C] = SMF_CREATE_STATE(parent_c_entry, parent_c_run,
parent_c_exit, NULL, &test_states[STATE_C]),
[ROOT] = SMF_CREATE_STATE(root_entry, root_run, root_exit, NULL, &test_states[PARENT_AB]),
[PARENT_AB] = SMF_CREATE_STATE(parent_ab_entry, parent_ab_run, parent_ab_exit,
&test_states[ROOT], &test_states[STATE_A]),
[PARENT_C] = SMF_CREATE_STATE(parent_c_entry, parent_c_run, parent_c_exit,
&test_states[ROOT], &test_states[STATE_C]),
[STATE_A] = SMF_CREATE_STATE(state_a_entry, state_a_run, state_a_exit,
&test_states[PARENT_AB], NULL),
[STATE_B] = SMF_CREATE_STATE(state_b_entry, state_b_run, state_b_exit,
&test_states[PARENT_AB], NULL),
[STATE_C] = SMF_CREATE_STATE(state_c_entry, state_c_run, state_c_exit,
&test_states[PARENT_C], NULL),
[STATE_D] = SMF_CREATE_STATE(state_d_entry, state_d_run, state_d_exit,
NULL, NULL),
[STATE_D] = SMF_CREATE_STATE(state_d_entry, state_d_run, state_d_exit, &test_states[ROOT],
NULL),
};
ZTEST(smf_tests, test_smf_initial_transitions)
ZTEST(smf_tests, test_smf_self_transition)
{
/* A) Test state transitions */
test_obj.transition_bits = 0;
test_obj.first_time = 1;
test_obj.first_time = FIRST_TIME_BITS;
test_obj.terminate = NONE;
smf_set_initial((struct smf_ctx *)&test_obj, &test_states[PARENT_AB]);
@ -386,15 +463,14 @@ ZTEST(smf_tests, test_smf_initial_transitions)
}
}
zassert_equal(TEST_VALUE_NUM, test_obj.tv_idx,
"Incorrect test value index");
zassert_equal(TEST_VALUE_NUM, test_obj.tv_idx, "Incorrect test value index");
zassert_equal(test_obj.transition_bits, test_value[test_obj.tv_idx],
"Final state not reached");
/* B) Test termination in parent entry action */
test_obj.transition_bits = 0;
test_obj.first_time = 1;
test_obj.first_time = FIRST_TIME_BITS;
test_obj.terminate = PARENT_ENTRY;
smf_set_initial((struct smf_ctx *)&test_obj, &test_states[PARENT_AB]);
@ -412,7 +488,7 @@ ZTEST(smf_tests, test_smf_initial_transitions)
/* C) Test termination in parent run action */
test_obj.transition_bits = 0;
test_obj.first_time = 1;
test_obj.first_time = FIRST_TIME_BITS;
test_obj.terminate = PARENT_RUN;
smf_set_initial((struct smf_ctx *)&test_obj, &test_states[PARENT_AB]);
@ -430,7 +506,7 @@ ZTEST(smf_tests, test_smf_initial_transitions)
/* D) Test termination in parent exit action */
test_obj.transition_bits = 0;
test_obj.first_time = 1;
test_obj.first_time = FIRST_TIME_BITS;
test_obj.terminate = PARENT_EXIT;
smf_set_initial((struct smf_ctx *)&test_obj, &test_states[PARENT_AB]);
@ -448,7 +524,7 @@ ZTEST(smf_tests, test_smf_initial_transitions)
/* E) Test termination in child entry action */
test_obj.transition_bits = 0;
test_obj.first_time = 1;
test_obj.first_time = FIRST_TIME_BITS;
test_obj.terminate = ENTRY;
smf_set_initial((struct smf_ctx *)&test_obj, &test_states[PARENT_AB]);
@ -466,7 +542,7 @@ ZTEST(smf_tests, test_smf_initial_transitions)
/* F) Test termination in child run action */
test_obj.transition_bits = 0;
test_obj.first_time = 1;
test_obj.first_time = FIRST_TIME_BITS;
test_obj.terminate = RUN;
smf_set_initial((struct smf_ctx *)&test_obj, &test_states[PARENT_AB]);
@ -484,7 +560,7 @@ ZTEST(smf_tests, test_smf_initial_transitions)
/* G) Test termination in child exit action */
test_obj.transition_bits = 0;
test_obj.first_time = 1;
test_obj.first_time = FIRST_TIME_BITS;
test_obj.terminate = EXIT;
smf_set_initial((struct smf_ctx *)&test_obj, &test_states[PARENT_AB]);

View file

@ -10,6 +10,6 @@
void test_smf_flat(void);
void test_smf_hierarchical(void);
void test_smf_hierarchical_5_ancestors(void);
void test_smf_initial_transitions(void);
void test_smf_self_transition(void);
#endif /* ZEPHYR_TEST_LIB_SMF_H_ */