blob: eb457a176271266277192d43808ee58728382c7f [file] [log] [blame]
Neels Hofmeyr17518fe2017-06-20 04:35:06 +02001/*! \file fsm.c
2 * Osmocom generic Finite State Machine implementation. */
3/*
Harald Welte136e7372016-05-29 10:53:17 +09004 * (C) 2016 by Harald Welte <laforge@gnumonks.org>
5 *
Harald Weltee08da972017-11-13 01:00:26 +09006 * SPDX-License-Identifier: GPL-2.0+
7 *
Harald Welte136e7372016-05-29 10:53:17 +09008 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
21 * MA 02110-1301, USA.
22 */
23
24#include <errno.h>
25#include <stdbool.h>
Harald Welte8808bb42017-01-07 11:11:03 +010026#include <string.h>
Pau Espin Pedrol4f8857e2017-06-18 12:23:00 +020027#include <inttypes.h>
Harald Welte136e7372016-05-29 10:53:17 +090028
29#include <osmocom/core/fsm.h>
30#include <osmocom/core/talloc.h>
31#include <osmocom/core/logging.h>
32#include <osmocom/core/utils.h>
33
34/*! \addtogroup fsm
35 * @{
Neels Hofmeyr87e45502017-06-20 00:17:59 +020036 * Finite State Machine abstraction
Harald Welte136e7372016-05-29 10:53:17 +090037 *
38 * This is a generic C-language abstraction for implementing finite
39 * state machines within the Osmocom framework. It is intended to
40 * replace existing hand-coded or even only implicitly existing FSMs
41 * all over the existing code base.
42 *
43 * An libosmocore FSM is described by its \ref osmo_fsm description,
44 * which in turn refers to an array of \ref osmo_fsm_state descriptor,
45 * each describing a single state in the FSM.
46 *
47 * The general idea is that all actions performed within one state are
48 * located at one position in the code (the state's action function),
49 * as opposed to the 'message-centric' view of e.g. the existing
50 * state machines of the LAPD(m) coe, where there is one message for
51 * eahc possible event (primitive), and the function then needs to
52 * concern itself on how to handle that event over all possible states.
53 *
54 * For each state, there is a bit-mask of permitted input events for
55 * this state, as well as a bit-mask of permitted new output states to
56 * which the state can change. Furthermore, there is a function
57 * pointer implementing the actual handling of the input events
58 * occurring whilst in thta state.
59 *
60 * Furthermore, each state offers a function pointer that can be
61 * executed just before leaving a state, and another one just after
62 * entering a state.
63 *
64 * When transitioning into a new state, an optional timer number and
65 * time-out can be passed along. The timer is started just after
66 * entering the new state, and will call the \ref osmo_fsm timer_cb
67 * function once it expires. This is intended to be used in telecom
68 * state machines where a given timer (identified by a certain number)
69 * is started to terminate the fsm or terminate the fsm once expected
70 * events are not happening before timeout expiration.
71 *
72 * As there can often be many concurrent FSMs of one given class, we
73 * introduce the concept of \ref osmo_fsm_inst, i.e. an FSM instance.
74 * The instance keeps the actual state, while the \ref osmo_fsm
75 * descriptor contains the static/const descriptor of the FSM's states
76 * and possible transitions.
77 *
78 * osmo_fsm are integrated with the libosmocore logging system. The
79 * logging sub-system is determined by the FSM descriptor, as we assume
80 * one FSM (let's say one related to a location update procedure) is
81 * inevitably always tied to a sub-system. The logging level however
82 * is configurable for each FSM instance, to ensure that e.g. DEBUG
83 * logging can be used for the LU procedure of one subscriber, while
84 * NOTICE level is used for all other subscribers.
85 *
86 * In order to attach private state to the \ref osmo_fsm_inst, it
87 * offers an opaque priv pointer.
Neels Hofmeyr17518fe2017-06-20 04:35:06 +020088 *
89 * \file fsm.c */
Harald Welte96e2a002017-06-12 21:44:18 +020090
Harald Welte34193912017-01-07 11:49:55 +010091LLIST_HEAD(osmo_g_fsms);
Harald Welte136e7372016-05-29 10:53:17 +090092static bool fsm_log_addr = true;
Neels Hofmeyr050f2d32018-05-31 15:30:15 +020093static bool fsm_log_timeouts = false;
Harald Welte136e7372016-05-29 10:53:17 +090094
Neels Hofmeyr87e45502017-06-20 00:17:59 +020095/*! specify if FSM instance addresses should be logged or not
Harald Welte136e7372016-05-29 10:53:17 +090096 *
97 * By default, the FSM name includes the pointer address of the \ref
Neels Hofmeyra3953e02016-12-14 18:34:30 +010098 * osmo_fsm_inst. This behavior can be disabled (and re-enabled)
Harald Welte136e7372016-05-29 10:53:17 +090099 * using this function.
100 *
101 * \param[in] log_addr Indicate if FSM instance address shall be logged
102 */
103void osmo_fsm_log_addr(bool log_addr)
104{
Max61281f42016-11-01 10:49:31 +0100105 fsm_log_addr = log_addr;
Harald Welte136e7372016-05-29 10:53:17 +0900106}
107
Neels Hofmeyr050f2d32018-05-31 15:30:15 +0200108/*! Enable or disable logging of timeout values for FSM instance state changes.
109 *
110 * By default, state changes are logged by state name only, omitting the timeout. When passing true, each state change
111 * will also log the T number and the chosen timeout in seconds. osmo_fsm_inst_state_chg_keep_timer() will log remaining
112 * timeout in millisecond precision.
113 *
114 * The default for this is false to reflect legacy behavior. Since various C tests that verify logging output already
115 * existed prior to this option, keeping timeout logging off makes sure that they continue to pass. Particularly,
116 * osmo_fsm_inst_state_chg_keep_timer() may cause non-deterministic logging of remaining timeout values.
117 *
118 * For any program that does not explicitly require deterministic logging output, i.e. anything besides regression tests
119 * involving FSM instances, it is recommended to call osmo_fsm_log_timeouts(true).
120 *
121 * \param[in] log_timeouts Pass true to log timeouts on state transitions, false to omit timeouts.
122 */
123void osmo_fsm_log_timeouts(bool log_timeouts)
124{
125 fsm_log_timeouts = log_timeouts;
126}
127
Harald Welte8808bb42017-01-07 11:11:03 +0100128struct osmo_fsm *osmo_fsm_find_by_name(const char *name)
129{
130 struct osmo_fsm *fsm;
Harald Welte34193912017-01-07 11:49:55 +0100131 llist_for_each_entry(fsm, &osmo_g_fsms, list) {
Harald Welte8808bb42017-01-07 11:11:03 +0100132 if (!strcmp(name, fsm->name))
133 return fsm;
134 }
135 return NULL;
136}
137
Harald Welte4585e672017-04-16 17:23:56 +0200138struct osmo_fsm_inst *osmo_fsm_inst_find_by_name(const struct osmo_fsm *fsm,
139 const char *name)
140{
141 struct osmo_fsm_inst *fi;
142
Neels Hofmeyr2bcc8732018-04-09 01:35:02 +0200143 if (!name)
144 return NULL;
145
Harald Welte4585e672017-04-16 17:23:56 +0200146 llist_for_each_entry(fi, &fsm->instances, list) {
Neels Hofmeyr2bcc8732018-04-09 01:35:02 +0200147 if (!fi->name)
148 continue;
Harald Welte4585e672017-04-16 17:23:56 +0200149 if (!strcmp(name, fi->name))
150 return fi;
151 }
152 return NULL;
153}
154
155struct osmo_fsm_inst *osmo_fsm_inst_find_by_id(const struct osmo_fsm *fsm,
156 const char *id)
157{
158 struct osmo_fsm_inst *fi;
159
160 llist_for_each_entry(fi, &fsm->instances, list) {
161 if (!strcmp(id, fi->id))
162 return fi;
163 }
164 return NULL;
165}
166
Neels Hofmeyr87e45502017-06-20 00:17:59 +0200167/*! register a FSM with the core
Harald Welte136e7372016-05-29 10:53:17 +0900168 *
169 * A FSM descriptor needs to be registered with the core before any
170 * instances can be created for it.
171 *
172 * \param[in] fsm Descriptor of Finite State Machine to be registered
173 * \returns 0 on success; negative on error
174 */
175int osmo_fsm_register(struct osmo_fsm *fsm)
176{
Harald Welte8c4f5452017-10-03 17:44:03 +0800177 if (!osmo_identifier_valid(fsm->name)) {
178 LOGP(DLGLOBAL, LOGL_ERROR, "Attempting to register FSM with illegal identifier '%s'\n", fsm->name);
179 return -EINVAL;
180 }
Harald Welte8808bb42017-01-07 11:11:03 +0100181 if (osmo_fsm_find_by_name(fsm->name))
182 return -EEXIST;
Stefan Sperling888dc7d2018-02-26 19:17:02 +0100183 if (fsm->event_names == NULL)
184 LOGP(DLGLOBAL, LOGL_ERROR, "FSM '%s' has no event names! Please fix!\n", fsm->name);
Harald Welte34193912017-01-07 11:49:55 +0100185 llist_add_tail(&fsm->list, &osmo_g_fsms);
Harald Welte136e7372016-05-29 10:53:17 +0900186 INIT_LLIST_HEAD(&fsm->instances);
187
188 return 0;
189}
190
Neels Hofmeyr87e45502017-06-20 00:17:59 +0200191/*! unregister a FSM from the core
Harald Welte136e7372016-05-29 10:53:17 +0900192 *
193 * Once the FSM descriptor is unregistered, active instances can still
194 * use it, but no new instances may be created for it.
195 *
196 * \param[in] fsm Descriptor of Finite State Machine to be removed
197 */
198void osmo_fsm_unregister(struct osmo_fsm *fsm)
199{
200 llist_del(&fsm->list);
201}
202
203/* small wrapper function around timer expiration (for logging) */
204static void fsm_tmr_cb(void *data)
205{
206 struct osmo_fsm_inst *fi = data;
207 struct osmo_fsm *fsm = fi->fsm;
Harald Weltef627c0f2016-06-18 10:36:25 +0200208 uint32_t T = fi->T;
Harald Welte136e7372016-05-29 10:53:17 +0900209
210 LOGPFSM(fi, "Timeout of T%u\n", fi->T);
211
Harald Weltef627c0f2016-06-18 10:36:25 +0200212 if (fsm->timer_cb) {
213 int rc = fsm->timer_cb(fi);
Neels Hofmeyr19ec7b92017-11-18 23:10:24 +0100214 if (rc != 1)
215 /* We don't actually know whether fi exists anymore.
216 * Make sure to not access it and return right away. */
Harald Weltef627c0f2016-06-18 10:36:25 +0200217 return;
Neels Hofmeyr19ec7b92017-11-18 23:10:24 +0100218 /* The timer_cb told us to terminate, so we can safely assume
219 * that fi still exists. */
Harald Weltef627c0f2016-06-18 10:36:25 +0200220 LOGPFSM(fi, "timer_cb requested termination\n");
221 } else
222 LOGPFSM(fi, "No timer_cb, automatic termination\n");
223
224 /* if timer_cb returns 1 or there is no timer_cb */
225 osmo_fsm_inst_term(fi, OSMO_FSM_TERM_TIMEOUT, &T);
Harald Welte136e7372016-05-29 10:53:17 +0900226}
227
Daniel Willmannb0c43a62018-02-08 18:00:37 +0100228/*! Change id of the FSM instance
229 * \param[in] fi FSM instance
230 * \param[in] id new ID
231 * \returns 0 if the ID was updated, otherwise -EINVAL
232 */
233int osmo_fsm_inst_update_id(struct osmo_fsm_inst *fi, const char *id)
234{
Neels Hofmeyra64c45a2018-03-31 16:34:49 +0200235 if (!id)
236 return osmo_fsm_inst_update_id_f(fi, NULL);
237 else
238 return osmo_fsm_inst_update_id_f(fi, "%s", id);
239}
240
241static void update_name(struct osmo_fsm_inst *fi)
242{
243 if (fi->name)
244 talloc_free((char*)fi->name);
245
246 if (!fsm_log_addr) {
247 if (fi->id)
248 fi->name = talloc_asprintf(fi, "%s(%s)", fi->fsm->name, fi->id);
249 else
250 fi->name = talloc_asprintf(fi, "%s", fi->fsm->name);
251 } else {
252 if (fi->id)
253 fi->name = talloc_asprintf(fi, "%s(%s)[%p]", fi->fsm->name, fi->id, fi);
254 else
255 fi->name = talloc_asprintf(fi, "%s[%p]", fi->fsm->name, fi);
256 }
257}
258
259/*! Change id of the FSM instance using a string format.
260 * \param[in] fi FSM instance.
261 * \param[in] fmt format string to compose new ID.
262 * \param[in] ... variable argument list for format string.
263 * \returns 0 if the ID was updated, otherwise -EINVAL.
264 */
265int osmo_fsm_inst_update_id_f(struct osmo_fsm_inst *fi, const char *fmt, ...)
266{
267 char *id = NULL;
268
269 if (fmt) {
270 va_list ap;
271
272 va_start(ap, fmt);
273 id = talloc_vasprintf(fi, fmt, ap);
274 va_end(ap);
275
Daniel Willmannb0c43a62018-02-08 18:00:37 +0100276 if (!osmo_identifier_valid(id)) {
Neels Hofmeyr6e8c0882018-04-09 02:28:34 +0200277 LOGP(DLGLOBAL, LOGL_ERROR,
278 "Attempting to set illegal id for FSM instance of type '%s': %s\n",
279 fi->fsm->name, osmo_quote_str(id, -1));
Neels Hofmeyra64c45a2018-03-31 16:34:49 +0200280 talloc_free(id);
Daniel Willmannb0c43a62018-02-08 18:00:37 +0100281 return -EINVAL;
282 }
Daniel Willmannb0c43a62018-02-08 18:00:37 +0100283 }
Daniel Willmann04a2a322018-03-14 18:31:33 +0100284
285 if (fi->id)
Neels Hofmeyra64c45a2018-03-31 16:34:49 +0200286 talloc_free((char*)fi->id);
287 fi->id = id;
Daniel Willmann04a2a322018-03-14 18:31:33 +0100288
Neels Hofmeyra64c45a2018-03-31 16:34:49 +0200289 update_name(fi);
Daniel Willmann04a2a322018-03-14 18:31:33 +0100290 return 0;
Daniel Willmannb0c43a62018-02-08 18:00:37 +0100291}
292
Neels Hofmeyr87e45502017-06-20 00:17:59 +0200293/*! allocate a new instance of a specified FSM
Harald Welte136e7372016-05-29 10:53:17 +0900294 * \param[in] fsm Descriptor of the FSM
295 * \param[in] ctx talloc context from which to allocate memory
296 * \param[in] priv private data reference store in fsm instance
297 * \param[in] log_level The log level for events of this FSM
Daniel Willmannb0c43a62018-02-08 18:00:37 +0100298 * \param[in] id The name/ID of the FSM instance
Harald Welte136e7372016-05-29 10:53:17 +0900299 * \returns newly-allocated, initialized and registered FSM instance
300 */
301struct osmo_fsm_inst *osmo_fsm_inst_alloc(struct osmo_fsm *fsm, void *ctx, void *priv,
302 int log_level, const char *id)
303{
304 struct osmo_fsm_inst *fi = talloc_zero(ctx, struct osmo_fsm_inst);
305
306 fi->fsm = fsm;
307 fi->priv = priv;
308 fi->log_level = log_level;
Pablo Neira Ayuso44f423f2017-05-08 18:00:28 +0200309 osmo_timer_setup(&fi->timer, fsm_tmr_cb, fi);
Daniel Willmannb0c43a62018-02-08 18:00:37 +0100310
Neels Hofmeyr71f76a12018-03-31 16:30:25 +0200311 if (osmo_fsm_inst_update_id(fi, id) < 0) {
312 talloc_free(fi);
313 return NULL;
Harald Welte8c4f5452017-10-03 17:44:03 +0800314 }
Harald Welte136e7372016-05-29 10:53:17 +0900315
Harald Welte136e7372016-05-29 10:53:17 +0900316 INIT_LLIST_HEAD(&fi->proc.children);
317 INIT_LLIST_HEAD(&fi->proc.child);
318 llist_add(&fi->list, &fsm->instances);
319
320 LOGPFSM(fi, "Allocated\n");
321
322 return fi;
323}
324
Neels Hofmeyr87e45502017-06-20 00:17:59 +0200325/*! allocate a new instance of a specified FSM as child of
Harald Welte136e7372016-05-29 10:53:17 +0900326 * other FSM instance
327 *
328 * This is like \ref osmo_fsm_inst_alloc but using the parent FSM as
329 * talloc context, and inheriting the log level of the parent.
330 *
331 * \param[in] fsm Descriptor of the to-be-allocated FSM
332 * \param[in] parent Parent FSM instance
333 * \param[in] parent_term_event Event to be sent to parent when terminating
334 * \returns newly-allocated, initialized and registered FSM instance
335 */
336struct osmo_fsm_inst *osmo_fsm_inst_alloc_child(struct osmo_fsm *fsm,
337 struct osmo_fsm_inst *parent,
338 uint32_t parent_term_event)
339{
340 struct osmo_fsm_inst *fi;
341
342 fi = osmo_fsm_inst_alloc(fsm, parent, NULL, parent->log_level,
343 parent->id);
344 if (!fi) {
345 /* indicate immediate termination to caller */
346 osmo_fsm_inst_dispatch(parent, parent_term_event, NULL);
347 return NULL;
348 }
349
350 LOGPFSM(fi, "is child of %s\n", osmo_fsm_inst_name(parent));
351
Philipp Maier2a06a492018-01-16 18:45:56 +0100352 osmo_fsm_inst_change_parent(fi, parent, parent_term_event);
Harald Welte136e7372016-05-29 10:53:17 +0900353
354 return fi;
355}
356
Philipp Maier2a06a492018-01-16 18:45:56 +0100357/*! unlink child FSM from its parent FSM.
358 * \param[in] fi Descriptor of the child FSM to unlink.
Philipp Maierd1f57932018-02-14 18:20:07 +0100359 * \param[in] ctx New talloc context
360 *
361 * Never call this function from the cleanup callback, because at that time
362 * the child FSMs will already be terminated. If unlinking should be performed
363 * on FSM termination, use the grace callback instead. */
Philipp Maier2a06a492018-01-16 18:45:56 +0100364void osmo_fsm_inst_unlink_parent(struct osmo_fsm_inst *fi, void *ctx)
365{
366 if (fi->proc.parent) {
367 talloc_steal(ctx, fi);
368 fi->proc.parent = NULL;
369 fi->proc.parent_term_event = 0;
370 llist_del(&fi->proc.child);
371 }
372}
373
374/*! change parent instance of an FSM.
375 * \param[in] fi Descriptor of the to-be-allocated FSM.
376 * \param[in] new_parent New parent FSM instance.
Philipp Maierd1f57932018-02-14 18:20:07 +0100377 * \param[in] new_parent_term_event Event to be sent to parent when terminating.
378 *
379 * Never call this function from the cleanup callback!
380 * (see also osmo_fsm_inst_unlink_parent()).*/
Philipp Maier2a06a492018-01-16 18:45:56 +0100381void osmo_fsm_inst_change_parent(struct osmo_fsm_inst *fi,
382 struct osmo_fsm_inst *new_parent,
383 uint32_t new_parent_term_event)
384{
385 /* Make sure a possibly existing old parent is unlinked first
386 * (new_parent can be NULL) */
387 osmo_fsm_inst_unlink_parent(fi, new_parent);
388
389 /* Add new parent */
390 if (new_parent) {
391 fi->proc.parent = new_parent;
392 fi->proc.parent_term_event = new_parent_term_event;
393 llist_add(&fi->proc.child, &new_parent->proc.children);
394 }
395}
396
Neels Hofmeyr87e45502017-06-20 00:17:59 +0200397/*! delete a given instance of a FSM
Harald Welte136e7372016-05-29 10:53:17 +0900398 * \param[in] fsm The FSM to be un-registered and deleted
399 */
400void osmo_fsm_inst_free(struct osmo_fsm_inst *fi)
401{
Max3de97e12016-11-02 10:37:58 +0100402 LOGPFSM(fi, "Deallocated\n");
Harald Welte136e7372016-05-29 10:53:17 +0900403 osmo_timer_del(&fi->timer);
404 llist_del(&fi->list);
405 talloc_free(fi);
406}
407
Neels Hofmeyr87e45502017-06-20 00:17:59 +0200408/*! get human-readable name of FSM event
Harald Welte136e7372016-05-29 10:53:17 +0900409 * \param[in] fsm FSM descriptor of event
410 * \param[in] event Event integer value
411 * \returns string rendering of the event
412 */
413const char *osmo_fsm_event_name(struct osmo_fsm *fsm, uint32_t event)
414{
415 static char buf[32];
416 if (!fsm->event_names) {
Pau Espin Pedrol4f8857e2017-06-18 12:23:00 +0200417 snprintf(buf, sizeof(buf), "%"PRIu32, event);
Harald Welte136e7372016-05-29 10:53:17 +0900418 return buf;
419 } else
420 return get_value_string(fsm->event_names, event);
421}
422
Neels Hofmeyr87e45502017-06-20 00:17:59 +0200423/*! get human-readable name of FSM instance
Harald Welte136e7372016-05-29 10:53:17 +0900424 * \param[in] fi FSM instance
425 * \returns string rendering of the FSM identity
426 */
427const char *osmo_fsm_inst_name(struct osmo_fsm_inst *fi)
428{
429 if (!fi)
430 return "NULL";
431
432 if (fi->name)
433 return fi->name;
434 else
435 return fi->fsm->name;
436}
437
Philipp Maieraf6710f2018-11-16 17:45:40 +0100438/*! get human-readable name of FSM state
Harald Welte136e7372016-05-29 10:53:17 +0900439 * \param[in] fsm FSM descriptor
440 * \param[in] state FSM state number
441 * \returns string rendering of the FSM state
442 */
443const char *osmo_fsm_state_name(struct osmo_fsm *fsm, uint32_t state)
444{
445 static char buf[32];
446 if (state >= fsm->num_states) {
Pau Espin Pedrol4f8857e2017-06-18 12:23:00 +0200447 snprintf(buf, sizeof(buf), "unknown %"PRIu32, state);
Harald Welte136e7372016-05-29 10:53:17 +0900448 return buf;
449 } else
450 return fsm->states[state].name;
451}
452
Neels Hofmeyr407df022018-05-25 18:20:06 +0200453static int state_chg(struct osmo_fsm_inst *fi, uint32_t new_state,
454 bool keep_timer, unsigned long timeout_secs, int T,
455 const char *file, int line)
456{
457 struct osmo_fsm *fsm = fi->fsm;
458 uint32_t old_state = fi->state;
459 const struct osmo_fsm_state *st = &fsm->states[fi->state];
Neels Hofmeyr050f2d32018-05-31 15:30:15 +0200460 struct timeval remaining;
Neels Hofmeyr407df022018-05-25 18:20:06 +0200461
Neels Hofmeyr89991fd2019-01-28 19:06:53 +0100462 /* Limit to 0x7fffffff seconds as explained by
463 * _osmo_fsm_inst_state_chg()'s API doc. */
464 if (timeout_secs > 0x7fffffff)
465 timeout_secs = 0x7fffffff;
466
Neels Hofmeyr407df022018-05-25 18:20:06 +0200467 /* validate if new_state is a valid state */
468 if (!(st->out_state_mask & (1 << new_state))) {
469 LOGPFSMLSRC(fi, LOGL_ERROR, file, line,
470 "transition to state %s not permitted!\n",
471 osmo_fsm_state_name(fsm, new_state));
472 return -EPERM;
473 }
474
475 if (!keep_timer) {
476 /* delete the old timer */
477 osmo_timer_del(&fi->timer);
478 }
479
480 if (st->onleave)
481 st->onleave(fi, new_state);
482
Neels Hofmeyr050f2d32018-05-31 15:30:15 +0200483 if (fsm_log_timeouts) {
484 if (keep_timer && fi->timer.active && (osmo_timer_remaining(&fi->timer, NULL, &remaining) == 0))
485 LOGPFSMSRC(fi, file, line, "State change to %s (keeping T%d, %ld.%03lds remaining)\n",
486 osmo_fsm_state_name(fsm, new_state),
487 fi->T, remaining.tv_sec, remaining.tv_usec / 1000);
488 else if (timeout_secs && !keep_timer)
489 LOGPFSMSRC(fi, file, line, "State change to %s (T%d, %lus)\n",
490 osmo_fsm_state_name(fsm, new_state),
491 T, timeout_secs);
492 else
493 LOGPFSMSRC(fi, file, line, "State change to %s (no timeout)\n",
494 osmo_fsm_state_name(fsm, new_state));
495 } else {
496 LOGPFSMSRC(fi, file, line, "state_chg to %s\n",
497 osmo_fsm_state_name(fsm, new_state));
498 }
499
Neels Hofmeyr407df022018-05-25 18:20:06 +0200500 fi->state = new_state;
501 st = &fsm->states[new_state];
502
Neels Hofmeyrbd5a1dc2019-01-28 15:38:09 +0100503 if (!keep_timer) {
Neels Hofmeyr407df022018-05-25 18:20:06 +0200504 fi->T = T;
Neels Hofmeyrbd5a1dc2019-01-28 15:38:09 +0100505 if (timeout_secs)
506 osmo_timer_schedule(&fi->timer, timeout_secs, 0);
Neels Hofmeyr407df022018-05-25 18:20:06 +0200507 }
508
509 /* Call 'onenter' last, user might terminate FSM from there */
510 if (st->onenter)
511 st->onenter(fi, old_state);
512
513 return 0;
514}
515
Neels Hofmeyr87e45502017-06-20 00:17:59 +0200516/*! perform a state change of the given FSM instance
Harald Welte136e7372016-05-29 10:53:17 +0900517 *
Neels Hofmeyr725698a2016-12-14 17:24:54 +0100518 * Best invoke via the osmo_fsm_inst_state_chg() macro which logs the source
519 * file where the state change was effected. Alternatively, you may pass \a
520 * file as NULL to use the normal file/line indication instead.
521 *
Neels Hofmeyr407df022018-05-25 18:20:06 +0200522 * All changes to the FSM instance state must be made via an osmo_fsm_inst_state_chg_*
Harald Welte136e7372016-05-29 10:53:17 +0900523 * function. It verifies that the existing state actually permits a
Neels Hofmeyr407df022018-05-25 18:20:06 +0200524 * transition to new_state.
Harald Welte136e7372016-05-29 10:53:17 +0900525 *
Neels Hofmeyrbd5a1dc2019-01-28 15:38:09 +0100526 * If timeout_secs is 0, stay in the new state indefinitely, without a timeout
527 * (stop the FSM instance's timer if it was runnning).
528 *
529 * If timeout_secs > 0, start or reset the FSM instance's timer with this
530 * timeout. On expiry, invoke the FSM instance's timer_cb -- if no timer_cb is
531 * set, an expired timer immediately terminates the FSM instance with
532 * OSMO_FSM_TERM_TIMEOUT.
533 *
534 * The value of T is stored in fi->T and is then available for query in
535 * timer_cb. If passing timeout_secs == 0, it is recommended to also pass T ==
536 * 0, so that fi->T is reset to 0 when no timeout is invoked.
Harald Welte136e7372016-05-29 10:53:17 +0900537 *
Neels Hofmeyr0fd615f2019-01-26 20:36:12 +0100538 * See also osmo_tdef_fsm_inst_state_chg() from the osmo_tdef API, which
539 * provides a unified way to configure and apply GSM style Tnnnn timers to FSM
540 * state transitions.
541 *
Neels Hofmeyr89991fd2019-01-28 19:06:53 +0100542 * Range: since time_t's maximum value is not well defined in a cross platform
543 * way, clamp timeout_secs to the maximum of the signed 32bit range, or roughly
544 * 68 years (float(0x7fffffff) / (60. * 60 * 24 * 365.25) = 68.0497). Thus
545 * ensure that very large timeouts do not wrap around to become very small
546 * ones. Note though that this might still be unsafe on systems with a time_t
547 * range below 32 bits.
548 *
Harald Welte136e7372016-05-29 10:53:17 +0900549 * \param[in] fi FSM instance whose state is to change
550 * \param[in] new_state The new state into which we should change
Neels Hofmeyr89991fd2019-01-28 19:06:53 +0100551 * \param[in] timeout_secs Timeout in seconds (if !=0), maximum-clamped to 2147483647 seconds.
Harald Welte136e7372016-05-29 10:53:17 +0900552 * \param[in] T Timer number (if \ref timeout_secs != 0)
Neels Hofmeyrb805cc12016-12-23 04:23:18 +0100553 * \param[in] file Calling source file (from osmo_fsm_inst_state_chg macro)
554 * \param[in] line Calling source line (from osmo_fsm_inst_state_chg macro)
Harald Welte136e7372016-05-29 10:53:17 +0900555 * \returns 0 on success; negative on error
556 */
Neels Hofmeyr725698a2016-12-14 17:24:54 +0100557int _osmo_fsm_inst_state_chg(struct osmo_fsm_inst *fi, uint32_t new_state,
558 unsigned long timeout_secs, int T,
559 const char *file, int line)
Harald Welte136e7372016-05-29 10:53:17 +0900560{
Neels Hofmeyr407df022018-05-25 18:20:06 +0200561 return state_chg(fi, new_state, false, timeout_secs, T, file, line);
562}
Harald Welte136e7372016-05-29 10:53:17 +0900563
Neels Hofmeyr407df022018-05-25 18:20:06 +0200564/*! perform a state change while keeping the current timer running.
565 *
566 * This is useful to keep a timeout across several states (without having to round the
567 * remaining time to seconds).
568 *
569 * Best invoke via the osmo_fsm_inst_state_chg_keep_timer() macro which logs the source
570 * file where the state change was effected. Alternatively, you may pass \a
571 * file as NULL to use the normal file/line indication instead.
572 *
573 * All changes to the FSM instance state must be made via an osmo_fsm_inst_state_chg_*
574 * function. It verifies that the existing state actually permits a
575 * transition to new_state.
576 *
577 * \param[in] fi FSM instance whose state is to change
578 * \param[in] new_state The new state into which we should change
579 * \param[in] file Calling source file (from osmo_fsm_inst_state_chg macro)
580 * \param[in] line Calling source line (from osmo_fsm_inst_state_chg macro)
581 * \returns 0 on success; negative on error
582 */
583int _osmo_fsm_inst_state_chg_keep_timer(struct osmo_fsm_inst *fi, uint32_t new_state,
584 const char *file, int line)
585{
586 return state_chg(fi, new_state, true, 0, 0, file, line);
Harald Welte136e7372016-05-29 10:53:17 +0900587}
588
Neels Hofmeyr87e45502017-06-20 00:17:59 +0200589/*! dispatch an event to an osmocom finite state machine instance
Harald Welte136e7372016-05-29 10:53:17 +0900590 *
Neels Hofmeyr725698a2016-12-14 17:24:54 +0100591 * Best invoke via the osmo_fsm_inst_dispatch() macro which logs the source
592 * file where the event was effected. Alternatively, you may pass \a file as
593 * NULL to use the normal file/line indication instead.
594 *
Harald Welte136e7372016-05-29 10:53:17 +0900595 * Any incoming events to \ref osmo_fsm instances must be dispatched to
596 * them via this function. It verifies, whether the event is permitted
597 * based on the current state of the FSM. If not, -1 is returned.
598 *
599 * \param[in] fi FSM instance
600 * \param[in] event Event to send to FSM instance
601 * \param[in] data Data to pass along with the event
Neels Hofmeyrb805cc12016-12-23 04:23:18 +0100602 * \param[in] file Calling source file (from osmo_fsm_inst_dispatch macro)
603 * \param[in] line Calling source line (from osmo_fsm_inst_dispatch macro)
Harald Welte136e7372016-05-29 10:53:17 +0900604 * \returns 0 in case of success; negative on error
605 */
Neels Hofmeyr725698a2016-12-14 17:24:54 +0100606int _osmo_fsm_inst_dispatch(struct osmo_fsm_inst *fi, uint32_t event, void *data,
607 const char *file, int line)
Harald Welte136e7372016-05-29 10:53:17 +0900608{
609 struct osmo_fsm *fsm;
610 const struct osmo_fsm_state *fs;
611
612 if (!fi) {
Neels Hofmeyrc7155df2016-12-23 04:24:51 +0100613 LOGPSRC(DLGLOBAL, LOGL_ERROR, file, line,
Pau Espin Pedrol4f8857e2017-06-18 12:23:00 +0200614 "Trying to dispatch event %"PRIu32" to non-existent"
Neels Hofmeyrc7155df2016-12-23 04:24:51 +0100615 " FSM instance!\n", event);
Harald Welte136e7372016-05-29 10:53:17 +0900616 osmo_log_backtrace(DLGLOBAL, LOGL_ERROR);
617 return -ENODEV;
618 }
619
620 fsm = fi->fsm;
621 OSMO_ASSERT(fi->state < fsm->num_states);
622 fs = &fi->fsm->states[fi->state];
623
Neels Hofmeyr725698a2016-12-14 17:24:54 +0100624 LOGPFSMSRC(fi, file, line,
625 "Received Event %s\n", osmo_fsm_event_name(fsm, event));
Harald Welte136e7372016-05-29 10:53:17 +0900626
627 if (((1 << event) & fsm->allstate_event_mask) && fsm->allstate_action) {
628 fsm->allstate_action(fi, event, data);
629 return 0;
630 }
631
632 if (!((1 << event) & fs->in_event_mask)) {
Neels Hofmeyr725698a2016-12-14 17:24:54 +0100633 LOGPFSMLSRC(fi, LOGL_ERROR, file, line,
634 "Event %s not permitted\n",
635 osmo_fsm_event_name(fsm, event));
Harald Welte136e7372016-05-29 10:53:17 +0900636 return -1;
637 }
Philipp Maier3d4fb592018-05-15 10:06:22 +0200638
639 if (fs->action)
640 fs->action(fi, event, data);
Harald Welte136e7372016-05-29 10:53:17 +0900641
642 return 0;
643}
644
Neels Hofmeyr87e45502017-06-20 00:17:59 +0200645/*! Terminate FSM instance with given cause
Harald Welte136e7372016-05-29 10:53:17 +0900646 *
647 * This safely terminates the given FSM instance by first iterating
648 * over all children and sending them a termination event. Next, it
649 * calls the FSM descriptors cleanup function (if any), followed by
650 * releasing any memory associated with the FSM instance.
651 *
652 * Finally, the parent FSM instance (if any) is notified using the
653 * parent termination event configured at time of FSM instance start.
654 *
655 * \param[in] fi FSM instance to be terminated
656 * \param[in] cause Cause / reason for termination
Neels Hofmeyrb805cc12016-12-23 04:23:18 +0100657 * \param[in] data Opaque event data to be passed with the parent term event
658 * \param[in] file Calling source file (from osmo_fsm_inst_term macro)
659 * \param[in] line Calling source line (from osmo_fsm_inst_term macro)
Harald Welte136e7372016-05-29 10:53:17 +0900660 */
Neels Hofmeyr725698a2016-12-14 17:24:54 +0100661void _osmo_fsm_inst_term(struct osmo_fsm_inst *fi,
662 enum osmo_fsm_term_cause cause, void *data,
663 const char *file, int line)
Harald Welte136e7372016-05-29 10:53:17 +0900664{
Neels Hofmeyr3faa0142016-12-18 23:41:41 +0100665 struct osmo_fsm_inst *parent;
Harald Welte136e7372016-05-29 10:53:17 +0900666 uint32_t parent_term_event = fi->proc.parent_term_event;
667
Neels Hofmeyr5c5c78a2016-12-14 18:35:47 +0100668 LOGPFSMSRC(fi, file, line, "Terminating (cause = %s)\n",
669 osmo_fsm_term_cause_name(cause));
Harald Welte136e7372016-05-29 10:53:17 +0900670
Philipp Maierd1f57932018-02-14 18:20:07 +0100671 /* graceful exit (optional) */
672 if (fi->fsm->pre_term)
673 fi->fsm->pre_term(fi, cause);
674
Harald Welte65900442018-02-09 09:58:57 +0000675 _osmo_fsm_inst_term_children(fi, OSMO_FSM_TERM_PARENT, NULL,
676 file, line);
677
Neels Hofmeyrc014f602016-12-23 04:26:39 +0100678 /* delete ourselves from the parent */
Neels Hofmeyr3faa0142016-12-18 23:41:41 +0100679 parent = fi->proc.parent;
Philipp Maier23d31612018-01-16 18:50:23 +0100680 if (parent) {
Neels Hofmeyrc014f602016-12-23 04:26:39 +0100681 LOGPFSMSRC(fi, file, line, "Removing from parent %s\n",
682 osmo_fsm_inst_name(parent));
Philipp Maier23d31612018-01-16 18:50:23 +0100683 llist_del(&fi->proc.child);
684 }
Neels Hofmeyrc014f602016-12-23 04:26:39 +0100685
686 /* call destructor / clean-up function */
687 if (fi->fsm->cleanup)
688 fi->fsm->cleanup(fi, cause);
689
690 LOGPFSMSRC(fi, file, line, "Freeing instance\n");
Neels Hofmeyr3faa0142016-12-18 23:41:41 +0100691 /* Fetch parent again in case it has changed. */
692 parent = fi->proc.parent;
Neels Hofmeyrc014f602016-12-23 04:26:39 +0100693 osmo_fsm_inst_free(fi);
694
695 /* indicate our termination to the parent */
696 if (parent && cause != OSMO_FSM_TERM_PARENT)
697 _osmo_fsm_inst_dispatch(parent, parent_term_event, data,
698 file, line);
699}
700
Neels Hofmeyr87e45502017-06-20 00:17:59 +0200701/*! Terminate all child FSM instances of an FSM instance.
Neels Hofmeyrc014f602016-12-23 04:26:39 +0100702 *
703 * Iterate over all children and send them a termination event, with the given
704 * cause. Pass OSMO_FSM_TERM_PARENT to avoid dispatching events from the
705 * terminated child FSMs.
706 *
707 * \param[in] fi FSM instance that should be cleared of child FSMs
708 * \param[in] cause Cause / reason for termination (OSMO_FSM_TERM_PARENT)
709 * \param[in] data Opaque event data to be passed with the parent term events
710 * \param[in] file Calling source file (from osmo_fsm_inst_term_children macro)
711 * \param[in] line Calling source line (from osmo_fsm_inst_term_children macro)
712 */
713void _osmo_fsm_inst_term_children(struct osmo_fsm_inst *fi,
714 enum osmo_fsm_term_cause cause,
715 void *data,
716 const char *file, int line)
717{
718 struct osmo_fsm_inst *first_child, *last_seen_first_child;
719
Neels Hofmeyr06ac9b42016-12-20 12:05:19 +0100720 /* iterate over all children, starting from the beginning every time:
721 * terminating an FSM may emit events that cause other FSMs to also
722 * terminate and remove themselves from this list. */
723 last_seen_first_child = NULL;
724 while (!llist_empty(&fi->proc.children)) {
725 first_child = llist_entry(fi->proc.children.next,
726 typeof(*first_child),
727 proc.child);
728
729 /* paranoia: do not loop forever */
730 if (first_child == last_seen_first_child) {
731 LOGPFSMLSRC(fi, LOGL_ERROR, file, line,
732 "Internal error while terminating child"
733 " FSMs: a child FSM is stuck\n");
734 break;
735 }
736 last_seen_first_child = first_child;
737
Harald Welte136e7372016-05-29 10:53:17 +0900738 /* terminate child */
Neels Hofmeyrc014f602016-12-23 04:26:39 +0100739 _osmo_fsm_inst_term(first_child, cause, data,
Neels Hofmeyr725698a2016-12-14 17:24:54 +0100740 file, line);
Harald Welte136e7372016-05-29 10:53:17 +0900741 }
Harald Welte136e7372016-05-29 10:53:17 +0900742}
743
Neels Hofmeyr5c5c78a2016-12-14 18:35:47 +0100744const struct value_string osmo_fsm_term_cause_names[] = {
Neels Hofmeyr18080962016-12-16 13:43:54 +0100745 OSMO_VALUE_STRING(OSMO_FSM_TERM_PARENT),
746 OSMO_VALUE_STRING(OSMO_FSM_TERM_REQUEST),
747 OSMO_VALUE_STRING(OSMO_FSM_TERM_REGULAR),
748 OSMO_VALUE_STRING(OSMO_FSM_TERM_ERROR),
749 OSMO_VALUE_STRING(OSMO_FSM_TERM_TIMEOUT),
Neels Hofmeyr5c5c78a2016-12-14 18:35:47 +0100750 { 0, NULL }
751};
752
Harald Welte136e7372016-05-29 10:53:17 +0900753/*! @} */