blob: 007c6db367a50eed8ee7147a8dfe081399b9db25 [file] [log] [blame]
/*
* 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;
/* Execute all parent exit actions in reverse order */
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;
}