1 /*
2 * Copyright 2021 The Chromium OS Authors
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7 #include <zephyr/smf.h>
8
9 #include <zephyr/logging/log.h>
10 LOG_MODULE_REGISTER(smf);
11
12 /**
13 * @brief Private structure (to this file) used to track state machine context.
14 * The structure is not used directly, but instead to cast the "internal"
15 * member of the smf_ctx structure.
16 */
17 struct internal_ctx {
18 bool new_state: 1;
19 bool terminate: 1;
20 bool is_exit: 1;
21 bool handled: 1;
22 };
23
24 #ifdef CONFIG_SMF_ANCESTOR_SUPPORT
share_parent(const struct smf_state * test_state,const struct smf_state * target_state)25 static bool share_parent(const struct smf_state *test_state, const struct smf_state *target_state)
26 {
27 for (const struct smf_state *state = test_state; state != NULL; state = state->parent) {
28 if (target_state == state) {
29 return true;
30 }
31 }
32
33 return false;
34 }
35
get_child_of(const struct smf_state * states,const struct smf_state * parent)36 static const struct smf_state *get_child_of(const struct smf_state *states,
37 const struct smf_state *parent)
38 {
39 const struct smf_state *tmp = states;
40
41 while (true) {
42 if (tmp->parent == parent) {
43 return tmp;
44 }
45
46 if (tmp->parent == NULL) {
47 return NULL;
48 }
49
50 tmp = tmp->parent;
51 }
52 }
53
get_last_of(const struct smf_state * states)54 static const struct smf_state *get_last_of(const struct smf_state *states)
55 {
56 return get_child_of(states, NULL);
57 }
58
59 /**
60 * @brief Find the Least Common Ancestor (LCA) of two states
61 *
62 * @param source transition source
63 * @param dest transition destination
64 * @return LCA state, or NULL if states have no LCA.
65 */
get_lca_of(const struct smf_state * source,const struct smf_state * dest)66 static const struct smf_state *get_lca_of(const struct smf_state *source,
67 const struct smf_state *dest)
68 {
69 for (const struct smf_state *ancestor = source->parent; ancestor != NULL;
70 ancestor = ancestor->parent) {
71 if (ancestor == dest) {
72 return ancestor->parent;
73 } else if (share_parent(dest, ancestor)) {
74 return ancestor;
75 }
76 }
77
78 return NULL;
79 }
80
81 /**
82 * @brief Executes all entry actions from the direct child of topmost to the new state
83 *
84 * @param ctx State machine context
85 * @param new_state State we are transitioning to
86 * @param topmost State we are entering from. Its entry action is not executed
87 * @return true if the state machine should terminate, else false
88 */
smf_execute_all_entry_actions(struct smf_ctx * const ctx,const struct smf_state * new_state,const struct smf_state * topmost)89 static bool smf_execute_all_entry_actions(struct smf_ctx *const ctx,
90 const struct smf_state *new_state,
91 const struct smf_state *topmost)
92 {
93 struct internal_ctx *const internal = (void *)&ctx->internal;
94
95 if (new_state == topmost) {
96 /* There are no child states, so do nothing */
97 return false;
98 }
99
100 for (const struct smf_state *to_execute = get_child_of(new_state, topmost);
101 to_execute != NULL && to_execute != new_state;
102 to_execute = get_child_of(new_state, to_execute)) {
103 /* Keep track of the executing entry action in case it calls
104 * smf_set_state()
105 */
106 ctx->executing = to_execute;
107 /* Execute every entry action EXCEPT that of the topmost state */
108 if (to_execute->entry) {
109 to_execute->entry(ctx);
110
111 /* No need to continue if terminate was set */
112 if (internal->terminate) {
113 return true;
114 }
115 }
116 }
117
118 /* and execute the new state entry action */
119 ctx->executing = new_state;
120 if (new_state->entry) {
121 new_state->entry(ctx);
122
123 /* No need to continue if terminate was set */
124 if (internal->terminate) {
125 return true;
126 }
127 }
128
129 return false;
130 }
131
132 /**
133 * @brief Execute all ancestor run actions
134 *
135 * @param ctx State machine context
136 * @param target The run actions of this target's ancestors are executed
137 * @return true if the state machine should terminate, else false
138 */
smf_execute_ancestor_run_actions(struct smf_ctx * ctx)139 static bool smf_execute_ancestor_run_actions(struct smf_ctx *ctx)
140 {
141 struct internal_ctx *const internal = (void *)&ctx->internal;
142 /* Execute all run actions in reverse order */
143
144 /* Return if the current state terminated */
145 if (internal->terminate) {
146 return true;
147 }
148
149 /* The child state either transitioned or handled it. Either way, stop propagating. */
150 if (internal->new_state || internal->handled) {
151 return false;
152 }
153
154 /* Try to run parent run actions */
155 for (const struct smf_state *tmp_state = ctx->current->parent; tmp_state != NULL;
156 tmp_state = tmp_state->parent) {
157 /* Keep track of where we are in case an ancestor calls smf_set_state() */
158 ctx->executing = tmp_state;
159 /* Execute parent run action */
160 if (tmp_state->run) {
161 enum smf_state_result rc = tmp_state->run(ctx);
162
163 if (rc == SMF_EVENT_HANDLED) {
164 internal->handled = true;
165 }
166 /* No need to continue if terminate was set */
167 if (internal->terminate) {
168 return true;
169 }
170
171 /* This state dealt with it. Stop propagating. */
172 if (internal->new_state || internal->handled) {
173 break;
174 }
175 }
176 }
177
178 /* All done executing the run actions */
179
180 return false;
181 }
182
183 /**
184 * @brief Executes all exit actions from ctx->current to the direct child of topmost
185 *
186 * @param ctx State machine context
187 * @param topmost State we are exiting to. Its exit action is not executed
188 * @return true if the state machine should terminate, else false
189 */
smf_execute_all_exit_actions(struct smf_ctx * const ctx,const struct smf_state * topmost)190 static bool smf_execute_all_exit_actions(struct smf_ctx *const ctx, const struct smf_state *topmost)
191 {
192 struct internal_ctx *const internal = (void *)&ctx->internal;
193
194 for (const struct smf_state *to_execute = ctx->current;
195 to_execute != NULL && to_execute != topmost; to_execute = to_execute->parent) {
196 if (to_execute->exit) {
197 to_execute->exit(ctx);
198
199 /* No need to continue if terminate was set in the exit action */
200 if (internal->terminate) {
201 return true;
202 }
203 }
204 }
205
206 return false;
207 }
208 #endif /* CONFIG_SMF_ANCESTOR_SUPPORT */
209
210 /**
211 * @brief Reset the internal state of the state machine back to default values.
212 * Should be called on entry to smf_set_initial() and smf_set_state().
213 *
214 * @param ctx State machine context.
215 */
smf_clear_internal_state(struct smf_ctx * ctx)216 static void smf_clear_internal_state(struct smf_ctx *ctx)
217 {
218 struct internal_ctx *const internal = (void *)&ctx->internal;
219
220 internal->is_exit = false;
221 internal->terminate = false;
222 internal->handled = false;
223 internal->new_state = false;
224 }
225
smf_set_initial(struct smf_ctx * ctx,const struct smf_state * init_state)226 void smf_set_initial(struct smf_ctx *ctx, const struct smf_state *init_state)
227 {
228 #ifdef CONFIG_SMF_INITIAL_TRANSITION
229 /*
230 * The final target will be the deepest leaf state that
231 * the target contains. Set that as the real target.
232 */
233 while (init_state->initial) {
234 init_state = init_state->initial;
235 }
236 #endif
237
238 smf_clear_internal_state(ctx);
239 ctx->current = init_state;
240 ctx->previous = NULL;
241 ctx->terminate_val = 0;
242
243 #ifdef CONFIG_SMF_ANCESTOR_SUPPORT
244 struct internal_ctx *const internal = (void *)&ctx->internal;
245
246 ctx->executing = init_state;
247 const struct smf_state *topmost = get_last_of(init_state);
248
249 /* Execute topmost state entry action, since smf_execute_all_entry_actions()
250 * doesn't
251 */
252 if (topmost->entry) {
253 topmost->entry(ctx);
254 if (internal->terminate) {
255 /* No need to continue if terminate was set */
256 return;
257 }
258 }
259
260 if (smf_execute_all_entry_actions(ctx, init_state, topmost)) {
261 /* No need to continue if terminate was set */
262 return;
263 }
264 #else
265 /* execute entry action if it exists */
266 if (init_state->entry) {
267 init_state->entry(ctx);
268 }
269 #endif
270 }
271
smf_set_state(struct smf_ctx * const ctx,const struct smf_state * new_state)272 void smf_set_state(struct smf_ctx *const ctx, const struct smf_state *new_state)
273 {
274 struct internal_ctx *const internal = (void *)&ctx->internal;
275
276 if (new_state == NULL) {
277 LOG_ERR("new_state cannot be NULL");
278 return;
279 }
280
281 /*
282 * It does not make sense to call smf_set_state in an exit phase of a state
283 * since we are already in a transition; we would always ignore the
284 * intended state to transition into.
285 */
286 if (internal->is_exit) {
287 LOG_ERR("Calling %s from exit action", __func__);
288 return;
289 }
290
291 #ifdef CONFIG_SMF_ANCESTOR_SUPPORT
292 const struct smf_state *topmost;
293
294 if (share_parent(ctx->executing, new_state)) {
295 /* new state is a parent of where we are now*/
296 topmost = new_state;
297 } else if (share_parent(new_state, ctx->executing)) {
298 /* we are a parent of the new state */
299 topmost = ctx->executing;
300 } else {
301 /* not directly related, find LCA */
302 topmost = get_lca_of(ctx->executing, new_state);
303 }
304
305 internal->is_exit = true;
306 internal->new_state = true;
307
308 /* call all exit actions up to (but not including) the topmost */
309 if (smf_execute_all_exit_actions(ctx, topmost)) {
310 /* No need to continue if terminate was set in the exit action */
311 return;
312 }
313
314 /* if self-transition, call the exit action */
315 if ((ctx->executing == new_state) && (new_state->exit)) {
316 new_state->exit(ctx);
317
318 /* No need to continue if terminate was set in the exit action */
319 if (internal->terminate) {
320 return;
321 }
322 }
323
324 internal->is_exit = false;
325
326 /* if self transition, call the entry action */
327 if ((ctx->executing == new_state) && (new_state->entry)) {
328 new_state->entry(ctx);
329
330 /* No need to continue if terminate was set in the entry action */
331 if (internal->terminate) {
332 return;
333 }
334 }
335 #ifdef CONFIG_SMF_INITIAL_TRANSITION
336 /*
337 * The final target will be the deepest leaf state that
338 * the target contains. Set that as the real target.
339 */
340 while (new_state->initial) {
341 new_state = new_state->initial;
342 }
343 #endif
344
345 /* update the state variables */
346 ctx->previous = ctx->current;
347 ctx->current = new_state;
348
349 /* call all entry actions (except those of topmost) */
350 if (smf_execute_all_entry_actions(ctx, new_state, topmost)) {
351 /* No need to continue if terminate was set in the entry action */
352 return;
353 }
354 #else
355 /* Flat state machines have a very simple transition: */
356 if (ctx->current->exit) {
357 internal->is_exit = true;
358 ctx->current->exit(ctx);
359 /* No need to continue if terminate was set in the exit action */
360 if (internal->terminate) {
361 return;
362 }
363 internal->is_exit = false;
364 }
365 /* update the state variables */
366 ctx->previous = ctx->current;
367 ctx->current = new_state;
368
369 if (ctx->current->entry) {
370 ctx->current->entry(ctx);
371 /* No need to continue if terminate was set in the entry action */
372 if (internal->terminate) {
373 return;
374 }
375 }
376 #endif
377 }
378
smf_set_terminate(struct smf_ctx * ctx,int32_t val)379 void smf_set_terminate(struct smf_ctx *ctx, int32_t val)
380 {
381 struct internal_ctx *const internal = (void *)&ctx->internal;
382
383 internal->terminate = true;
384 ctx->terminate_val = val;
385 }
386
smf_run_state(struct smf_ctx * const ctx)387 int32_t smf_run_state(struct smf_ctx *const ctx)
388 {
389 struct internal_ctx *const internal = (void *)&ctx->internal;
390
391 /* No need to continue if terminate was set */
392 if (internal->terminate) {
393 return ctx->terminate_val;
394 }
395
396 /* Executing a states run function could cause a transition, so clear the
397 * internal state to ensure that the transition is handled correctly.
398 */
399 smf_clear_internal_state(ctx);
400
401 #ifdef CONFIG_SMF_ANCESTOR_SUPPORT
402 ctx->executing = ctx->current;
403 if (ctx->current->run) {
404 enum smf_state_result rc = ctx->current->run(ctx);
405
406 if (rc == SMF_EVENT_HANDLED) {
407 internal->handled = true;
408 }
409 }
410
411 if (smf_execute_ancestor_run_actions(ctx)) {
412 return ctx->terminate_val;
413 }
414 #else
415 if (ctx->current->run) {
416 ctx->current->run(ctx);
417 }
418 #endif
419 return 0;
420 }
421