/* * Copyright 2021 The Chromium OS Authors * * SPDX-License-Identifier: Apache-2.0 */ #include #include 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; }