aboutsummaryrefslogtreecommitdiffstats
path: root/src/osmo-bts-trx/trx_provision_fsm.c
diff options
context:
space:
mode:
authorPau Espin Pedrol <pespin@sysmocom.de>2020-07-06 19:56:08 +0200
committerPau Espin Pedrol <pespin@sysmocom.de>2020-07-14 11:44:30 +0200
commit1272af452932e00243d6fea5e073228e59a46ed7 (patch)
tree9c587315613ee084421d2e077e75e56545bebfd1 /src/osmo-bts-trx/trx_provision_fsm.c
parent414d649e01cbe45e81750c7a6fa91cf0de3c18a3 (diff)
bts-trx: introduce TRX provisioning FSM
With prior code state managing the TRXC side of osmo-bts-trx, there are plenty o cases (race conditions) where things can go wrong/unexpected, because there's really no infrastructure to wait and synchronize between different TRXs (eg wait until all are configured to POWERON), or to simply keep well known per-trx state regarding lower layers. In order to fix in the future all of those issues and to sanitize current code, a new per-trx FSM is introduced, which takes care of submitting TRXC commands and waiting for response when needed to manage the state of the TRX. Related: OS#4364 Change-Id: I2a00c23df15840e33fbb232c9e1dd6db128f63f6
Diffstat (limited to 'src/osmo-bts-trx/trx_provision_fsm.c')
-rw-r--r--src/osmo-bts-trx/trx_provision_fsm.c459
1 files changed, 459 insertions, 0 deletions
diff --git a/src/osmo-bts-trx/trx_provision_fsm.c b/src/osmo-bts-trx/trx_provision_fsm.c
new file mode 100644
index 00000000..c3b266d8
--- /dev/null
+++ b/src/osmo-bts-trx/trx_provision_fsm.c
@@ -0,0 +1,459 @@
+/* BTS shutdown FSM */
+
+/* (C) 2020 by sysmocom - s.m.f.c. GmbH <info@sysmocom.de>
+ * Author: Pau Espin Pedrol <pespin@sysmocom.de>
+ *
+ * All Rights Reserved
+ *
+ * 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 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/>.
+ *
+ */
+
+#include <errno.h>
+#include <unistd.h>
+#include <inttypes.h>
+
+#include <osmocom/core/fsm.h>
+#include <osmocom/core/tdef.h>
+#include <osmocom/gsm/protocol/gsm_12_21.h>
+
+#include <osmo-bts/logging.h>
+#include <osmo-bts/gsm_data.h>
+#include <osmo-bts/bts_model.h>
+#include <osmo-bts/bts.h>
+#include <osmo-bts/rsl.h>
+
+#include "l1_if.h"
+#include "trx_provision_fsm.h"
+
+#define X(s) (1 << (s))
+
+#define trx_prov_fsm_state_chg(fi, NEXT_STATE) \
+ osmo_fsm_inst_state_chg(fi, NEXT_STATE, 0, 0)
+
+static void l1if_poweronoff_cb(struct trx_l1h *l1h, bool poweronoff, int rc)
+{
+ struct phy_instance *pinst = l1h->phy_inst;
+ struct phy_link *plink = pinst->phy_link;
+
+ plink->u.osmotrx.powered = poweronoff;
+ plink->u.osmotrx.poweronoff_sent = false;
+
+ if (poweronoff)
+ osmo_fsm_inst_dispatch(l1h->provision_fi, TRX_PROV_EV_POWERON_CNF, (void*)(intptr_t)rc);
+ else
+ osmo_fsm_inst_dispatch(l1h->provision_fi, TRX_PROV_EV_POWEROFF_CNF, (void*)(intptr_t)rc);
+}
+
+static void l1if_getnompower_cb(struct trx_l1h *l1h, int nominal_power, int rc)
+{
+ struct phy_instance *pinst = l1h->phy_inst;
+ struct gsm_bts_trx *trx = pinst->trx;
+
+ LOGPPHI(pinst, DL1C, LOGL_DEBUG, "l1if_getnompower_cb(nominal_power=%d, rc=%d)\n", nominal_power, rc);
+
+ l1if_trx_set_nominal_power(trx, nominal_power);
+}
+
+/*
+ * transceiver provisioning
+ */
+int l1if_provision_transceiver_trx(struct trx_l1h *l1h)
+{
+ struct phy_instance *pinst = l1h->phy_inst;
+ struct phy_link *plink = pinst->phy_link;
+
+ /* During setup, pinst may still not be associated to a TRX nr */
+ if (!pinst->trx) {
+ LOGPPHI(pinst, DL1C, LOGL_INFO,
+ "Delaying provision, TRX not yet assigned to phy instance\n");
+ return -EIO;
+ }
+
+ if (phy_link_state_get(plink) == PHY_LINK_SHUTDOWN) {
+ LOGPPHI(pinst, DL1C, LOGL_INFO,
+ "Delaying provision, TRX not yet available\n");
+ return -EIO;
+ }
+
+ if (l1h->config.enabled
+ && l1h->config.tsc_valid
+ && l1h->config.bsic_valid
+ && l1h->config.arfcn_valid) {
+ /* before power on */
+ if (!l1h->config.arfcn_sent) {
+ trx_if_cmd_rxtune(l1h, l1h->config.arfcn);
+ trx_if_cmd_txtune(l1h, l1h->config.arfcn);
+ /* After TXTUNE is sent to TRX, get the tx nominal power
+ * (which may vary precisly on band/arfcn. Avoid sending
+ * it if we are forced by VTY to use a specific nominal
+ * power (because TRX may not support the command or
+ * provide broken values) */
+ if (!l1h->config.nominal_power_set_by_vty)
+ trx_if_cmd_getnompower(l1h, l1if_getnompower_cb);
+ l1h->config.arfcn_sent = 1;
+ }
+ if (!l1h->config.tsc_sent) {
+ trx_if_cmd_settsc(l1h, l1h->config.tsc);
+ l1h->config.tsc_sent = 1;
+ }
+ if (!l1h->config.bsic_sent) {
+ trx_if_cmd_setbsic(l1h, l1h->config.bsic);
+ l1h->config.bsic_sent = 1;
+ }
+
+ /* Ask transceiver to use the newest TRXD header version if not using it yet */
+ if (!l1h->config.setformat_sent) {
+ if (l1h->config.trxd_hdr_ver_use != plink->u.osmotrx.trxd_hdr_ver_max) {
+ trx_if_cmd_setformat(l1h, plink->u.osmotrx.trxd_hdr_ver_max);
+ l1h->config.trxd_hdr_ver_req = plink->u.osmotrx.trxd_hdr_ver_max;
+ } else {
+ LOGPPHI(pinst, DL1C, LOGL_INFO,
+ "No need to negotiate TRXD version, "
+ "already using maximum configured one: %" PRIu8 "\n",
+ l1h->config.trxd_hdr_ver_use);
+ }
+ l1h->config.setformat_sent = 1;
+ }
+
+ if (pinst->num == 0 && !plink->u.osmotrx.powered && !plink->u.osmotrx.poweronoff_sent) {
+ trx_if_cmd_poweron(l1h, l1if_poweronoff_cb);
+ plink->u.osmotrx.poweronoff_sent = true;
+ }
+
+ return 0;
+ }
+ LOGPPHI(pinst, DL1C, LOGL_INFO, "Delaying provision, TRX attributes not yet received from BSC:%s%s%s%s\n",
+ l1h->config.enabled ? "" :" enable",
+ l1h->config.tsc_valid ? "" : " tsc",
+ l1h->config.bsic_valid ? "" : " bsic",
+ l1h->config.arfcn_valid ? "" : " arfcn");
+ return 1;
+}
+
+static void l1if_setslot_cb(struct trx_l1h *l1h, uint8_t tn, uint8_t type, int rc)
+{
+ struct phy_instance *pinst = l1h->phy_inst;
+ struct gsm_bts_trx *trx = pinst->trx;
+ struct gsm_bts_trx_ts *ts;
+ enum gsm_phys_chan_config pchan;
+
+ if (tn >= TRX_NR_TS) {
+ LOGPPHI(pinst, DL1C, LOGL_ERROR, "transceiver SETSLOT invalid param TN (%" PRIu8 ")\n",
+ tn);
+ return;
+ }
+
+ pchan = transceiver_chan_type_2_pchan(type);
+ if (pchan == GSM_PCHAN_UNKNOWN) {
+ LOGPPHI(pinst, DL1C, LOGL_ERROR, "transceiver SETSLOT invalid param TS_TYPE (%" PRIu8 ")\n",
+ type);
+ return;
+ }
+
+ ts = &trx->ts[tn];
+ LOGPPHI(pinst, DL1C, LOGL_DEBUG, "%s l1if_setslot_cb(as_pchan=%s),"
+ " calling cb_ts_connected(rc=%d)\n",
+ gsm_ts_name(ts), gsm_pchan_name(pchan), rc);
+ cb_ts_connected(ts, rc);
+}
+
+/* Returns true if any TS changed, false otherwise */
+static bool update_ts_data(struct trx_l1h *l1h, struct trx_prov_ev_cfg_ts_data* ts_data) {
+
+ if (l1h->config.slottype[ts_data->tn] != ts_data->slottype ||
+ !l1h->config.slottype_valid[ts_data->tn]) {
+ l1h->config.slottype[ts_data->tn] = ts_data->slottype;
+ l1h->config.slottype_valid[ts_data->tn] = 1;
+ l1h->config.slottype_sent[ts_data->tn] = 0;
+ return true;
+ }
+ return false;
+}
+
+
+//////////////////////////
+// FSM STATE ACTIONS
+//////////////////////////
+
+static void st_closed(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct trx_l1h *l1h = (struct trx_l1h *)fi->priv;
+
+ switch(event) {
+ case TRX_PROV_EV_OPEN:
+ /* enable all slots */
+ l1h->config.slotmask = 0xff;
+ if (l1h->phy_inst->num == 0)
+ trx_if_cmd_poweroff(l1h, NULL); /* TODO: jump to poweroff upon cb received */
+ trx_prov_fsm_state_chg(fi, TRX_PROV_ST_OPEN_POWEROFF);
+ break;
+ }
+}
+
+static void st_open_poweroff(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct trx_l1h *l1h = (struct trx_l1h *)fi->priv;
+ uint8_t bsic;
+ uint16_t arfcn;
+ uint16_t tsc;
+
+ switch(event) {
+ case TRX_PROV_EV_CFG_ENABLE:
+ l1h->config.enabled =(bool)data;
+ break;
+ case TRX_PROV_EV_CFG_BSIC:
+ bsic = (uint8_t)(intptr_t)data;
+ if (l1h->config.bsic != bsic || !l1h->config.bsic_valid) {
+ l1h->config.bsic = bsic;
+ l1h->config.bsic_valid = 1;
+ l1h->config.bsic_sent = 0;
+ }
+ break;
+ case TRX_PROV_EV_CFG_ARFCN:
+ arfcn = (uint16_t)(intptr_t)data;
+ if (l1h->config.arfcn != arfcn || !l1h->config.arfcn_valid) {
+ l1h->config.arfcn = arfcn;
+ l1h->config.arfcn_valid = 1;
+ l1h->config.arfcn_sent = 0;
+ }
+ break;
+ case TRX_PROV_EV_CFG_TSC:
+ tsc = (uint16_t)(intptr_t)data;
+ if (l1h->config.tsc != tsc || !l1h->config.tsc_valid) {
+ l1h->config.tsc = tsc;
+ l1h->config.tsc_valid = 1;
+ l1h->config.tsc_sent = 0;
+ }
+ break;
+ case TRX_PROV_EV_CFG_TS:
+ update_ts_data(l1h, (struct trx_prov_ev_cfg_ts_data*)data);
+ break;
+ }
+
+ /* 0 = if we gathered all date and could go forward :*/
+ if (l1if_provision_transceiver_trx(l1h) == 0) {
+ if (l1h->phy_inst->num == 0)
+ trx_prov_fsm_state_chg(fi, TRX_PROV_ST_OPEN_WAIT_POWERON_CNF);
+ else
+ trx_prov_fsm_state_chg(fi, TRX_PROV_ST_OPEN_POWERON);
+ }
+}
+
+static void st_open_wait_power_cnf(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct trx_l1h *l1h = (struct trx_l1h *)fi->priv;
+ struct phy_instance *pinst = l1h->phy_inst;
+ struct phy_link *plink = pinst->phy_link;
+ int rc;
+
+ switch(event) {
+ case TRX_PROV_EV_POWERON_CNF:
+ rc = (uint16_t)(intptr_t)data;
+ if (rc == 0 && plink->state != PHY_LINK_CONNECTED) {
+ trx_sched_clock_started(pinst->trx->bts);
+ phy_link_state_set(plink, PHY_LINK_CONNECTED);
+
+ /* Begin to ramp up the power on all TRX associated with this phy */
+ llist_for_each_entry(pinst, &plink->instances, list) {
+ if (pinst->trx->mo.nm_state.administrative == NM_STATE_UNLOCKED)
+ l1if_trx_start_power_ramp(pinst->trx, NULL);
+ }
+
+ trx_prov_fsm_state_chg(fi, TRX_PROV_ST_OPEN_POWERON);
+ } else if (rc != 0 && plink->state != PHY_LINK_SHUTDOWN) {
+ trx_sched_clock_stopped(pinst->trx->bts);
+ phy_link_state_set(plink, PHY_LINK_SHUTDOWN);
+ }
+ break;
+ case TRX_PROV_EV_CFG_TS:
+ update_ts_data(l1h, (struct trx_prov_ev_cfg_ts_data*)data);
+ break;
+ }
+}
+
+static void st_open_poweron_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct trx_l1h *l1h = (struct trx_l1h *)fi->priv;
+ uint8_t tn;
+
+ /* after power on */
+ if (l1h->config.rxgain_valid && !l1h->config.rxgain_sent) {
+ trx_if_cmd_setrxgain(l1h, l1h->config.rxgain);
+ l1h->config.rxgain_sent = 1;
+ }
+ if (l1h->config.maxdly_valid && !l1h->config.maxdly_sent) {
+ trx_if_cmd_setmaxdly(l1h, l1h->config.maxdly);
+ l1h->config.maxdly_sent = 1;
+ }
+ if (l1h->config.maxdlynb_valid && !l1h->config.maxdlynb_sent) {
+ trx_if_cmd_setmaxdlynb(l1h, l1h->config.maxdlynb);
+ l1h->config.maxdlynb_sent = 1;
+ }
+
+ for (tn = 0; tn < TRX_NR_TS; tn++) {
+ if (l1h->config.slottype_valid[tn]
+ && !l1h->config.slottype_sent[tn]) {
+ trx_if_cmd_setslot(l1h, tn,
+ l1h->config.slottype[tn], l1if_setslot_cb);
+ l1h->config.slottype_sent[tn] = 1;
+ }
+ }
+}
+
+static void st_open_poweron(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct trx_l1h *l1h = (struct trx_l1h *)fi->priv;
+ struct phy_instance *pinst = l1h->phy_inst;
+ struct phy_link *plink = pinst->phy_link;
+ struct trx_prov_ev_cfg_ts_data* ts_data;
+ uint8_t tn;
+
+ switch(event) {
+ case TRX_PROV_EV_CLOSE:
+ /* power off transceiver, if not already */
+ if (l1h->config.enabled) {
+ if (pinst->num == 0 && plink->u.osmotrx.powered && !plink->u.osmotrx.poweronoff_sent) {
+ trx_if_cmd_poweroff(l1h, l1if_poweronoff_cb);
+ plink->u.osmotrx.poweronoff_sent = true;
+ }
+ l1h->config.rxgain_sent = 0;
+ l1h->config.maxdly_sent = 0;
+ l1h->config.maxdlynb_sent = 0;
+ for (tn = 0; tn < TRX_NR_TS; tn++)
+ l1h->config.slottype_sent[tn] = 0;
+ } else if (!pinst->phy_link->u.osmotrx.poweronoff_sent) {
+ bts_model_trx_close_cb(pinst->trx, 0);
+ } /* else: poweroff in progress, cb will be called upon TRXC RSP */
+
+ if (pinst->num == 0)
+ trx_prov_fsm_state_chg(fi, TRX_PROV_ST_OPEN_WAIT_POWEROFF_CNF);
+ else
+ trx_prov_fsm_state_chg(fi, TRX_PROV_ST_OPEN_POWEROFF);
+ break;
+ case TRX_PROV_EV_CFG_TS:
+ ts_data = (struct trx_prov_ev_cfg_ts_data*)data;
+ if (update_ts_data(l1h, ts_data)) {
+ trx_if_cmd_setslot(l1h, ts_data->tn,
+ l1h->config.slottype[ ts_data->tn], l1if_setslot_cb);
+ l1h->config.slottype_sent[ ts_data->tn] = 1;
+ }
+
+ break;
+ }
+}
+
+static void st_open_wait_poweroff_cnf(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct trx_l1h *l1h = (struct trx_l1h *)fi->priv;
+ struct phy_instance *pinst = l1h->phy_inst;
+ struct phy_link *plink = pinst->phy_link;
+ int rc;
+
+ switch(event) {
+ case TRX_PROV_EV_POWEROFF_CNF:
+ rc = (uint16_t)(intptr_t)data;
+ if (plink->state != PHY_LINK_SHUTDOWN) {
+ trx_sched_clock_stopped(pinst->trx->bts);
+ phy_link_state_set(plink, PHY_LINK_SHUTDOWN);
+
+ /* Notify TRX close on all TRX associated with this phy */
+ llist_for_each_entry(pinst, &plink->instances, list) {
+ bts_model_trx_close_cb(pinst->trx, rc);
+ }
+ }
+ break;
+ }
+}
+
+static struct osmo_fsm_state trx_prov_fsm_states[] = {
+ [TRX_PROV_ST_CLOSED] = {
+ .in_event_mask =
+ X(TRX_PROV_EV_OPEN),
+ .out_state_mask =
+ X(TRX_PROV_ST_OPEN_POWEROFF),
+ .name = "CLOSED",
+ .action = st_closed,
+ },
+ [TRX_PROV_ST_OPEN_POWEROFF] = {
+ .in_event_mask =
+ X(TRX_PROV_EV_CFG_ENABLE) |
+ X(TRX_PROV_EV_CFG_BSIC) |
+ X(TRX_PROV_EV_CFG_ARFCN) |
+ X(TRX_PROV_EV_CFG_TSC) |
+ X(TRX_PROV_EV_CFG_TS),
+ .out_state_mask =
+ X(TRX_PROV_ST_OPEN_WAIT_POWERON_CNF) |
+ X(TRX_PROV_ST_OPEN_POWERON),
+ .name = "OPEN_POWEROFF",
+ .action = st_open_poweroff,
+ },
+ [TRX_PROV_ST_OPEN_WAIT_POWERON_CNF] = {
+ .in_event_mask =
+ X(TRX_PROV_EV_POWERON_CNF) |
+ X(TRX_PROV_EV_CFG_TS),
+ .out_state_mask =
+ X(TRX_PROV_ST_OPEN_POWERON),
+ .name = "OPEN_WAIT_POWERON_CNF",
+ .action = st_open_wait_power_cnf,
+ },
+ [TRX_PROV_ST_OPEN_POWERON] = {
+ .in_event_mask =
+ X(TRX_PROV_EV_POWEROFF) |
+ X(TRX_PROV_EV_CFG_TS),
+ .out_state_mask =
+ X(TRX_PROV_ST_OPEN_WAIT_POWEROFF_CNF) |
+ X(TRX_PROV_ST_OPEN_POWEROFF),
+ .name = "OPEN_POWERON",
+ .onenter = st_open_poweron_on_enter,
+ .action = st_open_poweron,
+ },
+ [TRX_PROV_ST_OPEN_WAIT_POWEROFF_CNF] = {
+ .in_event_mask =
+ X(TRX_PROV_EV_POWEROFF_CNF),
+ .out_state_mask =
+ X(TRX_PROV_ST_OPEN_POWEROFF),
+ .name = "OPEN_WAIT_POWEROFF_CNF",
+ .action = st_open_wait_poweroff_cnf,
+ },
+};
+
+const struct value_string trx_prov_fsm_event_names[] = {
+ OSMO_VALUE_STRING(TRX_PROV_EV_OPEN),
+ OSMO_VALUE_STRING(TRX_PROV_EV_CFG_ENABLE),
+ OSMO_VALUE_STRING(TRX_PROV_EV_CFG_BSIC),
+ OSMO_VALUE_STRING(TRX_PROV_EV_CFG_ARFCN),
+ OSMO_VALUE_STRING(TRX_PROV_EV_CFG_TSC),
+ OSMO_VALUE_STRING(TRX_PROV_EV_CFG_TS),
+ OSMO_VALUE_STRING(TRX_PROV_EV_CFG_RXGAIN),
+ OSMO_VALUE_STRING(TRX_PROV_EV_CFG_SETMAXDLY),
+ OSMO_VALUE_STRING(TRX_PROV_EV_POWERON_CNF),
+ OSMO_VALUE_STRING(TRX_PROV_EV_POWEROFF),
+ OSMO_VALUE_STRING(TRX_PROV_EV_POWEROFF_CNF),
+ OSMO_VALUE_STRING(TRX_PROV_EV_CLOSE),
+ { 0, NULL }
+};
+
+struct osmo_fsm trx_prov_fsm = {
+ .name = "TRX_PROV",
+ .states = trx_prov_fsm_states,
+ .num_states = ARRAY_SIZE(trx_prov_fsm_states),
+ .event_names = trx_prov_fsm_event_names,
+ .log_subsys = DL1C,
+};
+
+static __attribute__((constructor)) void trx_prov_fsm_init(void)
+{
+ OSMO_ASSERT(osmo_fsm_register(&trx_prov_fsm) == 0);
+}