Reference Manual For QP/C 8.1.5
Loading...
Searching...
No Matches
qep_msm.c
Go to the documentation of this file.
1//============================================================================
2// QP/C Real-Time Event Framework (RTEF)
3//
4// Copyright (C) 2005 Quantum Leaps, LLC. All rights reserved.
5//
6// Q u a n t u m L e a P s
7// ------------------------
8// Modern Embedded Software
9//
10// SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-QL-commercial
11//
12// This software is dual-licensed under the terms of the open-source GNU
13// General Public License (GPL) or under the terms of one of the closed-
14// source Quantum Leaps commercial licenses.
15//
16// Redistributions in source code must retain this top-level comment block.
17// Plagiarizing this software to sidestep the license obligations is illegal.
18//
19// NOTE:
20// The GPL does NOT permit the incorporation of this code into proprietary
21// programs. Please contact Quantum Leaps for commercial licensing options,
22// which expressly supersede the GPL and are designed explicitly for
23// closed-source distribution.
24//
25// Quantum Leaps contact information:
26// <www.state-machine.com/licensing>
27// <info@state-machine.com>
28//============================================================================
29#define QP_IMPL // this is QP implementation
30#include "qp_port.h" // QP port
31#include "qp_pkg.h" // QP package-scope interface
32#include "qsafe.h" // QP Functional Safety (FuSa) Subsystem
33#ifdef Q_SPY // QS software tracing enabled?
34 #include "qs_port.h" // QS port
35 #include "qs_pkg.h" // QS facilities for pre-defined trace records
36#else
37 #include "qs_dummy.h" // disable the QS software tracing
38#endif // Q_SPY
39
40Q_DEFINE_THIS_MODULE("qep_msm")
41
42// maximum depth of state nesting in a QMsm (including the top level)
43#define QMSM_MAX_NEST_DEPTH_ ((size_t)6U)
44
45//! @cond INTERNAL
46
47// top-state object for QMsm-style state machines
48static struct QMState const l_msm_top_s = {
49 (struct QMState *)0,
50 Q_STATE_CAST(0),
54};
55
56#ifdef Q_SPY
57// helper macro to trace state action (entry/exit)
58#define QS_STATE_ACT_(rec_, state_) \
59 QS_CRIT_ENTRY(); \
60 QS_BEGIN_PRE((rec_), qsId) \
61 QS_OBJ_PRE(me); \
62 QS_FUN_PRE(state_); \
63 QS_END_PRE() \
64 QS_CRIT_EXIT()
65
66// internal helper macro to top-most init
67#define QS_TOP_INIT_(rec_, trg_) \
68 QS_CRIT_ENTRY(); \
69 QS_BEGIN_PRE((rec_), qsId) \
70 QS_TIME_PRE(); \
71 QS_OBJ_PRE(me); \
72 QS_FUN_PRE(trg_); \
73 QS_END_PRE() \
74 QS_CRIT_EXIT()
75
76// internal helper macro to trace transition segment
77#define QS_TRAN_SEG_(rec_, src_, trg_) \
78 QS_CRIT_ENTRY(); \
79 QS_BEGIN_PRE((rec_), qsId) \
80 QS_OBJ_PRE(me); \
81 QS_FUN_PRE(src_); \
82 QS_FUN_PRE(trg_); \
83 QS_END_PRE() \
84 QS_CRIT_EXIT()
85
86// internal helper macro to trace transition begin/end
87#define QS_TRAN0_(rec_, trg_) \
88 QS_CRIT_ENTRY(); \
89 QS_BEGIN_PRE((rec_), qsId) \
90 QS_TIME_PRE(); \
91 QS_SIG_PRE(e->sig); \
92 QS_OBJ_PRE(me); \
93 QS_FUN_PRE(trg_); \
94 QS_END_PRE() \
95 QS_CRIT_EXIT()
96
97// internal helper macro to trace regular transition
98#define QS_TRAN_END_(rec_, src_, trg_) \
99 QS_CRIT_ENTRY(); \
100 QS_BEGIN_PRE((rec_), qsId) \
101 QS_TIME_PRE(); \
102 QS_SIG_PRE(e->sig); \
103 QS_OBJ_PRE(me); \
104 QS_FUN_PRE(src_); \
105 QS_FUN_PRE(trg_); \
106 QS_END_PRE() \
107 QS_CRIT_EXIT()
108
109#else
110#define QS_STATE_ACT_(rec_, state_) ((void)0)
111#define QS_TOP_INIT_(rec_, trg_) ((void)0)
112#define QS_TRAN_SEG_(rec_, src_, trg_) ((void)0)
113#define QS_TRAN0_(rec_, trg_) ((void)0)
114#define QS_TRAN_END_(rec_, src_, trg_) ((void)0)
115#endif
116
117//! @endcond
118
119//============================================================================
120// static and private helper function prototypes...
121
122static QState QMsm_execTatbl_(
123 QAsm * const me,
124 QMTranActTable const * const tatbl,
125 uint_fast8_t const qsId);
126
127static void QMsm_exitToTranSource_(
128 QAsm * const me,
129 QMState const * const curr_state,
130 QMState const * const tran_source,
131 uint_fast8_t const qsId);
132
133static QState QMsm_enterHistory_(
134 QAsm * const me,
135 QMState const *const hist,
136 uint_fast8_t const qsId);
137
138//============================================================================
139//! @protected @memberof QMsm
140void QMsm_ctor(QMsm * const me,
141 QStateHandler const initial)
142{
143 static struct QAsmVtable const vtable = { // QAsm virtual table
144 &QMsm_init_,
146 &QMsm_isIn_,
148 };
149 // no need to call the superclass' constructor QAsm_ctor() here
150 me->super.vptr = &vtable; // QMsm class' VTABLE
151 me->super.state.obj = &l_msm_top_s; // the current state (top)
152 me->super.temp.fun = initial; // the initial tran. handler
153}
154
155//............................................................................
156//! @private @memberof QMsm
158 QAsm * const me,
159 void const * const e,
160 uint_fast8_t const qsId)
161{
162#ifndef Q_SPY
163 Q_UNUSED_PAR(qsId);
164#endif
165
166 // current state must be initialized to &l_msm_top_s in QMsm_ctor()
167 Q_REQUIRE_LOCAL(200, me->state.obj == &l_msm_top_s);
168
169 // temp contains the top-most initial tran. handler, which must be valid
170 Q_REQUIRE_LOCAL(210, me->temp.fun != Q_STATE_CAST(0));
171
172 // execute the top-most initial tran.
173 QState r = (*me->temp.fun)(me, Q_EVT_CAST(QEvt));
174
175 // the top-most initial tran. must be taken
176 Q_ASSERT_LOCAL(240, r == Q_RET_TRAN_INIT);
177
178 // the top-most initial tran. must set the tran-action table in temp
179 Q_ASSERT_LOCAL(250, me->temp.tatbl != (struct QMTranActTable *)0);
180
182 QS_TRAN_SEG_(QS_QEP_STATE_INIT,
183 me->state.obj->stateHandler, me->temp.tatbl->target->stateHandler);
184
185 // set state to the last tran. target
186 me->state.obj = me->temp.tatbl->target;
187
188 // drill down into the state hierarchy with initial transitions...
189 do {
190 r = QMsm_execTatbl_(me, me->temp.tatbl, qsId);
191 } while (r == Q_RET_TRAN_INIT);
192
193 QS_TOP_INIT_(QS_QEP_INIT_TRAN, me->state.obj->stateHandler);
194
195#ifndef Q_UNSAFE
196 // establish stable state configuration at the end of RTC step
197 me->temp.uint = QP_DIS_UPDATE(uintptr_t, me->state.uint);
198#endif
199}
200
201//............................................................................
202//! @private @memberof QMsm
204 QAsm * const me,
205 QEvt const * const e,
206 uint_fast8_t const qsId)
207{
208#ifndef Q_SPY
209 Q_UNUSED_PAR(qsId);
210#endif
211
212 // this state machine must be in a stable state configuration
213 // NOTE: stable state configuration is established after every RTC step.
215 QP_DIS_VERIFY(uintptr_t, me->state.uint, me->temp.uint));
216
217 // the event to be dispatched must be valid
218 Q_REQUIRE_LOCAL(310, e != (QEvt *)0);
219
220 QMState const *s = me->state.obj; // the current state
221 QMState const * const t = s; // store the current state for later
223 QS_TRAN0_(QS_QEP_DISPATCH, s->stateHandler);
224
225 // scan the state hierarchy up to the top state...
226 QState r;
227 do {
228 r = (*s->stateHandler)(me, e); // call state handler function
229 if (r >= Q_RET_HANDLED) { // event handled? (the most frequent case)
230 break; // done scanning the state hierarchy
231 }
232#ifdef Q_SPY
233 if (r == Q_RET_UNHANDLED) { // event unhandled due to a guard?
236 QS_SIG_PRE(e->sig);
237 QS_OBJ_PRE(me);
238 QS_FUN_PRE(s->stateHandler);
239 QS_END_PRE()
240 QS_CRIT_EXIT();
241 }
242#endif
243 s = s->superstate; // advance to the superstate
244
245 } while (s != (QMState *)0);
246
247 if (s == (QMState *)0) { // event bubbled to the 'top' state?
248#ifdef Q_SPY
249 QS_TRAN0_(QS_QEP_IGNORED, t->stateHandler);
250#endif
251 }
252 else if (r == Q_RET_HANDLED) { // was the event e handled?
253 QS_TRAN0_(QS_QEP_INTERN_TRAN, s->stateHandler); // output QS record
254 }
255 else if ((r == Q_RET_TRAN) || (r == Q_RET_TRAN_HIST)) { //any tran. taken?
256#ifdef Q_SPY
257 QMState const * const ts = s; // tran. source for QS tracing
258#endif // Q_SPY
259
260 if (r == Q_RET_TRAN) { // tran. taken?
261 struct QMTranActTable const * const tatbl = me->temp.tatbl;
262 QMsm_exitToTranSource_(me, t, s, qsId);
263 r = QMsm_execTatbl_(me, tatbl, qsId);
264 }
265 else { // must be tran. to history
266 QMState const * const hist = me->state.obj; // save history
267 me->state.obj = t; // restore the original state
268
269 QS_TRAN_SEG_(QS_QEP_TRAN_HIST,
270 s->stateHandler, hist->stateHandler);
271
272 // save the tran-action table before it gets clobbered
273 struct QMTranActTable const * const tatbl = me->temp.tatbl;
274 QMsm_exitToTranSource_(me, t, s, qsId);
275 (void)QMsm_execTatbl_(me, tatbl, qsId);
276 r = QMsm_enterHistory_(me, hist, qsId);
277 }
278#ifdef Q_SPY
279 s = me->state.obj;
280#endif
281
282 while (r == Q_RET_TRAN_INIT) { // initial tran. in the target?
283
284 r = QMsm_execTatbl_(me, me->temp.tatbl, qsId);
285#ifdef Q_SPY
286 s = me->state.obj;
287#endif
288
289 }
290
291 QS_TRAN_END_(QS_QEP_TRAN, ts->stateHandler, s->stateHandler);
292 }
293 else {
294 Q_ERROR_LOCAL(360); // last action handler returned impossible value
295 }
296
297#ifndef Q_UNSAFE
298 // establish stable state configuration at the end of RTC step
299 me->temp.uint = QP_DIS_UPDATE(uintptr_t, me->state.uint);
300#endif
301}
302
303//............................................................................
304//! @private @memberof QMsm
306 QAsm * const me,
307 QMTranActTable const * const tatbl,
308 uint_fast8_t const qsId)
309{
310#ifndef Q_SPY
311 Q_UNUSED_PAR(qsId);
312#endif
313
314 // the tran-action table parameter must be valid
315 Q_REQUIRE_LOCAL(400, tatbl != (struct QMTranActTable *)0);
316
318 QState r = Q_RET_SUPER;
319 QActionHandler const *a = &tatbl->act[0];
320 while (*a != Q_ACTION_CAST(0)) { // not the end of the tran-action table?
321 r = (*(*a))(me); // call the action through the 'a' pointer
322 ++a; // advance to the next entry in the tran-action table
323
324 if (r == Q_RET_ENTRY) { // was the action a state entry?
325 QS_STATE_ACT_(QS_QEP_STATE_ENTRY, me->temp.obj->stateHandler);
326 }
327 else if (r == Q_RET_EXIT) { // was the action a state exit?
328 QS_STATE_ACT_(QS_QEP_STATE_EXIT, me->temp.obj->stateHandler);
329 }
330 else if (r == Q_RET_TRAN_INIT) { // was the action a state init?
331 QS_TRAN_SEG_(QS_QEP_STATE_INIT,
332 tatbl->target->stateHandler,
333 me->temp.tatbl->target->stateHandler);
334 }
335 else {
336 // the last action handler returned impossible value (corrupt SM?)
337 Q_ERROR_LOCAL(460);
338 }
339
340 }
341
342 me->state.obj = tatbl->target; // set new current state
343 return r;
344}
345
346//............................................................................
347//! @private @memberof QMsm
348static void QMsm_exitToTranSource_(
349 QAsm * const me,
350 QMState const * const curr_state,
351 QMState const * const tran_source,
352 uint_fast8_t const qsId)
353{
354#ifndef Q_SPY
355 Q_UNUSED_PAR(qsId);
356#endif
358
359 // exit states from the current state to the tran. source state
360 // NOTE: the following loop does not need the fixed loop bound check
361 // because the path from the current state to the tran.source has
362 // been already checked in the invariant 340.
363 QMState const *s = curr_state;
364 while (s != tran_source) {
365 if (s->exitAction != Q_ACTION_CAST(0)) { // exit action provided?
366 // exit state s, ignore the result
367 (void)(*s->exitAction)(me);
368 QS_STATE_ACT_(QS_QEP_STATE_EXIT, me->temp.obj->stateHandler);
369 }
370 s = s->superstate; // advance to the superstate
371 }
372}
373
374//............................................................................
375//! @private @memberof QMsm
377 QAsm * const me,
378 QMState const *const hist,
379 uint_fast8_t const qsId)
380{
381#ifndef Q_SPY
382 Q_UNUSED_PAR(qsId);
383#endif
384
385 // record the entry path from current state to history
386 QMState const *path[QMSM_MAX_NEST_DEPTH_];
387 QMState const *s = hist;
388 size_t ip = 0U; // entry path index
389 while (s != me->state.obj) {
390 if (s->entryAction != Q_ACTION_CAST(0)) { // does s have an entry action?
392 path[ip] = s;
393 ++ip;
394 }
395 s = s->superstate;
396 }
397
399 // retrace the entry path in reverse (desired) order...
400 // NOTE: i is the fixed loop bound already checked in invariant 610
401 while (ip > 0U) {
402 --ip;
403 // enter the state in path[ip], ignore the result
404 (void)(*path[ip]->entryAction)(me);
405 QS_STATE_ACT_(QS_QEP_STATE_ENTRY, path[ip]->stateHandler);
406 }
407
408 me->state.obj = hist; // set current state to the tran. target
409
410 // initial tran. present?
411 QState r = Q_RET_SUPER;
412 if (hist->initAction != Q_ACTION_CAST(0)) { // init. action provided?
413 r = (*hist->initAction)(me); // execute the init. action
414 QS_TRAN_SEG_(QS_QEP_STATE_INIT,
415 hist->stateHandler, me->temp.tatbl->target->stateHandler);
416 }
417 return r; // inform the caller if the init action was taken
418}
419
420//............................................................................
421//! @public @memberof QMsm
422QMState const * QMsm_topQMState(void) {
423 // return the top state (object pointer)
424 return &l_msm_top_s;
425}
426//............................................................................
427//! @private @memberof QMsm
428bool QMsm_isIn_(QAsm * const me, QStateHandler const stateHndl) {
429 bool inState = false; // assume that this SM is not in 'state'
430 QMState const *s = me->state.obj;
431 while (s != (QMState *)0) {
432 if (s->stateHandler == stateHndl) { // match found?
433 inState = true;
434 break;
435 }
436 s = s->superstate; // advance to the superstate
437 }
438
439 return inState;
440}
441
442//............................................................................
443//! @public @memberof QMsm
445 QMsm const * const me,
446 QMState const * const parentHndl)
447{
448 QMState const *s = me->super.state.obj; // start with current state
449 QMState const *child = s;
450 bool isFound = false; // assume the child NOT found
451 while (s != (QMState *)0) { // top of state hierarchy not reached yet?
452 if (s == parentHndl) {
453 isFound = true; // child is found
454 break;
455 }
456 child = s;
457 s = s->superstate;
458 }
459 // the child state must be found, or the state machine is corrupt
460 Q_ENSURE_LOCAL(890, isFound);
461
462#ifdef Q_UNSAFE
463 Q_UNUSED_PAR(isFound);
464#endif
465
466 return child;
467}
468//............................................................................
469//! @public @memberof QMsm
470QMState const* QMsm_stateObj(QMsm const* const me) {
471 // return the current state (object pointer)
472 return me->super.state.obj;
473}
474//............................................................................
475//! @public @memberof QMsm
477 // return the current state handler (function pointer)
478 return me->state.obj->stateHandler;
479}
#define QMSM_MAX_NEST_DEPTH_
Maximum depth of nesting levels in a QMsm state machines \clearpage .
Definition qep_msm.c:43
#define Q_UNUSED_PAR(par_)
Helper macro to mark unused parameters of functions.
Definition qp.h:90
QState(*) QActionHandler(void *const me)
Pointer to an action-handler function.
Definition qp.h:148
#define Q_STATE_CAST(handler_)
Perform cast to QStateHandler.
Definition qp.h:177
#define Q_ACTION_CAST(action_)
Perform cast to QActionHandler.
Definition qp.h:178
#define Q_EVT_CAST(class_)
Perform downcast of an event onto a subclass of QEvt class_.
Definition qp.h:141
QState(*) QStateHandler(void *const me, QEvt const *const e)
Pointer to a state-handler function.
Definition qp.h:147
uint_fast8_t QState
Type returned from state-handler functions.
Definition qp.h:146
QP/C Framework in C internal (package-scope) interface.
#define QP_DIS_UPDATE(T_, org_)
Update Duplicate Inverse Storage integer variable.
Definition qp_pkg.h:74
#define QP_DIS_VERIFY(T_, org_, dis_)
Verify integer variable against its Duplicate Inverse Storage.
Definition qp_pkg.h:75
Sample QP/C port.
#define QS_CRIT_STAT
Internal QS macro for defining the critical section status \clearpage .
Definition qs.h:403
@ QS_QEP_STATE_INIT
an initial transition was taken in a state
Definition qs.h:57
@ QS_QEP_TRAN_HIST
a tran. to history was taken
Definition qs.h:135
@ QS_QEP_STATE_EXIT
a state was exited
Definition qs.h:56
@ QS_QEP_INIT_TRAN
the top-most initial transition was taken
Definition qs.h:58
@ QS_QEP_INTERN_TRAN
an internal transition was taken
Definition qs.h:59
@ QS_QEP_STATE_ENTRY
a state was entered
Definition qs.h:55
@ QS_QEP_UNHANDLED
an event was un-handled due to a guard
Definition qs.h:63
@ QS_QEP_TRAN
a regular transition was taken
Definition qs.h:60
@ QS_QEP_DISPATCH
an event was dispatched (begin of RTC step)
Definition qs.h:62
@ QS_QEP_IGNORED
an event was ignored (silently discarded)
Definition qs.h:61
#define QS_CRIT_EXIT()
Internal macro for exiting a critical section \clearpage .
Definition qs.h:411
#define QS_CRIT_ENTRY()
Internal macro for entering a critical section \clearpage .
Definition qs.h:407
QS (QP/Spy software tracing) internal (package-scope) interface. \clearpage .
#define QS_OBJ_PRE(obj_)
Output pre-formatted object pointer element.
Definition qs_pkg.h:46
#define QS_SIG_PRE(sig_)
Output pre-formatted event signal data element.
Definition qs_pkg.h:47
#define QS_FUN_PRE(fun_)
Output pre-formatted function pointer element.
Definition qs_pkg.h:52
#define QS_END_PRE()
Pre-formatted QS trace record end.
Definition qs_pkg.h:38
#define QS_BEGIN_PRE(rec_, qsId_)
Pre-formatted QS trace record begin.
Definition qs_pkg.h:32
Sample QS/C port.
QP Functional Safety (FuSa) Subsystem (fault detection).
#define Q_REQUIRE_LOCAL(id_, expr_)
Assertion for checking a precondition (local critical section).
Definition qsafe.h:99
#define Q_ERROR_LOCAL(id_)
Assertion with user-specified ID for a wrong path through the code (local critical section).
Definition qsafe.h:111
#define Q_ENSURE_LOCAL(id_, expr_)
Assertion for checking a postcondition (local critical section).
Definition qsafe.h:104
#define Q_ASSERT_LOCAL(id_, expr_)
General-purpose assertion with user-specified ID number (local critical section).
Definition qsafe.h:72
#define Q_INVARIANT_LOCAL(id_, expr_)
Assertion for checking a postcondition (local critical section).
Definition qsafe.h:109
Abstract State Machine class (state machine interface).
Definition qp.h:183
struct QAsmVtable const * vptr
Virtual pointer inherited by all QAsm subclasses (see also [SAS_QP_TECH_20]).
Definition qp.h:184
union QAsmAttr state
Current state (pointer to the current state-handler function).
Definition qp.h:185
union QAsmAttr temp
Temporary storage for target/act-table etc..
Definition qp.h:186
Virtual table for the QAsm class.
Definition qp.h:213
Event class.
Definition qp.h:100
uint32_t sig
Event signal (see Event Signal).
Definition qp.h:101
State object for the QMsm class (QM State Machine).
Definition qp.h:153
Transition-Action Table for the QMsm State Machine \clearpage .
Definition qp.h:161
Hierarchical State Machine class (QMsm-style implementation strategy).
Definition qp.h:317
static QState QMsm_enterHistory_(QAsm *const me, QMState const *const hist, uint_fast8_t const qsId)
Enter history of a composite state.
Definition qep_msm.c:376
void QMsm_init_(QAsm *const me, void const *const e, uint_fast8_t const qsId)
Implementation of the top-most initial transition in QMsm.
Definition qep_msm.c:157
static QState QMsm_execTatbl_(QAsm *const me, QMTranActTable const *const tatbl, uint_fast8_t const qsId)
Execute transition-action table.
Definition qep_msm.c:305
bool QMsm_isIn_(QAsm *const me, QStateHandler const stateHndl)
Tests if a given state is part of the current active state configuration.
Definition qep_msm.c:428
QMState const * QMsm_childStateObj(QMsm const *const me, QMState const *const parentHndl)
Obtain the current active child state of a given parent in QMsm.
Definition qep_msm.c:444
void QMsm_dispatch_(QAsm *const me, QEvt const *const e, uint_fast8_t const qsId)
Implementation of dispatching events to a QMsm.
Definition qep_msm.c:203
QStateHandler QMsm_getStateHandler_(QAsm const *const me)
Implementation of getting the state handler in an QMsm subclass.
Definition qep_msm.c:476
void QMsm_ctor(QMsm *const me, QStateHandler const initial)
Constructor of QMsm.
Definition qep_msm.c:140
QMState const * QMsm_stateObj(QMsm const *const me)
Obtain the current state from a MSM (read only).
Definition qep_msm.c:470