summaryrefslogtreecommitdiffstats
path: root/include/osmocom/core
diff options
context:
space:
mode:
authorNeels Hofmeyr <neels@hofmeyr.de>2019-01-26 20:36:12 +0100
committerNeels Hofmeyr <neels@hofmeyr.de>2019-02-04 18:52:16 +0100
commit0fd615fd7b701fc0b3fd7f96429691e3eaa5a877 (patch)
tree7c2885199ce93394c308e3399b4d76d843416505 /include/osmocom/core
parent44c15b7a8fadff949e79be0b843ec9caca61901c (diff)
add osmo_tdef API, originally adopted from osmo-bsc T_def
Move T_def from osmo-bsc to libosmocore as osmo_tdef. Adjust naming to be more consistent. Upgrade to first class API: - add timer grouping - add generic vty support - add mising API doc - add C test - add VTY transcript tests, also as examples for using the API From osmo_fsm_inst_state_chg() API doc, cross reference to osmo_tdef API. The root reason for moving to libosmocore is that I want to use the mgw_endpoint_fsm in osmo-msc for inter-MSC handover, and hence want to move the FSM to libosmo-mgcp-client. This FSM uses the T_def from osmo-bsc. Though the mgw_endpoint_fsm's use of T_def is minimal, I intend to use the osmo_tdef API in osmo-msc (and probably elsewhere) as well. libosmocore is the most sensible place for this. osmo_tdef provides: - a list of Tnnnn (GSM) timers with description, unit and default value. - vty UI to allow users to configure non-default timeouts. - API to tie T timers to osmo_fsm states and set them on state transitions. - a few standard units (minute, second, millisecond) as well as a custom unit (which relies on the timer's human readable description to indicate the meaning of the value). - conversion for standard units: for example, some GSM timers are defined in minutes, while our FSM definitions need timeouts in seconds. Conversion is for convenience only and can be easily avoided via the custom unit. By keeping separate osmo_tdef arrays, several groups of timers can be kept separately. The VTY tests in tests/tdef/ showcase different schemes: - tests/vty/tdef_vty_test_config_root.c: Keep several timer definitions in separately named groups: showcase the osmo_tdef_vty_groups*() API. Each timer group exists exactly once. - tests/vty/tdef_vty_test_config_subnode.c: Keep a single list of timers without separate grouping. Put this list on a specific subnode below the CONFIG_NODE. There could be several separate subnodes with timers like this, i.e. continuing from this example, sets timers could be separated by placing timers in specific config subnodes instead of using the global group name. - tests/vty/tdef_vty_test_dynamic.c: Dynamically allocate timer definitions per each new created object. Thus there can be an arbitrary number of independent timer definitions, one per allocated object. T_def was introduced during the recent osmo-bsc refactoring for inter-BSC handover, and has proven useful: - without osmo_tdef, each invocation of osmo_fsm_inst_state_chg() needs to be programmed with the right timeout value, for all code paths that invoke this state change. It is a likely source of errors to get one of them wrong. By defining a T timer exactly for an FSM state, the caller can merely invoke the state change and trust on the original state definition to apply the correct timeout. - it is helpful to have a standardized config file UI to provide user configurable timeouts, instead of inventing new VTY commands for each separate application of T timer numbers. Change-Id: Ibd6b1ed7f1bd6e1f2e0fde53352055a4468f23e5
Diffstat (limited to 'include/osmocom/core')
-rw-r--r--include/osmocom/core/tdef.h172
1 files changed, 172 insertions, 0 deletions
diff --git a/include/osmocom/core/tdef.h b/include/osmocom/core/tdef.h
new file mode 100644
index 00000000..92b71597
--- /dev/null
+++ b/include/osmocom/core/tdef.h
@@ -0,0 +1,172 @@
+/*! \file tdef.h
+ * API to define Tnnn timers globally and use for FSM state changes.
+ */
+/*
+ * (C) 2018-2019 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ *
+ * All Rights Reserved
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ *
+ * Author: Neels Hofmeyr <neels@hofmeyr.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+#pragma once
+
+#include <stdint.h>
+#include <osmocom/core/utils.h>
+
+struct osmo_fsm_inst;
+
+/*! \defgroup Tdef Tnnn timer configuration
+ * @{
+ * \file tdef.h
+ */
+
+enum osmo_tdef_unit {
+ OSMO_TDEF_S = 0, /*!< most T are in seconds, keep 0 as default. */
+ OSMO_TDEF_MS, /*!< milliseconds */
+ OSMO_TDEF_M, /*!< minutes */
+ OSMO_TDEF_CUSTOM, /*!< unspecified unit, explained in osmo_tdef.desc. */
+};
+
+extern const struct value_string osmo_tdef_unit_names[];
+/*! \return enum osmo_tdef_unit value as human readable unit letter, or "custom-unit". */
+static inline const char *osmo_tdef_unit_name(enum osmo_tdef_unit val)
+{ return get_value_string(osmo_tdef_unit_names, val); }
+
+/*! Define a GSM timer of the form Tnnn, with unit, default value and doc string.
+ * Typically used as an array with the last entry being left zero-initialized, e.g.:
+ *
+ * struct osmo_tdef tdefs[] = {
+ * { .T=10, .default_val=6, .desc="RR Assignment" },
+ * { .T=101, .default_val=10, .desc="inter-BSC Handover MT, HO Request to HO Accept" },
+ * { .T=3101, .default_val=3, .desc="RR Immediate Assignment" },
+ * {}
+ * };
+ *
+ * Program initialization should call osmo_tdefs_reset() so that all timers return the default_val, until e.g. the VTY
+ * configuration sets user-defined values (see osmo_tdef_vty_init()).
+ */
+struct osmo_tdef {
+ /*! T1234 number; type corresponds to struct osmo_fsm_inst.T. Negative and zero T numbers are actually possible,
+ * but be aware that osmo_tdef_fsm_inst_state_chg() interprets T == 0 as "no timer". */
+ const int T;
+ /*! Timeout duration (according to unit), default value; type corresponds to osmo_fsm_inst_state_chg()'s
+ * timeout_secs argument. Note that osmo_fsm_inst_state_chg() clamps the range. */
+ const unsigned long default_val;
+ const enum osmo_tdef_unit unit;
+ /*! Human readable description. For unit == OSMO_TDEF_CUSTOM, this should include an explanation of the value's
+ * unit. Best keep this a short one-liner (e.g. for VTY output). */
+ const char *desc;
+ /*! Currently active timeout value, e.g. set by user config. This is the only mutable member: a user may
+ * configure the timeout value, but neither unit nor any other field. */
+ unsigned long val;
+};
+
+/*! Iterate an array of struct osmo_tdef, the last item should be fully zero, i.e. "{}".
+ * Example:
+ *
+ * struct osmo_tdef *t;
+ * osmo_tdef_for_each(t, tdefs) {
+ * printf("%lu %s %s\n", t->val, osmo_tdef_unit_name(t->unit), t->desc);
+ * }
+ *
+ * \param[inout] t A struct osmo_tdef *t used for iteration, will point at the current entry inside the loop scope.
+ * \param[in] tdefs Array of struct osmo_tdef to iterate, zero-terminated.
+ */
+#define osmo_tdef_for_each(t, tdefs) \
+ for (t = tdefs; t && (t->T || t->default_val || t->desc); t++)
+
+void osmo_tdefs_reset(struct osmo_tdef *tdefs);
+unsigned long osmo_tdef_get(const struct osmo_tdef *tdefs, int T, enum osmo_tdef_unit as_unit,
+ unsigned long val_if_not_present);
+struct osmo_tdef *osmo_tdef_get_entry(struct osmo_tdef *tdefs, int T);
+
+/*! Using osmo_tdef for osmo_fsm_inst: array entry for a mapping of state numbers to timeout definitions.
+ * For a usage example, see osmo_tdef_get_state_timeout() and test_tdef_state_timeout() in tdef_test.c. */
+struct osmo_tdef_state_timeout {
+ /*! Timer number to match struct osmo_tdef.T, and to pass to osmo_fsm_inst_state_chg(). */
+ int T;
+ /*! If true, call osmo_fsm_inst_state_chg_keep_timer().
+ * If T == 0, keep previous T number, otherwise also set fi->T. */
+ bool keep_timer;
+};
+
+const struct osmo_tdef_state_timeout *osmo_tdef_get_state_timeout(uint32_t state,
+ const struct osmo_tdef_state_timeout *timeouts_array);
+
+/*! Call osmo_fsm_inst_state_chg() or osmo_fsm_inst_state_chg_keep_timer(), depending on the timeouts_array, tdefs and
+ * default_timeout.
+ *
+ * A T timer configured in sub-second precision is rounded up to the next full second. A timer in unit =
+ * OSMO_TDEF_CUSTOM is applied as if the unit is in seconds (i.e. this macro does not make sense for custom units!).
+ *
+ * See osmo_tdef_get_state_timeout() and osmo_tdef_get().
+ *
+ * If no T timer is defined for the given state (T == 0), invoke the state change without a timeout.
+ *
+ * Should a T number be defined in timeouts_array that is not defined in tdefs, use default_timeout (in seconds). If
+ * default_timeout is negative, a missing T definition in tdefs instead causes a program abort.
+ *
+ * This is best used by wrapping this function call in a macro suitable for a specific FSM implementation, which can
+ * become as short as: my_fsm_state_chg(fi, NEXT_STATE):
+ *
+ * #define my_fsm_state_chg(fi, NEXT_STATE) \
+ * osmo_tdef_fsm_inst_state_chg(fi, NEXT_STATE, my_fsm_timeouts, global_T_defs, 5)
+ *
+ * my_fsm_state_chg(fi, MY_FSM_STATE_1);
+ * // -> No timeout configured, will enter state without timeout.
+ *
+ * my_fsm_state_chg(fi, MY_FSM_STATE_3);
+ * // T423 configured for this state, will look up T423 in tdefs, or use 5 seconds if unset.
+ *
+ * my_fsm_state_chg(fi, MY_FSM_STATE_8);
+ * // keep_timer == true for this state, will invoke osmo_fsm_inst_state_chg_keep_timer().
+ *
+ * \param[inout] fi osmo_fsm_inst to transition to another state.
+ * \param[in] state State number to transition to.
+ * \param[in] timeouts_array Array of struct osmo_tdef_state_timeout[32] to look up state in.
+ * \param[in] tdefs Array of struct osmo_tdef (last entry zero initialized) to look up T in.
+ * \param[in] default_timeout If a T is set in timeouts_array, but no timeout value is configured for T, then use this
+ * default timeout value as fallback, or pass -1 to abort the program.
+ * \return Return value from osmo_fsm_inst_state_chg() or osmo_fsm_inst_state_chg_keep_timer().
+ */
+#define osmo_tdef_fsm_inst_state_chg(fi, state, timeouts_array, tdefs, default_timeout) \
+ _osmo_tdef_fsm_inst_state_chg(fi, state, timeouts_array, tdefs, default_timeout, \
+ __FILE__, __LINE__)
+int _osmo_tdef_fsm_inst_state_chg(struct osmo_fsm_inst *fi, uint32_t state,
+ const struct osmo_tdef_state_timeout *timeouts_array,
+ const struct osmo_tdef *tdefs, unsigned long default_timeout,
+ const char *file, int line);
+
+/*! Manage timer definitions in named groups.
+ * This should be defined as an array with the final element kept fully zero-initialized,
+ * to be compatible with osmo_tdef_vty* API. There must not be any tdefs == NULL entries except on the final
+ * zero-initialized entry. */
+struct osmo_tdef_group {
+ const char *name;
+ const char *desc;
+ struct osmo_tdef *tdefs;
+};
+
+/*! Iterate an array of struct osmo_tdef_group, the last item should be fully zero, i.e. "{}".
+ * \param[inout] g A struct osmo_tdef_group *g used for iteration, will point at the current entry inside the loop scope.
+ * \param[in] tdefs Array of struct osmo_tdef_group to iterate, zero-terminated.
+ */
+#define osmo_tdef_groups_for_each(g, tdef_groups) \
+ for (g = tdef_groups; g && g->tdefs; g++)
+
+/*! @} */