osmo-bsc/src/osmo-bsc/bts_ipaccess_nanobts.c

978 lines
31 KiB
C

/* ip.access nanoBTS specific code */
/* (C) 2009-2018 by Harald Welte <laforge@gnumonks.org>
* (C) 2020 by sysmocom s.f.m.c. GmbH <info@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 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/>.
*
*/
#include <arpa/inet.h>
#include <time.h>
#include <osmocom/gsm/tlv.h>
#include <osmocom/bsc/gsm_data.h>
#include <osmocom/bsc/signal.h>
#include <osmocom/bsc/abis_nm.h>
#include <osmocom/abis/e1_input.h>
#include <osmocom/gsm/tlv.h>
#include <osmocom/core/msgb.h>
#include <osmocom/core/socket.h>
#include <osmocom/core/stat_item.h>
#include <osmocom/core/talloc.h>
#include <osmocom/core/utils.h>
#include <osmocom/bsc/gsm_data.h>
#include <osmocom/bsc/abis_nm.h>
#include <osmocom/bsc/abis_rsl.h>
#include <osmocom/bsc/abis_osmo.h>
#include <osmocom/bsc/debug.h>
#include <osmocom/abis/subchan_demux.h>
#include <osmocom/gsm/ipa.h>
#include <osmocom/abis/ipaccess.h>
#include <osmocom/core/logging.h>
#include <osmocom/bsc/ipaccess.h>
#include <osmocom/bsc/bts_ipaccess_nanobts_omlattr.h>
#include <osmocom/bsc/paging.h>
#include <osmocom/bsc/timeslot_fsm.h>
#include <osmocom/bsc/bts.h>
#include <osmocom/bsc/bts_sm.h>
#include <osmocom/bsc/nm_common_fsm.h>
#include <osmocom/bsc/bsc_stats.h>
static int bts_model_nanobts_start(struct gsm_network *net);
static void bts_model_nanobts_e1line_bind_ops(struct e1inp_line *line);
static int power_ctrl_send_def_params(const struct gsm_bts_trx *trx);
static int power_ctrl_enc_rsl_params(struct msgb *msg, const struct gsm_power_ctrl_params *cp);
static char *get_oml_status(const struct gsm_bts *bts)
{
if (bts->oml_link)
return all_trx_rsl_connected_unlocked(bts) ? "connected" : "degraded";
return "disconnected";
}
struct gsm_bts_model bts_model_nanobts = {
.type = GSM_BTS_TYPE_NANOBTS,
.name = "nanobts",
.start = bts_model_nanobts_start,
.oml_rcvmsg = &abis_nm_rcvmsg,
.oml_status = &get_oml_status,
.e1line_bind_ops = bts_model_nanobts_e1line_bind_ops,
/* MS/BS Power control specific API */
.power_ctrl_send_def_params = &power_ctrl_send_def_params,
.power_ctrl_enc_rsl_params = &power_ctrl_enc_rsl_params,
/* Some nanoBTS firmwares (if not all) don't support SI2ter and cause
* problems on some MS if it is enabled, see OS#3063. Disable it by
* default, can still be enabled through VTY cmd with same name.
*/
.force_combined_si = true,
.nm_att_tlvdef = {
.def = {
/* ip.access specifics */
[NM_ATT_IPACC_DST_IP] = { TLV_TYPE_FIXED, 4 },
[NM_ATT_IPACC_DST_IP_PORT] = { TLV_TYPE_FIXED, 2 },
[NM_ATT_IPACC_STREAM_ID] = { TLV_TYPE_TV, },
[NM_ATT_IPACC_SEC_OML_CFG] = { TLV_TYPE_FIXED, 6 },
[NM_ATT_IPACC_IP_IF_CFG] = { TLV_TYPE_FIXED, 8 },
[NM_ATT_IPACC_IP_GW_CFG] = { TLV_TYPE_FIXED, 12 },
[NM_ATT_IPACC_IN_SERV_TIME] = { TLV_TYPE_FIXED, 4 },
[NM_ATT_IPACC_LOCATION] = { TLV_TYPE_TL16V },
[NM_ATT_IPACC_PAGING_CFG] = { TLV_TYPE_FIXED, 2 },
[NM_ATT_IPACC_UNIT_ID] = { TLV_TYPE_TL16V },
[NM_ATT_IPACC_UNIT_NAME] = { TLV_TYPE_TL16V },
[NM_ATT_IPACC_SNMP_CFG] = { TLV_TYPE_TL16V },
[NM_ATT_IPACC_PRIM_OML_CFG_LIST] = { TLV_TYPE_TL16V },
[NM_ATT_IPACC_NV_FLAGS] = { TLV_TYPE_TL16V },
[NM_ATT_IPACC_FREQ_CTRL] = { TLV_TYPE_FIXED, 2 },
[NM_ATT_IPACC_PRIM_OML_FB_TOUT] = { TLV_TYPE_TL16V },
[NM_ATT_IPACC_CUR_SW_CFG] = { TLV_TYPE_TL16V },
[NM_ATT_IPACC_TIMING_BUS] = { TLV_TYPE_TL16V },
[NM_ATT_IPACC_CGI] = { TLV_TYPE_TL16V },
[NM_ATT_IPACC_RAC] = { TLV_TYPE_TL16V },
[NM_ATT_IPACC_OBJ_VERSION] = { TLV_TYPE_TL16V },
[NM_ATT_IPACC_GPRS_PAGING_CFG]= { TLV_TYPE_TL16V },
[NM_ATT_IPACC_NSEI] = { TLV_TYPE_TL16V },
[NM_ATT_IPACC_BVCI] = { TLV_TYPE_TL16V },
[NM_ATT_IPACC_NSVCI] = { TLV_TYPE_TL16V },
[NM_ATT_IPACC_NS_CFG] = { TLV_TYPE_TL16V },
[NM_ATT_IPACC_BSSGP_CFG] = { TLV_TYPE_TL16V },
[NM_ATT_IPACC_NS_LINK_CFG] = { TLV_TYPE_TL16V },
[NM_ATT_IPACC_RLC_CFG] = { TLV_TYPE_TL16V },
[NM_ATT_IPACC_ALM_THRESH_LIST]= { TLV_TYPE_TL16V },
[NM_ATT_IPACC_MONIT_VAL_LIST] = { TLV_TYPE_TL16V },
[NM_ATT_IPACC_TIB_CONTROL] = { TLV_TYPE_TL16V },
[NM_ATT_IPACC_SUPP_FEATURES] = { TLV_TYPE_TL16V },
[NM_ATT_IPACC_CODING_SCHEMES] = { TLV_TYPE_TL16V },
[NM_ATT_IPACC_RLC_CFG_2] = { TLV_TYPE_TL16V },
[NM_ATT_IPACC_HEARTB_TOUT] = { TLV_TYPE_TL16V },
[NM_ATT_IPACC_UPTIME] = { TLV_TYPE_TL16V },
[NM_ATT_IPACC_RLC_CFG_3] = { TLV_TYPE_TL16V },
[NM_ATT_IPACC_SSL_CFG] = { TLV_TYPE_TL16V },
[NM_ATT_IPACC_SEC_POSSIBLE] = { TLV_TYPE_TL16V },
[NM_ATT_IPACC_IML_SSL_STATE] = { TLV_TYPE_TL16V },
[NM_ATT_IPACC_REVOC_DATE] = { TLV_TYPE_TL16V },
},
},
.features_get_reported = false,
};
/* Callback function to be called whenever we get a GSM 12.21 state change event */
static int nm_statechg_event(int evt, struct nm_statechg_signal_data *nsd)
{
uint8_t obj_class = nsd->obj_class;
void *obj = nsd->obj;
struct gsm_bts_sm *bts_sm;
struct gsm_bts *bts;
struct gsm_bts_trx *trx;
struct gsm_bts_bb_trx *bb_transc;
struct gsm_bts_trx_ts *ts;
struct gsm_gprs_nsvc *nsvc;
struct gsm_gprs_nse *nse;
struct gsm_gprs_cell *cell;
if (!is_ipa_abisip_bts(nsd->bts))
return 0;
switch (obj_class) {
case NM_OC_SITE_MANAGER:
bts_sm = obj;
osmo_fsm_inst_dispatch(bts_sm->mo.fi, NM_EV_STATE_CHG_REP, nsd);
break;
case NM_OC_BTS:
bts = obj;
osmo_fsm_inst_dispatch(bts->mo.fi, NM_EV_STATE_CHG_REP, nsd);
break;
case NM_OC_BASEB_TRANSC:
bb_transc = obj;
osmo_fsm_inst_dispatch(bb_transc->mo.fi, NM_EV_STATE_CHG_REP, nsd);
break;
case NM_OC_CHANNEL:
ts = obj;
osmo_fsm_inst_dispatch(ts->mo.fi, NM_EV_STATE_CHG_REP, nsd);
break;
case NM_OC_RADIO_CARRIER:
trx = obj;
osmo_fsm_inst_dispatch(trx->mo.fi, NM_EV_STATE_CHG_REP, nsd);
break;
case NM_OC_GPRS_NSE:
nse = obj;
osmo_fsm_inst_dispatch(nse->mo.fi, NM_EV_STATE_CHG_REP, nsd);
break;
case NM_OC_GPRS_CELL:
cell = obj;
osmo_fsm_inst_dispatch(cell->mo.fi, NM_EV_STATE_CHG_REP, nsd);
break;
case NM_OC_GPRS_NSVC:
nsvc = obj;
osmo_fsm_inst_dispatch(nsvc->mo.fi, NM_EV_STATE_CHG_REP, nsd);
break;
default:
break;
}
return 0;
}
/* Callback function to be called every time we receive a 12.21 SW activated report */
static int sw_activ_rep(struct msgb *mb)
{
const struct abis_om_fom_hdr *foh = msgb_l3(mb);
struct e1inp_sign_link *sign_link = mb->dst;
struct gsm_bts *bts = sign_link->trx->bts;
struct gsm_abis_mo *mo;
struct tlv_parsed tp;
if (!is_ipa_abisip_bts(bts))
return 0;
mo = gsm_objclass2mo(bts, foh->obj_class, &foh->obj_inst);
if (mo == NULL) {
LOGPFOH(DNM, LOGL_ERROR, foh, "Rx SW activated report for non-existent MO\n");
return -ENOENT;
}
if (abis_nm_tlv_parse(&tp, bts, &foh->data[0], msgb_l3len(mb) - sizeof(*foh)) < 0) {
LOGPFOH(DNM, LOGL_ERROR, foh, "%s(): tlv_parse failed\n", __func__);
return -EINVAL;
}
mo->ipaccess.obj_version = 0; /* implicit default */
if (TLVP_PRES_LEN(&tp, NM_ATT_IPACC_OBJ_VERSION, 1)) {
const uint8_t *versions = TLVP_VAL(&tp, NM_ATT_IPACC_OBJ_VERSION);
char buf[256];
struct osmo_strbuf sb = { .buf = buf, .len = sizeof(buf) };
/* nanoBTS may report several Object Versions; the first one will
* be used by default unless requested explicitly before OPSTARTing. */
mo->ipaccess.obj_version = versions[0];
OSMO_STRBUF_PRINTF(sb, "%u (default)", versions[0]);
for (uint16_t i = 1; i < TLVP_LEN(&tp, NM_ATT_IPACC_OBJ_VERSION); i++)
OSMO_STRBUF_PRINTF(sb, ", %u", versions[i]);
LOGPFOH(DNM, LOGL_INFO, foh, "IPA Object Versions supported: %s\n", buf);
}
osmo_fsm_inst_dispatch(mo->fi, NM_EV_SW_ACT_REP, NULL);
return 0;
}
static void nm_rx_opstart_ack(struct msgb *oml_msg)
{
const struct abis_om_fom_hdr *foh = msgb_l3(oml_msg);
struct e1inp_sign_link *sign_link = oml_msg->dst;
struct gsm_bts *bts = sign_link->trx->bts;
struct gsm_abis_mo *mo;
if (!is_ipa_abisip_bts(bts))
return;
mo = gsm_objclass2mo(bts, foh->obj_class, &foh->obj_inst);
if (mo == NULL)
LOGPFOH(DNM, LOGL_ERROR, foh, "Rx OPSTART ACK for non-existent MO\n");
else
osmo_fsm_inst_dispatch(mo->fi, NM_EV_OPSTART_ACK, NULL);
}
static void nm_rx_opstart_nack(struct msgb *oml_msg)
{
const struct abis_om_fom_hdr *foh = msgb_l3(oml_msg);
struct e1inp_sign_link *sign_link = oml_msg->dst;
struct gsm_bts *bts = sign_link->trx->bts;
struct gsm_abis_mo *mo;
if (!is_ipa_abisip_bts(bts))
return;
mo = gsm_objclass2mo(bts, foh->obj_class, &foh->obj_inst);
if (mo == NULL)
LOGPFOH(DNM, LOGL_ERROR, foh, "Rx OPSTART NACK for non-existent MO\n");
else
osmo_fsm_inst_dispatch(mo->fi, NM_EV_OPSTART_NACK, NULL);
}
static void nm_rx_get_attr_rep(struct msgb *oml_msg)
{
struct abis_om_fom_hdr *foh = msgb_l3(oml_msg);
struct e1inp_sign_link *sign_link = oml_msg->dst;
struct gsm_bts *bts = sign_link->trx->bts;
struct gsm_abis_mo *mo;
if (!is_ipa_abisip_bts(bts))
return;
mo = gsm_objclass2mo(bts, foh->obj_class, &foh->obj_inst);
if (mo == NULL)
LOGPFOH(DNM, LOGL_ERROR, foh, "Rx Get Attribute Report for non-existent MO\n");
else
osmo_fsm_inst_dispatch(mo->fi, NM_EV_GET_ATTR_REP, NULL);
}
static void nm_rx_set_bts_attr_ack(struct msgb *oml_msg)
{
struct abis_om_fom_hdr *foh = msgb_l3(oml_msg);
struct e1inp_sign_link *sign_link = oml_msg->dst;
struct gsm_bts *bts = sign_link->trx->bts;
if (!is_ipa_abisip_bts(bts))
return;
if (foh->obj_class != NM_OC_BTS) {
LOG_BTS(bts, DNM, LOGL_ERROR, "Set BTS Attr Ack received on non BTS object!\n");
return;
}
osmo_fsm_inst_dispatch(bts->mo.fi, NM_EV_SET_ATTR_ACK, NULL);
}
static void nm_rx_set_radio_attr_ack(struct msgb *oml_msg)
{
struct abis_om_fom_hdr *foh = msgb_l3(oml_msg);
struct e1inp_sign_link *sign_link = oml_msg->dst;
struct gsm_bts *bts = sign_link->trx->bts;
struct gsm_bts_trx *trx;
if (!is_ipa_abisip_bts(bts))
return;
trx = gsm_bts_trx_num(bts, foh->obj_inst.trx_nr);
if (!trx || foh->obj_class != NM_OC_RADIO_CARRIER) {
LOGPFOH(DNM, LOGL_ERROR, foh, "Set Radio Carrier Attr Ack received on non Radio Carrier object!\n");
return;
}
osmo_fsm_inst_dispatch(trx->mo.fi, NM_EV_SET_ATTR_ACK, NULL);
}
static void nm_rx_set_chan_attr_ack(struct msgb *oml_msg)
{
struct abis_om_fom_hdr *foh = msgb_l3(oml_msg);
struct e1inp_sign_link *sign_link = oml_msg->dst;
struct gsm_bts *bts = sign_link->trx->bts;
struct gsm_bts_trx_ts *ts;
if (!is_ipa_abisip_bts(bts))
return;
ts = abis_nm_get_ts(oml_msg);
if (!ts || foh->obj_class != NM_OC_CHANNEL) {
LOGPFOH(DNM, LOGL_ERROR, foh, "Set Channel Attr Ack received on non Radio Channel object!\n");
return;
}
osmo_fsm_inst_dispatch(ts->mo.fi, NM_EV_SET_ATTR_ACK, NULL);
}
static void nm_rx_ipacc_set_attr_ack(struct ipacc_ack_signal_data *sig_data)
{
struct gsm_bts *bts = sig_data->bts;
struct abis_om_fom_hdr *foh = sig_data->foh;
void *obj;
struct gsm_gprs_nse *nse;
struct gsm_gprs_cell *cell;
struct gsm_gprs_nsvc *nsvc;
obj = gsm_objclass2obj(bts, foh->obj_class, &foh->obj_inst);
switch (foh->obj_class) {
case NM_OC_GPRS_NSE:
nse = obj;
osmo_fsm_inst_dispatch(nse->mo.fi, NM_EV_SET_ATTR_ACK, NULL);
break;
case NM_OC_GPRS_CELL:
cell = obj;
osmo_fsm_inst_dispatch(cell->mo.fi, NM_EV_SET_ATTR_ACK, NULL);
break;
case NM_OC_GPRS_NSVC:
if (!(nsvc = gsm_bts_sm_nsvc_num(bts->site_mgr, foh->obj_inst.trx_nr)))
return;
osmo_fsm_inst_dispatch(nsvc->mo.fi, NM_EV_SET_ATTR_ACK, NULL);
break;
default:
LOGPFOH(DNM, LOGL_ERROR, foh, "IPACC Set Attr Ack received on incorrect object class %d!\n", foh->obj_class);
}
}
static void nm_rx_ipacc_rsl_connect_ack(struct ipacc_ack_signal_data *sig_data)
{
struct gsm_bts *bts = sig_data->bts;
struct abis_om_fom_hdr *foh = sig_data->foh;
struct gsm_bts_trx *trx;
if (foh->obj_class != NM_OC_BASEB_TRANSC) {
LOGPFOH(DNM, LOGL_ERROR, foh, "IPACC RSL Connect ACK received on incorrect object class %d!\n", foh->obj_class);
return;
}
trx = gsm_bts_trx_num(bts, foh->obj_inst.trx_nr);
osmo_fsm_inst_dispatch(trx->bb_transc.mo.fi, NM_EV_RSL_CONNECT_ACK, NULL);
}
static void nm_rx_ipacc_ack(struct ipacc_ack_signal_data *sig_data)
{
switch (sig_data->foh->msg_type) {
case NM_MT_IPACC_SET_ATTR_ACK:
nm_rx_ipacc_set_attr_ack(sig_data);
break;
case NM_MT_IPACC_RSL_CONNECT_ACK:
nm_rx_ipacc_rsl_connect_ack(sig_data);
break;
default:
break;
}
}
static void nm_rx_ipacc_rsl_connect_nack(struct ipacc_ack_signal_data *sig_data)
{
struct gsm_bts *bts = sig_data->bts;
struct abis_om_fom_hdr *foh = sig_data->foh;
struct gsm_bts_trx *trx;
if (foh->obj_class != NM_OC_BASEB_TRANSC) {
LOGPFOH(DNM, LOGL_ERROR, foh, "IPACC RSL Connect NACK received on incorrect object class %d!\n", foh->obj_class);
return;
}
trx = gsm_bts_trx_num(bts, foh->obj_inst.trx_nr);
osmo_fsm_inst_dispatch(trx->bb_transc.mo.fi, NM_EV_RSL_CONNECT_NACK, NULL);
}
static void nm_rx_ipacc_nack(struct ipacc_ack_signal_data *sig_data)
{
switch (sig_data->foh->msg_type) {
case NM_MT_IPACC_RSL_CONNECT_ACK:
nm_rx_ipacc_rsl_connect_nack(sig_data);
break;
default:
break;
}
}
/* Callback function to be called every time we receive a signal from NM */
static int bts_ipa_nm_sig_cb(unsigned int subsys, unsigned int signal,
void *handler_data, void *signal_data)
{
if (subsys != SS_NM)
return 0;
switch (signal) {
case S_NM_SW_ACTIV_REP:
return sw_activ_rep(signal_data);
case S_NM_STATECHG:
return nm_statechg_event(signal, signal_data);
case S_NM_OPSTART_ACK:
nm_rx_opstart_ack(signal_data);
return 0;
case S_NM_OPSTART_NACK:
nm_rx_opstart_nack(signal_data);
return 0;
case S_NM_GET_ATTR_REP:
nm_rx_get_attr_rep(signal_data);
return 0;
case S_NM_SET_BTS_ATTR_ACK:
nm_rx_set_bts_attr_ack(signal_data);
return 0;
case S_NM_SET_RADIO_ATTR_ACK:
nm_rx_set_radio_attr_ack(signal_data);
return 0;
case S_NM_SET_CHAN_ATTR_ACK:
nm_rx_set_chan_attr_ack(signal_data);
return 0;
case S_NM_IPACC_ACK:
nm_rx_ipacc_ack(signal_data);
return 0;
case S_NM_IPACC_NACK:
nm_rx_ipacc_nack(signal_data);
return 0;
default:
break;
}
return 0;
}
static int bts_model_nanobts_start(struct gsm_network *net)
{
osmo_signal_unregister_handler(SS_NM, bts_ipa_nm_sig_cb, NULL);
osmo_signal_register_handler(SS_NM, bts_ipa_nm_sig_cb, NULL);
return 0;
}
int bts_model_nanobts_init(void)
{
bts_model_nanobts.features.data = &bts_model_nanobts._features_data[0];
bts_model_nanobts.features.data_len =
sizeof(bts_model_nanobts._features_data);
osmo_bts_set_feature(&bts_model_nanobts.features, BTS_FEAT_GPRS);
osmo_bts_set_feature(&bts_model_nanobts.features, BTS_FEAT_EGPRS);
osmo_bts_set_feature(&bts_model_nanobts.features, BTS_FEAT_MULTI_TSC);
return gsm_bts_model_register(&bts_model_nanobts);
}
#define OML_UP 0x0001
#define RSL_UP 0x0002
static struct gsm_bts *
find_bts_by_unitid(struct gsm_network *net, uint16_t site_id, uint16_t bts_id)
{
struct gsm_bts *bts;
llist_for_each_entry(bts, &net->bts_list, list) {
if (!is_ipa_abisip_bts(bts))
continue;
if (bts->ip_access.site_id == site_id &&
bts->ip_access.bts_id == bts_id)
return bts;
}
return NULL;
}
/* These are exported because they are used by the VTY interface. */
void ipaccess_drop_rsl(struct gsm_bts_trx *trx, const char *reason)
{
if (!trx->rsl_link_primary)
return;
LOG_TRX(trx, DLINP, LOGL_NOTICE, "Dropping RSL link: %s\n", reason);
e1inp_sign_link_destroy(trx->rsl_link_primary);
trx->rsl_link_primary = NULL;
osmo_stat_item_dec(osmo_stat_item_group_get_item(trx->bts->bts_statg, BTS_STAT_RSL_CONNECTED), 1);
if (trx->bts->c0 == trx)
paging_flush_bts(trx->bts, NULL);
}
void ipaccess_drop_oml(struct gsm_bts *bts, const char *reason)
{
struct gsm_bts *rdep_bts;
struct gsm_bts_trx *trx;
struct gsm_bts_trx_ts *ts ;
uint8_t tn;
uint8_t i;
struct timespec tp;
int rc;
/* First of all, remove deferred drop if enabled */
osmo_timer_del(&bts->oml_drop_link_timer);
if (!bts->oml_link)
return;
LOG_BTS(bts, DLINP, LOGL_NOTICE, "Dropping OML link: %s\n", reason);
e1inp_sign_link_destroy(bts->oml_link);
bts->oml_link = NULL;
rc = osmo_clock_gettime(CLOCK_MONOTONIC, &tp);
bts->updowntime = (rc < 0) ? 0 : tp.tv_sec; /* we don't need sub-second precision for downtime */
osmo_stat_item_dec(osmo_stat_item_group_get_item(bts->bts_statg, BTS_STAT_OML_CONNECTED), 1);
gsm_bts_stats_reset(bts);
/* Also drop the associated OSMO link */
OSMO_ASSERT(bts->osmo_link);
e1inp_sign_link_destroy(bts->osmo_link);
bts->osmo_link = NULL;
bts_setup_ramp_remove(bts);
/* we have issues reconnecting RSL, drop everything. */
llist_for_each_entry(trx, &bts->trx_list, list) {
ipaccess_drop_rsl(trx, "OML link drop");
osmo_fsm_inst_dispatch(trx->bb_transc.mo.fi, NM_EV_OML_DOWN, NULL);
osmo_fsm_inst_dispatch(trx->mo.fi, NM_EV_OML_DOWN, NULL);
for (tn = 0; tn < TRX_NR_TS; tn++) {
ts = &trx->ts[tn];
osmo_fsm_inst_dispatch(ts->mo.fi, NM_EV_OML_DOWN, NULL);
}
}
osmo_fsm_inst_dispatch(bts->mo.fi, NM_EV_OML_DOWN, NULL);
osmo_fsm_inst_dispatch(bts->gprs.cell.mo.fi, NM_EV_OML_DOWN, NULL);
osmo_fsm_inst_dispatch(bts->site_mgr->mo.fi, NM_EV_OML_DOWN, NULL);
osmo_fsm_inst_dispatch(bts->site_mgr->gprs.nse.mo.fi, NM_EV_OML_DOWN, NULL);
for (i = 0; i < ARRAY_SIZE(bts->site_mgr->gprs.nsvc); i++)
osmo_fsm_inst_dispatch(bts->site_mgr->gprs.nsvc[i].mo.fi, NM_EV_OML_DOWN, NULL);
bts->ip_access.flags = 0;
if (bts->model->features_get_reported) {
/* Reset the feature vector */
memset(bts->_features_data, 0, sizeof(bts->_features_data));
bts->features_known = false;
}
/*
* Go through the list and see if we are the depndency of a BTS
* and then drop the BTS. This can lead to some recursion but it
* should be fine in userspace.
* The oml_link is serving as recursion anchor for us and
* it is set to NULL some lines above.
*/
llist_for_each_entry(rdep_bts, &bts->network->bts_list, list) {
if (!bts_depend_is_depedency(rdep_bts, bts))
continue;
LOGP(DLINP, LOGL_NOTICE, "Dropping BTS(%u) due BTS(%u).\n",
rdep_bts->nr, bts->nr);
ipaccess_drop_oml(rdep_bts, "Dependency link drop");
}
}
/*! Callback for \ref ipaccess_drop_oml_deferred_cb.
*/
static void ipaccess_drop_oml_deferred_cb(void *data)
{
struct gsm_bts *bts = (struct gsm_bts *) data;
ipaccess_drop_oml(bts, "Deferred link drop");
}
/*! Deferr \ref ipacces_drop_oml through a timer to avoid dropping structures in
* current code context. This may be needed if we want to destroy the OML link
* while being called from a lower layer "struct osmo_fd" cb, were it is
* mandatory to return -EBADF if the osmo_fd has been destroyed. In case code
* destroying an OML link is called through an osmo_signal, it becomes
* impossible to return any value, thus deferring the destruction is required.
*/
void ipaccess_drop_oml_deferred(struct gsm_bts *bts)
{
if (!osmo_timer_pending(&bts->oml_drop_link_timer) && bts->oml_link) {
LOG_BTS(bts, DLINP, LOGL_NOTICE, "Deferring Drop of OML link.\n");
osmo_timer_setup(&bts->oml_drop_link_timer, ipaccess_drop_oml_deferred_cb, bts);
osmo_timer_schedule(&bts->oml_drop_link_timer, 0, 0);
}
}
/* Reject BTS because of an unknown unit ID */
static void ipaccess_sign_link_reject(const struct ipaccess_unit *dev, const struct e1inp_ts* ts)
{
uint16_t site_id = dev->site_id;
uint16_t bts_id = dev->bts_id;
uint16_t trx_id = dev->trx_id;
char ip[INET6_ADDRSTRLEN];
struct gsm_bts_rejected *entry = NULL;
struct gsm_bts_rejected *pos;
/* Write to log and increase counter */
LOGP(DLINP, LOGL_ERROR, "Unable to find BTS configuration for %u/%u/%u, disconnecting\n", site_id, bts_id,
trx_id);
rate_ctr_inc(rate_ctr_group_get_ctr(bsc_gsmnet->bsc_ctrs, BSC_CTR_UNKNOWN_UNIT_ID));
/* Get remote IP */
if (osmo_sock_get_remote_ip(ts->driver.ipaccess.fd.fd, ip, sizeof(ip)))
return;
/* Rejected list: unlink existing entry */
llist_for_each_entry(pos, &bsc_gsmnet->bts_rejected, list) {
if (pos->site_id == site_id && pos->bts_id == bts_id && !strcmp(pos->ip, ip)) {
entry = pos;
llist_del(&entry->list);
break;
}
}
/* Allocate new entry */
if (!entry) {
entry = talloc_zero(tall_bsc_ctx, struct gsm_bts_rejected);
if (!entry)
return;
entry->site_id = site_id;
entry->bts_id = bts_id;
osmo_strlcpy(entry->ip, ip, sizeof(entry->ip));
}
/* Add to beginning with current timestamp */
llist_add(&entry->list, &bsc_gsmnet->bts_rejected);
entry->time = time(NULL);
/* Cut off last (oldest) element if we have too many */
if (llist_count(&bsc_gsmnet->bts_rejected) > 25) {
pos = llist_last_entry(&bsc_gsmnet->bts_rejected, struct gsm_bts_rejected, list);
llist_del(&pos->list);
talloc_free(pos);
}
}
/* This function is called once the OML/RSL link becomes up. */
static struct e1inp_sign_link *
ipaccess_sign_link_up(void *unit_data, struct e1inp_line *line,
enum e1inp_sign_type type)
{
struct gsm_bts *bts;
struct ipaccess_unit *dev = unit_data;
struct e1inp_sign_link *sign_link = NULL;
struct timespec tp;
int rc;
struct e1inp_ts *sign_ts = e1inp_line_ipa_oml_ts(line);
bts = find_bts_by_unitid(bsc_gsmnet, dev->site_id, dev->bts_id);
if (!bts) {
ipaccess_sign_link_reject(dev, sign_ts);
return NULL;
}
DEBUGP(DLINP, "%s: Identified BTS %u/%u/%u\n", e1inp_signtype_name(type),
dev->site_id, dev->bts_id, dev->trx_id);
/* Check if this BTS has a valid configuration. If not we will drop it
* immediately. */
if (gsm_bts_check_cfg(bts) != 0) {
LOGP(DLINP, LOGL_NOTICE, "(bts=%u) BTS config invalid, dropping BTS!\n", bts->nr);
ipaccess_drop_oml_deferred(bts);
return NULL;
}
switch(type) {
case E1INP_SIGN_OML:
/* remove old OML signal link for this BTS. */
ipaccess_drop_oml(bts, "new OML link");
if (!bts_depend_check(bts)) {
LOGP(DLINP, LOGL_NOTICE,
"Dependency not full-filled for %u/%u/%u\n",
dev->site_id, dev->bts_id, dev->trx_id);
return NULL;
}
/* create new OML link. */
sign_link = bts->oml_link =
e1inp_sign_link_create(sign_ts,
E1INP_SIGN_OML, bts->c0,
bts->oml_tei, 0);
rc = osmo_clock_gettime(CLOCK_MONOTONIC, &tp);
bts->updowntime = (rc < 0) ? 0 : tp.tv_sec; /* we don't need sub-second precision for uptime */
if (!(sign_link->trx->bts->ip_access.flags & OML_UP)) {
e1inp_event(sign_link->ts, S_L_INP_TEI_UP,
sign_link->tei, sign_link->sapi);
sign_link->trx->bts->ip_access.flags |= OML_UP;
}
osmo_stat_item_inc(osmo_stat_item_group_get_item(bts->bts_statg, BTS_STAT_OML_CONNECTED), 1);
/* Create link for E1INP_SIGN_OSMO */
//SAPI must be 0, no IPAC_PROTO_EXT_PCU, see ipaccess_bts_read_cb
bts->osmo_link = e1inp_sign_link_create(sign_ts, E1INP_SIGN_OSMO, bts->c0, IPAC_PROTO_OSMO, 0);
break;
case E1INP_SIGN_RSL: {
struct e1inp_ts *ts;
struct gsm_bts_trx *trx = gsm_bts_trx_num(bts, dev->trx_id);
/* no OML link set yet? give up. */
if (!bts->oml_link || !trx)
return NULL;
/* remove old RSL link for this TRX. */
ipaccess_drop_rsl(trx, "new RSL link");
/* set new RSL link for this TRX. */
line = bts->oml_link->ts->line;
ts = e1inp_line_ipa_rsl_ts(line, dev->trx_id);
e1inp_ts_config_sign(ts, line);
sign_link = trx->rsl_link_primary =
e1inp_sign_link_create(ts, E1INP_SIGN_RSL,
trx, trx->rsl_tei_primary, 0);
trx->rsl_link_primary->ts->sign.delay = 0;
if (!(sign_link->trx->bts->ip_access.flags &
(RSL_UP << sign_link->trx->nr))) {
e1inp_event(sign_link->ts, S_L_INP_TEI_UP,
sign_link->tei, sign_link->sapi);
sign_link->trx->bts->ip_access.flags |=
(RSL_UP << sign_link->trx->nr);
}
osmo_stat_item_inc(osmo_stat_item_group_get_item(bts->bts_statg, BTS_STAT_RSL_CONNECTED), 1);
break;
}
default:
break;
}
return sign_link;
}
static void ipaccess_sign_link_down(struct e1inp_line *line)
{
/* No matter what link went down, we close both signal links. */
struct e1inp_ts *ts = e1inp_line_ipa_oml_ts(line);
struct gsm_bts *bts = NULL;
struct e1inp_sign_link *link;
LOGPIL(line, DLINP, LOGL_NOTICE, "Signalling link down\n");
llist_for_each_entry(link, &ts->sign.sign_links, list) {
/* Get bts pointer from the first element of the list. */
if (bts == NULL)
bts = link->trx->bts;
/* Cancel RSL connection timeout in case are still waiting for an RSL connection. */
if (link->trx->mo.nm_state.administrative == NM_STATE_UNLOCKED)
osmo_timer_del(&link->trx->rsl_connect_timeout);
}
if (bts != NULL)
ipaccess_drop_oml(bts, "link down");
else
LOGPIL(line, DLINP, LOGL_NOTICE, "Signalling link down for unknown BTS\n");
}
/* This function is called if we receive one OML/RSL message. */
static int ipaccess_sign_link(struct msgb *msg)
{
int ret = 0;
struct e1inp_sign_link *link = msg->dst;
switch (link->type) {
case E1INP_SIGN_RSL:
ret = abis_rsl_rcvmsg(msg);
break;
case E1INP_SIGN_OML:
ret = abis_nm_rcvmsg(msg);
break;
case E1INP_SIGN_OSMO:
ret = abis_osmo_rcvmsg(msg);
break;
default:
LOGP(DLINP, LOGL_ERROR, "Unknown signal link type %d\n",
link->type);
msgb_free(msg);
break;
}
return ret;
}
/* not static, ipaccess-config needs it. */
struct e1inp_line_ops ipaccess_e1inp_line_ops = {
.cfg = {
.ipa = {
.addr = "0.0.0.0",
.role = E1INP_LINE_R_BSC,
},
},
.sign_link_up = ipaccess_sign_link_up,
.sign_link_down = ipaccess_sign_link_down,
.sign_link = ipaccess_sign_link,
};
static void bts_model_nanobts_e1line_bind_ops(struct e1inp_line *line)
{
e1inp_line_bind_ops(line, &ipaccess_e1inp_line_ops);
}
static void enc_meas_proc_params(struct msgb *msg, uint8_t ptype,
const struct gsm_power_ctrl_meas_params *mp)
{
struct ipac_preproc_ave_cfg *ave_cfg;
uint8_t *ie_len;
/* No averaging => no Measurement Averaging parameters */
if (mp->algo == GSM_PWR_CTRL_MEAS_AVG_ALGO_NONE)
return;
/* (TLV) Measurement Averaging parameters for RxLev/RxQual */
ie_len = msgb_tl_put(msg, RSL_IPAC_EIE_MEAS_AVG_CFG);
ave_cfg = (struct ipac_preproc_ave_cfg *) msgb_put(msg, sizeof(*ave_cfg));
ave_cfg->param_id = ptype & 0x03;
/* H_REQAVE and H_REQT */
ave_cfg->h_reqave = mp->h_reqave & 0x1f;
ave_cfg->h_reqt = mp->h_reqt & 0x1f;
/* Averaging method and parameters */
ave_cfg->ave_method = (mp->algo - 1) & 0x07;
switch (mp->algo) {
case GSM_PWR_CTRL_MEAS_AVG_ALGO_OSMO_EWMA:
msgb_v_put(msg, mp->ewma.alpha);
break;
case GSM_PWR_CTRL_MEAS_AVG_ALGO_WEIGHTED:
case GSM_PWR_CTRL_MEAS_AVG_ALGO_MOD_MEDIAN:
/* FIXME: unknown format */
break;
case GSM_PWR_CTRL_MEAS_AVG_ALGO_UNWEIGHTED:
case GSM_PWR_CTRL_MEAS_AVG_ALGO_NONE:
/* No parameters here */
break;
}
/* Update length part of the containing IE */
*ie_len = msg->tail - (ie_len + 1);
}
static void enc_power_params(struct msgb *msg, const struct gsm_power_ctrl_params *cp)
{
struct ipac_preproc_pc_comp *thresh_comp;
struct ipac_preproc_pc_thresh *thresh;
/* These parameters are valid for dynamic mode only */
OSMO_ASSERT(cp->mode == GSM_PWR_CTRL_MODE_DYN_BTS);
/* (TLV) Measurement Averaging Configure */
enc_meas_proc_params(msg, IPAC_RXQUAL_AVE, &cp->rxqual_meas);
enc_meas_proc_params(msg, IPAC_RXLEV_AVE, &cp->rxlev_meas);
/* (TV) Thresholds: {L,U}_RXLEV_XX_P and {L,U}_RXQUAL_XX_P */
if (cp->dir == GSM_PWR_CTRL_DIR_UL)
msgb_v_put(msg, RSL_IPAC_EIE_MS_PWR_CTL);
else
msgb_v_put(msg, RSL_IPAC_EIE_BS_PWR_CTL);
thresh = (struct ipac_preproc_pc_thresh *) msgb_put(msg, sizeof(*thresh));
/* {L,U}_RXLEV_XX_P (see 3GPP TS 45.008, A.3.2.1, a & b) */
thresh->l_rxlev = cp->rxlev_meas.lower_thresh & 0x3f;
thresh->u_rxlev = cp->rxlev_meas.upper_thresh & 0x3f;
/* {L,U}_RXQUAL_XX_P (see 3GPP TS 45.008, A.3.2.1, c & d) */
thresh->l_rxqual = cp->rxqual_meas.lower_thresh & 0x07;
thresh->u_rxqual = cp->rxqual_meas.upper_thresh & 0x07;
/* (TV) PC Threshold Comparators */
msgb_v_put(msg, RSL_IPAC_EIE_PC_THRESH_COMP);
thresh_comp = (struct ipac_preproc_pc_comp *) msgb_put(msg, sizeof(*thresh_comp));
/* RxLev: P1, N1, P2, N2 (see 3GPP TS 45.008, A.3.2.1, a & b) */
thresh_comp->p1 = cp->rxlev_meas.lower_cmp_p & 0x1f;
thresh_comp->n1 = cp->rxlev_meas.lower_cmp_n & 0x1f;
thresh_comp->p2 = cp->rxlev_meas.upper_cmp_p & 0x1f;
thresh_comp->n2 = cp->rxlev_meas.upper_cmp_n & 0x1f;
/* RxQual: P3, N3, P4, N4 (see 3GPP TS 45.008, A.3.2.1, c & d) */
thresh_comp->p3 = cp->rxqual_meas.lower_cmp_p & 0x1f;
thresh_comp->n3 = cp->rxqual_meas.lower_cmp_n & 0x1f;
thresh_comp->p4 = cp->rxqual_meas.upper_cmp_p & 0x1f;
thresh_comp->n4 = cp->rxqual_meas.upper_cmp_n & 0x1f;
/* Minimum interval between power level changes (P_CON_INTERVAL) */
thresh_comp->pc_interval = cp->ctrl_interval;
/* Change step limitations: POWER_{INC,RED}_STEP_SIZE */
thresh_comp->inc_step_size = cp->inc_step_size_db & 0x0f;
thresh_comp->red_step_size = cp->red_step_size_db & 0x0f;
}
void osmobts_enc_power_params_osmo_ext(struct msgb *msg, const struct gsm_power_ctrl_params *cp);
static void add_power_params_ie(struct msgb *msg, enum abis_rsl_ie iei,
const struct gsm_bts_trx *trx,
const struct gsm_power_ctrl_params *cp)
{
uint8_t *ie_len = msgb_tl_put(msg, iei);
uint8_t msg_len = msgb_length(msg);
enc_power_params(msg, cp);
if (iei == RSL_IE_MS_POWER_PARAM && is_osmobts(trx->bts))
osmobts_enc_power_params_osmo_ext(msg, cp);
*ie_len = msgb_length(msg) - msg_len;
}
static int power_ctrl_send_def_params(const struct gsm_bts_trx *trx)
{
const struct gsm_power_ctrl_params *ms_power_ctrl = &trx->bts->ms_power_ctrl;
const struct gsm_power_ctrl_params *bs_power_ctrl = &trx->bts->bs_power_ctrl;
struct abis_rsl_common_hdr *ch;
struct msgb *msg;
/* Sending this message does not make sense if neither MS Power control
* nor BS Power control is to be performed by the BTS itself ('dyn-bts'). */
if (ms_power_ctrl->mode != GSM_PWR_CTRL_MODE_DYN_BTS &&
bs_power_ctrl->mode != GSM_PWR_CTRL_MODE_DYN_BTS)
return 0;
msg = rsl_msgb_alloc();
if (msg == NULL)
return -ENOMEM;
ch = (struct abis_rsl_common_hdr *) msgb_put(msg, sizeof(*ch));
ch->msg_discr = ABIS_RSL_MDISC_TRX;
ch->msg_type = RSL_MT_IPAC_MEAS_PREPROC_DFT;
/* BS/MS Power IEs (to be re-defined in channel specific messages) */
msgb_tv_put(msg, RSL_IE_MS_POWER, 0); /* dummy value */
msgb_tv_put(msg, RSL_IE_BS_POWER, 0); /* dummy value */
/* MS/BS Power Parameters IEs */
if (ms_power_ctrl->mode == GSM_PWR_CTRL_MODE_DYN_BTS)
add_power_params_ie(msg, RSL_IE_MS_POWER_PARAM, trx, ms_power_ctrl);
if (bs_power_ctrl->mode == GSM_PWR_CTRL_MODE_DYN_BTS)
add_power_params_ie(msg, RSL_IE_BS_POWER_PARAM, trx, bs_power_ctrl);
msg->dst = trx->rsl_link_primary;
return abis_rsl_sendmsg(msg);
}
static int power_ctrl_enc_rsl_params(struct msgb *msg, const struct gsm_power_ctrl_params *cp)
{
/* We send everything in "Measurement Pre-processing Defaults" */
return 0;
}