/* TRX provision FSM */ /* (C) 2020 by sysmocom - s.m.f.c. GmbH * Author: Pau Espin Pedrol * * 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 . * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #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; if (poweronoff) { plink->u.osmotrx.poweron_sent = false; osmo_fsm_inst_dispatch(l1h->provision_fi, TRX_PROV_EV_POWERON_CNF, (void*)(intptr_t)rc); } else { plink->u.osmotrx.poweroff_sent = false; osmo_fsm_inst_dispatch(l1h->provision_fi, TRX_PROV_EV_POWEROFF_CNF, (void*)(intptr_t)rc); } } void l1if_rxtune_cb(struct trx_l1h *l1h, int rc) { osmo_fsm_inst_dispatch(l1h->provision_fi, TRX_PROV_EV_RXTUNE_CNF, (void*)(intptr_t)rc); } void l1if_txtune_cb(struct trx_l1h *l1h, int rc) { osmo_fsm_inst_dispatch(l1h->provision_fi, TRX_PROV_EV_TXTUNE_CNF, (void*)(intptr_t)rc); } void l1if_settsc_cb(struct trx_l1h *l1h, int rc) { osmo_fsm_inst_dispatch(l1h->provision_fi, TRX_PROV_EV_SETTSC_CNF, (void*)(intptr_t)rc); } void l1if_setbsic_cb(struct trx_l1h *l1h, int rc) { osmo_fsm_inst_dispatch(l1h->provision_fi, TRX_PROV_EV_SETBSIC_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; LOGPPHI(pinst, DL1C, LOGL_DEBUG, "l1if_getnompower_cb(nominal_power=%d, rc=%d)\n", nominal_power, rc); osmo_fsm_inst_dispatch(l1h->provision_fi, TRX_PROV_EV_NOMTXPOWER_CNF, (void*)(intptr_t)nominal_power); } void l1if_setformat_cb(struct trx_l1h *l1h, int rc) { osmo_fsm_inst_dispatch(l1h->provision_fi, TRX_PROV_EV_SETFORMAT_CNF, (void*)(intptr_t)rc); } /* * transceiver provisioning */ static void trx_provision_reset(struct trx_l1h *l1h) { struct phy_instance *pinst = l1h->phy_inst; uint8_t tn; l1h->config.trxd_pdu_ver_req = pinst->phy_link->u.osmotrx.trxd_pdu_ver_max; l1h->config.trxd_pdu_ver_use = 0; l1h->config.setformat_sent = false; l1h->config.setformat_acked = false; l1h->config.enabled = false; l1h->config.arfcn_valid = false; l1h->config.arfcn = 0; l1h->config.rxtune_sent = false; l1h->config.rxtune_acked = false; l1h->config.txtune_sent = false; l1h->config.txtune_acked = false; l1h->config.tsc_valid = false; l1h->config.tsc = 0; l1h->config.tsc_sent = false; l1h->config.tsc_acked = false; l1h->config.bsic_valid = false; l1h->config.bsic = 0; l1h->config.bsic_sent = false; l1h->config.bsic_acked = false; l1h->config.rxgain_sent = false; l1h->config.nomtxpower_sent = false; l1h->config.nomtxpower_acked = false; l1h->config.maxdly_sent = false; l1h->config.maxdlynb_sent = false; for (tn = 0; tn < TRX_NR_TS; tn++) { l1h->config.setslot_valid[tn] = false; l1h->config.setslot_sent[tn] = false; l1h->config.setslot[tn].slottype = 0; l1h->config.setslot[tn].tsc_set = 0; l1h->config.setslot[tn].tsc_val = 0; l1h->config.setslot[tn].tsc_valid = 0; } } 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; } /* before power on */ if (l1h->config.arfcn_valid) { if (!l1h->config.rxtune_sent) { trx_if_cmd_rxtune(l1h, l1h->config.arfcn, l1if_rxtune_cb); l1h->config.rxtune_sent = true; l1h->config.rxtune_acked = false; } if (!l1h->config.txtune_sent) { trx_if_cmd_txtune(l1h, l1h->config.arfcn, l1if_txtune_cb); l1h->config.txtune_sent = true; l1h->config.txtune_acked = false; } if (l1h->config.txtune_acked) { /* 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 && !l1h->config.nomtxpower_sent) { trx_if_cmd_getnompower(l1h, l1if_getnompower_cb); l1h->config.nomtxpower_sent = true; l1h->config.nomtxpower_acked = false; } } } if (!pinst->phy_link->u.osmotrx.use_legacy_setbsic && l1h->config.tsc_valid && !l1h->config.tsc_sent) { trx_if_cmd_settsc(l1h, l1h->config.tsc, l1if_settsc_cb); l1h->config.tsc_sent = true; l1h->config.tsc_acked = false; } if (pinst->phy_link->u.osmotrx.use_legacy_setbsic && l1h->config.bsic_valid && !l1h->config.bsic_sent) { trx_if_cmd_setbsic(l1h, l1h->config.bsic, l1if_setbsic_cb); l1h->config.bsic_sent = true; l1h->config.bsic_acked = false; } /* Ask transceiver to use the newest TRXD PDU version if not using it yet */ if (!l1h->config.setformat_sent) { l1h->config.setformat_sent = true; if (plink->u.osmotrx.trxd_pdu_ver_max == 0) { LOGPPHI(pinst, DL1C, LOGL_INFO, "No need to negotiate max TRXD version 0"); l1h->config.trxd_pdu_ver_use = 0; l1h->config.setformat_acked = true; } else { trx_if_cmd_setformat(l1h, l1h->config.trxd_pdu_ver_req, l1if_setformat_cb); l1h->config.setformat_acked = false; } } return 0; } 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); } static void update_ts_data(struct trx_l1h *l1h, struct trx_prov_ev_cfg_ts_data *data) { l1h->config.setslot[data->tn].slottype = data->slottype; l1h->config.setslot[data->tn].tsc_set = data->tsc_set; l1h->config.setslot[data->tn].tsc_val = data->tsc_val; l1h->config.setslot[data->tn].tsc_valid = data->tsc_valid; l1h->config.setslot_valid[data->tn] = true; l1h->config.setslot_sent[data->tn] = false; } /* Whether a given TRX is fully configured */ static bool trx_is_provisioned(struct trx_l1h *l1h) { struct phy_instance *pinst = l1h->phy_inst; if (l1h->config.rxtune_acked && l1h->config.txtune_acked && (l1h->config.bsic_acked || !pinst->phy_link->u.osmotrx.use_legacy_setbsic) && (l1h->config.tsc_acked || pinst->phy_link->u.osmotrx.use_legacy_setbsic) && (l1h->config.nomtxpower_acked || l1h->config.nominal_power_set_by_vty) && (l1h->config.setformat_acked)) { return true; } return false; } /* Whether a given TRX is fully configured and can be powered on */ static bool trx_is_provisioned_and_enabled(struct trx_l1h *l1h) { return l1h->config.enabled && trx_is_provisioned(l1h); } static void trx_signal_ready_trx0(struct trx_l1h *l1h) { struct phy_instance *pinst = l1h->phy_inst; struct phy_instance *pinst_it; llist_for_each_entry(pinst_it, &pinst->phy_link->instances, list) { struct trx_l1h *l1h_it = pinst_it->u.osmotrx.hdl; if (l1h_it->phy_inst->num != 0) continue; osmo_fsm_inst_dispatch(l1h_it->provision_fi, TRX_PROV_EV_OTHER_TRX_READY, NULL); return; } } /* Called from TRX0 to check if other TRX are already configured so POWERON can be sent */ static bool trx_other_trx0_ready(struct trx_l1h *l1h) { struct phy_instance *pinst = l1h->phy_inst; struct phy_instance *pinst_it; /* Don't POWERON until all trx are ready */ llist_for_each_entry(pinst_it, &pinst->phy_link->instances, list) { struct trx_l1h *l1h_it = pinst_it->u.osmotrx.hdl; if (l1h_it->phy_inst->num == 0) continue; if (!trx_is_provisioned(l1h_it)) return false; } return true; } /* Closes a phy_link and all its associated TRX */ static void trx_prov_fsm_apply_close(struct phy_link *plink, int rc) { struct trx_l1h *l1h; struct phy_instance *pinst; if (plink->state == PHY_LINK_SHUTDOWN) return; bts_model_phy_link_close(plink); /* Notify TRX close on all TRX associated with this phy */ llist_for_each_entry(pinst, &plink->instances, list) { l1h = pinst->u.osmotrx.hdl; trx_prov_fsm_state_chg(l1h->provision_fi, TRX_PROV_ST_CLOSED); bts_model_trx_close_cb(pinst->trx, rc); } } static int trx_prov_fsm_signal_cb(unsigned int subsys, unsigned int signal, void *hdlr_data, void *signal_data) { struct nm_statechg_signal_data *nsd; struct gsm_bts_trx *trx; if (subsys != SS_GLOBAL) return -EINVAL; if (signal != S_NEW_OP_STATE) return 0; nsd = (struct nm_statechg_signal_data *)signal_data; if (nsd->mo->obj_class != NM_OC_RADIO_CARRIER) return 0; if (nsd->old_state != NM_OPSTATE_ENABLED && nsd->new_state == NM_OPSTATE_ENABLED) { trx = gsm_objclass2obj(nsd->mo->bts, nsd->mo->obj_class, &nsd->mo->obj_inst); l1if_trx_start_power_ramp(trx, NULL); } return 0; } ////////////////////////// // 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; default: OSMO_ASSERT(0); } } static void st_open_poweroff_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state) { struct trx_l1h *l1h = (struct trx_l1h *)fi->priv; struct phy_instance *pinst = l1h->phy_inst; trx_provision_reset(l1h); if (pinst->trx == NULL) { trx_if_cmd_rfmute(l1h, true); return; } /* Apply initial RFMUTE state */ trx_if_cmd_rfmute(l1h, pinst->trx->mo.nm_state.administrative != NM_STATE_UNLOCKED); osmo_fsm_inst_dispatch(pinst->trx->mo.fi, NM_EV_SW_ACT, NULL); osmo_fsm_inst_dispatch(pinst->trx->bb_transc.mo.fi, NM_EV_SW_ACT, NULL); } static void st_open_poweroff(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 gsm_bts_trx *trx = pinst->trx; uint16_t arfcn; int nominal_power; int status; bool waiting_other_trx; bool was_ready = trx_is_provisioned(l1h); switch (event) { case TRX_PROV_EV_CLOSE: /* In this state, we didn't for sure send a POWERON yet, hence we are save directly applying the close as if we received a POWEROFF RSP: */ if (pinst->num == 0) trx_prov_fsm_apply_close(pinst->phy_link, 0); return; case TRX_PROV_EV_CFG_ENABLE: l1h->config.enabled =(bool)data; break; case TRX_PROV_EV_CFG_BSIC: /* We always get BSIC from the BSC, TSC can be derived from the BCC */ if (!pinst->phy_link->u.osmotrx.use_legacy_setbsic) { const uint8_t tsc = BSIC2BCC((uint8_t)(intptr_t)data); if (l1h->config.tsc != tsc || !l1h->config.tsc_valid) { l1h->config.tsc = tsc; l1h->config.tsc_valid = true; l1h->config.tsc_sent = false; } } else { const uint8_t bsic = (uint8_t)(intptr_t)data; if (l1h->config.bsic != bsic || !l1h->config.bsic_valid) { l1h->config.bsic = bsic; l1h->config.bsic_valid = true; l1h->config.bsic_sent = false; } } 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 = true; l1h->config.txtune_sent = false; l1h->config.rxtune_sent = false; l1h->config.nomtxpower_sent = false; } break; case TRX_PROV_EV_CFG_TS: update_ts_data(l1h, (struct trx_prov_ev_cfg_ts_data*)data); break; /* CONFIRMATIONS FROM TRXC */ case TRX_PROV_EV_RXTUNE_CNF: if (l1h->config.rxtune_sent) l1h->config.rxtune_acked = true; break; case TRX_PROV_EV_TXTUNE_CNF: if (l1h->config.txtune_sent) l1h->config.txtune_acked = true; break; case TRX_PROV_EV_NOMTXPOWER_CNF: nominal_power = (int)(intptr_t)data; if (l1h->config.nomtxpower_sent) l1h->config.nomtxpower_acked = true; l1if_trx_set_nominal_power(trx, nominal_power); break; case TRX_PROV_EV_SETBSIC_CNF: if (l1h->config.bsic_sent) l1h->config.bsic_acked = true; break; case TRX_PROV_EV_SETTSC_CNF: if (l1h->config.tsc_sent) l1h->config.tsc_acked = true; break; case TRX_PROV_EV_SETFORMAT_CNF: status = (int)(intptr_t)data; /* Transceiver may suggest a lower version (than requested) */ if (status == l1h->config.trxd_pdu_ver_req) { l1h->config.trxd_pdu_ver_use = status; l1h->config.setformat_acked = true; LOGPPHI(l1h->phy_inst, DTRX, LOGL_INFO, "Using TRXD PDU version %u\n", l1h->config.trxd_pdu_ver_use); } else { LOGPPHI(l1h->phy_inst, DTRX, LOGL_DEBUG, "Transceiver suggests TRXD PDU version %u (requested %u)\n", status, l1h->config.trxd_pdu_ver_req); /* Send another SETFORMAT with suggested version */ l1h->config.trxd_pdu_ver_req = status; l1h->config.setformat_sent = false; } break; case TRX_PROV_EV_OTHER_TRX_READY: OSMO_ASSERT(pinst->num == 0); /* Do nothing here, we were triggered to see if we can finally poweron TRX0 below */ break; default: OSMO_ASSERT(0); } l1if_provision_transceiver_trx(l1h); if (l1h->phy_inst->num == 0) { waiting_other_trx = !trx_other_trx0_ready(l1h); } else { waiting_other_trx = false; /* we don't care about others in TRX!=0 */ /* If we just became ready for TRX0 POWERON (aka this TRX becomes provisioned), signal it to TRX0: */ if (l1h->phy_inst->num != 0 && (!was_ready && trx_is_provisioned(l1h))) trx_signal_ready_trx0(l1h); } /* if we gathered all data and could go forward. For TRX0, only after * all other TRX are prepared, since it will send POWERON commad */ if (trx_is_provisioned_and_enabled(l1h) && !waiting_other_trx) { if (l1h->phy_inst->num != 0) trx_prov_fsm_state_chg(fi, TRX_PROV_ST_OPEN_POWERON); else trx_prov_fsm_state_chg(fi, TRX_PROV_ST_OPEN_WAIT_POWERON_CNF); } else { LOGPFSML(fi, LOGL_INFO, "Delay poweron, wait for:%s%s%s%s%s%s%s%s\n", l1h->config.enabled ? "" :" enable", pinst->phy_link->u.osmotrx.use_legacy_setbsic ? (l1h->config.bsic_valid ? (l1h->config.bsic_acked ? "" : " bsic-ack") : " bsic") : (l1h->config.tsc_valid ? (l1h->config.tsc_acked ? "" : " tsc-ack") : " tsc"), l1h->config.arfcn_valid ? "" : " arfcn", l1h->config.rxtune_acked ? "" : " rxtune-ack", l1h->config.txtune_acked ? "" : " txtune-ack", l1h->config.nominal_power_set_by_vty ? "" : (l1h->config.nomtxpower_acked ? "" : " nomtxpower-ack"), l1h->config.setformat_acked ? "" : " setformat-ack", waiting_other_trx ? "" : " other-trx" ); } } static void st_open_wait_power_cnf_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state) { struct trx_l1h *l1h = (struct trx_l1h *)fi->priv; struct phy_instance *pinst = l1h->phy_inst; trx_if_cmd_poweron(l1h, l1if_poweronoff_cb); pinst->phy_link->u.osmotrx.poweron_sent = true; } 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); 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; case TRX_PROV_EV_CLOSE: /* power off transceiver, if not already */ if (pinst->num == 0 && !plink->u.osmotrx.poweroff_sent) { trx_if_cmd_poweroff(l1h, l1if_poweronoff_cb); plink->u.osmotrx.poweroff_sent = true; } trx_prov_fsm_state_chg(fi, TRX_PROV_ST_OPEN_WAIT_POWEROFF_CNF); break; default: OSMO_ASSERT(0); } } 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 = true; } if (l1h->config.maxdly_valid && !l1h->config.maxdly_sent) { trx_if_cmd_setmaxdly(l1h, l1h->config.maxdly); l1h->config.maxdly_sent = true; } if (l1h->config.maxdlynb_valid && !l1h->config.maxdlynb_sent) { trx_if_cmd_setmaxdlynb(l1h, l1h->config.maxdlynb); l1h->config.maxdlynb_sent = true; } for (tn = 0; tn < TRX_NR_TS; tn++) { if (l1h->config.setslot_valid[tn] && !l1h->config.setslot_sent[tn]) { trx_if_cmd_setslot(l1h, tn, l1if_setslot_cb); l1h->config.setslot_sent[tn] = true; } } } 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; switch (event) { case TRX_PROV_EV_CLOSE: /* power off transceiver, if not already */ if (pinst->num == 0 && plink->u.osmotrx.powered && !plink->u.osmotrx.poweroff_sent) { trx_if_cmd_poweroff(l1h, l1if_poweronoff_cb); plink->u.osmotrx.poweroff_sent = true; } trx_prov_fsm_state_chg(fi, TRX_PROV_ST_OPEN_WAIT_POWEROFF_CNF); break; case TRX_PROV_EV_CFG_TS: ts_data = (struct trx_prov_ev_cfg_ts_data*)data; update_ts_data(l1h, ts_data); /* While in this state we can send SETSLOT immediately */ trx_if_cmd_setslot(l1h, ts_data->tn, l1if_setslot_cb); l1h->config.setslot_sent[ts_data->tn] = true; break; default: OSMO_ASSERT(0); } } 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; trx_prov_fsm_apply_close(plink, rc); break; default: OSMO_ASSERT(0); } } 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_CLOSE) | X(TRX_PROV_EV_OTHER_TRX_READY) | X(TRX_PROV_EV_CFG_ENABLE) | X(TRX_PROV_EV_CFG_BSIC) | X(TRX_PROV_EV_CFG_ARFCN) | X(TRX_PROV_EV_CFG_TS) | X(TRX_PROV_EV_RXTUNE_CNF) | X(TRX_PROV_EV_TXTUNE_CNF) | X(TRX_PROV_EV_NOMTXPOWER_CNF) | X(TRX_PROV_EV_SETBSIC_CNF) | X(TRX_PROV_EV_SETTSC_CNF) | X(TRX_PROV_EV_SETFORMAT_CNF), .out_state_mask = X(TRX_PROV_ST_CLOSED) | X(TRX_PROV_ST_OPEN_WAIT_POWERON_CNF) | X(TRX_PROV_ST_OPEN_POWERON), .name = "OPEN_POWEROFF", .onenter = st_open_poweroff_on_enter, .action = st_open_poweroff, }, [TRX_PROV_ST_OPEN_WAIT_POWERON_CNF] = { .in_event_mask = X(TRX_PROV_EV_CLOSE) | X(TRX_PROV_EV_POWERON_CNF) | X(TRX_PROV_EV_CFG_TS), .out_state_mask = X(TRX_PROV_ST_OPEN_POWERON) | X(TRX_PROV_ST_OPEN_WAIT_POWEROFF_CNF), .name = "OPEN_WAIT_POWERON_CNF", .onenter = st_open_wait_power_cnf_on_enter, .action = st_open_wait_power_cnf, }, [TRX_PROV_ST_OPEN_POWERON] = { .in_event_mask = X(TRX_PROV_EV_CLOSE) | X(TRX_PROV_EV_CFG_TS), .out_state_mask = X(TRX_PROV_ST_OPEN_WAIT_POWEROFF_CNF), .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_CLOSED), .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_OTHER_TRX_READY), 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_TS), OSMO_VALUE_STRING(TRX_PROV_EV_CFG_RXGAIN), OSMO_VALUE_STRING(TRX_PROV_EV_CFG_SETMAXDLY), OSMO_VALUE_STRING(TRX_PROV_EV_RXTUNE_CNF), OSMO_VALUE_STRING(TRX_PROV_EV_TXTUNE_CNF), OSMO_VALUE_STRING(TRX_PROV_EV_NOMTXPOWER_CNF), OSMO_VALUE_STRING(TRX_PROV_EV_SETBSIC_CNF), OSMO_VALUE_STRING(TRX_PROV_EV_SETTSC_CNF), OSMO_VALUE_STRING(TRX_PROV_EV_SETFORMAT_CNF), OSMO_VALUE_STRING(TRX_PROV_EV_POWERON_CNF), 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); OSMO_ASSERT(osmo_signal_register_handler(SS_GLOBAL, trx_prov_fsm_signal_cb, NULL) == 0); }