407 lines
10 KiB
C
407 lines
10 KiB
C
/*
|
|
* Copyright 2021 The Chromium OS Authors
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#include <zephyr/smf.h>
|
|
|
|
#include <zephyr/logging/log.h>
|
|
LOG_MODULE_REGISTER(smf);
|
|
|
|
/**
|
|
* @brief Private structure (to this file) used to track state machine context.
|
|
* The structure is not used directly, but instead to cast the "internal"
|
|
* member of the smf_ctx structure.
|
|
*/
|
|
struct internal_ctx {
|
|
bool new_state: 1;
|
|
bool terminate: 1;
|
|
bool is_exit: 1;
|
|
bool handled: 1;
|
|
};
|
|
|
|
#ifdef CONFIG_SMF_ANCESTOR_SUPPORT
|
|
static bool share_paren(const struct smf_state *test_state, const struct smf_state *target_state)
|
|
{
|
|
for (const struct smf_state *state = test_state; state != NULL; state = state->parent) {
|
|
if (target_state == state) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static const struct smf_state *get_child_of(const struct smf_state *states,
|
|
const struct smf_state *parent)
|
|
{
|
|
const struct smf_state *tmp = states;
|
|
|
|
while (true) {
|
|
if (tmp->parent == parent) {
|
|
return tmp;
|
|
}
|
|
|
|
if (tmp->parent == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
tmp = tmp->parent;
|
|
}
|
|
}
|
|
|
|
static const struct smf_state *get_last_of(const struct smf_state *states)
|
|
{
|
|
return get_child_of(states, NULL);
|
|
}
|
|
|
|
/**
|
|
* @brief Find the Least Common Ancestor (LCA) of two states
|
|
*
|
|
* @param source transition source
|
|
* @param dest transition destination
|
|
* @return LCA state, or NULL if states have no LCA.
|
|
*/
|
|
static const struct smf_state *get_lca_of(const struct smf_state *source,
|
|
const struct smf_state *dest)
|
|
{
|
|
for (const struct smf_state *ancestor = source->parent; ancestor != NULL;
|
|
ancestor = ancestor->parent) {
|
|
if (ancestor == dest) {
|
|
return ancestor->parent;
|
|
} else if (share_paren(dest, ancestor)) {
|
|
return ancestor;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* @brief Executes all entry actions from the direct child of topmost to the new state
|
|
*
|
|
* @param ctx State machine context
|
|
* @param new_state State we are transitioning to
|
|
* @param topmost State we are entering from. Its entry action is not executed
|
|
* @return true if the state machine should terminate, else false
|
|
*/
|
|
static bool smf_execute_all_entry_actions(struct smf_ctx *const ctx,
|
|
const struct smf_state *new_state,
|
|
const struct smf_state *topmost)
|
|
{
|
|
struct internal_ctx *const internal = (void *)&ctx->internal;
|
|
|
|
if (new_state == topmost) {
|
|
/* There are no child states, so do nothing */
|
|
return false;
|
|
}
|
|
|
|
for (const struct smf_state *to_execute = get_child_of(new_state, topmost);
|
|
to_execute != NULL && to_execute != new_state;
|
|
to_execute = get_child_of(new_state, to_execute)) {
|
|
/* Keep track of the executing entry action in case it calls
|
|
* smf_set_state()
|
|
*/
|
|
ctx->executing = to_execute;
|
|
/* Execute every entry action EXCEPT that of the topmost state */
|
|
if (to_execute->entry) {
|
|
to_execute->entry(ctx);
|
|
|
|
/* No need to continue if terminate was set */
|
|
if (internal->terminate) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* and execute the new state entry action */
|
|
ctx->executing = new_state;
|
|
if (new_state->entry) {
|
|
new_state->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
|
|
*/
|
|
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 terminated */
|
|
if (internal->terminate) {
|
|
return true;
|
|
}
|
|
|
|
/* The child state either transitioned or handled it. Either way, stop propagating. */
|
|
if (internal->new_state || internal->handled) {
|
|
internal->new_state = false;
|
|
internal->handled = false;
|
|
return false;
|
|
}
|
|
|
|
/* Try to run parent run actions */
|
|
for (const struct smf_state *tmp_state = ctx->current->parent; tmp_state != NULL;
|
|
tmp_state = tmp_state->parent) {
|
|
/* Keep track of where we are in case an ancestor calls smf_set_state() */
|
|
ctx->executing = tmp_state;
|
|
/* Execute parent run action */
|
|
if (tmp_state->run) {
|
|
tmp_state->run(ctx);
|
|
/* No need to continue if terminate was set */
|
|
if (internal->terminate) {
|
|
return true;
|
|
}
|
|
|
|
/* This state dealt with it. Stop propagating. */
|
|
if (internal->new_state || internal->handled) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
internal->new_state = false;
|
|
internal->handled = false;
|
|
|
|
/* All done executing the run actions */
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* @brief Executes all exit actions from ctx->current to the direct child of topmost
|
|
*
|
|
* @param ctx State machine context
|
|
* @param topmost State we are exiting to. Its exit action is not executed
|
|
* @return true if the state machine should terminate, else false
|
|
*/
|
|
static bool smf_execute_all_exit_actions(struct smf_ctx *const ctx, const struct smf_state *topmost)
|
|
{
|
|
struct internal_ctx *const internal = (void *)&ctx->internal;
|
|
|
|
for (const struct smf_state *to_execute = ctx->current;
|
|
to_execute != NULL && to_execute != topmost;
|
|
to_execute = to_execute->parent) {
|
|
if (to_execute->exit) {
|
|
to_execute->exit(ctx);
|
|
|
|
/* No need to continue if terminate was set in the exit action */
|
|
if (internal->terminate) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
#endif /* CONFIG_SMF_ANCESTOR_SUPPORT */
|
|
|
|
void smf_set_initial(struct smf_ctx *ctx, const struct smf_state *init_state)
|
|
{
|
|
struct internal_ctx *const internal = (void *)&ctx->internal;
|
|
|
|
#ifdef CONFIG_SMF_INITIAL_TRANSITION
|
|
/*
|
|
* The final target will be the deepest leaf state that
|
|
* the target contains. Set that as the real target.
|
|
*/
|
|
while (init_state->initial) {
|
|
init_state = init_state->initial;
|
|
}
|
|
#endif
|
|
|
|
internal->is_exit = false;
|
|
internal->terminate = false;
|
|
internal->handled = false;
|
|
internal->new_state = false;
|
|
ctx->current = init_state;
|
|
ctx->previous = NULL;
|
|
ctx->terminate_val = 0;
|
|
|
|
#ifdef CONFIG_SMF_ANCESTOR_SUPPORT
|
|
ctx->executing = init_state;
|
|
const struct smf_state *topmost = get_last_of(init_state);
|
|
|
|
/* Execute topmost state entry action, since smf_execute_all_entry_actions()
|
|
* doesn't
|
|
*/
|
|
if (topmost->entry) {
|
|
topmost->entry(ctx);
|
|
if (internal->terminate) {
|
|
/* No need to continue if terminate was set */
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (smf_execute_all_entry_actions(ctx, init_state, topmost)) {
|
|
/* No need to continue if terminate was set */
|
|
return;
|
|
}
|
|
#else
|
|
/* execute entry action if it exists */
|
|
if (init_state->entry) {
|
|
init_state->entry(ctx);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void smf_set_state(struct smf_ctx *const ctx, const struct smf_state *new_state)
|
|
{
|
|
struct internal_ctx *const internal = (void *)&ctx->internal;
|
|
|
|
if (new_state == NULL) {
|
|
LOG_ERR("new_state cannot be NULL");
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* It does not make sense to call smf_set_state in an exit phase of a state
|
|
* since we are already in a transition; we would always ignore the
|
|
* intended state to transition into.
|
|
*/
|
|
if (internal->is_exit) {
|
|
LOG_ERR("Calling %s from exit action", __func__);
|
|
return;
|
|
}
|
|
|
|
#ifdef CONFIG_SMF_ANCESTOR_SUPPORT
|
|
const struct smf_state *topmost;
|
|
|
|
if (share_paren(ctx->executing, new_state)) {
|
|
/* new state is a parent of where we are now*/
|
|
topmost = new_state;
|
|
} else if (share_paren(new_state, ctx->executing)) {
|
|
/* we are a parent of the new state */
|
|
topmost = ctx->executing;
|
|
} else {
|
|
/* not directly related, find LCA */
|
|
topmost = get_lca_of(ctx->executing, new_state);
|
|
}
|
|
|
|
internal->is_exit = true;
|
|
internal->new_state = true;
|
|
|
|
/* call all exit actions up to (but not including) the topmost */
|
|
if (smf_execute_all_exit_actions(ctx, topmost)) {
|
|
/* No need to continue if terminate was set in the exit action */
|
|
return;
|
|
}
|
|
|
|
/* if self-transition, call the exit action */
|
|
if ((ctx->executing == new_state) && (new_state->exit)) {
|
|
new_state->exit(ctx);
|
|
|
|
/* No need to continue if terminate was set in the exit action */
|
|
if (internal->terminate) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
internal->is_exit = false;
|
|
|
|
/* if self transition, call the entry action */
|
|
if ((ctx->executing == new_state) && (new_state->entry)) {
|
|
new_state->entry(ctx);
|
|
|
|
/* No need to continue if terminate was set in the entry action */
|
|
if (internal->terminate) {
|
|
return;
|
|
}
|
|
}
|
|
#ifdef CONFIG_SMF_INITIAL_TRANSITION
|
|
/*
|
|
* The final target will be the deepest leaf state that
|
|
* the target contains. Set that as the real target.
|
|
*/
|
|
while (new_state->initial) {
|
|
new_state = new_state->initial;
|
|
}
|
|
#endif
|
|
|
|
/* update the state variables */
|
|
ctx->previous = ctx->current;
|
|
ctx->current = new_state;
|
|
|
|
/* call all entry actions (except those of topmost) */
|
|
if (smf_execute_all_entry_actions(ctx, new_state, topmost)) {
|
|
/* No need to continue if terminate was set in the entry action */
|
|
return;
|
|
}
|
|
#else
|
|
/* Flat state machines have a very simple transition: */
|
|
if (ctx->current->exit) {
|
|
internal->is_exit = true;
|
|
ctx->current->exit(ctx);
|
|
/* No need to continue if terminate was set in the exit action */
|
|
if (internal->terminate) {
|
|
return;
|
|
}
|
|
internal->is_exit = false;
|
|
}
|
|
/* update the state variables */
|
|
ctx->previous = ctx->current;
|
|
ctx->current = new_state;
|
|
|
|
if (ctx->current->entry) {
|
|
ctx->current->entry(ctx);
|
|
/* No need to continue if terminate was set in the entry action */
|
|
if (internal->terminate) {
|
|
return;
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
void smf_set_handled(struct smf_ctx *ctx)
|
|
{
|
|
struct internal_ctx *const internal = (void *)&ctx->internal;
|
|
|
|
internal->handled = true;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
#ifdef CONFIG_SMF_ANCESTOR_SUPPORT
|
|
ctx->executing = ctx->current;
|
|
#endif
|
|
|
|
if (ctx->current->run) {
|
|
ctx->current->run(ctx);
|
|
}
|
|
|
|
#ifdef CONFIG_SMF_ANCESTOR_SUPPORT
|
|
if (smf_execute_ancestor_run_actions(ctx)) {
|
|
return ctx->terminate_val;
|
|
}
|
|
#endif
|
|
return 0;
|
|
}
|