blob: 5723bab4eb979ad4f8b34143a8c286838c755d06 [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
Neels Hofmeyr5734bff2019-02-21 02:27:48 +0100111 * will also log the T number (or Osmocom-specific X number) and the chosen timeout in seconds.
112 * osmo_fsm_inst_state_chg_keep_timer() will log remaining timeout in millisecond precision.
Neels Hofmeyr050f2d32018-05-31 15:30:15 +0200113 *
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;
Neels Hofmeyr5734bff2019-02-21 02:27:48 +0100208 int32_t T = fi->T;
Harald Welte136e7372016-05-29 10:53:17 +0900209
Neels Hofmeyr5734bff2019-02-21 02:27:48 +0100210 LOGPFSM(fi, "Timeout of " OSMO_T_FMT "\n", OSMO_T_FMT_ARGS(fi->T));
Harald Welte136e7372016-05-29 10:53:17 +0900211
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) {
Neels Hofmeyrd4b79c82019-03-06 05:43:23 +0100484 if (keep_timer && fi->timer.active) {
485 /* This should always give us a timeout, but just in case the return value indicates error, omit
486 * logging the remaining time. */
487 if (osmo_timer_remaining(&fi->timer, NULL, &remaining))
488 LOGPFSMSRC(fi, file, line,
489 "State change to %s (keeping " OSMO_T_FMT ")\n",
490 osmo_fsm_state_name(fsm, new_state),
491 OSMO_T_FMT_ARGS(fi->T));
492 else
493 LOGPFSMSRC(fi, file, line,
494 "State change to %s (keeping " OSMO_T_FMT ", %ld.%03lds remaining)\n",
495 osmo_fsm_state_name(fsm, new_state),
496 OSMO_T_FMT_ARGS(fi->T), remaining.tv_sec, remaining.tv_usec / 1000);
497 } else if (timeout_secs)
Neels Hofmeyr5734bff2019-02-21 02:27:48 +0100498 LOGPFSMSRC(fi, file, line, "State change to %s (" OSMO_T_FMT ", %lus)\n",
Neels Hofmeyr050f2d32018-05-31 15:30:15 +0200499 osmo_fsm_state_name(fsm, new_state),
Neels Hofmeyr5734bff2019-02-21 02:27:48 +0100500 OSMO_T_FMT_ARGS(T), timeout_secs);
Neels Hofmeyr050f2d32018-05-31 15:30:15 +0200501 else
502 LOGPFSMSRC(fi, file, line, "State change to %s (no timeout)\n",
503 osmo_fsm_state_name(fsm, new_state));
504 } else {
505 LOGPFSMSRC(fi, file, line, "state_chg to %s\n",
506 osmo_fsm_state_name(fsm, new_state));
507 }
508
Neels Hofmeyr407df022018-05-25 18:20:06 +0200509 fi->state = new_state;
510 st = &fsm->states[new_state];
511
Neels Hofmeyrd4b79c82019-03-06 05:43:23 +0100512 if (!keep_timer
513 || (keep_timer && !osmo_timer_pending(&fi->timer))) {
Neels Hofmeyr407df022018-05-25 18:20:06 +0200514 fi->T = T;
Neels Hofmeyrbd5a1dc2019-01-28 15:38:09 +0100515 if (timeout_secs)
516 osmo_timer_schedule(&fi->timer, timeout_secs, 0);
Neels Hofmeyr407df022018-05-25 18:20:06 +0200517 }
518
519 /* Call 'onenter' last, user might terminate FSM from there */
520 if (st->onenter)
521 st->onenter(fi, old_state);
522
523 return 0;
524}
525
Neels Hofmeyr87e45502017-06-20 00:17:59 +0200526/*! perform a state change of the given FSM instance
Harald Welte136e7372016-05-29 10:53:17 +0900527 *
Neels Hofmeyr725698a2016-12-14 17:24:54 +0100528 * Best invoke via the osmo_fsm_inst_state_chg() macro which logs the source
529 * file where the state change was effected. Alternatively, you may pass \a
530 * file as NULL to use the normal file/line indication instead.
531 *
Neels Hofmeyr407df022018-05-25 18:20:06 +0200532 * All changes to the FSM instance state must be made via an osmo_fsm_inst_state_chg_*
Harald Welte136e7372016-05-29 10:53:17 +0900533 * function. It verifies that the existing state actually permits a
Neels Hofmeyr407df022018-05-25 18:20:06 +0200534 * transition to new_state.
Harald Welte136e7372016-05-29 10:53:17 +0900535 *
Neels Hofmeyrbd5a1dc2019-01-28 15:38:09 +0100536 * If timeout_secs is 0, stay in the new state indefinitely, without a timeout
537 * (stop the FSM instance's timer if it was runnning).
538 *
539 * If timeout_secs > 0, start or reset the FSM instance's timer with this
540 * timeout. On expiry, invoke the FSM instance's timer_cb -- if no timer_cb is
541 * set, an expired timer immediately terminates the FSM instance with
542 * OSMO_FSM_TERM_TIMEOUT.
543 *
544 * The value of T is stored in fi->T and is then available for query in
545 * timer_cb. If passing timeout_secs == 0, it is recommended to also pass T ==
546 * 0, so that fi->T is reset to 0 when no timeout is invoked.
Harald Welte136e7372016-05-29 10:53:17 +0900547 *
Neels Hofmeyr5734bff2019-02-21 02:27:48 +0100548 * Positive values for T are considered to be 3GPP spec compliant and appear in
549 * logging and VTY as "T1234", while negative values are considered to be
550 * Osmocom specific timers, represented in logging and VTY as "X1234".
551 *
Neels Hofmeyr0fd615f2019-01-26 20:36:12 +0100552 * See also osmo_tdef_fsm_inst_state_chg() from the osmo_tdef API, which
553 * provides a unified way to configure and apply GSM style Tnnnn timers to FSM
554 * state transitions.
555 *
Neels Hofmeyr89991fd2019-01-28 19:06:53 +0100556 * Range: since time_t's maximum value is not well defined in a cross platform
557 * way, clamp timeout_secs to the maximum of the signed 32bit range, or roughly
558 * 68 years (float(0x7fffffff) / (60. * 60 * 24 * 365.25) = 68.0497). Thus
559 * ensure that very large timeouts do not wrap around to become very small
560 * ones. Note though that this might still be unsafe on systems with a time_t
561 * range below 32 bits.
562 *
Harald Welte136e7372016-05-29 10:53:17 +0900563 * \param[in] fi FSM instance whose state is to change
564 * \param[in] new_state The new state into which we should change
Neels Hofmeyr89991fd2019-01-28 19:06:53 +0100565 * \param[in] timeout_secs Timeout in seconds (if !=0), maximum-clamped to 2147483647 seconds.
Neels Hofmeyr5734bff2019-02-21 02:27:48 +0100566 * \param[in] T Timer number, where positive numbers are considered to be 3GPP spec compliant timer numbers and are
567 * logged as "T1234", while negative numbers are considered Osmocom specific timer numbers logged as
568 * "X1234".
Neels Hofmeyrb805cc12016-12-23 04:23:18 +0100569 * \param[in] file Calling source file (from osmo_fsm_inst_state_chg macro)
570 * \param[in] line Calling source line (from osmo_fsm_inst_state_chg macro)
Harald Welte136e7372016-05-29 10:53:17 +0900571 * \returns 0 on success; negative on error
572 */
Neels Hofmeyr725698a2016-12-14 17:24:54 +0100573int _osmo_fsm_inst_state_chg(struct osmo_fsm_inst *fi, uint32_t new_state,
574 unsigned long timeout_secs, int T,
575 const char *file, int line)
Harald Welte136e7372016-05-29 10:53:17 +0900576{
Neels Hofmeyr407df022018-05-25 18:20:06 +0200577 return state_chg(fi, new_state, false, timeout_secs, T, file, line);
578}
Harald Welte136e7372016-05-29 10:53:17 +0900579
Neels Hofmeyr407df022018-05-25 18:20:06 +0200580/*! perform a state change while keeping the current timer running.
581 *
582 * This is useful to keep a timeout across several states (without having to round the
583 * remaining time to seconds).
584 *
585 * Best invoke via the osmo_fsm_inst_state_chg_keep_timer() macro which logs the source
586 * file where the state change was effected. Alternatively, you may pass \a
587 * file as NULL to use the normal file/line indication instead.
588 *
589 * All changes to the FSM instance state must be made via an osmo_fsm_inst_state_chg_*
590 * function. It verifies that the existing state actually permits a
591 * transition to new_state.
592 *
593 * \param[in] fi FSM instance whose state is to change
594 * \param[in] new_state The new state into which we should change
595 * \param[in] file Calling source file (from osmo_fsm_inst_state_chg macro)
596 * \param[in] line Calling source line (from osmo_fsm_inst_state_chg macro)
597 * \returns 0 on success; negative on error
598 */
599int _osmo_fsm_inst_state_chg_keep_timer(struct osmo_fsm_inst *fi, uint32_t new_state,
600 const char *file, int line)
601{
602 return state_chg(fi, new_state, true, 0, 0, file, line);
Harald Welte136e7372016-05-29 10:53:17 +0900603}
604
Neels Hofmeyrd4b79c82019-03-06 05:43:23 +0100605/*! perform a state change while keeping the current timer if running, or starting a timer otherwise.
606 *
607 * This is useful to keep a timeout across several states, but to make sure that some timeout is actually running.
608 *
609 * Best invoke via the osmo_fsm_inst_state_chg_keep_or_start_timer() macro which logs the source file where the state
610 * change was effected. Alternatively, you may pass file as NULL to use the normal file/line indication instead.
611 *
612 * All changes to the FSM instance state must be made via an osmo_fsm_inst_state_chg_*
613 * function. It verifies that the existing state actually permits a
614 * transition to new_state.
615 *
616 * \param[in] fi FSM instance whose state is to change
617 * \param[in] new_state The new state into which we should change
618 * \param[in] timeout_secs If no timer is running yet, set this timeout in seconds (if !=0), maximum-clamped to
619 * 2147483647 seconds.
620 * \param[in] T Timer number, where positive numbers are considered to be 3GPP spec compliant timer numbers and are
621 * logged as "T1234", while negative numbers are considered Osmocom specific timer numbers logged as
622 * "X1234".
623 * \param[in] file Calling source file (from osmo_fsm_inst_state_chg macro)
624 * \param[in] line Calling source line (from osmo_fsm_inst_state_chg macro)
625 * \returns 0 on success; negative on error
626 */
627int _osmo_fsm_inst_state_chg_keep_or_start_timer(struct osmo_fsm_inst *fi, uint32_t new_state,
628 unsigned long timeout_secs, int T,
629 const char *file, int line)
630{
631 return state_chg(fi, new_state, true, timeout_secs, T, file, line);
632}
633
Neels Hofmeyr87e45502017-06-20 00:17:59 +0200634/*! dispatch an event to an osmocom finite state machine instance
Harald Welte136e7372016-05-29 10:53:17 +0900635 *
Neels Hofmeyr725698a2016-12-14 17:24:54 +0100636 * Best invoke via the osmo_fsm_inst_dispatch() macro which logs the source
637 * file where the event was effected. Alternatively, you may pass \a file as
638 * NULL to use the normal file/line indication instead.
639 *
Harald Welte136e7372016-05-29 10:53:17 +0900640 * Any incoming events to \ref osmo_fsm instances must be dispatched to
641 * them via this function. It verifies, whether the event is permitted
642 * based on the current state of the FSM. If not, -1 is returned.
643 *
644 * \param[in] fi FSM instance
645 * \param[in] event Event to send to FSM instance
646 * \param[in] data Data to pass along with the event
Neels Hofmeyrb805cc12016-12-23 04:23:18 +0100647 * \param[in] file Calling source file (from osmo_fsm_inst_dispatch macro)
648 * \param[in] line Calling source line (from osmo_fsm_inst_dispatch macro)
Harald Welte136e7372016-05-29 10:53:17 +0900649 * \returns 0 in case of success; negative on error
650 */
Neels Hofmeyr725698a2016-12-14 17:24:54 +0100651int _osmo_fsm_inst_dispatch(struct osmo_fsm_inst *fi, uint32_t event, void *data,
652 const char *file, int line)
Harald Welte136e7372016-05-29 10:53:17 +0900653{
654 struct osmo_fsm *fsm;
655 const struct osmo_fsm_state *fs;
656
657 if (!fi) {
Neels Hofmeyrc7155df2016-12-23 04:24:51 +0100658 LOGPSRC(DLGLOBAL, LOGL_ERROR, file, line,
Pau Espin Pedrol4f8857e2017-06-18 12:23:00 +0200659 "Trying to dispatch event %"PRIu32" to non-existent"
Neels Hofmeyrc7155df2016-12-23 04:24:51 +0100660 " FSM instance!\n", event);
Harald Welte136e7372016-05-29 10:53:17 +0900661 osmo_log_backtrace(DLGLOBAL, LOGL_ERROR);
662 return -ENODEV;
663 }
664
665 fsm = fi->fsm;
666 OSMO_ASSERT(fi->state < fsm->num_states);
667 fs = &fi->fsm->states[fi->state];
668
Neels Hofmeyr725698a2016-12-14 17:24:54 +0100669 LOGPFSMSRC(fi, file, line,
670 "Received Event %s\n", osmo_fsm_event_name(fsm, event));
Harald Welte136e7372016-05-29 10:53:17 +0900671
672 if (((1 << event) & fsm->allstate_event_mask) && fsm->allstate_action) {
673 fsm->allstate_action(fi, event, data);
674 return 0;
675 }
676
677 if (!((1 << event) & fs->in_event_mask)) {
Neels Hofmeyr725698a2016-12-14 17:24:54 +0100678 LOGPFSMLSRC(fi, LOGL_ERROR, file, line,
679 "Event %s not permitted\n",
680 osmo_fsm_event_name(fsm, event));
Harald Welte136e7372016-05-29 10:53:17 +0900681 return -1;
682 }
Philipp Maier3d4fb592018-05-15 10:06:22 +0200683
684 if (fs->action)
685 fs->action(fi, event, data);
Harald Welte136e7372016-05-29 10:53:17 +0900686
687 return 0;
688}
689
Neels Hofmeyr87e45502017-06-20 00:17:59 +0200690/*! Terminate FSM instance with given cause
Harald Welte136e7372016-05-29 10:53:17 +0900691 *
692 * This safely terminates the given FSM instance by first iterating
693 * over all children and sending them a termination event. Next, it
694 * calls the FSM descriptors cleanup function (if any), followed by
695 * releasing any memory associated with the FSM instance.
696 *
697 * Finally, the parent FSM instance (if any) is notified using the
698 * parent termination event configured at time of FSM instance start.
699 *
700 * \param[in] fi FSM instance to be terminated
701 * \param[in] cause Cause / reason for termination
Neels Hofmeyrb805cc12016-12-23 04:23:18 +0100702 * \param[in] data Opaque event data to be passed with the parent term event
703 * \param[in] file Calling source file (from osmo_fsm_inst_term macro)
704 * \param[in] line Calling source line (from osmo_fsm_inst_term macro)
Harald Welte136e7372016-05-29 10:53:17 +0900705 */
Neels Hofmeyr725698a2016-12-14 17:24:54 +0100706void _osmo_fsm_inst_term(struct osmo_fsm_inst *fi,
707 enum osmo_fsm_term_cause cause, void *data,
708 const char *file, int line)
Harald Welte136e7372016-05-29 10:53:17 +0900709{
Neels Hofmeyr3faa0142016-12-18 23:41:41 +0100710 struct osmo_fsm_inst *parent;
Harald Welte136e7372016-05-29 10:53:17 +0900711 uint32_t parent_term_event = fi->proc.parent_term_event;
712
Neels Hofmeyr5c5c78a2016-12-14 18:35:47 +0100713 LOGPFSMSRC(fi, file, line, "Terminating (cause = %s)\n",
714 osmo_fsm_term_cause_name(cause));
Harald Welte136e7372016-05-29 10:53:17 +0900715
Philipp Maierd1f57932018-02-14 18:20:07 +0100716 /* graceful exit (optional) */
717 if (fi->fsm->pre_term)
718 fi->fsm->pre_term(fi, cause);
719
Harald Welte65900442018-02-09 09:58:57 +0000720 _osmo_fsm_inst_term_children(fi, OSMO_FSM_TERM_PARENT, NULL,
721 file, line);
722
Neels Hofmeyrc014f602016-12-23 04:26:39 +0100723 /* delete ourselves from the parent */
Neels Hofmeyr3faa0142016-12-18 23:41:41 +0100724 parent = fi->proc.parent;
Philipp Maier23d31612018-01-16 18:50:23 +0100725 if (parent) {
Neels Hofmeyrc014f602016-12-23 04:26:39 +0100726 LOGPFSMSRC(fi, file, line, "Removing from parent %s\n",
727 osmo_fsm_inst_name(parent));
Philipp Maier23d31612018-01-16 18:50:23 +0100728 llist_del(&fi->proc.child);
729 }
Neels Hofmeyrc014f602016-12-23 04:26:39 +0100730
731 /* call destructor / clean-up function */
732 if (fi->fsm->cleanup)
733 fi->fsm->cleanup(fi, cause);
734
735 LOGPFSMSRC(fi, file, line, "Freeing instance\n");
Neels Hofmeyr3faa0142016-12-18 23:41:41 +0100736 /* Fetch parent again in case it has changed. */
737 parent = fi->proc.parent;
Neels Hofmeyrc014f602016-12-23 04:26:39 +0100738 osmo_fsm_inst_free(fi);
739
740 /* indicate our termination to the parent */
741 if (parent && cause != OSMO_FSM_TERM_PARENT)
742 _osmo_fsm_inst_dispatch(parent, parent_term_event, data,
743 file, line);
744}
745
Neels Hofmeyr87e45502017-06-20 00:17:59 +0200746/*! Terminate all child FSM instances of an FSM instance.
Neels Hofmeyrc014f602016-12-23 04:26:39 +0100747 *
748 * Iterate over all children and send them a termination event, with the given
749 * cause. Pass OSMO_FSM_TERM_PARENT to avoid dispatching events from the
750 * terminated child FSMs.
751 *
752 * \param[in] fi FSM instance that should be cleared of child FSMs
753 * \param[in] cause Cause / reason for termination (OSMO_FSM_TERM_PARENT)
754 * \param[in] data Opaque event data to be passed with the parent term events
755 * \param[in] file Calling source file (from osmo_fsm_inst_term_children macro)
756 * \param[in] line Calling source line (from osmo_fsm_inst_term_children macro)
757 */
758void _osmo_fsm_inst_term_children(struct osmo_fsm_inst *fi,
759 enum osmo_fsm_term_cause cause,
760 void *data,
761 const char *file, int line)
762{
763 struct osmo_fsm_inst *first_child, *last_seen_first_child;
764
Neels Hofmeyr06ac9b42016-12-20 12:05:19 +0100765 /* iterate over all children, starting from the beginning every time:
766 * terminating an FSM may emit events that cause other FSMs to also
767 * terminate and remove themselves from this list. */
768 last_seen_first_child = NULL;
769 while (!llist_empty(&fi->proc.children)) {
770 first_child = llist_entry(fi->proc.children.next,
771 typeof(*first_child),
772 proc.child);
773
774 /* paranoia: do not loop forever */
775 if (first_child == last_seen_first_child) {
776 LOGPFSMLSRC(fi, LOGL_ERROR, file, line,
777 "Internal error while terminating child"
778 " FSMs: a child FSM is stuck\n");
779 break;
780 }
781 last_seen_first_child = first_child;
782
Harald Welte136e7372016-05-29 10:53:17 +0900783 /* terminate child */
Neels Hofmeyrc014f602016-12-23 04:26:39 +0100784 _osmo_fsm_inst_term(first_child, cause, data,
Neels Hofmeyr725698a2016-12-14 17:24:54 +0100785 file, line);
Harald Welte136e7372016-05-29 10:53:17 +0900786 }
Harald Welte136e7372016-05-29 10:53:17 +0900787}
788
Neels Hofmeyr5c5c78a2016-12-14 18:35:47 +0100789const struct value_string osmo_fsm_term_cause_names[] = {
Neels Hofmeyr18080962016-12-16 13:43:54 +0100790 OSMO_VALUE_STRING(OSMO_FSM_TERM_PARENT),
791 OSMO_VALUE_STRING(OSMO_FSM_TERM_REQUEST),
792 OSMO_VALUE_STRING(OSMO_FSM_TERM_REGULAR),
793 OSMO_VALUE_STRING(OSMO_FSM_TERM_ERROR),
794 OSMO_VALUE_STRING(OSMO_FSM_TERM_TIMEOUT),
Neels Hofmeyr5c5c78a2016-12-14 18:35:47 +0100795 { 0, NULL }
796};
797
Harald Welte136e7372016-05-29 10:53:17 +0900798/*! @} */