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