diff options
Diffstat (limited to 'src/osmo-bsc')
49 files changed, 33963 insertions, 7 deletions
diff --git a/src/osmo-bsc/Makefile.am b/src/osmo-bsc/Makefile.am index cc9674396..a459a928b 100644 --- a/src/osmo-bsc/Makefile.am +++ b/src/osmo-bsc/Makefile.am @@ -26,21 +26,65 @@ bin_PROGRAMS = \ $(NULL) osmo_bsc_SOURCES = \ - osmo_bsc_main.c \ - osmo_bsc_vty.c \ + a_reset.c \ + abis_nm.c \ + abis_nm_vty.c \ + abis_om2000.c \ + abis_om2000_vty.c \ + abis_rsl.c \ + acc_ramp.c \ + arfcn_range_encode.c \ + bsc_api.c \ + bsc_ctrl_commands.c \ + bsc_ctrl_lookup.c \ + bsc_dyn_ts.c \ + bsc_init.c \ + bsc_rf_ctrl.c \ + bsc_rll.c \ + bsc_subscr_conn_fsm.c \ + bsc_subscriber.c \ + bsc_vty.c \ + bts_ericsson_rbs2000.c \ + bts_init.c \ + bts_ipaccess_nanobts.c \ + bts_ipaccess_nanobts_omlattr.c \ + bts_nokia_site.c \ + bts_siemens_bs11.c \ + bts_sysmobts.c \ + bts_unknown.c \ + chan_alloc.c \ + e1_config.c \ + gsm_04_08_utils.c \ + gsm_04_80_utils.c \ + gsm_data.c \ + handover_cfg.c \ + handover_decision.c \ + handover_decision_2.c \ + handover_logic.c \ + handover_vty.c \ + meas_feed.c \ + meas_rep.c \ + net_init.c \ osmo_bsc_api.c \ + osmo_bsc_audio.c \ + osmo_bsc_bssap.c \ + osmo_bsc_ctrl.c \ + osmo_bsc_filter.c \ osmo_bsc_grace.c \ + osmo_bsc_lcls.c \ + osmo_bsc_main.c \ osmo_bsc_msc.c \ osmo_bsc_sigtran.c \ - osmo_bsc_filter.c \ - osmo_bsc_bssap.c \ - osmo_bsc_audio.c \ - osmo_bsc_ctrl.c \ + osmo_bsc_vty.c \ + paging.c \ + pcu_sock.c \ + penalty_timers.c \ + rest_octets.c \ + system_information.c \ $(NULL) osmo_bsc_LDADD = \ $(top_builddir)/src/libfilter/libfilter.a \ - $(top_builddir)/src/libbsc/libbsc.a \ $(LIBOSMOCORE_LIBS) \ $(LIBOSMOGSM_LIBS) \ $(LIBOSMOVTY_LIBS) \ diff --git a/src/osmo-bsc/a_reset.c b/src/osmo-bsc/a_reset.c new file mode 100644 index 000000000..b8f8c8cbd --- /dev/null +++ b/src/osmo-bsc/a_reset.c @@ -0,0 +1,205 @@ +/* (C) 2017 by sysmocom s.f.m.c. GmbH + * All Rights Reserved + * + * Author: Philipp Maier + * + * 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 <osmocom/core/logging.h> +#include <osmocom/core/utils.h> +#include <osmocom/core/timer.h> +#include <osmocom/core/fsm.h> +#include <unistd.h> +#include <errno.h> +#include <string.h> +#include <osmocom/bsc/debug.h> +#include <osmocom/bsc/bsc_msc_data.h> +#include <osmocom/bsc/osmo_bsc_sigtran.h> + +#define RESET_RESEND_INTERVAL 2 /* sec */ +#define RESET_RESEND_TIMER_NO 4 /* See also 3GPP TS 48.008 Chapter 3.1.4.1.3.1 */ +#define BAD_CONNECTION_THRESOLD 3 /* connection failures */ + +/* Reset context data (callbacks, state machine etc...) */ +struct reset_ctx { + /* Connection failure counter. When this counter + * reaches a certain threshold, the reset procedure + * will be triggered */ + int conn_loss_counter; + + /* Callback function to be called when a connection + * failure is detected and a rest must occur */ + void (*cb)(void *priv); + + /* Privated data for the callback function */ + void *priv; +}; + +enum reset_fsm_states { + ST_DISC, /* Disconnected from remote end */ + ST_CONN, /* We have a confirmed connection */ +}; + +enum reset_fsm_evt { + EV_RESET_ACK, /* got reset acknowlegement from remote end */ + EV_N_DISCONNECT, /* lost a connection */ + EV_N_CONNECT, /* made a successful connection */ +}; + +static const struct value_string fsm_event_names[] = { + OSMO_VALUE_STRING(EV_RESET_ACK), + OSMO_VALUE_STRING(EV_N_DISCONNECT), + OSMO_VALUE_STRING(EV_N_CONNECT), + {0, NULL} +}; + +/* Disconnected state */ +static void fsm_disc_cb(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct reset_ctx *reset_ctx = (struct reset_ctx *)fi->priv; + OSMO_ASSERT(reset_ctx); + LOGPFSML(fi, LOGL_NOTICE, "SIGTRAN connection succeded.\n"); + + reset_ctx->conn_loss_counter = 0; + osmo_fsm_inst_state_chg(fi, ST_CONN, 0, 0); +} + +/* Connected state */ +static void fsm_conn_cb(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct reset_ctx *reset_ctx = (struct reset_ctx *)fi->priv; + OSMO_ASSERT(reset_ctx); + + switch (event) { + case EV_N_DISCONNECT: + if (reset_ctx->conn_loss_counter >= BAD_CONNECTION_THRESOLD) { + LOGPFSML(fi, LOGL_NOTICE, "SIGTRAN connection down, reconnecting...\n"); + osmo_fsm_inst_state_chg(fi, ST_DISC, RESET_RESEND_INTERVAL, RESET_RESEND_TIMER_NO); + } else + reset_ctx->conn_loss_counter++; + break; + case EV_N_CONNECT: + reset_ctx->conn_loss_counter = 0; + break; + } +} + +/* Timer callback to retransmit the reset signal */ +static int fsm_reset_ack_timeout_cb(struct osmo_fsm_inst *fi) +{ + struct reset_ctx *reset_ctx = (struct reset_ctx *)fi->priv; + OSMO_ASSERT(reset_ctx); + + LOGPFSML(fi, LOGL_NOTICE, "(re)sending BSSMAP RESET message...\n"); + + reset_ctx->cb(reset_ctx->priv); + + osmo_fsm_inst_state_chg(fi, ST_DISC, RESET_RESEND_INTERVAL, RESET_RESEND_TIMER_NO); + return 0; +} + +static struct osmo_fsm_state reset_fsm_states[] = { + [ST_DISC] = { + .in_event_mask = (1 << EV_RESET_ACK), + .out_state_mask = (1 << ST_DISC) | (1 << ST_CONN), + .name = "DISC", + .action = fsm_disc_cb, + }, + [ST_CONN] = { + .in_event_mask = (1 << EV_N_DISCONNECT) | (1 << EV_N_CONNECT), + .out_state_mask = (1 << ST_DISC) | (1 << ST_CONN), + .name = "CONN", + .action = fsm_conn_cb, + }, +}; + +/* State machine definition */ +static struct osmo_fsm fsm = { + .name = "A-RESET", + .states = reset_fsm_states, + .num_states = ARRAY_SIZE(reset_fsm_states), + .log_subsys = DMSC, + .timer_cb = fsm_reset_ack_timeout_cb, + .event_names = fsm_event_names, +}; + +/* Create and start state machine which handles the reset/reset-ack procedure */ +struct osmo_fsm_inst *a_reset_alloc(void *ctx, const char *name, void *cb, void *priv) +{ + OSMO_ASSERT(name); + + struct reset_ctx *reset_ctx; + struct osmo_fsm_inst *reset_fsm; + + /* Register the fsm description (if not already done) */ + if (osmo_fsm_find_by_name(fsm.name) != &fsm) + osmo_fsm_register(&fsm); + + /* Allocate and configure a new fsm instance */ + reset_ctx = talloc_zero(ctx, struct reset_ctx); + OSMO_ASSERT(reset_ctx); + reset_ctx->priv = priv; + reset_ctx->cb = cb; + reset_ctx->conn_loss_counter = 0; + reset_fsm = osmo_fsm_inst_alloc(&fsm, ctx, reset_ctx, LOGL_DEBUG, name); + OSMO_ASSERT(reset_fsm); + + /* kick off reset-ack sending mechanism */ + osmo_fsm_inst_state_chg(reset_fsm, ST_DISC, RESET_RESEND_INTERVAL, RESET_RESEND_TIMER_NO); + + return reset_fsm; +} + +/* Confirm that we sucessfully received a reset acknowlege message */ +void a_reset_ack_confirm(struct osmo_fsm_inst *reset_fsm) +{ + OSMO_ASSERT(reset_fsm); + osmo_fsm_inst_dispatch(reset_fsm, EV_RESET_ACK, NULL); +} + +/* Report a failed connection */ +void a_reset_conn_fail(struct osmo_fsm_inst *reset_fsm) +{ + /* If no reset context is supplied, just drop the info */ + if (!reset_fsm) + return; + + osmo_fsm_inst_dispatch(reset_fsm, EV_N_DISCONNECT, NULL); +} + +/* Report a successful connection */ +void a_reset_conn_success(struct osmo_fsm_inst *reset_fsm) +{ + /* If no reset context is supplied, just drop the info */ + if (!reset_fsm) + return; + + osmo_fsm_inst_dispatch(reset_fsm, EV_N_CONNECT, NULL); +} + +/* Check if we have a connection to a specified msc */ +bool a_reset_conn_ready(struct osmo_fsm_inst *reset_fsm) +{ + /* If no reset context is supplied, we assume that + * the connection can't be ready! */ + if (!reset_fsm) + return false; + + if (reset_fsm->state == ST_CONN) + return true; + + return false; +} diff --git a/src/osmo-bsc/abis_bs11.c b/src/osmo-bsc/abis_bs11.c new file mode 100644 index 000000000..8b721fa77 --- /dev/null +++ b/src/osmo-bsc/abis_bs11.c @@ -0,0 +1,21 @@ +/* Siemens BS11 specific Abis implementations */ + +/* (C) 2008-2018 by Harald Welte <laforge@gnumonks.org> + * + * 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/>. + * + */ + diff --git a/src/osmo-bsc/abis_nm.c b/src/osmo-bsc/abis_nm.c new file mode 100644 index 000000000..9dc1f62cb --- /dev/null +++ b/src/osmo-bsc/abis_nm.c @@ -0,0 +1,2971 @@ +/* GSM Network Management (OML) messages on the A-bis interface + * 3GPP TS 12.21 version 8.0.0 Release 1999 / ETSI TS 100 623 V8.0.0 */ + +/* (C) 2008-2018 by Harald Welte <laforge@gnumonks.org> + * + * 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 <errno.h> +#include <unistd.h> +#include <stdio.h> +#include <fcntl.h> +#include <stdlib.h> +#include <libgen.h> +#include <time.h> +#include <limits.h> + +#include <sys/stat.h> +#include <netinet/in.h> +#include <arpa/inet.h> + +#include <osmocom/bsc/gsm_data.h> +#include <osmocom/bsc/debug.h> +#include <osmocom/core/msgb.h> +#include <osmocom/gsm/protocol/gsm_12_21.h> +#include <osmocom/gsm/tlv.h> +#include <osmocom/gsm/abis_nm.h> +#include <osmocom/core/talloc.h> +#include <osmocom/core/utils.h> +#include <osmocom/bsc/abis_nm.h> +#include <osmocom/bsc/misdn.h> +#include <osmocom/bsc/signal.h> +#include <osmocom/abis/e1_input.h> +#include <osmocom/bsc/chan_alloc.h> +#include <osmocom/gsm/bts_features.h> + +#define OM_ALLOC_SIZE 1024 +#define OM_HEADROOM_SIZE 128 +#define IPACC_SEGMENT_SIZE 245 + +#define LOGPFOH(ss, lvl, foh, fmt, args ...) LOGP(ss, lvl, "%s: " fmt, abis_nm_dump_foh(foh), ## args) +#define DEBUGPFOH(ss, foh, fmt, args ...) LOGPFOH(ss, LOGL_DEBUG, foh, fmt, ## args) + +int abis_nm_tlv_parse(struct tlv_parsed *tp, struct gsm_bts *bts, const uint8_t *buf, int len) +{ + if (!bts->model) + return -EIO; + return tlv_parse(tp, &bts->model->nm_att_tlvdef, buf, len, 0, 0); +} + +static int is_in_arr(enum abis_nm_msgtype mt, const enum abis_nm_msgtype *arr, int size) +{ + int i; + + for (i = 0; i < size; i++) { + if (arr[i] == mt) + return 1; + } + + return 0; +} + +#if 0 +/* is this msgtype the usual ACK/NACK type ? */ +static int is_ack_nack(enum abis_nm_msgtype mt) +{ + return !is_in_arr(mt, no_ack_nack, ARRAY_SIZE(no_ack_nack)); +} +#endif + +/* is this msgtype a report ? */ +static int is_report(enum abis_nm_msgtype mt) +{ + return is_in_arr(mt, abis_nm_reports, ARRAY_SIZE(abis_nm_reports)); +} + +#define MT_ACK(x) (x+1) +#define MT_NACK(x) (x+2) + +static void fill_om_hdr(struct abis_om_hdr *oh, uint8_t len) +{ + oh->mdisc = ABIS_OM_MDISC_FOM; + oh->placement = ABIS_OM_PLACEMENT_ONLY; + oh->sequence = 0; + oh->length = len; +} + +static struct abis_om_fom_hdr *fill_om_fom_hdr(struct abis_om_hdr *oh, uint8_t len, + uint8_t msg_type, uint8_t obj_class, + uint8_t bts_nr, uint8_t trx_nr, uint8_t ts_nr) +{ + struct abis_om_fom_hdr *foh = + (struct abis_om_fom_hdr *) oh->data; + + fill_om_hdr(oh, len+sizeof(*foh)); + foh->msg_type = msg_type; + foh->obj_class = obj_class; + foh->obj_inst.bts_nr = bts_nr; + foh->obj_inst.trx_nr = trx_nr; + foh->obj_inst.ts_nr = ts_nr; + return foh; +} + +static struct msgb *nm_msgb_alloc(void) +{ + return msgb_alloc_headroom(OM_ALLOC_SIZE, OM_HEADROOM_SIZE, + "OML"); +} + +int _abis_nm_sendmsg(struct msgb *msg) +{ + msg->l2h = msg->data; + + if (!msg->dst) { + LOGP(DNM, LOGL_ERROR, "%s: msg->dst == NULL\n", __func__); + return -EINVAL; + } + + return abis_sendmsg(msg); +} + +/* Send a OML NM Message from BSC to BTS */ +static int abis_nm_queue_msg(struct gsm_bts *bts, struct msgb *msg) +{ + msg->dst = bts->oml_link; + + /* queue OML messages */ + if (llist_empty(&bts->abis_queue) && !bts->abis_nm_pend) { + bts->abis_nm_pend = OBSC_NM_W_ACK_CB(msg); + return _abis_nm_sendmsg(msg); + } else { + msgb_enqueue(&bts->abis_queue, msg); + return 0; + } + +} + +int abis_nm_sendmsg(struct gsm_bts *bts, struct msgb *msg) +{ + OBSC_NM_W_ACK_CB(msg) = 1; + return abis_nm_queue_msg(bts, msg); +} + +static int abis_nm_sendmsg_direct(struct gsm_bts *bts, struct msgb *msg) +{ + OBSC_NM_W_ACK_CB(msg) = 0; + return abis_nm_queue_msg(bts, msg); +} + +static int abis_nm_rcvmsg_sw(struct msgb *mb); + +/* Update the administrative state of a given object in our in-memory data + * structures and send an event to the higher layer */ +static int update_admstate(struct gsm_bts *bts, uint8_t obj_class, + struct abis_om_obj_inst *obj_inst, uint8_t adm_state) +{ + struct gsm_nm_state *nm_state, new_state; + struct nm_statechg_signal_data nsd; + + memset(&nsd, 0, sizeof(nsd)); + + nsd.obj = gsm_objclass2obj(bts, obj_class, obj_inst); + if (!nsd.obj) + return -EINVAL; + nm_state = gsm_objclass2nmstate(bts, obj_class, obj_inst); + if (!nm_state) + return -1; + + new_state = *nm_state; + new_state.administrative = adm_state; + + nsd.bts = bts; + nsd.obj_class = obj_class; + nsd.old_state = nm_state; + nsd.new_state = &new_state; + nsd.obj_inst = obj_inst; + osmo_signal_dispatch(SS_NM, S_NM_STATECHG_ADM, &nsd); + + nm_state->administrative = adm_state; + + return 0; +} + +static int abis_nm_rx_statechg_rep(struct msgb *mb) +{ + struct abis_om_hdr *oh = msgb_l2(mb); + 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 tlv_parsed tp; + struct gsm_nm_state *nm_state, new_state; + + memset(&new_state, 0, sizeof(new_state)); + + nm_state = gsm_objclass2nmstate(bts, foh->obj_class, &foh->obj_inst); + if (!nm_state) { + LOGPFOH(DNM, LOGL_ERROR, foh, "unknown managed object\n"); + return -EINVAL; + } + + new_state = *nm_state; + + DEBUGPFOH(DNM, foh, "STATE CHG: "); + abis_nm_tlv_parse(&tp, bts, foh->data, oh->length-sizeof(*foh)); + if (TLVP_PRESENT(&tp, NM_ATT_OPER_STATE)) { + new_state.operational = *TLVP_VAL(&tp, NM_ATT_OPER_STATE); + DEBUGPC(DNM, "OP_STATE=%s ", + abis_nm_opstate_name(new_state.operational)); + } + if (TLVP_PRESENT(&tp, NM_ATT_AVAIL_STATUS)) { + if (TLVP_LEN(&tp, NM_ATT_AVAIL_STATUS) == 0) + new_state.availability = 0xff; + else + new_state.availability = *TLVP_VAL(&tp, NM_ATT_AVAIL_STATUS); + DEBUGPC(DNM, "AVAIL=%s(%02x) ", + abis_nm_avail_name(new_state.availability), + new_state.availability); + } else + new_state.availability = 0xff; + if (TLVP_PRESENT(&tp, NM_ATT_ADM_STATE)) { + new_state.administrative = *TLVP_VAL(&tp, NM_ATT_ADM_STATE); + DEBUGPC(DNM, "ADM=%2s ", + get_value_string(abis_nm_adm_state_names, + new_state.administrative)); + } + DEBUGPC(DNM, "\n"); + + if ((new_state.administrative != 0 && nm_state->administrative == 0) || + new_state.operational != nm_state->operational || + new_state.availability != nm_state->availability) { + /* Update the operational state of a given object in our in-memory data + * structures and send an event to the higher layer */ + struct nm_statechg_signal_data nsd; + nsd.obj = gsm_objclass2obj(bts, foh->obj_class, &foh->obj_inst); + nsd.obj_class = foh->obj_class; + nsd.old_state = nm_state; + nsd.new_state = &new_state; + nsd.obj_inst = &foh->obj_inst; + nsd.bts = bts; + osmo_signal_dispatch(SS_NM, S_NM_STATECHG_OPER, &nsd); + nm_state->operational = new_state.operational; + nm_state->availability = new_state.availability; + if (nm_state->administrative == 0) + nm_state->administrative = new_state.administrative; + } +#if 0 + if (op_state == 1) { + /* try to enable objects that are disabled */ + abis_nm_opstart(bts, foh->obj_class, + foh->obj_inst.bts_nr, + foh->obj_inst.trx_nr, + foh->obj_inst.ts_nr); + } +#endif + return 0; +} + +static inline void log_oml_fail_rep(const struct gsm_bts *bts, const char *type, + const char *severity, const uint8_t *p_val, + const char *text) +{ + enum abis_nm_pcause_type pcause = p_val[0]; + enum abis_mm_event_causes cause = osmo_load16be(p_val + 1); + + LOGPC(DNM, LOGL_ERROR, "BTS %u: Failure Event Report: ", bts->nr); + if (type) + LOGPC(DNM, LOGL_ERROR, "Type=%s, ", type); + if (severity) + LOGPC(DNM, LOGL_ERROR, "Severity=%s, ", severity); + + LOGPC(DNM, LOGL_ERROR, "Probable cause=%s: ", + get_value_string(abis_nm_pcause_type_names, pcause)); + + if (pcause == NM_PCAUSE_T_MANUF) + LOGPC(DNM, LOGL_ERROR, "%s, ", + get_value_string(abis_mm_event_cause_names, cause)); + else + LOGPC(DNM, LOGL_ERROR, "%02X %02X ", p_val[1], p_val[2]); + + if (text) { + LOGPC(DNM, LOGL_ERROR, "Additional Text=%s. ", text); + } + + LOGPC(DNM, LOGL_ERROR, "\n"); +} + +static inline void handle_manufact_report(struct gsm_bts *bts, const uint8_t *p_val, const char *type, + const char *severity, const char *text) +{ + enum abis_mm_event_causes cause = osmo_load16be(p_val + 1); + + switch (cause) { + case OSMO_EVT_PCU_VERS: + if (text) { + LOGPC(DNM, LOGL_NOTICE, "BTS %u reported connected PCU version %s\n", bts->nr, text); + osmo_strlcpy(bts->pcu_version, text, sizeof(bts->pcu_version)); + } else { + LOGPC(DNM, LOGL_ERROR, "BTS %u reported PCU disconnection.\n", bts->nr); + bts->pcu_version[0] = '\0'; + } + break; + default: + log_oml_fail_rep(bts, type, severity, p_val, text); + }; +} + +static int rx_fail_evt_rep(struct msgb *mb, struct gsm_bts *bts) +{ + struct abis_om_hdr *oh = msgb_l2(mb); + struct abis_om_fom_hdr *foh = msgb_l3(mb); + struct e1inp_sign_link *sign_link = mb->dst; + struct tlv_parsed tp; + int rc = 0; + const uint8_t *p_val = NULL; + char *p_text = NULL; + const char *e_type = NULL, *severity = NULL; + + abis_nm_tlv_parse(&tp, sign_link->trx->bts, foh->data, + oh->length-sizeof(*foh)); + + if (TLVP_PRESENT(&tp, NM_ATT_ADD_TEXT)) { + p_val = TLVP_VAL(&tp, NM_ATT_ADD_TEXT); + p_text = talloc_strndup(tall_bsc_ctx, (const char *) p_val, + TLVP_LEN(&tp, NM_ATT_ADD_TEXT)); + } + + if (TLVP_PRESENT(&tp, NM_ATT_EVENT_TYPE)) + e_type = abis_nm_event_type_name(*TLVP_VAL(&tp, + NM_ATT_EVENT_TYPE)); + + if (TLVP_PRESENT(&tp, NM_ATT_SEVERITY)) + severity = abis_nm_severity_name(*TLVP_VAL(&tp, + NM_ATT_SEVERITY)); + + if (TLVP_PRESENT(&tp, NM_ATT_PROB_CAUSE)) { + p_val = TLVP_VAL(&tp, NM_ATT_PROB_CAUSE); + + switch (p_val[0]) { + case NM_PCAUSE_T_MANUF: + handle_manufact_report(bts, p_val, e_type, severity, + p_text); + break; + default: + log_oml_fail_rep(bts, e_type, severity, p_val, p_text); + }; + } else { + LOGPFOH(DNM, LOGL_ERROR, foh, "BTS%u: Failure Event Report without " + "Probable Cause?!\n", bts->nr); + rc = -EINVAL; + } + + if (p_text) + talloc_free(p_text); + + return rc; +} + +static int abis_nm_rcvmsg_report(struct msgb *mb, struct gsm_bts *bts) +{ + struct abis_om_fom_hdr *foh = msgb_l3(mb); + uint8_t mt = foh->msg_type; + + switch (mt) { + case NM_MT_STATECHG_EVENT_REP: + return abis_nm_rx_statechg_rep(mb); + break; + case NM_MT_SW_ACTIVATED_REP: + DEBUGPFOH(DNM, foh, "Software Activated Report\n"); + osmo_signal_dispatch(SS_NM, S_NM_SW_ACTIV_REP, mb); + break; + case NM_MT_FAILURE_EVENT_REP: + rx_fail_evt_rep(mb, bts); + osmo_signal_dispatch(SS_NM, S_NM_FAIL_REP, mb); + break; + case NM_MT_TEST_REP: + DEBUGPFOH(DNM, foh, "Test Report\n"); + osmo_signal_dispatch(SS_NM, S_NM_TEST_REP, mb); + break; + default: + LOGPFOH(DNM, LOGL_NOTICE, foh, "unknown NM report MT 0x%02x\n", mt); + break; + }; + + return 0; +} + +/* Activate the specified software into the BTS */ +static int ipacc_sw_activate(struct gsm_bts *bts, uint8_t obj_class, uint8_t i0, uint8_t i1, + uint8_t i2, const struct abis_nm_sw_desc *sw_desc) +{ + struct abis_om_hdr *oh; + struct msgb *msg = nm_msgb_alloc(); + uint16_t len = abis_nm_sw_desc_len(sw_desc, true); + + oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); + fill_om_fom_hdr(oh, len, NM_MT_ACTIVATE_SW, obj_class, i0, i1, i2); + abis_nm_put_sw_desc(msg, sw_desc, true); + + return abis_nm_sendmsg(bts, msg); +} + +int abis_nm_select_newest_sw(const struct abis_nm_sw_desc *sw_descr, + const size_t size) +{ + int res = 0; + int i; + + for (i = 1; i < size; ++i) { + if (memcmp(sw_descr[res].file_version, sw_descr[i].file_version, + OSMO_MIN(sw_descr[i].file_version_len, + sw_descr[res].file_version_len)) < 0) { + res = i; + } + } + + return res; +} + +static inline bool handle_attr(const struct gsm_bts *bts, enum bts_attribute id, uint8_t *val, uint8_t len) +{ + switch (id) { + case BTS_TYPE_VARIANT: + LOGP(DNM, LOGL_NOTICE, "BTS%u reported variant: %s\n", bts->nr, val); + break; + case BTS_SUB_MODEL: + LOGP(DNM, LOGL_NOTICE, "BTS%u reported submodel: %s\n", bts->nr, val); + break; + default: + return false; + } + return true; +} + +/* Parse Attribute Response Info - return pointer to the actual content */ +static inline const uint8_t *parse_attr_resp_info_unreported(uint8_t bts_nr, const uint8_t *ari, uint16_t ari_len, uint16_t *out_len) +{ + uint8_t num_unreported = ari[0], i; + + DEBUGP(DNM, "BTS%u Get Attributes Response Info: %u bytes total with %u unreported attributes\n", + bts_nr, ari_len, num_unreported); + + /* +1 because we have to account for number of unreported attributes, prefixing the list: */ + for (i = 0; i < num_unreported; i++) + LOGP(DNM, LOGL_ERROR, "BTS%u Attribute %s is unreported\n", + bts_nr, get_value_string(abis_nm_att_names, ari[i + 1])); + + /* the data starts right after the list of unreported attributes + space for length of that list */ + *out_len = ari_len - (num_unreported + 2); + + return ari + num_unreported + 1; /* we have to account for 1st byte with number of unreported attributes */ +} + +/* Parse Attribute Response Info content for 3GPP TS 52.021 §9.4.30 Manufacturer Id */ +static inline const uint8_t *parse_attr_resp_info_manuf_id(struct gsm_bts *bts, const uint8_t *data, uint16_t *data_len) +{ + struct tlv_parsed tp; + uint16_t m_id_len = 0; + uint8_t adjust = 0, i; + + abis_nm_tlv_parse(&tp, bts, data, *data_len); + if (TLVP_PRES_LEN(&tp, NM_ATT_MANUF_ID, 2)) { + m_id_len = TLVP_LEN(&tp, NM_ATT_MANUF_ID); + + /* log potential BTS feature vector overflow */ + if (m_id_len > sizeof(bts->_features_data)) + LOGP(DNM, LOGL_NOTICE, "BTS%u Get Attributes Response: feature vector is truncated to %u bytes\n", + bts->nr, MAX_BTS_FEATURES/8); + + /* check that max. expected BTS attribute is above given feature vector length */ + if (m_id_len > OSMO_BYTES_FOR_BITS(_NUM_BTS_FEAT)) + LOGP(DNM, LOGL_NOTICE, "BTS%u Get Attributes Response: reported unexpectedly long (%u bytes) " + "feature vector - most likely it was compiled against newer BSC headers. " + "Consider upgrading your BSC to later version.\n", + bts->nr, m_id_len); + + memcpy(bts->_features_data, TLVP_VAL(&tp, NM_ATT_MANUF_ID), sizeof(bts->_features_data)); + adjust = m_id_len + 3; /* adjust for parsed TL16V struct */ + + for (i = 0; i < _NUM_BTS_FEAT; i++) + if (osmo_bts_has_feature(&bts->features, i) != osmo_bts_has_feature(&bts->model->features, i)) + LOGP(DNM, LOGL_NOTICE, "BTS%u feature '%s' reported via OML does not match statically " + "set feature: %u != %u. Please fix.\n", bts->nr, + get_value_string(osmo_bts_features_descs, i), + osmo_bts_has_feature(&bts->features, i), osmo_bts_has_feature(&bts->model->features, i)); + } + + *data_len -= adjust; + + return data + adjust; +} + +/* Parse Attribute Response Info content for 3GPP TS 52.021 §9.4.28 Manufacturer Dependent State */ +static inline const uint8_t *parse_attr_resp_info_manuf_state(const struct gsm_bts_trx *trx, const uint8_t *data, uint16_t *data_len) +{ + struct tlv_parsed tp; + const uint8_t *power; + uint8_t adjust = 0; + + if (!trx) /* this attribute does not make sense on BTS level, only on TRX level */ + return data; + + abis_nm_tlv_parse(&tp, trx->bts, data, *data_len); + if (TLVP_PRES_LEN(&tp, NM_ATT_MANUF_STATE, 1)) { + power = TLVP_VAL(&tp, NM_ATT_MANUF_STATE); + LOGP(DNM, LOGL_NOTICE, "%s Get Attributes Response: nominal power is %u\n", gsm_trx_name(trx), *power); + adjust = 2; /* adjust for parsed TV struct */ + } + + *data_len -= adjust; + + return data + adjust; +} + +/* Handle 3GPP TS 52.021 §9.4.64 Get Attribute Response Info */ +static int abis_nm_rx_get_attr_resp(struct msgb *mb, const struct gsm_bts_trx *trx) +{ + struct abis_om_hdr *oh = msgb_l2(mb); + struct abis_om_fom_hdr *foh = msgb_l3(mb); + struct e1inp_sign_link *sign_link = mb->dst; + struct gsm_bts *bts = trx ? trx->bts : sign_link->trx->bts; + struct tlv_parsed tp; + const uint8_t *data; + int i; + uint16_t data_len; + int rc; + struct abis_nm_sw_desc sw_descr[MAX_BTS_ATTR]; + + DEBUGPFOH(DNM, foh, "Get Attributes Response for BTS%u\n", bts->nr); + + abis_nm_tlv_parse(&tp, bts, foh->data, oh->length-sizeof(*foh)); + if (!TLVP_PRES_LEN(&tp, NM_ATT_GET_ARI, 1)) { + LOGPFOH(DNM, LOGL_ERROR, foh, "BTS%u: Get Attr Response without Response Info?!\n", + bts->nr); + return -EINVAL; + } + + data = parse_attr_resp_info_unreported(bts->nr, TLVP_VAL(&tp, NM_ATT_GET_ARI), TLVP_LEN(&tp, NM_ATT_GET_ARI), + &data_len); + + data = parse_attr_resp_info_manuf_state(trx, data, &data_len); + data = parse_attr_resp_info_manuf_id(bts, data, &data_len); + + /* after parsing manufacturer-specific attributes there's list of replies in form of sw-conf structure: */ + rc = abis_nm_get_sw_conf(data, data_len, &sw_descr[0], ARRAY_SIZE(sw_descr)); + if (rc > 0) { + for (i = 0; i < rc; i++) { + if (!handle_attr(bts, str2btsattr((const char *)sw_descr[i].file_id), + sw_descr[i].file_version, sw_descr[i].file_version_len)) + LOGPFOH(DNM, LOGL_NOTICE, foh, "BTS%u: ARI reported sw[%d/%d]: %s " + "is %s\n", bts->nr, i, rc, sw_descr[i].file_id, + sw_descr[i].file_version); + } + } else { + LOGPFOH(DNM, LOGL_ERROR, foh, "BTS%u: failed to parse SW-Config part of " + "Get Attribute Response Info: %s\n", bts->nr, strerror(-rc)); + } + + return 0; +} + +/* 3GPP TS 52.021 §6.2.5 */ +static int abis_nm_rx_sw_act_req(struct msgb *mb) +{ + struct abis_om_hdr *oh = msgb_l2(mb); + struct abis_om_fom_hdr *foh = msgb_l3(mb); + struct e1inp_sign_link *sign_link = mb->dst; + struct tlv_parsed tp; + const uint8_t *sw_config; + int ret, sw_config_len, len; + struct abis_nm_sw_desc sw_descr[MAX_BTS_ATTR]; + + DEBUGPFOH(DNM, foh, "Software Activate Request, ACKing and Activating\n"); + + ret = abis_nm_sw_act_req_ack(sign_link->trx->bts, foh->obj_class, + foh->obj_inst.bts_nr, + foh->obj_inst.trx_nr, + foh->obj_inst.ts_nr, 0, + foh->data, oh->length-sizeof(*foh)); + if (ret != 0) { + LOGPFOH(DNM, LOGL_ERROR, foh, "Sending SW ActReq ACK failed: %d\n", ret); + return ret; + } + + abis_nm_tlv_parse(&tp, sign_link->trx->bts, foh->data, oh->length-sizeof(*foh)); + sw_config = TLVP_VAL(&tp, NM_ATT_SW_CONFIG); + sw_config_len = TLVP_LEN(&tp, NM_ATT_SW_CONFIG); + if (!TLVP_PRESENT(&tp, NM_ATT_SW_CONFIG)) { + LOGPFOH(DNM, LOGL_ERROR, foh, "SW config not found! Can't continue.\n"); + return -EINVAL; + } else { + DEBUGP(DNM, "Found SW config: %s\n", osmo_hexdump(sw_config, sw_config_len)); + } + + /* Parse up to two sw descriptions from the data */ + len = abis_nm_get_sw_conf(sw_config, sw_config_len, &sw_descr[0], + ARRAY_SIZE(sw_descr)); + if (len <= 0) { + LOGPFOH(DNM, LOGL_ERROR, foh, "Failed to parse SW Config.\n"); + return -EINVAL; + } + + ret = abis_nm_select_newest_sw(&sw_descr[0], len); + DEBUGPFOH(DNM, foh, "Selected sw description %d of %d\n", ret, len); + + return ipacc_sw_activate(sign_link->trx->bts, foh->obj_class, + foh->obj_inst.bts_nr, + foh->obj_inst.trx_nr, + foh->obj_inst.ts_nr, + &sw_descr[ret]); +} + +/* Receive a CHANGE_ADM_STATE_ACK, parse the TLV and update local state */ +static int abis_nm_rx_chg_adm_state_ack(struct msgb *mb) +{ + struct abis_om_hdr *oh = msgb_l2(mb); + struct abis_om_fom_hdr *foh = msgb_l3(mb); + struct e1inp_sign_link *sign_link = mb->dst; + struct tlv_parsed tp; + uint8_t adm_state; + + abis_nm_tlv_parse(&tp, sign_link->trx->bts, foh->data, oh->length-sizeof(*foh)); + if (!TLVP_PRESENT(&tp, NM_ATT_ADM_STATE)) + return -EINVAL; + + adm_state = *TLVP_VAL(&tp, NM_ATT_ADM_STATE); + + return update_admstate(sign_link->trx->bts, foh->obj_class, &foh->obj_inst, adm_state); +} + +static int abis_nm_rx_lmt_event(struct msgb *mb) +{ + struct abis_om_hdr *oh = msgb_l2(mb); + struct abis_om_fom_hdr *foh = msgb_l3(mb); + struct e1inp_sign_link *sign_link = mb->dst; + struct tlv_parsed tp; + + DEBUGPFOH(DNM, foh, "LMT Event "); + abis_nm_tlv_parse(&tp, sign_link->trx->bts, foh->data, oh->length-sizeof(*foh)); + if (TLVP_PRESENT(&tp, NM_ATT_BS11_LMT_LOGON_SESSION) && + TLVP_LEN(&tp, NM_ATT_BS11_LMT_LOGON_SESSION) >= 1) { + uint8_t onoff = *TLVP_VAL(&tp, NM_ATT_BS11_LMT_LOGON_SESSION); + DEBUGPC(DNM, "LOG%s ", onoff ? "ON" : "OFF"); + } + if (TLVP_PRESENT(&tp, NM_ATT_BS11_LMT_USER_ACC_LEV) && + TLVP_LEN(&tp, NM_ATT_BS11_LMT_USER_ACC_LEV) >= 1) { + uint8_t level = *TLVP_VAL(&tp, NM_ATT_BS11_LMT_USER_ACC_LEV); + DEBUGPC(DNM, "Level=%u ", level); + } + if (TLVP_PRESENT(&tp, NM_ATT_BS11_LMT_USER_NAME) && + TLVP_LEN(&tp, NM_ATT_BS11_LMT_USER_NAME) >= 1) { + char *name = (char *) TLVP_VAL(&tp, NM_ATT_BS11_LMT_USER_NAME); + DEBUGPC(DNM, "Username=%s ", name); + } + DEBUGPC(DNM, "\n"); + /* FIXME: parse LMT LOGON TIME */ + return 0; +} + +static int abis_nm_rx_opstart_ack(struct msgb *mb) +{ + struct abis_om_fom_hdr *foh = msgb_l3(mb); + DEBUGPFOH(DNM, foh, "Opstart ACK\n"); + osmo_signal_dispatch(SS_NM, S_NM_OPSTART_ACK, foh); + return 0; +} + +bool all_trx_rsl_connected_unlocked(const struct gsm_bts *bts) +{ + const struct gsm_bts_trx *trx; + + if (bts->mo.nm_state.administrative == NM_STATE_LOCKED) + return false; + + if (bts->gprs.mode != BTS_GPRS_NONE) { + if (bts->gprs.cell.mo.nm_state.administrative == NM_STATE_LOCKED) + return false; + + if (bts->gprs.nse.mo.nm_state.administrative == NM_STATE_LOCKED) + return false; + + if (bts->gprs.nsvc[0].mo.nm_state.administrative == NM_STATE_LOCKED && + bts->gprs.nsvc[1].mo.nm_state.administrative == NM_STATE_LOCKED) + return false; + } + + llist_for_each_entry(trx, &bts->trx_list, list) { + if (!trx->rsl_link) + return false; + + if (!trx_is_usable(trx)) + return false; + + if (trx->mo.nm_state.administrative == NM_STATE_LOCKED) + return false; + } + + return true; +} + +char *get_model_oml_status(const struct gsm_bts *bts) +{ + if (bts->model->oml_status) + return bts->model->oml_status(bts); + + return "unknown"; +} + +void abis_nm_queue_send_next(struct gsm_bts *bts) +{ + int wait = 0; + struct msgb *msg; + /* the queue is empty */ + while (!llist_empty(&bts->abis_queue)) { + msg = msgb_dequeue(&bts->abis_queue); + wait = OBSC_NM_W_ACK_CB(msg); + _abis_nm_sendmsg(msg); + + if (wait) + break; + } + + bts->abis_nm_pend = wait; +} + +/* Receive a OML NM Message from BTS */ +static int abis_nm_rcvmsg_fom(struct msgb *mb) +{ + struct abis_om_hdr *oh = msgb_l2(mb); + struct abis_om_fom_hdr *foh = msgb_l3(mb); + struct e1inp_sign_link *sign_link = mb->dst; + uint8_t mt = foh->msg_type; + /* sign_link might get deleted via osmo_signal_dispatch -> save bts */ + struct gsm_bts *bts = sign_link->trx->bts; + int ret = 0; + + /* check for unsolicited message */ + if (is_report(mt)) + return abis_nm_rcvmsg_report(mb, bts); + + if (is_in_arr(mt, abis_nm_sw_load_msgs, ARRAY_SIZE(abis_nm_sw_load_msgs))) + return abis_nm_rcvmsg_sw(mb); + + if (is_in_arr(mt, abis_nm_nacks, ARRAY_SIZE(abis_nm_nacks))) { + struct nm_nack_signal_data nack_data; + struct tlv_parsed tp; + + LOGPFOH(DNM, LOGL_NOTICE, foh, "%s NACK ", abis_nm_nack_name(mt)); + + abis_nm_tlv_parse(&tp, bts, foh->data, oh->length-sizeof(*foh)); + if (TLVP_PRESENT(&tp, NM_ATT_NACK_CAUSES)) + DEBUGPC(DNM, "CAUSE=%s\n", + abis_nm_nack_cause_name(*TLVP_VAL(&tp, NM_ATT_NACK_CAUSES))); + else + DEBUGPC(DNM, "\n"); + + nack_data.msg = mb; + nack_data.mt = mt; + nack_data.bts = bts; + osmo_signal_dispatch(SS_NM, S_NM_NACK, &nack_data); + abis_nm_queue_send_next(bts); + return 0; + } +#if 0 + /* check if last message is to be acked */ + if (is_ack_nack(nmh->last_msgtype)) { + if (mt == MT_ACK(nmh->last_msgtype)) { + DEBUGP(DNM, "received ACK (0x%x)\n", foh->msg_type); + /* we got our ACK, continue sending the next msg */ + } else if (mt == MT_NACK(nmh->last_msgtype)) { + /* we got a NACK, signal this to the caller */ + DEBUGP(DNM, "received NACK (0x%x)\n", foh->msg_type); + /* FIXME: somehow signal this to the caller */ + } else { + /* really strange things happen */ + return -EINVAL; + } + } +#endif + + switch (mt) { + case NM_MT_CHG_ADM_STATE_ACK: + ret = abis_nm_rx_chg_adm_state_ack(mb); + break; + case NM_MT_SW_ACT_REQ: + ret = abis_nm_rx_sw_act_req(mb); + break; + case NM_MT_BS11_LMT_SESSION: + ret = abis_nm_rx_lmt_event(mb); + break; + case NM_MT_OPSTART_ACK: + abis_nm_rx_opstart_ack(mb); + break; + case NM_MT_SET_CHAN_ATTR_ACK: + DEBUGPFOH(DNM, foh, "Set Channel Attributes ACK\n"); + break; + case NM_MT_SET_RADIO_ATTR_ACK: + DEBUGPFOH(DNM, foh, "Set Radio Carrier Attributes ACK\n"); + break; + case NM_MT_CONN_MDROP_LINK_ACK: + DEBUGPFOH(DNM, foh, "CONN MDROP LINK ACK\n"); + break; + case NM_MT_IPACC_RESTART_ACK: + DEBUGPFOH(DNM, foh, "IPA Restart ACK\n"); + osmo_signal_dispatch(SS_NM, S_NM_IPACC_RESTART_ACK, NULL); + break; + case NM_MT_IPACC_RESTART_NACK: + LOGPFOH(DNM, LOGL_NOTICE, foh, "IPA Restart NACK\n"); + osmo_signal_dispatch(SS_NM, S_NM_IPACC_RESTART_NACK, NULL); + break; + case NM_MT_SET_BTS_ATTR_ACK: + DEBUGPFOH(DNM, foh, "Set BTS Attribute ACK\n"); + break; + case NM_MT_GET_ATTR_RESP: + ret = abis_nm_rx_get_attr_resp(mb, gsm_bts_trx_num(bts, (foh)->obj_inst.trx_nr)); + break; + default: + LOGPFOH(DNM, LOGL_ERROR, foh, "Unhandled message %s\n", + get_value_string(abis_nm_msgtype_names, mt)); + } + + abis_nm_queue_send_next(bts); + return ret; +} + +static int abis_nm_rx_ipacc(struct msgb *mb); + +static int abis_nm_rcvmsg_manuf(struct msgb *mb) +{ + int rc; + struct e1inp_sign_link *sign_link = mb->dst; + int bts_type = sign_link->trx->bts->type; + + switch (bts_type) { + case GSM_BTS_TYPE_NANOBTS: + case GSM_BTS_TYPE_OSMOBTS: + rc = abis_nm_rx_ipacc(mb); + abis_nm_queue_send_next(sign_link->trx->bts); + break; + default: + LOGP(DNM, LOGL_ERROR, "don't know how to parse OML for this " + "BTS type (%u)\n", bts_type); + rc = 0; + break; + } + + return rc; +} + +/* High-Level API */ +/* Entry-point where L2 OML from BTS enters the NM code */ +int abis_nm_rcvmsg(struct msgb *msg) +{ + struct abis_om_hdr *oh = msgb_l2(msg); + int rc = 0; + + /* Various consistency checks */ + if (oh->placement != ABIS_OM_PLACEMENT_ONLY) { + LOGP(DNM, LOGL_ERROR, "ABIS OML placement 0x%x not supported\n", + oh->placement); + if (oh->placement != ABIS_OM_PLACEMENT_FIRST) { + rc = -EINVAL; + goto err; + } + } + if (oh->sequence != 0) { + LOGP(DNM, LOGL_ERROR, "ABIS OML sequence 0x%x != 0x00\n", + oh->sequence); + rc = -EINVAL; + goto err; + } +#if 0 + unsigned int l2_len = msg->tail - (uint8_t *)msgb_l2(msg); + unsigned int hlen = sizeof(*oh) + sizeof(struct abis_om_fom_hdr); + if (oh->length + hlen > l2_len) { + LOGP(DNM, LOGL_ERROR, "ABIS OML truncated message (%u > %u)\n", + oh->length + sizeof(*oh), l2_len); + return -EINVAL; + } + if (oh->length + hlen < l2_len) + LOGP(DNM, LOGL_ERROR, "ABIS OML message with extra trailer?!? (oh->len=%d, sizeof_oh=%d l2_len=%d\n", oh->length, sizeof(*oh), l2_len); +#endif + msg->l3h = (unsigned char *)oh + sizeof(*oh); + + switch (oh->mdisc) { + case ABIS_OM_MDISC_FOM: + rc = abis_nm_rcvmsg_fom(msg); + break; + case ABIS_OM_MDISC_MANUF: + rc = abis_nm_rcvmsg_manuf(msg); + break; + case ABIS_OM_MDISC_MMI: + case ABIS_OM_MDISC_TRAU: + LOGP(DNM, LOGL_ERROR, "unimplemented ABIS OML message discriminator 0x%x\n", + oh->mdisc); + break; + default: + LOGP(DNM, LOGL_ERROR, "unknown ABIS OML message discriminator 0x%x\n", + oh->mdisc); + rc = -EINVAL; + break; + } +err: + msgb_free(msg); + return rc; +} + +#if 0 +/* initialized all resources */ +struct abis_nm_h *abis_nm_init(struct abis_nm_cfg *cfg) +{ + struct abis_nm_h *nmh; + + nmh = malloc(sizeof(*nmh)); + if (!nmh) + return NULL; + + nmh->cfg = cfg; + + return nmh; +} + +/* free all resources */ +void abis_nm_fini(struct abis_nm_h *nmh) +{ + free(nmh); +} +#endif + +/* Here we are trying to define a high-level API that can be used by + * the actual BSC implementation. However, the architecture is currently + * still under design. Ideally the calls to this API would be synchronous, + * while the underlying stack behind the APi runs in a traditional select + * based state machine. + */ + +/* 6.2 Software Load: */ +enum sw_state { + SW_STATE_NONE, + SW_STATE_WAIT_INITACK, + SW_STATE_WAIT_SEGACK, + SW_STATE_WAIT_ENDACK, + SW_STATE_WAIT_ACTACK, + SW_STATE_ERROR, +}; + +struct abis_nm_sw { + struct gsm_bts *bts; + int trx_nr; + gsm_cbfn *cbfn; + void *cb_data; + int forced; + + /* this will become part of the SW LOAD INITIATE */ + uint8_t obj_class; + uint8_t obj_instance[3]; + + uint8_t file_id[255]; + uint8_t file_id_len; + + uint8_t file_version[255]; + uint8_t file_version_len; + + uint8_t window_size; + uint8_t seg_in_window; + + int fd; + FILE *stream; + enum sw_state state; + int last_seg; +}; + +static struct abis_nm_sw g_sw; + +static void sw_add_file_id_and_ver(struct abis_nm_sw *sw, struct msgb *msg) +{ + if (sw->bts->type == GSM_BTS_TYPE_NANOBTS) { + msgb_v_put(msg, NM_ATT_SW_DESCR); + msgb_tl16v_put(msg, NM_ATT_FILE_ID, sw->file_id_len, sw->file_id); + msgb_tl16v_put(msg, NM_ATT_FILE_VERSION, sw->file_version_len, + sw->file_version); + } else if (sw->bts->type == GSM_BTS_TYPE_BS11) { + msgb_tlv_put(msg, NM_ATT_FILE_ID, sw->file_id_len, sw->file_id); + msgb_tlv_put(msg, NM_ATT_FILE_VERSION, sw->file_version_len, + sw->file_version); + } else { + LOGP(DNM, LOGL_ERROR, "Please implement this for the BTS.\n"); + } +} + +/* 6.2.1 / 8.3.1: Load Data Initiate */ +static int sw_load_init(struct abis_nm_sw *sw) +{ + struct abis_om_hdr *oh; + struct msgb *msg = nm_msgb_alloc(); + uint8_t len = 3*2 + sw->file_id_len + sw->file_version_len; + + oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); + fill_om_fom_hdr(oh, len, NM_MT_LOAD_INIT, sw->obj_class, + sw->obj_instance[0], sw->obj_instance[1], + sw->obj_instance[2]); + + sw_add_file_id_and_ver(sw, msg); + msgb_tv_put(msg, NM_ATT_WINDOW_SIZE, sw->window_size); + + return abis_nm_sendmsg(sw->bts, msg); +} + +static int is_last_line(FILE *stream) +{ + char next_seg_buf[256]; + long pos; + + /* check if we're sending the last line */ + pos = ftell(stream); + + /* Did ftell fail? Then we are at the end for sure */ + if (pos < 0) + return 1; + + if (!fgets(next_seg_buf, sizeof(next_seg_buf)-2, stream)) { + int rc = fseek(stream, pos, SEEK_SET); + if (rc < 0) + return rc; + return 1; + } + + fseek(stream, pos, SEEK_SET); + return 0; +} + +/* 6.2.2 / 8.3.2 Load Data Segment */ +static int sw_load_segment(struct abis_nm_sw *sw) +{ + struct abis_om_hdr *oh; + struct msgb *msg = nm_msgb_alloc(); + char seg_buf[256]; + char *line_buf = seg_buf+2; + unsigned char *tlv; + int len; + + oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); + + switch (sw->bts->type) { + case GSM_BTS_TYPE_BS11: + if (fgets(line_buf, sizeof(seg_buf)-2, sw->stream) == NULL) { + perror("fgets reading segment"); + return -EINVAL; + } + seg_buf[0] = 0x00; + + /* check if we're sending the last line */ + sw->last_seg = is_last_line(sw->stream); + if (sw->last_seg) + seg_buf[1] = 0; + else + seg_buf[1] = 1 + sw->seg_in_window++; + + len = strlen(line_buf) + 2; + tlv = msgb_put(msg, TLV_GROSS_LEN(len)); + tlv_put(tlv, NM_ATT_BS11_FILE_DATA, len, (uint8_t *)seg_buf); + /* BS11 wants CR + LF in excess of the TLV length !?! */ + tlv[1] -= 2; + + /* we only now know the exact length for the OM hdr */ + len = strlen(line_buf)+2; + break; + case GSM_BTS_TYPE_NANOBTS: { + osmo_static_assert(sizeof(seg_buf) >= IPACC_SEGMENT_SIZE, buffer_big_enough); + len = read(sw->fd, &seg_buf, IPACC_SEGMENT_SIZE); + if (len < 0) { + perror("read failed"); + return -EINVAL; + } + + if (len != IPACC_SEGMENT_SIZE) + sw->last_seg = 1; + + ++sw->seg_in_window; + msgb_tl16v_put(msg, NM_ATT_IPACC_FILE_DATA, len, (const uint8_t *) seg_buf); + len += 3; + break; + } + default: + LOGP(DNM, LOGL_ERROR, "sw_load_segment needs implementation for the BTS.\n"); + /* FIXME: Other BTS types */ + return -1; + } + + fill_om_fom_hdr(oh, len, NM_MT_LOAD_SEG, sw->obj_class, + sw->obj_instance[0], sw->obj_instance[1], + sw->obj_instance[2]); + + return abis_nm_sendmsg_direct(sw->bts, msg); +} + +/* 6.2.4 / 8.3.4 Load Data End */ +static int sw_load_end(struct abis_nm_sw *sw) +{ + struct abis_om_hdr *oh; + struct msgb *msg = nm_msgb_alloc(); + uint8_t len = 2*2 + sw->file_id_len + sw->file_version_len; + + oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); + fill_om_fom_hdr(oh, len, NM_MT_LOAD_END, sw->obj_class, + sw->obj_instance[0], sw->obj_instance[1], + sw->obj_instance[2]); + + sw_add_file_id_and_ver(sw, msg); + return abis_nm_sendmsg(sw->bts, msg); +} + +/* Activate the specified software into the BTS */ +static int sw_activate(struct abis_nm_sw *sw) +{ + struct abis_om_hdr *oh; + struct msgb *msg = nm_msgb_alloc(); + uint8_t len = 2*2 + sw->file_id_len + sw->file_version_len; + + oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); + fill_om_fom_hdr(oh, len, NM_MT_ACTIVATE_SW, sw->obj_class, + sw->obj_instance[0], sw->obj_instance[1], + sw->obj_instance[2]); + + /* FIXME: this is BS11 specific format */ + msgb_tlv_put(msg, NM_ATT_FILE_ID, sw->file_id_len, sw->file_id); + msgb_tlv_put(msg, NM_ATT_FILE_VERSION, sw->file_version_len, + sw->file_version); + + return abis_nm_sendmsg(sw->bts, msg); +} + +struct sdp_firmware { + char magic[4]; + char more_magic[4]; + unsigned int header_length; + unsigned int file_length; +} __attribute__ ((packed)); + +static int parse_sdp_header(struct abis_nm_sw *sw) +{ + struct sdp_firmware firmware_header; + int rc; + struct stat stat; + + rc = read(sw->fd, &firmware_header, sizeof(firmware_header)); + if (rc != sizeof(firmware_header)) { + LOGP(DNM, LOGL_ERROR, "Could not read SDP file header.\n"); + return -1; + } + + if (strncmp(firmware_header.magic, " SDP", 4) != 0) { + LOGP(DNM, LOGL_ERROR, "The magic number1 is wrong.\n"); + return -1; + } + + if (firmware_header.more_magic[0] != 0x10 || + firmware_header.more_magic[1] != 0x02 || + firmware_header.more_magic[2] != 0x00 || + firmware_header.more_magic[3] != 0x00) { + LOGP(DNM, LOGL_ERROR, "The more magic number is wrong.\n"); + return -1; + } + + + if (fstat(sw->fd, &stat) == -1) { + LOGP(DNM, LOGL_ERROR, "Could not stat the file.\n"); + return -1; + } + + if (ntohl(firmware_header.file_length) != stat.st_size) { + LOGP(DNM, LOGL_ERROR, "The filesizes do not match.\n"); + return -1; + } + + /* go back to the start as we checked the whole filesize.. */ + lseek(sw->fd, 0l, SEEK_SET); + LOGP(DNM, LOGL_NOTICE, "The ipaccess SDP header is not fully understood.\n" + "There might be checksums in the file that are not\n" + "verified and incomplete firmware might be flashed.\n" + "There is absolutely no WARRANTY that flashing will\n" + "work.\n"); + return 0; +} + +static int sw_open_file(struct abis_nm_sw *sw, const char *fname) +{ + char file_id[12+1]; + char file_version[80+1]; + int rc; + + sw->fd = open(fname, O_RDONLY); + if (sw->fd < 0) + return sw->fd; + + switch (sw->bts->type) { + case GSM_BTS_TYPE_BS11: + sw->stream = fdopen(sw->fd, "r"); + if (!sw->stream) { + perror("fdopen"); + return -1; + } + /* read first line and parse file ID and VERSION */ + rc = fscanf(sw->stream, "@(#)%12s:%80s\r\n", + file_id, file_version); + if (rc != 2) { + perror("parsing header line of software file"); + return -1; + } + strcpy((char *)sw->file_id, file_id); + sw->file_id_len = strlen(file_id); + strcpy((char *)sw->file_version, file_version); + sw->file_version_len = strlen(file_version); + /* rewind to start of file */ + rewind(sw->stream); + break; + case GSM_BTS_TYPE_NANOBTS: + /* TODO: extract that from the filename or content */ + rc = parse_sdp_header(sw); + if (rc < 0) { + fprintf(stderr, "Could not parse the ipaccess SDP header\n"); + return -1; + } + + strcpy((char *)sw->file_id, "id"); + sw->file_id_len = 3; + strcpy((char *)sw->file_version, "version"); + sw->file_version_len = 8; + break; + default: + /* We don't know how to treat them yet */ + close(sw->fd); + return -EINVAL; + } + + return 0; +} + +static void sw_close_file(struct abis_nm_sw *sw) +{ + switch (sw->bts->type) { + case GSM_BTS_TYPE_BS11: + fclose(sw->stream); + break; + default: + close(sw->fd); + break; + } +} + +/* Fill the window */ +static int sw_fill_window(struct abis_nm_sw *sw) +{ + int rc; + + while (sw->seg_in_window < sw->window_size) { + rc = sw_load_segment(sw); + if (rc < 0) + return rc; + if (sw->last_seg) + break; + } + return 0; +} + +/* callback function from abis_nm_rcvmsg() handler */ +static int abis_nm_rcvmsg_sw(struct msgb *mb) +{ + struct abis_om_fom_hdr *foh = msgb_l3(mb); + struct e1inp_sign_link *sign_link = mb->dst; + int rc = -1; + struct abis_nm_sw *sw = &g_sw; + enum sw_state old_state = sw->state; + + //DEBUGP(DNM, "state %u, NM MT 0x%02x\n", sw->state, foh->msg_type); + + switch (sw->state) { + case SW_STATE_WAIT_INITACK: + switch (foh->msg_type) { + case NM_MT_LOAD_INIT_ACK: + /* fill window with segments */ + if (sw->cbfn) + sw->cbfn(GSM_HOOK_NM_SWLOAD, + NM_MT_LOAD_INIT_ACK, mb, + sw->cb_data, NULL); + rc = sw_fill_window(sw); + sw->state = SW_STATE_WAIT_SEGACK; + abis_nm_queue_send_next(sign_link->trx->bts); + break; + case NM_MT_LOAD_INIT_NACK: + if (sw->forced) { + DEBUGPFOH(DNM, foh, "FORCED: Ignoring Software Load Init NACK\n"); + if (sw->cbfn) + sw->cbfn(GSM_HOOK_NM_SWLOAD, + NM_MT_LOAD_INIT_ACK, mb, + sw->cb_data, NULL); + rc = sw_fill_window(sw); + sw->state = SW_STATE_WAIT_SEGACK; + } else { + LOGPFOH(DNM, LOGL_NOTICE, foh, "Software Load Init NACK\n"); + /* FIXME: cause */ + if (sw->cbfn) + sw->cbfn(GSM_HOOK_NM_SWLOAD, + NM_MT_LOAD_INIT_NACK, mb, + sw->cb_data, NULL); + sw->state = SW_STATE_ERROR; + } + abis_nm_queue_send_next(sign_link->trx->bts); + break; + } + break; + case SW_STATE_WAIT_SEGACK: + switch (foh->msg_type) { + case NM_MT_LOAD_SEG_ACK: + if (sw->cbfn) + sw->cbfn(GSM_HOOK_NM_SWLOAD, + NM_MT_LOAD_SEG_ACK, mb, + sw->cb_data, NULL); + sw->seg_in_window = 0; + if (!sw->last_seg) { + /* fill window with more segments */ + rc = sw_fill_window(sw); + sw->state = SW_STATE_WAIT_SEGACK; + } else { + /* end the transfer */ + sw->state = SW_STATE_WAIT_ENDACK; + rc = sw_load_end(sw); + } + abis_nm_queue_send_next(sign_link->trx->bts); + break; + case NM_MT_LOAD_ABORT: + if (sw->cbfn) + sw->cbfn(GSM_HOOK_NM_SWLOAD, + NM_MT_LOAD_ABORT, mb, + sw->cb_data, NULL); + break; + } + break; + case SW_STATE_WAIT_ENDACK: + switch (foh->msg_type) { + case NM_MT_LOAD_END_ACK: + sw_close_file(sw); + DEBUGPFOH(DNM, foh, "Software Load End (BTS %u)\n", sw->bts->nr); + sw->state = SW_STATE_NONE; + if (sw->cbfn) + sw->cbfn(GSM_HOOK_NM_SWLOAD, + NM_MT_LOAD_END_ACK, mb, + sw->cb_data, NULL); + rc = 0; + abis_nm_queue_send_next(sign_link->trx->bts); + break; + case NM_MT_LOAD_END_NACK: + if (sw->forced) { + DEBUGPFOH(DNM, foh, "FORCED: Ignoring Software Load End NACK\n"); + sw->state = SW_STATE_NONE; + if (sw->cbfn) + sw->cbfn(GSM_HOOK_NM_SWLOAD, + NM_MT_LOAD_END_ACK, mb, + sw->cb_data, NULL); + } else { + LOGPFOH(DNM, LOGL_NOTICE, foh, "Software Load End NACK\n"); + /* FIXME: cause */ + sw->state = SW_STATE_ERROR; + if (sw->cbfn) + sw->cbfn(GSM_HOOK_NM_SWLOAD, + NM_MT_LOAD_END_NACK, mb, + sw->cb_data, NULL); + } + abis_nm_queue_send_next(sign_link->trx->bts); + break; + } + case SW_STATE_WAIT_ACTACK: + switch (foh->msg_type) { + case NM_MT_ACTIVATE_SW_ACK: + /* we're done */ + LOGPFOH(DNM, LOGL_INFO, foh, "Activate Software DONE!\n"); + sw->state = SW_STATE_NONE; + rc = 0; + if (sw->cbfn) + sw->cbfn(GSM_HOOK_NM_SWLOAD, + NM_MT_ACTIVATE_SW_ACK, mb, + sw->cb_data, NULL); + abis_nm_queue_send_next(sign_link->trx->bts); + break; + case NM_MT_ACTIVATE_SW_NACK: + LOGPFOH(DNM, LOGL_ERROR, foh, "Activate Software NACK\n"); + /* FIXME: cause */ + sw->state = SW_STATE_ERROR; + if (sw->cbfn) + sw->cbfn(GSM_HOOK_NM_SWLOAD, + NM_MT_ACTIVATE_SW_NACK, mb, + sw->cb_data, NULL); + abis_nm_queue_send_next(sign_link->trx->bts); + break; + } + case SW_STATE_NONE: + switch (foh->msg_type) { + case NM_MT_ACTIVATE_SW_ACK: + rc = 0; + break; + } + break; + case SW_STATE_ERROR: + break; + } + + if (rc) + LOGPFOH(DNM, LOGL_ERROR, foh, "unexpected NM MT 0x%02x in state %u -> %u\n", + foh->msg_type, old_state, sw->state); + + return rc; +} + +/* Load the specified software into the BTS */ +int abis_nm_software_load(struct gsm_bts *bts, int trx_nr, const char *fname, + uint8_t win_size, int forced, + gsm_cbfn *cbfn, void *cb_data) +{ + struct abis_nm_sw *sw = &g_sw; + int rc; + + DEBUGP(DNM, "Software Load (BTS %u, File \"%s\")\n", bts->nr, fname); + + if (sw->state != SW_STATE_NONE) + return -EBUSY; + + sw->bts = bts; + sw->trx_nr = trx_nr; + + switch (bts->type) { + case GSM_BTS_TYPE_BS11: + sw->obj_class = NM_OC_SITE_MANAGER; + sw->obj_instance[0] = 0xff; + sw->obj_instance[1] = 0xff; + sw->obj_instance[2] = 0xff; + break; + case GSM_BTS_TYPE_NANOBTS: + sw->obj_class = NM_OC_BASEB_TRANSC; + sw->obj_instance[0] = sw->bts->nr; + sw->obj_instance[1] = sw->trx_nr; + sw->obj_instance[2] = 0xff; + break; + case GSM_BTS_TYPE_UNKNOWN: + default: + LOGPC(DNM, LOGL_ERROR, "Software Load not properly implemented.\n"); + return -1; + break; + } + sw->window_size = win_size; + sw->state = SW_STATE_WAIT_INITACK; + sw->cbfn = cbfn; + sw->cb_data = cb_data; + sw->forced = forced; + + rc = sw_open_file(sw, fname); + if (rc < 0) { + sw->state = SW_STATE_NONE; + return rc; + } + + return sw_load_init(sw); +} + +int abis_nm_software_load_status(struct gsm_bts *bts) +{ + struct abis_nm_sw *sw = &g_sw; + struct stat st; + int rc, percent; + + rc = fstat(sw->fd, &st); + if (rc < 0) { + perror("ERROR during stat"); + return rc; + } + + if (sw->stream) + percent = (ftell(sw->stream) * 100) / st.st_size; + else + percent = (lseek(sw->fd, 0, SEEK_CUR) * 100) / st.st_size; + return percent; +} + +/* Activate the specified software into the BTS */ +int abis_nm_software_activate(struct gsm_bts *bts, const char *fname, + gsm_cbfn *cbfn, void *cb_data) +{ + struct abis_nm_sw *sw = &g_sw; + int rc; + + DEBUGP(DNM, "Activating Software (BTS %u, File \"%s\")\n", bts->nr, fname); + + if (sw->state != SW_STATE_NONE) + return -EBUSY; + + sw->bts = bts; + sw->obj_class = NM_OC_SITE_MANAGER; + sw->obj_instance[0] = 0xff; + sw->obj_instance[1] = 0xff; + sw->obj_instance[2] = 0xff; + sw->state = SW_STATE_WAIT_ACTACK; + sw->cbfn = cbfn; + sw->cb_data = cb_data; + + /* Open the file in order to fill some sw struct members */ + rc = sw_open_file(sw, fname); + if (rc < 0) { + sw->state = SW_STATE_NONE; + return rc; + } + sw_close_file(sw); + + return sw_activate(sw); +} + +static void fill_nm_channel(struct abis_nm_channel *ch, uint8_t bts_port, + uint8_t ts_nr, uint8_t subslot_nr) +{ + ch->attrib = NM_ATT_ABIS_CHANNEL; + ch->bts_port = bts_port; + ch->timeslot = ts_nr; + ch->subslot = subslot_nr; +} + +int abis_nm_establish_tei(struct gsm_bts *bts, uint8_t trx_nr, + uint8_t e1_port, uint8_t e1_timeslot, uint8_t e1_subslot, + uint8_t tei) +{ + struct abis_om_hdr *oh; + struct abis_nm_channel *ch; + uint8_t len = sizeof(*ch) + 2; + struct msgb *msg = nm_msgb_alloc(); + + oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); + fill_om_fom_hdr(oh, len, NM_MT_ESTABLISH_TEI, NM_OC_RADIO_CARRIER, + bts->bts_nr, trx_nr, 0xff); + + msgb_tv_put(msg, NM_ATT_TEI, tei); + + ch = (struct abis_nm_channel *) msgb_put(msg, sizeof(*ch)); + fill_nm_channel(ch, e1_port, e1_timeslot, e1_subslot); + + return abis_nm_sendmsg(bts, msg); +} + +/* connect signalling of one (BTS,TRX) to a particular timeslot on the E1 */ +int abis_nm_conn_terr_sign(struct gsm_bts_trx *trx, + uint8_t e1_port, uint8_t e1_timeslot, uint8_t e1_subslot) +{ + struct gsm_bts *bts = trx->bts; + struct abis_om_hdr *oh; + struct abis_nm_channel *ch; + struct msgb *msg = nm_msgb_alloc(); + + oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); + fill_om_fom_hdr(oh, sizeof(*ch), NM_MT_CONN_TERR_SIGN, + NM_OC_RADIO_CARRIER, bts->bts_nr, trx->nr, 0xff); + + ch = (struct abis_nm_channel *) msgb_put(msg, sizeof(*ch)); + fill_nm_channel(ch, e1_port, e1_timeslot, e1_subslot); + + return abis_nm_sendmsg(bts, msg); +} + +#if 0 +int abis_nm_disc_terr_sign(struct abis_nm_h *h, struct abis_om_obj_inst *inst, + struct abis_nm_abis_channel *chan) +{ +} +#endif + +int abis_nm_conn_terr_traf(struct gsm_bts_trx_ts *ts, + uint8_t e1_port, uint8_t e1_timeslot, + uint8_t e1_subslot) +{ + struct gsm_bts *bts = ts->trx->bts; + struct abis_om_hdr *oh; + struct abis_nm_channel *ch; + struct msgb *msg = nm_msgb_alloc(); + + oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); + fill_om_fom_hdr(oh, sizeof(*ch), NM_MT_CONN_TERR_TRAF, + NM_OC_CHANNEL, bts->bts_nr, ts->trx->nr, ts->nr); + + ch = (struct abis_nm_channel *) msgb_put(msg, sizeof(*ch)); + fill_nm_channel(ch, e1_port, e1_timeslot, e1_subslot); + + DEBUGP(DNM, "CONNECT TERR TRAF Um=%s E1=(%u,%u,%u)\n", + gsm_ts_name(ts), + e1_port, e1_timeslot, e1_subslot); + + return abis_nm_sendmsg(bts, msg); +} + +#if 0 +int abis_nm_disc_terr_traf(struct abis_nm_h *h, struct abis_om_obj_inst *inst, + struct abis_nm_abis_channel *chan, + uint8_t subchan) +{ +} +#endif + +/* 3GPP TS 52.021 § 8.11.1 */ +int abis_nm_get_attr(struct gsm_bts *bts, uint8_t obj_class, uint8_t bts_nr, uint8_t trx_nr, uint8_t ts_nr, + const uint8_t *attr, uint8_t attr_len) +{ + struct abis_om_hdr *oh; + struct msgb *msg; + + if (bts->type != GSM_BTS_TYPE_OSMOBTS) { + LOGPC(DNM, LOGL_NOTICE, "Getting attributes from BTS%d type %s is not supported.\n", + bts->nr, btstype2str(bts->type)); + return -EINVAL; + } + + DEBUGP(DNM, "Get Attr (bts=%d)\n", bts->nr); + + msg = nm_msgb_alloc(); + oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); + fill_om_fom_hdr(oh, attr_len, NM_MT_GET_ATTR, obj_class, + bts_nr, trx_nr, ts_nr); + msgb_tl16v_put(msg, NM_ATT_LIST_REQ_ATTR, attr_len, attr); + + return abis_nm_sendmsg(bts, msg); +} + +/* Chapter 8.6.1 */ +int abis_nm_set_bts_attr(struct gsm_bts *bts, uint8_t *attr, int attr_len) +{ + struct abis_om_hdr *oh; + struct msgb *msg = nm_msgb_alloc(); + uint8_t *cur; + + DEBUGP(DNM, "Set BTS Attr (bts=%d)\n", bts->nr); + + oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); + fill_om_fom_hdr(oh, attr_len, NM_MT_SET_BTS_ATTR, NM_OC_BTS, bts->bts_nr, 0xff, 0xff); + cur = msgb_put(msg, attr_len); + memcpy(cur, attr, attr_len); + + return abis_nm_sendmsg(bts, msg); +} + +/* Chapter 8.6.2 */ +int abis_nm_set_radio_attr(struct gsm_bts_trx *trx, uint8_t *attr, int attr_len) +{ + struct abis_om_hdr *oh; + struct msgb *msg = nm_msgb_alloc(); + uint8_t *cur; + + DEBUGP(DNM, "Set TRX Attr (bts=%d,trx=%d)\n", trx->bts->nr, trx->nr); + + oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); + fill_om_fom_hdr(oh, attr_len, NM_MT_SET_RADIO_ATTR, NM_OC_RADIO_CARRIER, + trx->bts->bts_nr, trx->nr, 0xff); + cur = msgb_put(msg, attr_len); + memcpy(cur, attr, attr_len); + + return abis_nm_sendmsg(trx->bts, msg); +} + +int abis_nm_update_max_power_red(struct gsm_bts_trx *trx) +{ + uint8_t attr[] = { NM_ATT_RF_MAXPOWR_R, trx->max_power_red / 2 }; + return abis_nm_set_radio_attr(trx, attr, ARRAY_SIZE(attr)); +} + +static int verify_chan_comb(struct gsm_bts_trx_ts *ts, uint8_t chan_comb, + const char **reason) +{ + int i; + + *reason = "Reason unknown"; + + /* As it turns out, the BS-11 has some very peculiar restrictions + * on the channel combinations it allows */ + switch (ts->trx->bts->type) { + case GSM_BTS_TYPE_BS11: + switch (chan_comb) { + case NM_CHANC_TCHHalf: + case NM_CHANC_TCHHalf2: + case NM_CHANC_OSMO_TCHFull_TCHHalf_PDCH: + /* not supported */ + *reason = "TCH/H is not supported."; + return -EINVAL; + case NM_CHANC_SDCCH: + /* only one SDCCH/8 per TRX */ + for (i = 0; i < TRX_NR_TS; i++) { + if (i == ts->nr) + continue; + if (ts->trx->ts[i].nm_chan_comb == + NM_CHANC_SDCCH) { + *reason = "Only one SDCCH/8 per TRX allowed."; + return -EINVAL; + } + } + /* not allowed for TS0 of BCCH-TRX */ + if (ts->trx == ts->trx->bts->c0 && + ts->nr == 0) { + *reason = "SDCCH/8 must be on TS0."; + return -EINVAL; + } + + /* not on the same TRX that has a BCCH+SDCCH4 + * combination */ + if (ts->trx != ts->trx->bts->c0 && + (ts->trx->ts[0].nm_chan_comb == 5 || + ts->trx->ts[0].nm_chan_comb == 8)) { + *reason = "SDCCH/8 and BCCH must be on the same TRX."; + return -EINVAL; + } + break; + case NM_CHANC_mainBCCH: + case NM_CHANC_BCCHComb: + /* allowed only for TS0 of C0 */ + if (ts->trx != ts->trx->bts->c0 || ts->nr != 0) { + *reason = "Main BCCH must be on TS0."; + return -EINVAL; + } + break; + case NM_CHANC_BCCH: + /* allowed only for TS 2/4/6 of C0 */ + if (ts->trx != ts->trx->bts->c0) { + *reason = "BCCH must be on C0."; + return -EINVAL; + } + if (ts->nr != 2 && ts->nr != 4 && ts->nr != 6) { + *reason = "BCCH must be on TS 2/4/6."; + return -EINVAL; + } + break; + case 8: /* this is not like 08.58, but in fact + * FCCH+SCH+BCCH+CCCH+SDCCH/4+SACCH/C4+CBCH */ + /* FIXME: only one CBCH allowed per cell */ + break; + } + break; + case GSM_BTS_TYPE_NANOBTS: + switch (ts->nr) { + case 0: + if (ts->trx->nr == 0) { + /* only on TRX0 */ + switch (chan_comb) { + case NM_CHANC_BCCH: + case NM_CHANC_mainBCCH: + case NM_CHANC_BCCHComb: + return 0; + break; + default: + *reason = "TS0 of TRX0 must carry a BCCH."; + return -EINVAL; + } + } else { + switch (chan_comb) { + case NM_CHANC_TCHFull: + case NM_CHANC_TCHHalf: + case NM_CHANC_IPAC_TCHFull_TCHHalf: + return 0; + default: + *reason = "TS0 must carry a TCH/F or TCH/H."; + return -EINVAL; + } + } + break; + case 1: + if (ts->trx->nr == 0) { + switch (chan_comb) { + case NM_CHANC_SDCCH_CBCH: + if (ts->trx->ts[0].nm_chan_comb == + NM_CHANC_mainBCCH) + return 0; + *reason = "TS0 must be the main BCCH for CBCH."; + return -EINVAL; + case NM_CHANC_SDCCH: + case NM_CHANC_TCHFull: + case NM_CHANC_TCHHalf: + case NM_CHANC_IPAC_TCHFull_TCHHalf: + case NM_CHANC_IPAC_TCHFull_PDCH: + case NM_CHANC_OSMO_TCHFull_TCHHalf_PDCH: + return 0; + default: + *reason = "TS1 must carry a CBCH, SDCCH or TCH."; + return -EINVAL; + } + } else { + switch (chan_comb) { + case NM_CHANC_SDCCH: + case NM_CHANC_TCHFull: + case NM_CHANC_TCHHalf: + case NM_CHANC_IPAC_TCHFull_TCHHalf: + return 0; + default: + *reason = "TS1 must carry a SDCCH or TCH."; + return -EINVAL; + } + } + break; + case 2: + case 3: + case 4: + case 5: + case 6: + case 7: + switch (chan_comb) { + case NM_CHANC_TCHFull: + case NM_CHANC_TCHHalf: + case NM_CHANC_IPAC_TCHFull_TCHHalf: + return 0; + case NM_CHANC_IPAC_PDCH: + case NM_CHANC_IPAC_TCHFull_PDCH: + case NM_CHANC_OSMO_TCHFull_TCHHalf_PDCH: + if (ts->trx->nr == 0) + return 0; + else { + *reason = "PDCH must be on TRX0."; + return -EINVAL; + } + } + break; + } + *reason = "Unknown combination"; + return -EINVAL; + case GSM_BTS_TYPE_OSMOBTS: + /* no known restrictions */ + return 0; + default: + /* unknown BTS type */ + return 0; + } + return 0; +} + +/* Chapter 8.6.3 */ +int abis_nm_set_channel_attr(struct gsm_bts_trx_ts *ts, uint8_t chan_comb) +{ + struct gsm_bts *bts = ts->trx->bts; + struct abis_om_hdr *oh; + struct abis_om_fom_hdr *foh; + uint8_t zero = 0x00; + struct msgb *msg = nm_msgb_alloc(); + uint8_t len = 2 + 2; + const char *reason = NULL; + + if (bts->type == GSM_BTS_TYPE_BS11) + len += 4 + 2 + 2 + 3; + + oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); + foh = fill_om_fom_hdr(oh, len, NM_MT_SET_CHAN_ATTR, NM_OC_CHANNEL, bts->bts_nr, + ts->trx->nr, ts->nr); + + DEBUGPFOH(DNM, foh, "Set Chan Attr %s\n", gsm_ts_name(ts)); + if (verify_chan_comb(ts, chan_comb, &reason) < 0) { + msgb_free(msg); + LOGPFOH(DNM, LOGL_ERROR, foh, "Invalid Channel Combination %d on %s. Reason: %s\n", + chan_comb, gsm_ts_name(ts), reason); + return -EINVAL; + } + ts->nm_chan_comb = chan_comb; + + msgb_tv_put(msg, NM_ATT_CHAN_COMB, chan_comb); + if (ts->hopping.enabled) { + unsigned int i; + uint8_t *len; + + msgb_tv_put(msg, NM_ATT_HSN, ts->hopping.hsn); + msgb_tv_put(msg, NM_ATT_MAIO, ts->hopping.maio); + + /* build the ARFCN list */ + msgb_put_u8(msg, NM_ATT_ARFCN_LIST); + len = msgb_put(msg, 1); + *len = 0; + for (i = 0; i < ts->hopping.arfcns.data_len*8; i++) { + if (bitvec_get_bit_pos(&ts->hopping.arfcns, i)) { + msgb_put_u16(msg, i); + /* At least BS-11 wants a TLV16 here */ + if (bts->type == GSM_BTS_TYPE_BS11) + *len += 1; + else + *len += sizeof(uint16_t); + } + } + } + msgb_tv_put(msg, NM_ATT_TSC, gsm_ts_tsc(ts)); /* training sequence */ + if (bts->type == GSM_BTS_TYPE_BS11) + msgb_tlv_put(msg, 0x59, 1, &zero); + + DEBUGPFOH(DNM, foh, "%s(): sending %s\n", __func__, msgb_hexdump(msg)); + return abis_nm_sendmsg(bts, msg); +} + +int abis_nm_sw_act_req_ack(struct gsm_bts *bts, uint8_t obj_class, uint8_t i1, + uint8_t i2, uint8_t i3, int nack, uint8_t *attr, int att_len) +{ + struct abis_om_hdr *oh; + struct msgb *msg = nm_msgb_alloc(); + uint8_t msgtype = NM_MT_SW_ACT_REQ_ACK; + uint8_t len = att_len; + + if (nack) { + len += 2; + msgtype = NM_MT_SW_ACT_REQ_NACK; + } + + oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); + fill_om_fom_hdr(oh, att_len, msgtype, obj_class, i1, i2, i3); + + if (attr) { + uint8_t *ptr = msgb_put(msg, att_len); + memcpy(ptr, attr, att_len); + } + if (nack) + msgb_tv_put(msg, NM_ATT_NACK_CAUSES, NM_NACK_OBJCLASS_NOTSUPP); + + return abis_nm_sendmsg_direct(bts, msg); +} + +int abis_nm_raw_msg(struct gsm_bts *bts, int len, uint8_t *rawmsg) +{ + struct msgb *msg = nm_msgb_alloc(); + struct abis_om_hdr *oh; + uint8_t *data; + + oh = (struct abis_om_hdr *) msgb_put(msg, sizeof(*oh)); + fill_om_hdr(oh, len); + data = msgb_put(msg, len); + memcpy(data, rawmsg, len); + + return abis_nm_sendmsg(bts, msg); +} + +/* Siemens specific commands */ +static int __simple_cmd(struct gsm_bts *bts, uint8_t msg_type) +{ + struct abis_om_hdr *oh; + struct msgb *msg = nm_msgb_alloc(); + + oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); + fill_om_fom_hdr(oh, 0, msg_type, NM_OC_SITE_MANAGER, + 0xff, 0xff, 0xff); + + return abis_nm_sendmsg(bts, msg); +} + +/* Chapter 8.9.2 */ +int abis_nm_opstart(struct gsm_bts *bts, uint8_t obj_class, uint8_t i0, uint8_t i1, uint8_t i2) +{ + struct abis_om_hdr *oh; + struct abis_om_fom_hdr *foh; + struct msgb *msg = nm_msgb_alloc(); + + oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); + foh = fill_om_fom_hdr(oh, 0, NM_MT_OPSTART, obj_class, i0, i1, i2); + + DEBUGPFOH(DNM, foh, "Sending OPSTART\n"); + + return abis_nm_sendmsg(bts, msg); +} + +/* Chapter 8.8.5 */ +int abis_nm_chg_adm_state(struct gsm_bts *bts, uint8_t obj_class, uint8_t i0, + uint8_t i1, uint8_t i2, enum abis_nm_adm_state adm_state) +{ + struct abis_om_hdr *oh; + struct msgb *msg = nm_msgb_alloc(); + + oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); + fill_om_fom_hdr(oh, 2, NM_MT_CHG_ADM_STATE, obj_class, i0, i1, i2); + msgb_tv_put(msg, NM_ATT_ADM_STATE, adm_state); + + return abis_nm_sendmsg(bts, msg); +} + +int abis_nm_conn_mdrop_link(struct gsm_bts *bts, uint8_t e1_port0, uint8_t ts0, + uint8_t e1_port1, uint8_t ts1) +{ + struct abis_om_hdr *oh; + struct msgb *msg = nm_msgb_alloc(); + uint8_t *attr; + + DEBUGP(DNM, "CONNECT MDROP LINK E1=(%u,%u) -> E1=(%u, %u)\n", + e1_port0, ts0, e1_port1, ts1); + + oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); + fill_om_fom_hdr(oh, 6, NM_MT_CONN_MDROP_LINK, + NM_OC_SITE_MANAGER, 0x00, 0x00, 0x00); + + attr = msgb_put(msg, 3); + attr[0] = NM_ATT_MDROP_LINK; + attr[1] = e1_port0; + attr[2] = ts0; + + attr = msgb_put(msg, 3); + attr[0] = NM_ATT_MDROP_NEXT; + attr[1] = e1_port1; + attr[2] = ts1; + + return abis_nm_sendmsg(bts, msg); +} + +/* Chapter 8.7.1 */ +int abis_nm_perform_test(struct gsm_bts *bts, uint8_t obj_class, + uint8_t bts_nr, uint8_t trx_nr, uint8_t ts_nr, + uint8_t test_nr, uint8_t auton_report, struct msgb *msg) +{ + struct abis_om_hdr *oh; + + DEBUGP(DNM, "PEFORM TEST %s\n", abis_nm_test_name(test_nr)); + + if (!msg) + msg = nm_msgb_alloc(); + + msgb_tv_push(msg, NM_ATT_AUTON_REPORT, auton_report); + msgb_tv_push(msg, NM_ATT_TEST_NO, test_nr); + oh = (struct abis_om_hdr *) msgb_push(msg, ABIS_OM_FOM_HDR_SIZE); + fill_om_fom_hdr(oh, msgb_l3len(msg), NM_MT_PERF_TEST, + obj_class, bts_nr, trx_nr, ts_nr); + + return abis_nm_sendmsg(bts, msg); +} + +int abis_nm_event_reports(struct gsm_bts *bts, int on) +{ + if (on == 0) + return __simple_cmd(bts, NM_MT_STOP_EVENT_REP); + else + return __simple_cmd(bts, NM_MT_REST_EVENT_REP); +} + +/* Siemens (or BS-11) specific commands */ + +int abis_nm_bs11_bsc_disconnect(struct gsm_bts *bts, int reconnect) +{ + if (reconnect == 0) + return __simple_cmd(bts, NM_MT_BS11_DISCONNECT); + else + return __simple_cmd(bts, NM_MT_BS11_RECONNECT); +} + +int abis_nm_bs11_restart(struct gsm_bts *bts) +{ + return __simple_cmd(bts, NM_MT_BS11_RESTART); +} + + +struct bs11_date_time { + uint16_t year; + uint8_t month; + uint8_t day; + uint8_t hour; + uint8_t min; + uint8_t sec; +} __attribute__((packed)); + + +void get_bs11_date_time(struct bs11_date_time *aet) +{ + time_t t; + struct tm *tm; + + t = time(NULL); + tm = localtime(&t); + aet->sec = tm->tm_sec; + aet->min = tm->tm_min; + aet->hour = tm->tm_hour; + aet->day = tm->tm_mday; + aet->month = tm->tm_mon; + aet->year = htons(1900 + tm->tm_year); +} + +int abis_nm_bs11_reset_resource(struct gsm_bts *bts) +{ + return __simple_cmd(bts, NM_MT_BS11_RESET_RESOURCE); +} + +int abis_nm_bs11_db_transmission(struct gsm_bts *bts, int begin) +{ + if (begin) + return __simple_cmd(bts, NM_MT_BS11_BEGIN_DB_TX); + else + return __simple_cmd(bts, NM_MT_BS11_END_DB_TX); +} + +int abis_nm_bs11_create_object(struct gsm_bts *bts, + enum abis_bs11_objtype type, uint8_t idx, + uint8_t attr_len, const uint8_t *attr) +{ + struct abis_om_hdr *oh; + struct msgb *msg = nm_msgb_alloc(); + uint8_t *cur; + + oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); + fill_om_fom_hdr(oh, attr_len, NM_MT_BS11_CREATE_OBJ, + NM_OC_BS11, type, 0, idx); + cur = msgb_put(msg, attr_len); + memcpy(cur, attr, attr_len); + + return abis_nm_sendmsg(bts, msg); +} + +int abis_nm_bs11_delete_object(struct gsm_bts *bts, + enum abis_bs11_objtype type, uint8_t idx) +{ + struct abis_om_hdr *oh; + struct msgb *msg = nm_msgb_alloc(); + + oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); + fill_om_fom_hdr(oh, 0, NM_MT_BS11_DELETE_OBJ, + NM_OC_BS11, type, 0, idx); + + return abis_nm_sendmsg(bts, msg); +} + +int abis_nm_bs11_create_envaBTSE(struct gsm_bts *bts, uint8_t idx) +{ + struct abis_om_hdr *oh; + struct msgb *msg = nm_msgb_alloc(); + uint8_t zero = 0x00; + + oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); + fill_om_fom_hdr(oh, 3, NM_MT_BS11_CREATE_OBJ, + NM_OC_BS11_ENVABTSE, 0, idx, 0xff); + msgb_tlv_put(msg, 0x99, 1, &zero); + + return abis_nm_sendmsg(bts, msg); +} + +int abis_nm_bs11_create_bport(struct gsm_bts *bts, uint8_t idx) +{ + struct abis_om_hdr *oh; + struct msgb *msg = nm_msgb_alloc(); + + oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); + fill_om_fom_hdr(oh, 0, NM_MT_BS11_CREATE_OBJ, NM_OC_BS11_BPORT, + idx, 0xff, 0xff); + + return abis_nm_sendmsg(bts, msg); +} + +int abis_nm_bs11_delete_bport(struct gsm_bts *bts, uint8_t idx) +{ + struct abis_om_hdr *oh; + struct msgb *msg = nm_msgb_alloc(); + + oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); + fill_om_fom_hdr(oh, 0, NM_MT_BS11_DELETE_OBJ, NM_OC_BS11_BPORT, + idx, 0xff, 0xff); + + return abis_nm_sendmsg(bts, msg); +} + +static const uint8_t sm_attr[] = { NM_ATT_TEI, NM_ATT_ABIS_CHANNEL }; +int abis_nm_bs11_get_oml_tei_ts(struct gsm_bts *bts) +{ + struct abis_om_hdr *oh; + struct msgb *msg = nm_msgb_alloc(); + + oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); + fill_om_fom_hdr(oh, 2+sizeof(sm_attr), NM_MT_GET_ATTR, NM_OC_SITE_MANAGER, + 0xff, 0xff, 0xff); + msgb_tlv_put(msg, NM_ATT_LIST_REQ_ATTR, sizeof(sm_attr), sm_attr); + + return abis_nm_sendmsg(bts, msg); +} + +/* like abis_nm_conn_terr_traf + set_tei */ +int abis_nm_bs11_conn_oml_tei(struct gsm_bts *bts, uint8_t e1_port, + uint8_t e1_timeslot, uint8_t e1_subslot, + uint8_t tei) +{ + struct abis_om_hdr *oh; + struct abis_nm_channel *ch; + struct msgb *msg = nm_msgb_alloc(); + + oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); + fill_om_fom_hdr(oh, sizeof(*ch)+2, NM_MT_BS11_SET_ATTR, + NM_OC_SITE_MANAGER, 0xff, 0xff, 0xff); + + ch = (struct abis_nm_channel *) msgb_put(msg, sizeof(*ch)); + fill_nm_channel(ch, e1_port, e1_timeslot, e1_subslot); + msgb_tv_put(msg, NM_ATT_TEI, tei); + + return abis_nm_sendmsg(bts, msg); +} + +int abis_nm_bs11_set_trx_power(struct gsm_bts_trx *trx, uint8_t level) +{ + struct abis_om_hdr *oh; + struct msgb *msg = nm_msgb_alloc(); + + oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); + fill_om_fom_hdr(oh, 3, NM_MT_BS11_SET_ATTR, + NM_OC_BS11, BS11_OBJ_PA, 0x00, trx->nr); + msgb_tlv_put(msg, NM_ATT_BS11_TXPWR, 1, &level); + + return abis_nm_sendmsg(trx->bts, msg); +} + +int abis_nm_bs11_get_trx_power(struct gsm_bts_trx *trx) +{ + struct abis_om_hdr *oh; + struct msgb *msg = nm_msgb_alloc(); + uint8_t attr = NM_ATT_BS11_TXPWR; + + oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); + fill_om_fom_hdr(oh, 2+sizeof(attr), NM_MT_GET_ATTR, + NM_OC_BS11, BS11_OBJ_PA, 0x00, trx->nr); + msgb_tlv_put(msg, NM_ATT_LIST_REQ_ATTR, sizeof(attr), &attr); + + return abis_nm_sendmsg(trx->bts, msg); +} + +int abis_nm_bs11_get_pll_mode(struct gsm_bts *bts) +{ + struct abis_om_hdr *oh; + struct msgb *msg = nm_msgb_alloc(); + uint8_t attr[] = { NM_ATT_BS11_PLL_MODE }; + + oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); + fill_om_fom_hdr(oh, 2+sizeof(attr), NM_MT_GET_ATTR, + NM_OC_BS11, BS11_OBJ_LI, 0x00, 0x00); + msgb_tlv_put(msg, NM_ATT_LIST_REQ_ATTR, sizeof(attr), attr); + + return abis_nm_sendmsg(bts, msg); +} + +int abis_nm_bs11_get_cclk(struct gsm_bts *bts) +{ + struct abis_om_hdr *oh; + struct msgb *msg = nm_msgb_alloc(); + uint8_t attr[] = { NM_ATT_BS11_CCLK_ACCURACY, + NM_ATT_BS11_CCLK_TYPE }; + + oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); + fill_om_fom_hdr(oh, 2+sizeof(attr), NM_MT_GET_ATTR, + NM_OC_BS11, BS11_OBJ_CCLK, 0x00, 0x00); + msgb_tlv_put(msg, NM_ATT_LIST_REQ_ATTR, sizeof(attr), attr); + + return abis_nm_sendmsg(bts, msg); + +} + +//static const uint8_t bs11_logon_c7[] = { 0x07, 0xd9, 0x01, 0x11, 0x0d, 0x10, 0x20 }; + +int abis_nm_bs11_factory_logon(struct gsm_bts *bts, int on) +{ + return abis_nm_bs11_logon(bts, 0x02, "FACTORY", on); +} + +int abis_nm_bs11_infield_logon(struct gsm_bts *bts, int on) +{ + return abis_nm_bs11_logon(bts, 0x03, "FIELD ", on); +} + +int abis_nm_bs11_logon(struct gsm_bts *bts, uint8_t level, const char *name, int on) +{ + struct abis_om_hdr *oh; + struct msgb *msg = nm_msgb_alloc(); + struct bs11_date_time bdt; + + get_bs11_date_time(&bdt); + + oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); + if (on) { + uint8_t len = 3*2 + sizeof(bdt) + + 1 + strlen(name); + fill_om_fom_hdr(oh, len, NM_MT_BS11_LMT_LOGON, + NM_OC_BS11_BTSE, 0xff, 0xff, 0xff); + msgb_tlv_put(msg, NM_ATT_BS11_LMT_LOGIN_TIME, + sizeof(bdt), (uint8_t *) &bdt); + msgb_tlv_put(msg, NM_ATT_BS11_LMT_USER_ACC_LEV, + 1, &level); + msgb_tlv_put(msg, NM_ATT_BS11_LMT_USER_NAME, + strlen(name), (uint8_t *)name); + } else { + fill_om_fom_hdr(oh, 0, NM_MT_BS11_LMT_LOGOFF, + NM_OC_BS11_BTSE, 0xff, 0xff, 0xff); + } + + return abis_nm_sendmsg(bts, msg); +} + +int abis_nm_bs11_set_trx1_pw(struct gsm_bts *bts, const char *password) +{ + struct abis_om_hdr *oh; + struct msgb *msg; + + if (strlen(password) != 10) + return -EINVAL; + + msg = nm_msgb_alloc(); + oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); + fill_om_fom_hdr(oh, 2+strlen(password), NM_MT_BS11_SET_ATTR, + NM_OC_BS11, BS11_OBJ_TRX1, 0x00, 0x00); + msgb_tlv_put(msg, NM_ATT_BS11_PASSWORD, 10, (const uint8_t *)password); + + return abis_nm_sendmsg(bts, msg); +} + +/* change the BS-11 PLL Mode to either locked (E1 derived) or standalone */ +int abis_nm_bs11_set_pll_locked(struct gsm_bts *bts, int locked) +{ + struct abis_om_hdr *oh; + struct msgb *msg; + uint8_t tlv_value; + + msg = nm_msgb_alloc(); + oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); + fill_om_fom_hdr(oh, 3, NM_MT_BS11_SET_ATTR, NM_OC_BS11, + BS11_OBJ_LI, 0x00, 0x00); + + if (locked) + tlv_value = BS11_LI_PLL_LOCKED; + else + tlv_value = BS11_LI_PLL_STANDALONE; + + msgb_tlv_put(msg, NM_ATT_BS11_PLL_MODE, 1, &tlv_value); + + return abis_nm_sendmsg(bts, msg); +} + +/* Set the calibration value of the PLL (work value/set value) + * It depends on the login which one is changed */ +int abis_nm_bs11_set_pll(struct gsm_bts *bts, int value) +{ + struct abis_om_hdr *oh; + struct msgb *msg; + uint8_t tlv_value[2]; + + msg = nm_msgb_alloc(); + oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); + fill_om_fom_hdr(oh, 3, NM_MT_BS11_SET_ATTR, NM_OC_BS11, + BS11_OBJ_TRX1, 0x00, 0x00); + + tlv_value[0] = value>>8; + tlv_value[1] = value&0xff; + + msgb_tlv_put(msg, NM_ATT_BS11_PLL, 2, tlv_value); + + return abis_nm_sendmsg(bts, msg); +} + +int abis_nm_bs11_get_state(struct gsm_bts *bts) +{ + return __simple_cmd(bts, NM_MT_BS11_GET_STATE); +} + +/* BS11 SWL */ + +void *tall_fle_ctx = NULL; + +struct abis_nm_bs11_sw { + struct gsm_bts *bts; + char swl_fname[PATH_MAX]; + uint8_t win_size; + int forced; + struct llist_head file_list; + gsm_cbfn *user_cb; /* specified by the user */ +}; +static struct abis_nm_bs11_sw _g_bs11_sw, *g_bs11_sw = &_g_bs11_sw; + +struct file_list_entry { + struct llist_head list; + char fname[PATH_MAX]; +}; + +struct file_list_entry *fl_dequeue(struct llist_head *queue) +{ + struct llist_head *lh; + + if (llist_empty(queue)) + return NULL; + + lh = queue->next; + llist_del(lh); + + return llist_entry(lh, struct file_list_entry, list); +} + +static int bs11_read_swl_file(struct abis_nm_bs11_sw *bs11_sw) +{ + char linebuf[255]; + struct llist_head *lh, *lh2; + FILE *swl; + int rc = 0; + + swl = fopen(bs11_sw->swl_fname, "r"); + if (!swl) + return -ENODEV; + + /* zero the stale file list, if any */ + llist_for_each_safe(lh, lh2, &bs11_sw->file_list) { + llist_del(lh); + talloc_free(lh); + } + + while (fgets(linebuf, sizeof(linebuf), swl)) { + char file_id[12+1]; + char file_version[80+1]; + struct file_list_entry *fle; + static char dir[PATH_MAX]; + + if (strlen(linebuf) < 4) + continue; + + rc = sscanf(linebuf+4, "%12s:%80s\r\n", file_id, file_version); + if (rc < 0) { + perror("ERR parsing SWL file"); + rc = -EINVAL; + goto out; + } + if (rc < 2) + continue; + + fle = talloc_zero(tall_fle_ctx, struct file_list_entry); + if (!fle) { + rc = -ENOMEM; + goto out; + } + + /* construct new filename */ + osmo_strlcpy(dir, bs11_sw->swl_fname, sizeof(dir)); + strncat(fle->fname, dirname(dir), sizeof(fle->fname) - 1); + strcat(fle->fname, "/"); + strncat(fle->fname, file_id, sizeof(fle->fname) - 1 -strlen(fle->fname)); + + llist_add_tail(&fle->list, &bs11_sw->file_list); + } + +out: + fclose(swl); + return rc; +} + +/* bs11 swload specific callback, passed to abis_nm core swload */ +static int bs11_swload_cbfn(unsigned int hook, unsigned int event, + struct msgb *msg, void *data, void *param) +{ + struct abis_nm_bs11_sw *bs11_sw = data; + struct file_list_entry *fle; + int rc = 0; + + switch (event) { + case NM_MT_LOAD_END_ACK: + fle = fl_dequeue(&bs11_sw->file_list); + if (fle) { + /* start download the next file of our file list */ + rc = abis_nm_software_load(bs11_sw->bts, 0xff, fle->fname, + bs11_sw->win_size, + bs11_sw->forced, + &bs11_swload_cbfn, bs11_sw); + talloc_free(fle); + } else { + /* activate the SWL */ + rc = abis_nm_software_activate(bs11_sw->bts, + bs11_sw->swl_fname, + bs11_swload_cbfn, + bs11_sw); + } + break; + case NM_MT_LOAD_SEG_ACK: + case NM_MT_LOAD_END_NACK: + case NM_MT_LOAD_INIT_ACK: + case NM_MT_LOAD_INIT_NACK: + case NM_MT_ACTIVATE_SW_NACK: + case NM_MT_ACTIVATE_SW_ACK: + default: + /* fallthrough to the user callback */ + if (bs11_sw->user_cb) + rc = bs11_sw->user_cb(hook, event, msg, NULL, NULL); + break; + } + + return rc; +} + +/* Siemens provides a SWL file that is a mere listing of all the other + * files that are part of a software release. We need to upload first + * the list file, and then each file that is listed in the list file */ +int abis_nm_bs11_load_swl(struct gsm_bts *bts, const char *fname, + uint8_t win_size, int forced, gsm_cbfn *cbfn) +{ + struct abis_nm_bs11_sw *bs11_sw = g_bs11_sw; + struct file_list_entry *fle; + int rc = 0; + + INIT_LLIST_HEAD(&bs11_sw->file_list); + bs11_sw->bts = bts; + bs11_sw->win_size = win_size; + bs11_sw->user_cb = cbfn; + bs11_sw->forced = forced; + + osmo_strlcpy(bs11_sw->swl_fname, fname, sizeof(bs11_sw->swl_fname)); + rc = bs11_read_swl_file(bs11_sw); + if (rc < 0) + return rc; + + /* dequeue next item in file list */ + fle = fl_dequeue(&bs11_sw->file_list); + if (!fle) + return -EINVAL; + + /* start download the next file of our file list */ + rc = abis_nm_software_load(bts, 0xff, fle->fname, win_size, forced, + bs11_swload_cbfn, bs11_sw); + talloc_free(fle); + return rc; +} + +#if 0 +static uint8_t req_attr_btse[] = { + NM_ATT_ADM_STATE, NM_ATT_BS11_LMT_LOGON_SESSION, + NM_ATT_BS11_LMT_LOGIN_TIME, NM_ATT_BS11_LMT_USER_ACC_LEV, + NM_ATT_BS11_LMT_USER_NAME, + + 0xaf, NM_ATT_BS11_RX_OFFSET, NM_ATT_BS11_VENDOR_NAME, + + NM_ATT_BS11_SW_LOAD_INTENDED, NM_ATT_BS11_SW_LOAD_SAFETY, + + NM_ATT_BS11_SW_LOAD_STORED }; + +static uint8_t req_attr_btsm[] = { + NM_ATT_ABIS_CHANNEL, NM_ATT_TEI, NM_ATT_BS11_ABIS_EXT_TIME, + NM_ATT_ADM_STATE, NM_ATT_AVAIL_STATUS, 0xce, NM_ATT_FILE_ID, + NM_ATT_FILE_VERSION, NM_ATT_OPER_STATE, 0xe8, NM_ATT_BS11_ALL_TEST_CATG, + NM_ATT_SW_DESCR, NM_ATT_GET_ARI }; +#endif + +static uint8_t req_attr[] = { + NM_ATT_ADM_STATE, NM_ATT_AVAIL_STATUS, 0xa8, NM_ATT_OPER_STATE, + 0xd5, 0xa1, NM_ATT_BS11_ESN_FW_CODE_NO, NM_ATT_BS11_ESN_HW_CODE_NO, + 0x42, NM_ATT_BS11_ESN_PCB_SERIAL, NM_ATT_BS11_PLL }; + +int abis_nm_bs11_get_serno(struct gsm_bts *bts) +{ + struct abis_om_hdr *oh; + struct msgb *msg = nm_msgb_alloc(); + + oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); + /* SiemensHW CCTRL object */ + fill_om_fom_hdr(oh, 2+sizeof(req_attr), NM_MT_GET_ATTR, NM_OC_BS11, + 0x03, 0x00, 0x00); + msgb_tlv_put(msg, NM_ATT_LIST_REQ_ATTR, sizeof(req_attr), req_attr); + + return abis_nm_sendmsg(bts, msg); +} + +int abis_nm_bs11_set_ext_time(struct gsm_bts *bts) +{ + struct abis_om_hdr *oh; + struct msgb *msg = nm_msgb_alloc(); + struct bs11_date_time aet; + + get_bs11_date_time(&aet); + oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); + /* SiemensHW CCTRL object */ + fill_om_fom_hdr(oh, 2+sizeof(aet), NM_MT_BS11_SET_ATTR, NM_OC_SITE_MANAGER, + 0xff, 0xff, 0xff); + msgb_tlv_put(msg, NM_ATT_BS11_ABIS_EXT_TIME, sizeof(aet), (uint8_t *) &aet); + + return abis_nm_sendmsg(bts, msg); +} + +int abis_nm_bs11_get_bport_line_cfg(struct gsm_bts *bts, uint8_t bport) +{ + struct abis_om_hdr *oh; + struct msgb *msg = nm_msgb_alloc(); + uint8_t attr = NM_ATT_BS11_LINE_CFG; + + oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); + fill_om_fom_hdr(oh, 2+sizeof(attr), NM_MT_GET_ATTR, + NM_OC_BS11_BPORT, bport, 0xff, 0x02); + msgb_tlv_put(msg, NM_ATT_LIST_REQ_ATTR, sizeof(attr), &attr); + + return abis_nm_sendmsg(bts, msg); +} + +int abis_nm_bs11_set_bport_line_cfg(struct gsm_bts *bts, uint8_t bport, enum abis_bs11_line_cfg line_cfg) +{ + struct abis_om_hdr *oh; + struct msgb *msg = nm_msgb_alloc(); + struct bs11_date_time aet; + + get_bs11_date_time(&aet); + oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); + fill_om_fom_hdr(oh, 2, NM_MT_BS11_SET_ATTR, NM_OC_BS11_BPORT, + bport, 0xff, 0x02); + msgb_tv_put(msg, NM_ATT_BS11_LINE_CFG, line_cfg); + + return abis_nm_sendmsg(bts, msg); +} + +/* ip.access nanoBTS specific commands */ +static const char ipaccess_magic[] = "com.ipaccess"; + + +static int abis_nm_rx_ipacc(struct msgb *msg) +{ + struct in_addr addr; + struct abis_om_hdr *oh = msgb_l2(msg); + struct abis_om_fom_hdr *foh; + uint8_t idstrlen = oh->data[0]; + struct tlv_parsed tp; + struct ipacc_ack_signal_data signal; + struct e1inp_sign_link *sign_link = msg->dst; + + foh = (struct abis_om_fom_hdr *) (oh->data + 1 + idstrlen); + + if (strncmp((char *)&oh->data[1], ipaccess_magic, idstrlen)) { + LOGPFOH(DNM, LOGL_ERROR, foh, "id string is not com.ipaccess !?!\n"); + return -EINVAL; + } + + abis_nm_tlv_parse(&tp, sign_link->trx->bts, foh->data, oh->length-sizeof(*foh)); + + DEBUGPFOH(DNM, foh, "IPACCESS(0x%02x): ", foh->msg_type); + + switch (foh->msg_type) { + case NM_MT_IPACC_RSL_CONNECT_ACK: + DEBUGPC(DNM, "RSL CONNECT ACK "); + if (TLVP_PRESENT(&tp, NM_ATT_IPACC_DST_IP)) { + memcpy(&addr, + TLVP_VAL(&tp, NM_ATT_IPACC_DST_IP), sizeof(addr)); + + DEBUGPC(DNM, "IP=%s ", inet_ntoa(addr)); + } + if (TLVP_PRESENT(&tp, NM_ATT_IPACC_DST_IP_PORT)) + DEBUGPC(DNM, "PORT=%u ", + ntohs(*((uint16_t *) + TLVP_VAL(&tp, NM_ATT_IPACC_DST_IP_PORT)))); + if (TLVP_PRESENT(&tp, NM_ATT_IPACC_STREAM_ID)) + DEBUGPC(DNM, "STREAM=0x%02x ", + *TLVP_VAL(&tp, NM_ATT_IPACC_STREAM_ID)); + DEBUGPC(DNM, "\n"); + osmo_timer_del(&sign_link->trx->rsl_connect_timeout); + break; + case NM_MT_IPACC_RSL_CONNECT_NACK: + LOGPFOH(DNM, LOGL_ERROR, foh, "RSL CONNECT NACK "); + if (TLVP_PRESENT(&tp, NM_ATT_NACK_CAUSES)) + LOGPC(DNM, LOGL_ERROR, " CAUSE=%s\n", + abis_nm_nack_cause_name(*TLVP_VAL(&tp, NM_ATT_NACK_CAUSES))); + else + LOGPC(DNM, LOGL_ERROR, "\n"); + osmo_timer_del(&sign_link->trx->rsl_connect_timeout); + break; + case NM_MT_IPACC_SET_NVATTR_ACK: + DEBUGPFOH(DNM, foh, "SET NVATTR ACK\n"); + /* FIXME: decode and show the actual attributes */ + break; + case NM_MT_IPACC_SET_NVATTR_NACK: + LOGPFOH(DNM, LOGL_ERROR, foh, "SET NVATTR NACK "); + if (TLVP_PRESENT(&tp, NM_ATT_NACK_CAUSES)) + LOGPC(DNM, LOGL_ERROR, " CAUSE=%s\n", + abis_nm_nack_cause_name(*TLVP_VAL(&tp, NM_ATT_NACK_CAUSES))); + else + LOGPC(DNM, LOGL_ERROR, "\n"); + break; + case NM_MT_IPACC_GET_NVATTR_ACK: + DEBUGPFOH(DNM, foh, "GET NVATTR ACK\n"); + /* FIXME: decode and show the actual attributes */ + break; + case NM_MT_IPACC_GET_NVATTR_NACK: + LOGPFOH(DNM, LOGL_ERROR, foh, "GET NVATTR NACK "); + if (TLVP_PRESENT(&tp, NM_ATT_NACK_CAUSES)) + LOGPC(DNM, LOGL_ERROR, " CAUSE=%s\n", + abis_nm_nack_cause_name(*TLVP_VAL(&tp, NM_ATT_NACK_CAUSES))); + else + LOGPC(DNM, LOGL_ERROR, "\n"); + break; + case NM_MT_IPACC_SET_ATTR_ACK: + DEBUGPFOH(DNM, foh, "SET ATTR ACK\n"); + break; + case NM_MT_IPACC_SET_ATTR_NACK: + LOGPFOH(DNM, LOGL_ERROR, foh, "SET ATTR NACK "); + if (TLVP_PRESENT(&tp, NM_ATT_NACK_CAUSES)) + LOGPC(DNM, LOGL_ERROR, " CAUSE=%s\n", + abis_nm_nack_cause_name(*TLVP_VAL(&tp, NM_ATT_NACK_CAUSES))); + else + LOGPC(DNM, LOGL_ERROR, "\n"); + break; + default: + DEBUGPC(DNM, "unknown\n"); + break; + } + + /* signal handling */ + switch (foh->msg_type) { + case NM_MT_IPACC_RSL_CONNECT_NACK: + case NM_MT_IPACC_SET_NVATTR_NACK: + case NM_MT_IPACC_GET_NVATTR_NACK: + signal.trx = gsm_bts_trx_by_nr(sign_link->trx->bts, foh->obj_inst.trx_nr); + signal.msg_type = foh->msg_type; + osmo_signal_dispatch(SS_NM, S_NM_IPACC_NACK, &signal); + break; + case NM_MT_IPACC_SET_NVATTR_ACK: + signal.trx = gsm_bts_trx_by_nr(sign_link->trx->bts, foh->obj_inst.trx_nr); + signal.msg_type = foh->msg_type; + osmo_signal_dispatch(SS_NM, S_NM_IPACC_ACK, &signal); + break; + default: + break; + } + + return 0; +} + +/* send an ip-access manufacturer specific message */ +int abis_nm_ipaccess_msg(struct gsm_bts *bts, uint8_t msg_type, + uint8_t obj_class, uint8_t bts_nr, + uint8_t trx_nr, uint8_t ts_nr, + uint8_t *attr, int attr_len) +{ + struct msgb *msg = nm_msgb_alloc(); + struct abis_om_hdr *oh; + struct abis_om_fom_hdr *foh; + uint8_t *data; + + /* construct the 12.21 OM header, observe the erroneous length */ + oh = (struct abis_om_hdr *) msgb_put(msg, sizeof(*oh)); + fill_om_hdr(oh, sizeof(*foh) + attr_len); + oh->mdisc = ABIS_OM_MDISC_MANUF; + + /* add the ip.access magic */ + data = msgb_put(msg, sizeof(ipaccess_magic)+1); + *data++ = sizeof(ipaccess_magic); + memcpy(data, ipaccess_magic, sizeof(ipaccess_magic)); + + /* fill the 12.21 FOM header */ + foh = (struct abis_om_fom_hdr *) msgb_put(msg, sizeof(*foh)); + foh->msg_type = msg_type; + foh->obj_class = obj_class; + foh->obj_inst.bts_nr = bts_nr; + foh->obj_inst.trx_nr = trx_nr; + foh->obj_inst.ts_nr = ts_nr; + + if (attr && attr_len) { + data = msgb_put(msg, attr_len); + memcpy(data, attr, attr_len); + } + + return abis_nm_sendmsg(bts, msg); +} + +/* set some attributes in NVRAM */ +int abis_nm_ipaccess_set_nvattr(struct gsm_bts_trx *trx, uint8_t *attr, + int attr_len) +{ + return abis_nm_ipaccess_msg(trx->bts, NM_MT_IPACC_SET_NVATTR, + NM_OC_BASEB_TRANSC, 0, trx->nr, 0xff, attr, + attr_len); +} + +static void rsl_connect_timeout(void *data) +{ + struct gsm_bts_trx *trx = data; + struct ipacc_ack_signal_data signal; + + LOGP(DRSL, LOGL_NOTICE, "(bts=%d,trx=%d) RSL connection request timed out\n", trx->bts->nr, trx->nr); + + /* Fake an RSL CONECT NACK message from the BTS. */ + signal.trx = trx; + signal.msg_type = NM_MT_IPACC_RSL_CONNECT_NACK; + osmo_signal_dispatch(SS_NM, S_NM_IPACC_NACK, &signal); +} + +int abis_nm_ipaccess_rsl_connect(struct gsm_bts_trx *trx, + uint32_t ip, uint16_t port, uint8_t stream) +{ + struct in_addr ia; + uint8_t attr[] = { NM_ATT_IPACC_STREAM_ID, 0, + NM_ATT_IPACC_DST_IP_PORT, 0, 0, + NM_ATT_IPACC_DST_IP, 0, 0, 0, 0 }; + + int attr_len = sizeof(attr); + int error; + + osmo_timer_setup(&trx->rsl_connect_timeout, rsl_connect_timeout, trx); + + ia.s_addr = htonl(ip); + attr[1] = stream; + attr[3] = port >> 8; + attr[4] = port & 0xff; + memcpy(attr + 6, &ia.s_addr, sizeof(uint32_t)); + + /* if ip == 0, we use the default IP */ + if (ip == 0) + attr_len -= 5; + + LOGP(DNM, LOGL_INFO, "IPA RSL CONNECT IP=%s PORT=%u STREAM=0x%02x\n", + inet_ntoa(ia), port, stream); + + error = abis_nm_ipaccess_msg(trx->bts, NM_MT_IPACC_RSL_CONNECT, + NM_OC_BASEB_TRANSC, trx->bts->bts_nr, + trx->nr, 0xff, attr, attr_len); + if (error == 0) + osmo_timer_schedule(&trx->rsl_connect_timeout, 60, 0); + + return error; +} + +/* restart / reboot an ip.access nanoBTS */ +int abis_nm_ipaccess_restart(struct gsm_bts_trx *trx) +{ + struct abis_om_hdr *oh; + struct msgb *msg = nm_msgb_alloc(); + + oh = (struct abis_om_hdr *) msgb_put(msg, ABIS_OM_FOM_HDR_SIZE); + fill_om_fom_hdr(oh, 0, NM_MT_IPACC_RESTART, NM_OC_BASEB_TRANSC, + trx->bts->nr, trx->nr, 0xff); + + return abis_nm_sendmsg_direct(trx->bts, msg); +} + +int abis_nm_ipaccess_set_attr(struct gsm_bts *bts, uint8_t obj_class, + uint8_t bts_nr, uint8_t trx_nr, uint8_t ts_nr, + uint8_t *attr, uint8_t attr_len) +{ + return abis_nm_ipaccess_msg(bts, NM_MT_IPACC_SET_ATTR, + obj_class, bts_nr, trx_nr, ts_nr, + attr, attr_len); +} + +void abis_nm_ipaccess_cgi(uint8_t *buf, struct gsm_bts *bts) +{ + struct gsm48_ra_id *_buf = (struct gsm48_ra_id*)buf; + uint16_t ci = htons(bts->cell_identity); + /* we simply reuse the GSM48 function and write the Cell ID over the position where the RAC + * starts */ + gsm48_ra_id_by_bts(_buf, bts); + memcpy(&_buf->rac, &ci, sizeof(ci)); +} + +void gsm_trx_lock_rf(struct gsm_bts_trx *trx, bool locked, const char *reason) +{ + uint8_t new_state = locked ? NM_STATE_LOCKED : NM_STATE_UNLOCKED; + + + if (!trx->bts || !trx->bts->oml_link) { + /* Set initial state which will be sent when BTS connects. */ + trx->mo.nm_state.administrative = new_state; + return; + } + + LOGP(DNM, LOGL_NOTICE, "(bts=%d,trx=%d) Requesting administrative state change %s -> %s [%s]\n", + trx->bts->nr, trx->nr, + get_value_string(abis_nm_adm_state_names, trx->mo.nm_state.administrative), + get_value_string(abis_nm_adm_state_names, new_state), reason); + + abis_nm_chg_adm_state(trx->bts, NM_OC_RADIO_CARRIER, + trx->bts->bts_nr, trx->nr, 0xff, + new_state); +} + +static const struct value_string ipacc_testres_names[] = { + { NM_IPACC_TESTRES_SUCCESS, "SUCCESS" }, + { NM_IPACC_TESTRES_TIMEOUT, "TIMEOUT" }, + { NM_IPACC_TESTRES_NO_CHANS, "NO CHANNELS" }, + { NM_IPACC_TESTRES_PARTIAL, "PARTIAL" }, + { NM_IPACC_TESTRES_STOPPED, "STOPPED" }, + { 0, NULL } +}; + +const char *ipacc_testres_name(uint8_t res) +{ + return get_value_string(ipacc_testres_names, res); +} + +void ipac_parse_cgi(struct osmo_cell_global_id *cid, const uint8_t *buf) +{ + osmo_plmn_from_bcd(buf, &cid->lai.plmn); + cid->lai.lac = ntohs(*((uint16_t *)&buf[3])); + cid->cell_identity = ntohs(*((uint16_t *)&buf[5])); +} + +/* parse BCCH information IEI from wire format to struct ipac_bcch_info */ +int ipac_parse_bcch_info(struct ipac_bcch_info *binf, uint8_t *buf) +{ + uint8_t *cur = buf; + uint16_t len __attribute__((unused)); + + memset(binf, 0, sizeof(*binf)); + + if (cur[0] != NM_IPAC_EIE_BCCH_INFO) + return -EINVAL; + cur++; + + len = ntohs(*(uint16_t *)cur); + cur += 2; + + binf->info_type = ntohs(*(uint16_t *)cur); + cur += 2; + + if (binf->info_type & IPAC_BINF_FREQ_ERR_QUAL) + binf->freq_qual = *cur >> 2; + + binf->arfcn = (*cur++ & 3) << 8; + binf->arfcn |= *cur++; + + if (binf->info_type & IPAC_BINF_RXLEV) + binf->rx_lev = *cur & 0x3f; + cur++; + + if (binf->info_type & IPAC_BINF_RXQUAL) + binf->rx_qual = *cur & 0x7; + cur++; + + if (binf->info_type & IPAC_BINF_FREQ_ERR_QUAL) + binf->freq_err = ntohs(*(uint16_t *)cur); + cur += 2; + + if (binf->info_type & IPAC_BINF_FRAME_OFFSET) + binf->frame_offset = ntohs(*(uint16_t *)cur); + cur += 2; + + if (binf->info_type & IPAC_BINF_FRAME_NR_OFFSET) + binf->frame_nr_offset = ntohl(*(uint32_t *)cur); + cur += 4; + +#if 0 + /* Somehow this is not set correctly */ + if (binf->info_type & IPAC_BINF_BSIC) +#endif + binf->bsic = *cur & 0x3f; + cur++; + + ipac_parse_cgi(&binf->cgi, cur); + cur += 7; + + if (binf->info_type & IPAC_BINF_NEIGH_BA_SI2) { + memcpy(binf->ba_list_si2, cur, sizeof(binf->ba_list_si2)); + cur += sizeof(binf->ba_list_si2); + } + + if (binf->info_type & IPAC_BINF_NEIGH_BA_SI2bis) { + memcpy(binf->ba_list_si2bis, cur, + sizeof(binf->ba_list_si2bis)); + cur += sizeof(binf->ba_list_si2bis); + } + + if (binf->info_type & IPAC_BINF_NEIGH_BA_SI2ter) { + memcpy(binf->ba_list_si2ter, cur, + sizeof(binf->ba_list_si2ter)); + cur += sizeof(binf->ba_list_si2ter); + } + + return 0; +} + +void abis_nm_clear_queue(struct gsm_bts *bts) +{ + struct msgb *msg; + + while (!llist_empty(&bts->abis_queue)) { + msg = msgb_dequeue(&bts->abis_queue); + msgb_free(msg); + } + + bts->abis_nm_pend = 0; +} diff --git a/src/osmo-bsc/abis_nm_ipaccess.c b/src/osmo-bsc/abis_nm_ipaccess.c new file mode 100644 index 000000000..964b92e26 --- /dev/null +++ b/src/osmo-bsc/abis_nm_ipaccess.c @@ -0,0 +1,89 @@ +/* GSM Network Management (OML) messages on the A-bis interface + * Extensions for the ip.access A-bis over IP protocol*/ + +/* (C) 2008-2009 by Harald Welte <laforge@gnumonks.org> + * + * 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 <stdint.h> + +/* A list of all the 'embedded' attributes of ip.access */ +enum ipa_embedded_att { + IPA_ATT_ARFCN_WHITELIST = 0x01, + IPA_ATT_ARFCN_BLACKLIST = 0x02, + IPA_ATT_FREQ_ERR_LIST = 0x03, + IPA_ATT_CHAN_USAGE_LIST = 0x04, + IPA_ATT_BCCH_INF_TYPE = 0x05, + IPA_ATT_BCCH_INF = 0x06, + IPA_ATT_CONFIG = 0x07, + IPA_ATT_RESULT_DETAILS = 0x08, + IPA_ATT_RXLEV_THRESH = 0x09, + IPA_ATT_FREQ_SYNC_OPT = 0x0a, + IPA_ATT_MAC_ADDR = 0x0b, + IPA_ATT_HW_SW_COMPAT_NR = 0x0c, + IPA_ATT_MANUF_SER_NR = 0x0d, + IPA_ATT_OEM_ID = 0x0e, + IPA_ATT_DATETIME_MANUF = 0x0f, + IPA_ATT_DATETIME_CALIB = 0x10, + IPA_ATT_BEACON_INF = 0x11, + IPA_ATT_FREQ_ERR = 0x12, + IPA_ATT_SNMP_COMM_STRING = 0x13, + IPA_ATT_SNMP_TRAP_ADDR = 0x14, + IPA_ATT_SNMP_TRAP_PORT = 0x15, + IPA_ATT_SNMP_MAN_ADDR = 0x16, + IPA_ATT_SNMP_SYS_CONTACT = 0x17, + IPA_ATT_FACTORY_ID = 0x18, + IPA_ATT_FACTORY_SERIAL = 0x19, + IPA_ATT_LOGGED_EVT_IND = 0x1a, + IPA_ATT_LOCAL_ADD_TEXT = 0x1b, + IPA_ATT_FREQ_BANDS = 0x1c, + IPA_ATT_MAX_TA = 0x1d, + IPA_ATT_CIPH_ALG = 0x1e, + IPA_ATT_CHAN_TYPES = 0x1f, + IPA_ATT_CHAN_MODES = 0x20, + IPA_ATT_GPRS_CODING_SCHEMES = 0x21, + IPA_ATT_RTP_FEATURES = 0x22, + IPA_ATT_RSL_FEATURES = 0x23, + IPA_ATT_BTS_HW_CLASS = 0x24, + IPA_ATT_BTS_ID = 0x25, + IPA_ATT_BCAST_L2_MSG = 0x26, +}; + +/* append an ip.access channel list to the given msgb */ +static int ipa_chan_list_append(struct msgb *msg, uint8_t ie, + uint16_t *arfcns, int arfcn_count) +{ + int i; + uint8_t *u8; + uint16_t *u16; + + /* tag */ + u8 = msgb_push(msg, 1); + *u8 = ie; + + /* length in octets */ + u16 = msgb_push(msg, 2); + *u16 = htons(arfcn_count * 2); + + for (i = 0; i < arfcn_count; i++) { + u16 = msgb_push(msg, 2); + *u16 = htons(arfcns[i]); + } + + return 0; +} diff --git a/src/osmo-bsc/abis_nm_vty.c b/src/osmo-bsc/abis_nm_vty.c new file mode 100644 index 000000000..3019eb828 --- /dev/null +++ b/src/osmo-bsc/abis_nm_vty.c @@ -0,0 +1,188 @@ +/* VTY interface for A-bis OML (Netowrk Management) */ + +/* (C) 2009-2018 by Harald Welte <laforge@gnumonks.org> + * + * 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 <stdlib.h> +#include <unistd.h> +#include <errno.h> +#include <stdint.h> + +#include <arpa/inet.h> + +#include <osmocom/gsm/abis_nm.h> + +#include <osmocom/bsc/gsm_data.h> +#include <osmocom/core/msgb.h> +#include <osmocom/gsm/tlv.h> +#include <osmocom/core/talloc.h> +#include <osmocom/bsc/debug.h> +#include <osmocom/bsc/signal.h> +#include <osmocom/bsc/abis_nm.h> +#include <osmocom/bsc/vty.h> + +#include <osmocom/vty/vty.h> +#include <osmocom/vty/command.h> +#include <osmocom/vty/logging.h> +#include <osmocom/vty/telnet_interface.h> + +static struct cmd_node oml_node = { + OML_NODE, + "%s(oml)# ", + 1, +}; + +struct oml_node_state { + struct gsm_bts *bts; + uint8_t obj_class; + uint8_t obj_inst[3]; +}; + +static int dummy_config_write(struct vty *v) +{ + return CMD_SUCCESS; +} + +/* FIXME: auto-generate those strings from the value_string lists */ +#define NM_OBJCLASS_VTY "(site-manager|bts|radio-carrier|baseband-transceiver|channel|adjc|handover|power-contorl|btse|rack|test|envabtse|bport|gprs-nse|gprs-cell|gprs-nsvc|siemenshw)" +#define NM_OBJCLASS_VTY_HELP "Site Manager Object\n" \ + "BTS Object\n" \ + "Radio Carrier Object\n" \ + "Baseband Transceiver Object\n" \ + "Channel (Timeslot) Object\n" \ + "Adjacent Object (Siemens)\n" \ + "Handover Object (Siemens)\n" \ + "Power Control Object (Siemens)\n" \ + "BTSE Object (Siemens)\n" \ + "Rack Object (Siemens)\n" \ + "Test Object (Siemens)\n" \ + "ENVABTSE Object (Siemens)\n" \ + "BPORT Object (Siemens)\n" \ + "GPRS NSE Object (ip.access/osmo-bts)\n" \ + "GPRS Cell Object (ip.acecss/osmo-bts)\n" \ + "GPRS NSVC Object (ip.acecss/osmo-bts)\n" \ + "SIEMENSHW Object (Siemens)\n" + + +DEFUN(oml_class_inst, oml_class_inst_cmd, + "bts <0-255> oml class " NM_OBJCLASS_VTY + " instance <0-255> <0-255> <0-255>", + "BTS related commands\n" "BTS Number\n" + "Manipulate the OML managed objects\n" + "Object Class\n" NM_OBJCLASS_VTY_HELP + "Object Instance\n" "BTS Number\n" "TRX Number\n" "TS Number\n") +{ + struct gsm_bts *bts; + struct oml_node_state *oms; + int bts_nr = atoi(argv[0]); + + bts = gsm_bts_num(gsmnet_from_vty(vty), bts_nr); + if (!bts) { + vty_out(vty, "%% No such BTS (%d)%s", bts_nr, VTY_NEWLINE); + return CMD_WARNING; + } + + oms = talloc_zero(tall_bsc_ctx, struct oml_node_state); + if (!oms) + return CMD_WARNING; + + oms->bts = bts; + oms->obj_class = get_string_value(abis_nm_obj_class_names, argv[1]); + oms->obj_inst[0] = atoi(argv[2]); + oms->obj_inst[1] = atoi(argv[3]); + oms->obj_inst[2] = atoi(argv[4]); + + vty->index = oms; + vty->node = OML_NODE; + + return CMD_SUCCESS; + +} + +DEFUN(oml_classnum_inst, oml_classnum_inst_cmd, + "bts <0-255> oml class <0-255> instance <0-255> <0-255> <0-255>", + "BTS related commands\n" "BTS Number\n" + "Manipulate the OML managed objects\n" + "Object Class\n" "Object Class\n" + "Object Instance\n" "BTS Number\n" "TRX Number\n" "TS Number\n") +{ + struct gsm_bts *bts; + struct oml_node_state *oms; + int bts_nr = atoi(argv[0]); + + bts = gsm_bts_num(gsmnet_from_vty(vty), bts_nr); + if (!bts) { + vty_out(vty, "%% No such BTS (%d)%s", bts_nr, VTY_NEWLINE); + return CMD_WARNING; + } + + oms = talloc_zero(tall_bsc_ctx, struct oml_node_state); + if (!oms) + return CMD_WARNING; + + oms->bts = bts; + oms->obj_class = atoi(argv[1]); + oms->obj_inst[0] = atoi(argv[2]); + oms->obj_inst[1] = atoi(argv[3]); + oms->obj_inst[2] = atoi(argv[4]); + + vty->index = oms; + vty->node = OML_NODE; + + return CMD_SUCCESS; +} + +DEFUN(oml_chg_adm_state, oml_chg_adm_state_cmd, + "change-adm-state (locked|unlocked|shutdown|null)", + "Change the Administrative State\n" + "Locked\n" "Unlocked\n" "Shutdown\n" "NULL\n") +{ + struct oml_node_state *oms = vty->index; + enum abis_nm_adm_state state; + + state = get_string_value(abis_nm_adm_state_names, argv[0]); + + abis_nm_chg_adm_state(oms->bts, oms->obj_class, oms->obj_inst[0], + oms->obj_inst[1], oms->obj_inst[2], state); + + return CMD_SUCCESS; +} + +DEFUN(oml_opstart, oml_opstart_cmd, + "opstart", "Send an OPSTART message to the object") +{ + struct oml_node_state *oms = vty->index; + + abis_nm_opstart(oms->bts, oms->obj_class, oms->obj_inst[0], + oms->obj_inst[1], oms->obj_inst[2]); + + return CMD_SUCCESS; +} + +int abis_nm_vty_init(void) +{ + install_element(ENABLE_NODE, &oml_class_inst_cmd); + install_element(ENABLE_NODE, &oml_classnum_inst_cmd); + install_node(&oml_node, dummy_config_write); + + install_element(OML_NODE, &oml_chg_adm_state_cmd); + install_element(OML_NODE, &oml_opstart_cmd); + + return 0; +} diff --git a/src/osmo-bsc/abis_om2000.c b/src/osmo-bsc/abis_om2000.c new file mode 100644 index 000000000..d533ea198 --- /dev/null +++ b/src/osmo-bsc/abis_om2000.c @@ -0,0 +1,2769 @@ +/* Ericsson RBS 2xxx GSM O&M (OM2000) messages on the A-bis interface + * implemented based on protocol trace analysis, no formal documentation */ + +/* (C) 2010-2011,2016 by Harald Welte <laforge@gnumonks.org> + * + * 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 <errno.h> +#include <unistd.h> +#include <stdio.h> +#include <stdlib.h> +#include <time.h> +#include <stdint.h> + +#include <arpa/inet.h> + +#include <osmocom/core/msgb.h> +#include <osmocom/gsm/tlv.h> +#include <osmocom/core/talloc.h> +#include <osmocom/core/utils.h> +#include <osmocom/core/fsm.h> + +#include <osmocom/bsc/gsm_data.h> +#include <osmocom/bsc/debug.h> +#include <osmocom/bsc/abis_nm.h> +#include <osmocom/bsc/abis_rsl.h> +#include <osmocom/bsc/abis_om2000.h> +#include <osmocom/bsc/signal.h> +#include <osmocom/abis/e1_input.h> + +/* FIXME: move to libosmocore */ +struct osmo_fsm_inst *osmo_fsm_inst_alloc_child_id(struct osmo_fsm *fsm, + struct osmo_fsm_inst *parent, + uint32_t parent_term_event, + const char *id) +{ + struct osmo_fsm_inst *fi; + + fi = osmo_fsm_inst_alloc(fsm, parent, NULL, parent->log_level, + id ? id : parent->id); + if (!fi) { + /* indicate immediate termination to caller */ + osmo_fsm_inst_dispatch(parent, parent_term_event, NULL); + return NULL; + } + + LOGPFSM(fi, "is child of %s\n", osmo_fsm_inst_name(parent)); + + fi->proc.parent = parent; + fi->proc.parent_term_event = parent_term_event; + llist_add(&fi->proc.child, &parent->proc.children); + + return fi; +} + + +#define OM_ALLOC_SIZE 1024 +#define OM_HEADROOM_SIZE 128 + +#define OM2K_TIMEOUT 10 +#define TRX_FSM_TIMEOUT 60 +#define BTS_FSM_TIMEOUT 60 + +/* use following functions from abis_nm.c: + * om2k_msgb_alloc() + * abis_om2k_sendmsg() + */ + +struct abis_om2k_hdr { + struct abis_om_hdr om; + uint16_t msg_type; + struct abis_om2k_mo mo; + uint8_t data[0]; +} __attribute__ ((packed)); + +enum abis_om2k_msgtype { + OM2K_MSGT_ABORT_SP_CMD = 0x0000, + OM2K_MSGT_ABORT_SP_COMPL = 0x0002, + OM2K_MSGT_ALARM_REP_ACK = 0x0004, + OM2K_MSGT_ALARM_REP_NACK = 0x0005, + OM2K_MSGT_ALARM_REP = 0x0006, + OM2K_MSGT_ALARM_STATUS_REQ = 0x0008, + OM2K_MSGT_ALARM_STATUS_REQ_ACK = 0x000a, + OM2K_MSGT_ALARM_STATUS_REQ_REJ = 0x000b, + OM2K_MSGT_ALARM_STATUS_RES_ACK = 0x000c, + OM2K_MSGT_ALARM_STATUS_RES_NACK = 0x000d, + OM2K_MSGT_ALARM_STATUS_RES = 0x000e, + OM2K_MSGT_CAL_TIME_RESP = 0x0010, + OM2K_MSGT_CAL_TIME_REJ = 0x0011, + OM2K_MSGT_CAL_TIME_REQ = 0x0012, + + OM2K_MSGT_CON_CONF_REQ = 0x0014, + OM2K_MSGT_CON_CONF_REQ_ACK = 0x0016, + OM2K_MSGT_CON_CONF_REQ_REJ = 0x0017, + OM2K_MSGT_CON_CONF_RES_ACK = 0x0018, + OM2K_MSGT_CON_CONF_RES_NACK = 0x0019, + OM2K_MSGT_CON_CONF_RES = 0x001a, + + OM2K_MSGT_CONNECT_CMD = 0x001c, + OM2K_MSGT_CONNECT_COMPL = 0x001e, + OM2K_MSGT_CONNECT_REJ = 0x001f, + + OM2K_MSGT_DISABLE_REQ = 0x0028, + OM2K_MSGT_DISABLE_REQ_ACK = 0x002a, + OM2K_MSGT_DISABLE_REQ_REJ = 0x002b, + OM2K_MSGT_DISABLE_RES_ACK = 0x002c, + OM2K_MSGT_DISABLE_RES_NACK = 0x002d, + OM2K_MSGT_DISABLE_RES = 0x002e, + OM2K_MSGT_DISCONNECT_CMD = 0x0030, + OM2K_MSGT_DISCONNECT_COMPL = 0x0032, + OM2K_MSGT_DISCONNECT_REJ = 0x0033, + OM2K_MSGT_ENABLE_REQ = 0x0034, + OM2K_MSGT_ENABLE_REQ_ACK = 0x0036, + OM2K_MSGT_ENABLE_REQ_REJ = 0x0037, + OM2K_MSGT_ENABLE_RES_ACK = 0x0038, + OM2K_MSGT_ENABLE_RES_NACK = 0x0039, + OM2K_MSGT_ENABLE_RES = 0x003a, + + OM2K_MSGT_FAULT_REP_ACK = 0x0040, + OM2K_MSGT_FAULT_REP_NACK = 0x0041, + OM2K_MSGT_FAULT_REP = 0x0042, + + OM2K_MSGT_IS_CONF_REQ = 0x0060, + OM2K_MSGT_IS_CONF_REQ_ACK = 0x0062, + OM2K_MSGT_IS_CONF_REQ_REJ = 0x0063, + OM2K_MSGT_IS_CONF_RES_ACK = 0x0064, + OM2K_MSGT_IS_CONF_RES_NACK = 0x0065, + OM2K_MSGT_IS_CONF_RES = 0x0066, + + OM2K_MSGT_OP_INFO = 0x0074, + OM2K_MSGT_OP_INFO_ACK = 0x0076, + OM2K_MSGT_OP_INFO_REJ = 0x0077, + OM2K_MSGT_RESET_CMD = 0x0078, + OM2K_MSGT_RESET_COMPL = 0x007a, + OM2K_MSGT_RESET_REJ = 0x007b, + OM2K_MSGT_RX_CONF_REQ = 0x007c, + OM2K_MSGT_RX_CONF_REQ_ACK = 0x007e, + OM2K_MSGT_RX_CONF_REQ_REJ = 0x007f, + OM2K_MSGT_RX_CONF_RES_ACK = 0x0080, + OM2K_MSGT_RX_CONF_RES_NACK = 0x0081, + OM2K_MSGT_RX_CONF_RES = 0x0082, + OM2K_MSGT_START_REQ = 0x0084, + OM2K_MSGT_START_REQ_ACK = 0x0086, + OM2K_MSGT_START_REQ_REJ = 0x0087, + OM2K_MSGT_START_RES_ACK = 0x0088, + OM2K_MSGT_START_RES_NACK = 0x0089, + OM2K_MSGT_START_RES = 0x008a, + OM2K_MSGT_STATUS_REQ = 0x008c, + OM2K_MSGT_STATUS_RESP = 0x008e, + OM2K_MSGT_STATUS_REJ = 0x008f, + + OM2K_MSGT_TEST_REQ = 0x0094, + OM2K_MSGT_TEST_REQ_ACK = 0x0096, + OM2K_MSGT_TEST_REQ_REJ = 0x0097, + OM2K_MSGT_TEST_RES_ACK = 0x0098, + OM2K_MSGT_TEST_RES_NACK = 0x0099, + OM2K_MSGT_TEST_RES = 0x009a, + + OM2K_MSGT_TF_CONF_REQ = 0x00a0, + OM2K_MSGT_TF_CONF_REQ_ACK = 0x00a2, + OM2K_MSGT_TF_CONF_REQ_REJ = 0x00a3, + OM2K_MSGT_TF_CONF_RES_ACK = 0x00a4, + OM2K_MSGT_TF_CONF_RES_NACK = 0x00a5, + OM2K_MSGT_TF_CONF_RES = 0x00a6, + OM2K_MSGT_TS_CONF_REQ = 0x00a8, + OM2K_MSGT_TS_CONF_REQ_ACK = 0x00aa, + OM2K_MSGT_TS_CONF_REQ_REJ = 0x00ab, + OM2K_MSGT_TS_CONF_RES_ACK = 0x00ac, + OM2K_MSGT_TS_CONF_RES_NACK = 0x00ad, + OM2K_MSGT_TS_CONF_RES = 0x00ae, + OM2K_MSGT_TX_CONF_REQ = 0x00b0, + OM2K_MSGT_TX_CONF_REQ_ACK = 0x00b2, + OM2K_MSGT_TX_CONF_REQ_REJ = 0x00b3, + OM2K_MSGT_TX_CONF_RES_ACK = 0x00b4, + OM2K_MSGT_TX_CONF_RES_NACK = 0x00b5, + OM2K_MSGT_TX_CONF_RES = 0x00b6, + + OM2K_MSGT_CAPA_REQ = 0x00e8, + OM2K_MSGT_CAPA_REQ_ACK = 0x00ea, + OM2K_MSGT_CAPA_REQ_REJ = 0x00eb, + OM2K_MSGT_CAPA_RES = 0x00ee, + OM2K_MSGT_CAPA_RES_ACK = 0x00ec, + OM2K_MSGT_CAPA_RES_NACK = 0x00ed, + + OM2K_MSGT_NEGOT_REQ_ACK = 0x0104, + OM2K_MSGT_NEGOT_REQ_NACK = 0x0105, + OM2K_MSGT_NEGOT_REQ = 0x0106, +}; + +enum abis_om2k_dei { + OM2K_DEI_ACCORDANCE_IND = 0x00, + OM2K_DEI_BCC = 0x06, + OM2K_DEI_BS_AG_BKS_RES = 0x07, + OM2K_DEI_BSIC = 0x09, + OM2K_DEI_BA_PA_MFRMS = 0x0a, + OM2K_DEI_CBCH_INDICATOR = 0x0b, + OM2K_DEI_CCCH_OPTIONS = 0x0c, + OM2K_DEI_CAL_TIME = 0x0d, + OM2K_DEI_COMBINATION = 0x0f, + OM2K_DEI_CON_CONN_LIST = 0x10, + OM2K_DEI_DRX_DEV_MAX = 0x12, + OM2K_DEI_END_LIST_NR = 0x13, + OM2K_DEI_EXT_COND_MAP_1 = 0x14, + OM2K_DEI_EXT_COND_MAP_2 = 0x15, + OM2K_DEI_FILLING_MARKER = 0x1c, + OM2K_DEI_FN_OFFSET = 0x1d, + OM2K_DEI_FREQ_LIST = 0x1e, + OM2K_DEI_FREQ_SPEC_RX = 0x1f, + OM2K_DEI_FREQ_SPEC_TX = 0x20, + OM2K_DEI_HSN = 0x21, + OM2K_DEI_ICM_INDICATOR = 0x22, + OM2K_DEI_INT_FAULT_MAP_1A = 0x23, + OM2K_DEI_INT_FAULT_MAP_1B = 0x24, + OM2K_DEI_INT_FAULT_MAP_2A = 0x25, + OM2K_DEI_INT_FAULT_MAP_2A_EXT = 0x26, + OM2K_DEI_IS_CONN_LIST = 0x27, + OM2K_DEI_LIST_NR = 0x28, + OM2K_DEI_LOCAL_ACCESS = 0x2a, + OM2K_DEI_MAIO = 0x2b, + OM2K_DEI_MO_STATE = 0x2c, + OM2K_DEI_NY1 = 0x2d, + OM2K_DEI_OP_INFO = 0x2e, + OM2K_DEI_POWER = 0x2f, + OM2K_DEI_REASON_CODE = 0x32, + OM2K_DEI_RX_DIVERSITY = 0x33, + OM2K_DEI_REPL_UNIT_MAP = 0x34, + OM2K_DEI_RESULT_CODE = 0x35, + OM2K_DEI_T3105 = 0x38, + OM2K_DEI_TF_MODE = 0x3a, + OM2K_DEI_TS_NR = 0x3c, + OM2K_DEI_TSC = 0x3d, + OM2K_DEI_BTS_VERSION = 0x40, + OM2K_DEI_OML_IWD_VERSION = 0x41, + OM2K_DEI_RSL_IWD_VERSION = 0x42, + OM2K_DEI_OML_FUNC_MAP_1 = 0x43, + OM2K_DEI_OML_FUNC_MAP_2 = 0x44, + OM2K_DEI_RSL_FUNC_MAP_1 = 0x45, + OM2K_DEI_RSL_FUNC_MAP_2 = 0x46, + OM2K_DEI_EXT_RANGE = 0x47, + OM2K_DEI_REQ_IND = 0x48, + OM2K_DEI_REPL_UNIT_MAP_EXT = 0x50, + OM2K_DEI_ICM_BOUND_PARAMS = 0x74, + OM2K_DEI_LSC = 0x79, + OM2K_DEI_LSC_FILT_TIME = 0x7a, + OM2K_DEI_CALL_SUPV_TIME = 0x7b, + OM2K_DEI_ICM_CHAN_RATE = 0x7e, + OM2K_DEI_HW_INFO_SIG = 0x84, + OM2K_DEI_TF_SYNC_SRC = 0x86, + OM2K_DEI_TTA = 0x87, + OM2K_DEI_CAPA_SIG = 0x8a, + OM2K_DEI_NEGOT_REC1 = 0x90, + OM2K_DEI_NEGOT_REC2 = 0x91, + OM2K_DEI_ENCR_ALG = 0x92, + OM2K_DEI_INTERF_REJ_COMB = 0x94, + OM2K_DEI_FS_OFFSET = 0x98, + OM2K_DEI_EXT_COND_MAP_2_EXT = 0x9c, + OM2K_DEI_TSS_MO_STATE = 0x9d, +}; + +const struct tlv_definition om2k_att_tlvdef = { + .def = { + [OM2K_DEI_ACCORDANCE_IND] = { TLV_TYPE_TV }, + [OM2K_DEI_BCC] = { TLV_TYPE_TV }, + [OM2K_DEI_BS_AG_BKS_RES] = { TLV_TYPE_TV }, + [OM2K_DEI_BSIC] = { TLV_TYPE_TV }, + [OM2K_DEI_BA_PA_MFRMS] = { TLV_TYPE_TV }, + [OM2K_DEI_CBCH_INDICATOR] = { TLV_TYPE_TV }, + [OM2K_DEI_INT_FAULT_MAP_1A] = { TLV_TYPE_FIXED, 6 }, + [OM2K_DEI_INT_FAULT_MAP_1B] = { TLV_TYPE_FIXED, 6 }, + [OM2K_DEI_INT_FAULT_MAP_2A] = { TLV_TYPE_FIXED, 6 }, + [OM2K_DEI_INT_FAULT_MAP_2A_EXT]={ TLV_TYPE_FIXED, 6 }, + [OM2K_DEI_CCCH_OPTIONS] = { TLV_TYPE_TV }, + [OM2K_DEI_CAL_TIME] = { TLV_TYPE_FIXED, 6 }, + [OM2K_DEI_COMBINATION] = { TLV_TYPE_TV }, + [OM2K_DEI_CON_CONN_LIST] = { TLV_TYPE_TLV }, + [OM2K_DEI_DRX_DEV_MAX] = { TLV_TYPE_TV }, + [OM2K_DEI_END_LIST_NR] = { TLV_TYPE_TV }, + [OM2K_DEI_EXT_COND_MAP_1] = { TLV_TYPE_FIXED, 2 }, + [OM2K_DEI_EXT_COND_MAP_2] = { TLV_TYPE_FIXED, 2 }, + [OM2K_DEI_FILLING_MARKER] = { TLV_TYPE_TV }, + [OM2K_DEI_FN_OFFSET] = { TLV_TYPE_FIXED, 2 }, + [OM2K_DEI_FREQ_LIST] = { TLV_TYPE_TLV }, + [OM2K_DEI_FREQ_SPEC_RX] = { TLV_TYPE_FIXED, 2 }, + [OM2K_DEI_FREQ_SPEC_TX] = { TLV_TYPE_FIXED, 2 }, + [OM2K_DEI_HSN] = { TLV_TYPE_TV }, + [OM2K_DEI_ICM_INDICATOR] = { TLV_TYPE_TV }, + [OM2K_DEI_IS_CONN_LIST] = { TLV_TYPE_TLV }, + [OM2K_DEI_LIST_NR] = { TLV_TYPE_TV }, + [OM2K_DEI_LOCAL_ACCESS] = { TLV_TYPE_TV }, + [OM2K_DEI_MAIO] = { TLV_TYPE_TV }, + [OM2K_DEI_MO_STATE] = { TLV_TYPE_TV }, + [OM2K_DEI_NY1] = { TLV_TYPE_TV }, + [OM2K_DEI_OP_INFO] = { TLV_TYPE_TV }, + [OM2K_DEI_POWER] = { TLV_TYPE_TV }, + [OM2K_DEI_REASON_CODE] = { TLV_TYPE_TV }, + [OM2K_DEI_RX_DIVERSITY] = { TLV_TYPE_TV }, + [OM2K_DEI_RESULT_CODE] = { TLV_TYPE_TV }, + [OM2K_DEI_T3105] = { TLV_TYPE_TV }, + [OM2K_DEI_TF_MODE] = { TLV_TYPE_TV }, + [OM2K_DEI_TS_NR] = { TLV_TYPE_TV }, + [OM2K_DEI_TSC] = { TLV_TYPE_TV }, + [OM2K_DEI_BTS_VERSION] = { TLV_TYPE_FIXED, 12 }, + [OM2K_DEI_OML_IWD_VERSION] = { TLV_TYPE_FIXED, 6 }, + [OM2K_DEI_RSL_IWD_VERSION] = { TLV_TYPE_FIXED, 6 }, + [OM2K_DEI_OML_FUNC_MAP_1] = { TLV_TYPE_TLV }, + [OM2K_DEI_OML_FUNC_MAP_2] = { TLV_TYPE_TLV }, + [OM2K_DEI_RSL_FUNC_MAP_1] = { TLV_TYPE_TLV }, + [OM2K_DEI_RSL_FUNC_MAP_2] = { TLV_TYPE_TLV }, + [OM2K_DEI_EXT_RANGE] = { TLV_TYPE_TV }, + [OM2K_DEI_REQ_IND] = { TLV_TYPE_TV }, + [OM2K_DEI_REPL_UNIT_MAP] = { TLV_TYPE_FIXED, 6 }, + [OM2K_DEI_REPL_UNIT_MAP_EXT] = {TLV_TYPE_FIXED, 6}, + [OM2K_DEI_ICM_BOUND_PARAMS] = { TLV_TYPE_FIXED, 5 }, + [OM2K_DEI_LSC] = { TLV_TYPE_TV }, + [OM2K_DEI_LSC_FILT_TIME] = { TLV_TYPE_TV }, + [OM2K_DEI_CALL_SUPV_TIME] = { TLV_TYPE_TV }, + [OM2K_DEI_ICM_CHAN_RATE] = { TLV_TYPE_TV }, + [OM2K_DEI_HW_INFO_SIG] = { TLV_TYPE_FIXED, 2 }, + [OM2K_DEI_TF_SYNC_SRC] = { TLV_TYPE_TV }, + [OM2K_DEI_TTA] = { TLV_TYPE_TV }, + [OM2K_DEI_CAPA_SIG] = { TLV_TYPE_FIXED, 2 }, + [OM2K_DEI_NEGOT_REC1] = { TLV_TYPE_TLV }, + [OM2K_DEI_NEGOT_REC2] = { TLV_TYPE_TLV }, + [OM2K_DEI_ENCR_ALG] = { TLV_TYPE_TV }, + [OM2K_DEI_INTERF_REJ_COMB] = { TLV_TYPE_TV }, + [OM2K_DEI_FS_OFFSET] = { TLV_TYPE_FIXED, 5 }, + [OM2K_DEI_EXT_COND_MAP_2_EXT] = { TLV_TYPE_FIXED, 4 }, + [OM2K_DEI_TSS_MO_STATE] = { TLV_TYPE_FIXED, 4 }, + }, +}; + +static const struct value_string om2k_msgcode_vals[] = { + { 0x0000, "Abort SP Command" }, + { 0x0002, "Abort SP Complete" }, + { 0x0004, "Alarm Report ACK" }, + { 0x0005, "Alarm Report NACK" }, + { 0x0006, "Alarm Report" }, + { 0x0008, "Alarm Status Request" }, + { 0x000a, "Alarm Status Request Accept" }, + { 0x000b, "Alarm Status Request Reject" }, + { 0x000c, "Alarm Status Result ACK" }, + { 0x000d, "Alarm Status Result NACK" }, + { 0x000e, "Alarm Status Result" }, + { 0x0010, "Calendar Time Response" }, + { 0x0011, "Calendar Time Reject" }, + { 0x0012, "Calendar Time Request" }, + { 0x0014, "CON Configuration Request" }, + { 0x0016, "CON Configuration Request Accept" }, + { 0x0017, "CON Configuration Request Reject" }, + { 0x0018, "CON Configuration Result ACK" }, + { 0x0019, "CON Configuration Result NACK" }, + { 0x001a, "CON Configuration Result" }, + { 0x001c, "Connect Command" }, + { 0x001e, "Connect Complete" }, + { 0x001f, "Connect Reject" }, + { 0x0028, "Disable Request" }, + { 0x002a, "Disable Request Accept" }, + { 0x002b, "Disable Request Reject" }, + { 0x002c, "Disable Result ACK" }, + { 0x002d, "Disable Result NACK" }, + { 0x002e, "Disable Result" }, + { 0x0030, "Disconnect Command" }, + { 0x0032, "Disconnect Complete" }, + { 0x0033, "Disconnect Reject" }, + { 0x0034, "Enable Request" }, + { 0x0036, "Enable Request Accept" }, + { 0x0037, "Enable Request Reject" }, + { 0x0038, "Enable Result ACK" }, + { 0x0039, "Enable Result NACK" }, + { 0x003a, "Enable Result" }, + { 0x003c, "Escape Downlink Normal" }, + { 0x003d, "Escape Downlink NACK" }, + { 0x003e, "Escape Uplink Normal" }, + { 0x003f, "Escape Uplink NACK" }, + { 0x0040, "Fault Report ACK" }, + { 0x0041, "Fault Report NACK" }, + { 0x0042, "Fault Report" }, + { 0x0044, "File Package End Command" }, + { 0x0046, "File Package End Result" }, + { 0x0047, "File Package End Reject" }, + { 0x0048, "File Relation Request" }, + { 0x004a, "File Relation Response" }, + { 0x004b, "File Relation Request Reject" }, + { 0x004c, "File Segment Transfer" }, + { 0x004e, "File Segment Transfer Complete" }, + { 0x004f, "File Segment Transfer Reject" }, + { 0x0050, "HW Information Request" }, + { 0x0052, "HW Information Request Accept" }, + { 0x0053, "HW Information Request Reject" }, + { 0x0054, "HW Information Result ACK" }, + { 0x0055, "HW Information Result NACK" }, + { 0x0056, "HW Information Result" }, + { 0x0060, "IS Configuration Request" }, + { 0x0062, "IS Configuration Request Accept" }, + { 0x0063, "IS Configuration Request Reject" }, + { 0x0064, "IS Configuration Result ACK" }, + { 0x0065, "IS Configuration Result NACK" }, + { 0x0066, "IS Configuration Result" }, + { 0x0068, "Load Data End" }, + { 0x006a, "Load Data End Result" }, + { 0x006b, "Load Data End Reject" }, + { 0x006c, "Load Data Init" }, + { 0x006e, "Load Data Init Accept" }, + { 0x006f, "Load Data Init Reject" }, + { 0x0070, "Loop Control Command" }, + { 0x0072, "Loop Control Complete" }, + { 0x0073, "Loop Control Reject" }, + { 0x0074, "Operational Information" }, + { 0x0076, "Operational Information Accept" }, + { 0x0077, "Operational Information Reject" }, + { 0x0078, "Reset Command" }, + { 0x007a, "Reset Complete" }, + { 0x007b, "Reset Reject" }, + { 0x007c, "RX Configuration Request" }, + { 0x007e, "RX Configuration Request Accept" }, + { 0x007f, "RX Configuration Request Reject" }, + { 0x0080, "RX Configuration Result ACK" }, + { 0x0081, "RX Configuration Result NACK" }, + { 0x0082, "RX Configuration Result" }, + { 0x0084, "Start Request" }, + { 0x0086, "Start Request Accept" }, + { 0x0087, "Start Request Reject" }, + { 0x0088, "Start Result ACK" }, + { 0x0089, "Start Result NACK" }, + { 0x008a, "Start Result" }, + { 0x008c, "Status Request" }, + { 0x008e, "Status Response" }, + { 0x008f, "Status Reject" }, + { 0x0094, "Test Request" }, + { 0x0096, "Test Request Accept" }, + { 0x0097, "Test Request Reject" }, + { 0x0098, "Test Result ACK" }, + { 0x0099, "Test Result NACK" }, + { 0x009a, "Test Result" }, + { 0x00a0, "TF Configuration Request" }, + { 0x00a2, "TF Configuration Request Accept" }, + { 0x00a3, "TF Configuration Request Reject" }, + { 0x00a4, "TF Configuration Result ACK" }, + { 0x00a5, "TF Configuration Result NACK" }, + { 0x00a6, "TF Configuration Result" }, + { 0x00a8, "TS Configuration Request" }, + { 0x00aa, "TS Configuration Request Accept" }, + { 0x00ab, "TS Configuration Request Reject" }, + { 0x00ac, "TS Configuration Result ACK" }, + { 0x00ad, "TS Configuration Result NACK" }, + { 0x00ae, "TS Configuration Result" }, + { 0x00b0, "TX Configuration Request" }, + { 0x00b2, "TX Configuration Request Accept" }, + { 0x00b3, "TX Configuration Request Reject" }, + { 0x00b4, "TX Configuration Result ACK" }, + { 0x00b5, "TX Configuration Result NACK" }, + { 0x00b6, "TX Configuration Result" }, + { 0x00bc, "DIP Alarm Report ACK" }, + { 0x00bd, "DIP Alarm Report NACK" }, + { 0x00be, "DIP Alarm Report" }, + { 0x00c0, "DIP Alarm Status Request" }, + { 0x00c2, "DIP Alarm Status Response" }, + { 0x00c3, "DIP Alarm Status Reject" }, + { 0x00c4, "DIP Quality Report I ACK" }, + { 0x00c5, "DIP Quality Report I NACK" }, + { 0x00c6, "DIP Quality Report I" }, + { 0x00c8, "DIP Quality Report II ACK" }, + { 0x00c9, "DIP Quality Report II NACK" }, + { 0x00ca, "DIP Quality Report II" }, + { 0x00dc, "DP Configuration Request" }, + { 0x00de, "DP Configuration Request Accept" }, + { 0x00df, "DP Configuration Request Reject" }, + { 0x00e0, "DP Configuration Result ACK" }, + { 0x00e1, "DP Configuration Result NACK" }, + { 0x00e2, "DP Configuration Result" }, + { 0x00e4, "Capabilities HW Info Report ACK" }, + { 0x00e5, "Capabilities HW Info Report NACK" }, + { 0x00e6, "Capabilities HW Info Report" }, + { 0x00e8, "Capabilities Request" }, + { 0x00ea, "Capabilities Request Accept" }, + { 0x00eb, "Capabilities Request Reject" }, + { 0x00ec, "Capabilities Result ACK" }, + { 0x00ed, "Capabilities Result NACK" }, + { 0x00ee, "Capabilities Result" }, + { 0x00f0, "FM Configuration Request" }, + { 0x00f2, "FM Configuration Request Accept" }, + { 0x00f3, "FM Configuration Request Reject" }, + { 0x00f4, "FM Configuration Result ACK" }, + { 0x00f5, "FM Configuration Result NACK" }, + { 0x00f6, "FM Configuration Result" }, + { 0x00f8, "FM Report Request" }, + { 0x00fa, "FM Report Response" }, + { 0x00fb, "FM Report Reject" }, + { 0x00fc, "FM Start Command" }, + { 0x00fe, "FM Start Complete" }, + { 0x00ff, "FM Start Reject" }, + { 0x0100, "FM Stop Command" }, + { 0x0102, "FM Stop Complete" }, + { 0x0103, "FM Stop Reject" }, + { 0x0104, "Negotiation Request ACK" }, + { 0x0105, "Negotiation Request NACK" }, + { 0x0106, "Negotiation Request" }, + { 0x0108, "BTS Initiated Request ACK" }, + { 0x0109, "BTS Initiated Request NACK" }, + { 0x010a, "BTS Initiated Request" }, + { 0x010c, "Radio Channels Release Command" }, + { 0x010e, "Radio Channels Release Complete" }, + { 0x010f, "Radio Channels Release Reject" }, + { 0x0118, "Feature Control Command" }, + { 0x011a, "Feature Control Complete" }, + { 0x011b, "Feature Control Reject" }, + + { 0, NULL } +}; + +/* TS 12.21 Section 9.4: Attributes */ +static const struct value_string om2k_attr_vals[] = { + { 0x00, "Accordance indication" }, + { 0x01, "Alarm Id" }, + { 0x02, "Alarm Data" }, + { 0x03, "Alarm Severity" }, + { 0x04, "Alarm Status" }, + { 0x05, "Alarm Status Type" }, + { 0x06, "BCC" }, + { 0x07, "BS_AG_BKS_RES" }, + { 0x09, "BSIC" }, + { 0x0a, "BA_PA_MFRMS" }, + { 0x0b, "CBCH Indicator" }, + { 0x0c, "CCCH Options" }, + { 0x0d, "Calendar Time" }, + { 0x0f, "Channel Combination" }, + { 0x10, "CON Connection List" }, + { 0x11, "Data End Indication" }, + { 0x12, "DRX_DEV_MAX" }, + { 0x13, "End List Number" }, + { 0x14, "External Condition Map Class 1" }, + { 0x15, "External Condition Map Class 2" }, + { 0x16, "File Relation Indication" }, + { 0x17, "File Revision" }, + { 0x18, "File Segment Data" }, + { 0x19, "File Segment Length" }, + { 0x1a, "File Segment Sequence Number" }, + { 0x1b, "File Size" }, + { 0x1c, "Filling Marker" }, + { 0x1d, "FN Offset" }, + { 0x1e, "Frequency List" }, + { 0x1f, "Frequency Specifier RX" }, + { 0x20, "Frequency Specifier TX" }, + { 0x21, "HSN" }, + { 0x22, "ICM Indicator" }, + { 0x23, "Internal Fault Map Class 1A" }, + { 0x24, "Internal Fault Map Class 1B" }, + { 0x25, "Internal Fault Map Class 2A" }, + { 0x26, "Internal Fault Map Class 2A Extension" }, + { 0x27, "IS Connection List" }, + { 0x28, "List Number" }, + { 0x29, "File Package State Indication" }, + { 0x2a, "Local Access State" }, + { 0x2b, "MAIO" }, + { 0x2c, "MO State" }, + { 0x2d, "Ny1" }, + { 0x2e, "Operational Information" }, + { 0x2f, "Power" }, + { 0x30, "RU Position Data" }, + { 0x31, "Protocol Error" }, + { 0x32, "Reason Code" }, + { 0x33, "Receiver Diversity" }, + { 0x34, "Replacement Unit Map" }, + { 0x35, "Result Code" }, + { 0x36, "RU Revision Data" }, + { 0x38, "T3105" }, + { 0x39, "Test Loop Setting" }, + { 0x3a, "TF Mode" }, + { 0x3b, "TF Compensation Value" }, + { 0x3c, "Time Slot Number" }, + { 0x3d, "TSC" }, + { 0x3e, "RU Logical Id" }, + { 0x3f, "RU Serial Number Data" }, + { 0x40, "BTS Version" }, + { 0x41, "OML IWD Version" }, + { 0x42, "RWL IWD Version" }, + { 0x43, "OML Function Map 1" }, + { 0x44, "OML Function Map 2" }, + { 0x45, "RSL Function Map 1" }, + { 0x46, "RSL Function Map 2" }, + { 0x47, "Extended Range Indicator" }, + { 0x48, "Request Indicators" }, + { 0x49, "DIP Alarm Condition Map" }, + { 0x4a, "ES Incoming" }, + { 0x4b, "ES Outgoing" }, + { 0x4e, "SES Incoming" }, + { 0x4f, "SES Outgoing" }, + { 0x50, "Replacement Unit Map Extension" }, + { 0x52, "UAS Incoming" }, + { 0x53, "UAS Outgoing" }, + { 0x58, "DF Incoming" }, + { 0x5a, "DF Outgoing" }, + { 0x5c, "SF" }, + { 0x60, "S Bits Setting" }, + { 0x61, "CRC-4 Use Option" }, + { 0x62, "T Parameter" }, + { 0x63, "N Parameter" }, + { 0x64, "N1 Parameter" }, + { 0x65, "N3 Parameter" }, + { 0x66, "N4 Parameter" }, + { 0x67, "P Parameter" }, + { 0x68, "Q Parameter" }, + { 0x69, "BI_Q1" }, + { 0x6a, "BI_Q2" }, + { 0x74, "ICM Boundary Parameters" }, + { 0x77, "AFT" }, + { 0x78, "AFT RAI" }, + { 0x79, "Link Supervision Control" }, + { 0x7a, "Link Supervision Filtering Time" }, + { 0x7b, "Call Supervision Time" }, + { 0x7c, "Interval Length UAS Incoming" }, + { 0x7d, "Interval Length UAS Outgoing" }, + { 0x7e, "ICM Channel Rate" }, + { 0x7f, "Attribute Identifier" }, + { 0x80, "FM Frequency List" }, + { 0x81, "FM Frequency Report" }, + { 0x82, "FM Percentile" }, + { 0x83, "FM Clear Indication" }, + { 0x84, "HW Info Signature" }, + { 0x85, "MO Record" }, + { 0x86, "TF Synchronisation Source" }, + { 0x87, "TTA" }, + { 0x88, "End Segment Number" }, + { 0x89, "Segment Number" }, + { 0x8a, "Capabilities Signature" }, + { 0x8c, "File Relation List" }, + { 0x90, "Negotiation Record I" }, + { 0x91, "Negotiation Record II" }, + { 0x92, "Encryption Algorithm" }, + { 0x94, "Interference Rejection Combining" }, + { 0x95, "Dedication Information" }, + { 0x97, "Feature Code" }, + { 0x98, "FS Offset" }, + { 0x99, "ESB Timeslot" }, + { 0x9a, "Master TG Instance" }, + { 0x9b, "Master TX Chain Delay" }, + { 0x9c, "External Condition Class 2 Extension" }, + { 0x9d, "TSs MO State" }, + { 0, NULL } +}; + +const struct value_string om2k_mo_class_short_vals[] = { + { 0x01, "TRXC" }, + { 0x03, "TS" }, + { 0x04, "TF" }, + { 0x05, "IS" }, + { 0x06, "CON" }, + { 0x07, "DP" }, + { 0x0a, "CF" }, + { 0x0b, "TX" }, + { 0x0c, "RX" }, + { 0, NULL } +}; + +const struct value_string om2k_result_strings[] = { + { 0x02, "Wrong state or out of sequence" }, + { 0x03, "File error" }, + { 0x04, "Fault, unspecified" }, + { 0x05, "Tuning fault" }, + { 0x06, "Protocol error" }, + { 0x07, "MO not connected" }, + { 0x08, "Parameter error" }, + { 0x09, "Optional function not supported" }, + { 0x0a, "Local access state LOCALLY DISCONNECTED" }, + { 0, NULL } +}; + +const struct value_string om2k_accordance_strings[] = { + { 0x00, "Data according to request" }, + { 0x01, "Data not according to request" }, + { 0x02, "Inconsistent MO data" }, + { 0x03, "Capability constraint violation" }, + { 0, NULL } +}; + +const struct value_string om2k_mostate_vals[] = { + { 0x00, "RESET" }, + { 0x01, "STARTED" }, + { 0x02, "ENABLED" }, + { 0x03, "DISABLED" }, + { 0, NULL } +}; + +/* entire decoded OM2K message (header + parsed TLV) */ +struct om2k_decoded_msg { + struct abis_om2k_hdr o2h; + uint16_t msg_type; + struct tlv_parsed tp; +}; + +/* resolve the OM2000 Managed Object by BTS + MO Address */ +static struct om2k_mo * +get_om2k_mo(struct gsm_bts *bts, const struct abis_om2k_mo *abis_mo) +{ + struct om2k_mo *mo = NULL; + struct gsm_bts_trx *trx; + + switch (abis_mo->class) { + case OM2K_MO_CLS_CF: + mo = &bts->rbs2000.cf.om2k_mo; + break; + case OM2K_MO_CLS_CON: + mo = &bts->rbs2000.con.om2k_mo; + break; + case OM2K_MO_CLS_IS: + mo = &bts->rbs2000.is.om2k_mo; + break; + case OM2K_MO_CLS_TF: + mo = &bts->rbs2000.tf.om2k_mo; + break; + + case OM2K_MO_CLS_TRXC: + trx = gsm_bts_trx_num(bts, abis_mo->inst); + if (!trx) + return NULL; + mo = &trx->rbs2000.trxc.om2k_mo; + break; + case OM2K_MO_CLS_TX: + trx = gsm_bts_trx_num(bts, abis_mo->inst); + if (!trx) + return NULL; + mo = &trx->rbs2000.tx.om2k_mo; + break; + case OM2K_MO_CLS_RX: + trx = gsm_bts_trx_num(bts, abis_mo->inst); + if (!trx) + return NULL; + mo = &trx->rbs2000.rx.om2k_mo; + break; + case OM2K_MO_CLS_TS: + trx = gsm_bts_trx_num(bts, abis_mo->assoc_so); + if (!trx) + return NULL; + if (abis_mo->inst >= ARRAY_SIZE(trx->ts)) + return NULL; + mo = &trx->ts[abis_mo->inst].rbs2000.om2k_mo; + break; + default: + return NULL; + }; + + return mo; +} + +static struct msgb *om2k_msgb_alloc(void) +{ + return msgb_alloc_headroom(OM_ALLOC_SIZE, OM_HEADROOM_SIZE, + "OM2000"); +} + +static int abis_om2k_tlv_parse(struct tlv_parsed *tp, const uint8_t *buf, int len) +{ + return tlv_parse(tp, &om2k_att_tlvdef, buf, len, 0, 0); +} + +static int abis_om2k_msg_tlv_parse(struct tlv_parsed *tp, struct abis_om2k_hdr *oh) +{ + return abis_om2k_tlv_parse(tp, oh->data, oh->om.length - 6); +} + +/* decode/parse the message */ +static int om2k_decode_msg(struct om2k_decoded_msg *odm, struct msgb *msg) +{ + struct abis_om2k_hdr *o2h = msgb_l2(msg); + odm->msg_type = ntohs(o2h->msg_type); + odm->o2h = *o2h; + return abis_om2k_msg_tlv_parse(&odm->tp, o2h); +} + +static char *om2k_mo_name(const struct abis_om2k_mo *mo) +{ + static char mo_buf[64]; + + memset(mo_buf, 0, sizeof(mo_buf)); + snprintf(mo_buf, sizeof(mo_buf), "%s/%02x/%02x/%02x", + get_value_string(om2k_mo_class_short_vals, mo->class), + mo->bts, mo->assoc_so, mo->inst); + return mo_buf; +} + +/* resolve the gsm_nm_state data structure for a given MO */ +static struct gsm_nm_state * +mo2nm_state(struct gsm_bts *bts, const struct abis_om2k_mo *mo) +{ + struct gsm_bts_trx *trx; + struct gsm_nm_state *nm_state = NULL; + + switch (mo->class) { + case OM2K_MO_CLS_TRXC: + trx = gsm_bts_trx_num(bts, mo->inst); + if (!trx) + return NULL; + nm_state = &trx->mo.nm_state; + break; + case OM2K_MO_CLS_TS: + trx = gsm_bts_trx_num(bts, mo->assoc_so); + if (!trx) + return NULL; + if (mo->inst >= ARRAY_SIZE(trx->ts)) + return NULL; + nm_state = &trx->ts[mo->inst].mo.nm_state; + break; + case OM2K_MO_CLS_TF: + nm_state = &bts->rbs2000.tf.mo.nm_state; + break; + case OM2K_MO_CLS_IS: + nm_state = &bts->rbs2000.is.mo.nm_state; + break; + case OM2K_MO_CLS_CON: + nm_state = &bts->rbs2000.con.mo.nm_state; + break; + case OM2K_MO_CLS_DP: + nm_state = &bts->rbs2000.con.mo.nm_state; + break; + case OM2K_MO_CLS_CF: + nm_state = &bts->mo.nm_state; + break; + case OM2K_MO_CLS_TX: + trx = gsm_bts_trx_num(bts, mo->inst); + if (!trx) + return NULL; + /* FIXME */ + break; + case OM2K_MO_CLS_RX: + trx = gsm_bts_trx_num(bts, mo->inst); + if (!trx) + return NULL; + /* FIXME */ + break; + } + + return nm_state; +} + +static void *mo2obj(struct gsm_bts *bts, struct abis_om2k_mo *mo) +{ + struct gsm_bts_trx *trx; + + switch (mo->class) { + case OM2K_MO_CLS_TX: + case OM2K_MO_CLS_RX: + case OM2K_MO_CLS_TRXC: + return gsm_bts_trx_num(bts, mo->inst); + case OM2K_MO_CLS_TS: + trx = gsm_bts_trx_num(bts, mo->assoc_so); + if (!trx) + return NULL; + if (mo->inst >= ARRAY_SIZE(trx->ts)) + return NULL; + return &trx->ts[mo->inst]; + case OM2K_MO_CLS_TF: + case OM2K_MO_CLS_IS: + case OM2K_MO_CLS_CON: + case OM2K_MO_CLS_DP: + case OM2K_MO_CLS_CF: + return bts; + } + + return NULL; +} + +static void update_mo_state(struct gsm_bts *bts, struct abis_om2k_mo *mo, + uint8_t mo_state) +{ + struct gsm_nm_state *nm_state = mo2nm_state(bts, mo); + struct gsm_nm_state new_state; + struct nm_statechg_signal_data nsd; + + if (!nm_state) + return; + + new_state = *nm_state; + /* NOTICE: 12.21 Availability state values != OM2000 */ + new_state.availability = mo_state; + + memset(&nsd, 0, sizeof(nsd)); + + nsd.bts = bts; + nsd.obj = mo2obj(bts, mo); + nsd.old_state = nm_state; + nsd.new_state = &new_state; + nsd.om2k_mo = mo; + + osmo_signal_dispatch(SS_NM, S_NM_STATECHG_ADM, &nsd); + + nm_state->availability = new_state.availability; +} + +static void update_op_state(struct gsm_bts *bts, const struct abis_om2k_mo *mo, + uint8_t op_state) +{ + struct gsm_nm_state *nm_state = mo2nm_state(bts, mo); + struct gsm_nm_state new_state; + + if (!nm_state) + return; + + new_state = *nm_state; + switch (op_state) { + case 1: + new_state.operational = NM_OPSTATE_ENABLED; + break; + case 0: + new_state.operational = NM_OPSTATE_DISABLED; + break; + default: + new_state.operational = NM_OPSTATE_NULL; + break; + } + + nm_state->operational = new_state.operational; +} + +static int abis_om2k_sendmsg(struct gsm_bts *bts, struct msgb *msg) +{ + struct abis_om2k_hdr *o2h; + struct gsm_bts_trx *trx; + + msg->l2h = msg->data; + o2h = (struct abis_om2k_hdr *) msg->l2h; + + /* Compute the length in the OML header */ + o2h->om.length = 6 + msgb_l2len(msg)-sizeof(*o2h); + + switch (o2h->mo.class) { + case OM2K_MO_CLS_TRXC: + case OM2K_MO_CLS_TX: + case OM2K_MO_CLS_RX: + /* Route through per-TRX OML Link to the appropriate TRX */ + trx = gsm_bts_trx_by_nr(bts, o2h->mo.inst); + if (!trx) { + LOGP(DNM, LOGL_ERROR, "MO=%s Tx Dropping msg to " + "non-existing TRX\n", om2k_mo_name(&o2h->mo)); + return -ENODEV; + } + msg->dst = trx->oml_link; + break; + case OM2K_MO_CLS_TS: + /* Route through per-TRX OML Link to the appropriate TRX */ + trx = gsm_bts_trx_by_nr(bts, o2h->mo.assoc_so); + if (!trx) { + LOGP(DNM, LOGL_ERROR, "MO=%s Tx Dropping msg to " + "non-existing TRX\n", om2k_mo_name(&o2h->mo)); + return -ENODEV; + } + msg->dst = trx->oml_link; + break; + default: + /* Route through the IXU/DXU OML Link */ + msg->dst = bts->oml_link; + break; + } + + return _abis_nm_sendmsg(msg); +} + +static void fill_om2k_hdr(struct abis_om2k_hdr *o2h, const struct abis_om2k_mo *mo, + uint16_t msg_type) +{ + o2h->om.mdisc = ABIS_OM_MDISC_FOM; + o2h->om.placement = ABIS_OM_PLACEMENT_ONLY; + o2h->om.sequence = 0; + /* We fill o2h->om.length later during om2k_sendmsg() */ + o2h->msg_type = htons(msg_type); + memcpy(&o2h->mo, mo, sizeof(o2h->mo)); +} + +static int abis_om2k_cal_time_resp(struct gsm_bts *bts) +{ + struct msgb *msg = om2k_msgb_alloc(); + struct abis_om2k_hdr *o2k; + time_t tm_t; + struct tm *tm; + + o2k = (struct abis_om2k_hdr *) msgb_put(msg, sizeof(*o2k)); + fill_om2k_hdr(o2k, &bts->rbs2000.cf.om2k_mo.addr, + OM2K_MSGT_CAL_TIME_RESP); + + tm_t = time(NULL); + tm = localtime(&tm_t); + + msgb_put_u8(msg, OM2K_DEI_CAL_TIME); + msgb_put_u8(msg, tm->tm_year % 100); + msgb_put_u8(msg, tm->tm_mon + 1); + msgb_put_u8(msg, tm->tm_mday); + msgb_put_u8(msg, tm->tm_hour); + msgb_put_u8(msg, tm->tm_min); + msgb_put_u8(msg, tm->tm_sec); + + return abis_om2k_sendmsg(bts, msg); +} + +static int abis_om2k_tx_simple(struct gsm_bts *bts, const struct abis_om2k_mo *mo, + uint8_t msg_type) +{ + struct msgb *msg = om2k_msgb_alloc(); + struct abis_om2k_hdr *o2k; + + o2k = (struct abis_om2k_hdr *) msgb_put(msg, sizeof(*o2k)); + fill_om2k_hdr(o2k, mo, msg_type); + + DEBUGP(DNM, "Tx MO=%s %s\n", om2k_mo_name(mo), + get_value_string(om2k_msgcode_vals, msg_type)); + + return abis_om2k_sendmsg(bts, msg); +} + +int abis_om2k_tx_reset_cmd(struct gsm_bts *bts, const struct abis_om2k_mo *mo) +{ + return abis_om2k_tx_simple(bts, mo, OM2K_MSGT_RESET_CMD); +} + +int abis_om2k_tx_start_req(struct gsm_bts *bts, const struct abis_om2k_mo *mo) +{ + return abis_om2k_tx_simple(bts, mo, OM2K_MSGT_START_REQ); +} + +int abis_om2k_tx_status_req(struct gsm_bts *bts, const struct abis_om2k_mo *mo) +{ + return abis_om2k_tx_simple(bts, mo, OM2K_MSGT_STATUS_REQ); +} + +int abis_om2k_tx_connect_cmd(struct gsm_bts *bts, const struct abis_om2k_mo *mo) +{ + return abis_om2k_tx_simple(bts, mo, OM2K_MSGT_CONNECT_CMD); +} + +int abis_om2k_tx_disconnect_cmd(struct gsm_bts *bts, const struct abis_om2k_mo *mo) +{ + return abis_om2k_tx_simple(bts, mo, OM2K_MSGT_DISCONNECT_CMD); +} + +int abis_om2k_tx_test_req(struct gsm_bts *bts, const struct abis_om2k_mo *mo) +{ + return abis_om2k_tx_simple(bts, mo, OM2K_MSGT_TEST_REQ); +} + +int abis_om2k_tx_enable_req(struct gsm_bts *bts, const struct abis_om2k_mo *mo) +{ + return abis_om2k_tx_simple(bts, mo, OM2K_MSGT_ENABLE_REQ); +} + +int abis_om2k_tx_disable_req(struct gsm_bts *bts, const struct abis_om2k_mo *mo) +{ + return abis_om2k_tx_simple(bts, mo, OM2K_MSGT_DISABLE_REQ); +} + +int abis_om2k_tx_op_info(struct gsm_bts *bts, const struct abis_om2k_mo *mo, + uint8_t operational) +{ + struct msgb *msg = om2k_msgb_alloc(); + struct abis_om2k_hdr *o2k; + + o2k = (struct abis_om2k_hdr *) msgb_put(msg, sizeof(*o2k)); + fill_om2k_hdr(o2k, mo, OM2K_MSGT_OP_INFO); + + msgb_tv_put(msg, OM2K_DEI_OP_INFO, operational); + + DEBUGP(DNM, "Tx MO=%s %s\n", om2k_mo_name(mo), + get_value_string(om2k_msgcode_vals, OM2K_MSGT_OP_INFO)); + + /* we update the state here... and send the signal at ACK */ + update_op_state(bts, mo, operational); + + return abis_om2k_sendmsg(bts, msg); +} + +int abis_om2k_tx_cap_req(struct gsm_bts *bts, const struct abis_om2k_mo *mo) +{ + return abis_om2k_tx_simple(bts, mo, OM2K_MSGT_CAPA_REQ); +} + +static void om2k_fill_is_conn_grp(struct om2k_is_conn_grp *grp, uint16_t icp1, + uint16_t icp2, uint8_t cont_idx) +{ + grp->icp1 = htons(icp1); + grp->icp2 = htons(icp2); + grp->cont_idx = cont_idx; +} + +int abis_om2k_tx_is_conf_req(struct gsm_bts *bts) +{ + struct msgb *msg = om2k_msgb_alloc(); + struct abis_om2k_hdr *o2k; + struct is_conn_group *grp; + unsigned int num_grps = 0, i = 0; + struct om2k_is_conn_grp *cg; + + /* count number of groups in linked list */ + llist_for_each_entry(grp, &bts->rbs2000.is.conn_groups, list) + num_grps++; + + if (!num_grps) + return -EINVAL; + + /* allocate buffer for oml group array */ + cg = talloc_zero_array(bts, struct om2k_is_conn_grp, num_grps); + + /* fill array with data from linked list */ + llist_for_each_entry(grp, &bts->rbs2000.is.conn_groups, list) + om2k_fill_is_conn_grp(&cg[i++], grp->icp1, grp->icp2, grp->ci); + + o2k = (struct abis_om2k_hdr *) msgb_put(msg, sizeof(*o2k)); + fill_om2k_hdr(o2k, &bts->rbs2000.is.om2k_mo.addr, + OM2K_MSGT_IS_CONF_REQ); + + msgb_tv_put(msg, OM2K_DEI_LIST_NR, 1); + msgb_tv_put(msg, OM2K_DEI_END_LIST_NR, 1); + + msgb_tlv_put(msg, OM2K_DEI_IS_CONN_LIST, + num_grps * sizeof(*cg), (uint8_t *)cg); + + talloc_free(cg); + + DEBUGP(DNM, "Tx MO=%s %s\n", + om2k_mo_name(&bts->rbs2000.is.om2k_mo.addr), + get_value_string(om2k_msgcode_vals, OM2K_MSGT_IS_CONF_REQ)); + + return abis_om2k_sendmsg(bts, msg); +} + +int abis_om2k_tx_con_conf_req(struct gsm_bts *bts) +{ + struct msgb *msg = om2k_msgb_alloc(); + struct abis_om2k_hdr *o2k; + struct con_group *grp; + unsigned int num_grps = 0; + + /* count number of groups in linked list */ + llist_for_each_entry(grp, &bts->rbs2000.con.conn_groups, list) + num_grps++; + + if (!num_grps) + return -EINVAL; + + /* first build the value part of the OM2K_DEI_CON_CONN_LIST DEI */ + msgb_put_u8(msg, num_grps); + llist_for_each_entry(grp, &bts->rbs2000.con.conn_groups, list) { + struct con_path *cp; + unsigned int num_paths = 0; + llist_for_each_entry(cp, &grp->paths, list) + num_paths++; + msgb_put_u8(msg, num_paths); + llist_for_each_entry(cp, &grp->paths, list) { + struct om2k_con_path *om2k_cp; + om2k_cp = (struct om2k_con_path *) msgb_put(msg, sizeof(*om2k_cp)); + om2k_cp->ccp = htons(cp->ccp); + om2k_cp->ci = cp->ci; + om2k_cp->tag = cp->tag; + om2k_cp->tei = cp->tei; + } + } + msgb_push_u8(msg, msgb_length(msg)); + msgb_push_u8(msg, OM2K_DEI_CON_CONN_LIST); + + /* pre-pend the list number DEIs */ + msgb_tv_push(msg, OM2K_DEI_END_LIST_NR, 1); + msgb_tv_push(msg, OM2K_DEI_LIST_NR, 1); + + /* pre-pend the OM2K header */ + o2k = (struct abis_om2k_hdr *) msgb_push(msg, sizeof(*o2k)); + fill_om2k_hdr(o2k, &bts->rbs2000.con.om2k_mo.addr, + OM2K_MSGT_CON_CONF_REQ); + + DEBUGP(DNM, "Tx MO=%s %s\n", + om2k_mo_name(&bts->rbs2000.con.om2k_mo.addr), + get_value_string(om2k_msgcode_vals, OM2K_MSGT_CON_CONF_REQ)); + + return abis_om2k_sendmsg(bts, msg); +} + +static void om2k_trx_to_mo(struct abis_om2k_mo *mo, + const struct gsm_bts_trx *trx, + enum abis_om2k_mo_cls cls) +{ + mo->class = cls; + mo->bts = 0; + mo->inst = trx->nr; + mo->assoc_so = 255; +} + +static void om2k_ts_to_mo(struct abis_om2k_mo *mo, + const struct gsm_bts_trx_ts *ts) +{ + mo->class = OM2K_MO_CLS_TS; + mo->bts = 0; + mo->inst = ts->nr; + mo->assoc_so = ts->trx->nr; +} + +/* Configure a Receiver MO */ +int abis_om2k_tx_rx_conf_req(struct gsm_bts_trx *trx) +{ + struct msgb *msg = om2k_msgb_alloc(); + struct abis_om2k_hdr *o2k; + struct abis_om2k_mo mo; + + om2k_trx_to_mo(&mo, trx, OM2K_MO_CLS_RX); + + o2k = (struct abis_om2k_hdr *) msgb_put(msg, sizeof(*o2k)); + fill_om2k_hdr(o2k, &mo, OM2K_MSGT_RX_CONF_REQ); + + msgb_tv16_put(msg, OM2K_DEI_FREQ_SPEC_RX, trx->arfcn); + msgb_tv_put(msg, OM2K_DEI_RX_DIVERSITY, 0x02); /* A */ + + return abis_om2k_sendmsg(trx->bts, msg); +} + +/* Configure a Transmitter MO */ +int abis_om2k_tx_tx_conf_req(struct gsm_bts_trx *trx) +{ + struct msgb *msg = om2k_msgb_alloc(); + struct abis_om2k_hdr *o2k; + struct abis_om2k_mo mo; + + om2k_trx_to_mo(&mo, trx, OM2K_MO_CLS_TX); + + o2k = (struct abis_om2k_hdr *) msgb_put(msg, sizeof(*o2k)); + fill_om2k_hdr(o2k, &mo, OM2K_MSGT_TX_CONF_REQ); + + msgb_tv16_put(msg, OM2K_DEI_FREQ_SPEC_TX, trx->arfcn); + msgb_tv_put(msg, OM2K_DEI_POWER, trx->nominal_power-trx->max_power_red); + msgb_tv_put(msg, OM2K_DEI_FILLING_MARKER, 0); /* Filling enabled */ + msgb_tv_put(msg, OM2K_DEI_BCC, trx->bts->bsic & 0x7); + /* Dedication Information is optional */ + + return abis_om2k_sendmsg(trx->bts, msg); +} + +enum abis_om2k_tf_mode { + OM2K_TF_MODE_MASTER = 0x00, + OM2K_TF_MODE_STANDALONE = 0x01, + OM2K_TF_MODE_SLAVE = 0x02, + OM2K_TF_MODE_UNDEFINED = 0xff, +}; + +static const uint8_t fs_offset_undef[5] = { 0xff, 0xff, 0xff, 0xff, 0xff }; + +int abis_om2k_tx_tf_conf_req(struct gsm_bts *bts) +{ + struct msgb *msg = om2k_msgb_alloc(); + struct abis_om2k_hdr *o2k; + + o2k = (struct abis_om2k_hdr *) msgb_put(msg, sizeof(*o2k)); + fill_om2k_hdr(o2k, &bts->rbs2000.tf.om2k_mo.addr, + OM2K_MSGT_TF_CONF_REQ); + + msgb_tv_put(msg, OM2K_DEI_TF_MODE, OM2K_TF_MODE_STANDALONE); + msgb_tv_put(msg, OM2K_DEI_TF_SYNC_SRC, 0x00); + msgb_tv_fixed_put(msg, OM2K_DEI_FS_OFFSET, + sizeof(fs_offset_undef), fs_offset_undef); + + DEBUGP(DNM, "Tx MO=%s %s\n", + om2k_mo_name(&bts->rbs2000.tf.om2k_mo.addr), + get_value_string(om2k_msgcode_vals, OM2K_MSGT_TF_CONF_REQ)); + + return abis_om2k_sendmsg(bts, msg); +} + +static uint8_t pchan2comb(enum gsm_phys_chan_config pchan) +{ + switch (pchan) { + case GSM_PCHAN_CCCH: + return 4; + case GSM_PCHAN_CCCH_SDCCH4: + return 5; + case GSM_PCHAN_SDCCH8_SACCH8C: + return 3; + case GSM_PCHAN_TCH_F: + case GSM_PCHAN_TCH_H: + case GSM_PCHAN_PDCH: + case GSM_PCHAN_TCH_F_PDCH: + case GSM_PCHAN_TCH_F_TCH_H_PDCH: + return 8; + default: + return 0; + } +} + +static uint8_t ts2comb(struct gsm_bts_trx_ts *ts) +{ + switch (ts->pchan) { + case GSM_PCHAN_TCH_F_PDCH: + LOGP(DNM, LOGL_ERROR, "%s pchan %s not intended for use" + " with OM2000, use %s instead\n", + gsm_ts_and_pchan_name(ts), + gsm_pchan_name(GSM_PCHAN_TCH_F_PDCH), + gsm_pchan_name(GSM_PCHAN_TCH_F_TCH_H_PDCH)); + /* If we allowed initialization of TCH/F_PDCH, it would fail + * when we try to send the ip.access specific RSL PDCH Act + * message for it. Rather fail completely right now: */ + return 0; + case GSM_PCHAN_TCH_F_TCH_H_PDCH: + return pchan2comb(GSM_PCHAN_TCH_F); + default: + return pchan2comb(ts->pchan); + } +} + +static int put_freq_list(uint8_t *buf, uint16_t arfcn) +{ + buf[0] = 0x00; /* TX/RX address */ + buf[1] = (arfcn >> 8); + buf[2] = (arfcn & 0xff); + + return 3; +} + +/* Compute a frequency list in OM2000 fomrmat */ +static int om2k_gen_freq_list(uint8_t *list, struct gsm_bts_trx_ts *ts) +{ + uint8_t *cur = list; + int len; + + if (ts->hopping.enabled) { + unsigned int i; + for (i = 0; i < ts->hopping.arfcns.data_len*8; i++) { + if (bitvec_get_bit_pos(&ts->hopping.arfcns, i)) + cur += put_freq_list(cur, i); + } + } else + cur += put_freq_list(cur, ts->trx->arfcn); + + len = cur - list; + + return len; +} + +const uint8_t icm_bound_params[] = { 0x02, 0x06, 0x0c, 0x16, 0x06 }; + +int abis_om2k_tx_ts_conf_req(struct gsm_bts_trx_ts *ts) +{ + struct msgb *msg = om2k_msgb_alloc(); + struct abis_om2k_hdr *o2k; + struct abis_om2k_mo mo; + uint8_t freq_list[64*3]; /* BA max size: 64 ARFCN */ + int freq_list_len; + + om2k_ts_to_mo(&mo, ts); + + memset(freq_list, 0, sizeof(freq_list)); + freq_list_len = om2k_gen_freq_list(freq_list, ts); + if (freq_list_len < 0) + return freq_list_len; + + o2k = (struct abis_om2k_hdr *) msgb_put(msg, sizeof(*o2k)); + fill_om2k_hdr(o2k, &mo, OM2K_MSGT_TS_CONF_REQ); + + msgb_tv_put(msg, OM2K_DEI_COMBINATION, ts2comb(ts)); + msgb_tv_put(msg, OM2K_DEI_TS_NR, ts->nr); + msgb_tlv_put(msg, OM2K_DEI_FREQ_LIST, freq_list_len, freq_list); + msgb_tv_put(msg, OM2K_DEI_HSN, ts->hopping.hsn); + msgb_tv_put(msg, OM2K_DEI_MAIO, ts->hopping.maio); + msgb_tv_put(msg, OM2K_DEI_BSIC, ts->trx->bts->bsic); + msgb_tv_put(msg, OM2K_DEI_RX_DIVERSITY, 0x02); /* A */ + msgb_tv16_put(msg, OM2K_DEI_FN_OFFSET, 0); + msgb_tv_put(msg, OM2K_DEI_EXT_RANGE, 0); /* Off */ + /* Optional: Interference Rejection Combining */ + msgb_tv_put(msg, OM2K_DEI_INTERF_REJ_COMB, 0x00); + switch (ts->pchan) { + case GSM_PCHAN_CCCH: + msgb_tv_put(msg, OM2K_DEI_BA_PA_MFRMS, 0x06); + msgb_tv_put(msg, OM2K_DEI_BS_AG_BKS_RES, 0x01); + msgb_tv_put(msg, OM2K_DEI_DRX_DEV_MAX, 0x05); + /* Repeat Paging/IMM.ASS: True, Allow Paging Type 3: Yes, Page for 5 seconds (default) */ + msgb_tv_put(msg, OM2K_DEI_CCCH_OPTIONS, 0x01); + break; + case GSM_PCHAN_CCCH_SDCCH4: + msgb_tv_put(msg, OM2K_DEI_T3105, ts->trx->bts->network->T3105 / 10); + msgb_tv_put(msg, OM2K_DEI_NY1, 35); + msgb_tv_put(msg, OM2K_DEI_BA_PA_MFRMS, 0x06); + msgb_tv_put(msg, OM2K_DEI_CBCH_INDICATOR, 0); + msgb_tv_put(msg, OM2K_DEI_TSC, gsm_ts_tsc(ts)); + msgb_tv_put(msg, OM2K_DEI_BS_AG_BKS_RES, 0x01); + msgb_tv_put(msg, OM2K_DEI_ICM_INDICATOR, 0); + msgb_tv_put(msg, OM2K_DEI_DRX_DEV_MAX, 0x05); + /* Repeat Paging/IMM.ASS: True, Allow Paging Type 3: Yes, Page for 5 seconds (default) */ + msgb_tv_put(msg, OM2K_DEI_CCCH_OPTIONS, 0x01); + msgb_tv_fixed_put(msg, OM2K_DEI_ICM_BOUND_PARAMS, + sizeof(icm_bound_params), icm_bound_params); + break; + case GSM_PCHAN_SDCCH8_SACCH8C: + msgb_tv_put(msg, OM2K_DEI_T3105, ts->trx->bts->network->T3105 / 10); + msgb_tv_put(msg, OM2K_DEI_NY1, 35); + msgb_tv_put(msg, OM2K_DEI_CBCH_INDICATOR, 0); + msgb_tv_put(msg, OM2K_DEI_TSC, gsm_ts_tsc(ts)); + /* Disable RF RESOURCE INDICATION on idle channels */ + msgb_tv_put(msg, OM2K_DEI_ICM_INDICATOR, 0); + msgb_tv_fixed_put(msg, OM2K_DEI_ICM_BOUND_PARAMS, + sizeof(icm_bound_params), icm_bound_params); + break; + default: + msgb_tv_put(msg, OM2K_DEI_T3105, ts->trx->bts->network->T3105 / 10); + msgb_tv_put(msg, OM2K_DEI_NY1, 35); + msgb_tv_put(msg, OM2K_DEI_TSC, gsm_ts_tsc(ts)); + /* Disable RF RESOURCE INDICATION on idle channels */ + msgb_tv_put(msg, OM2K_DEI_ICM_INDICATOR, 0); + msgb_tv_fixed_put(msg, OM2K_DEI_ICM_BOUND_PARAMS, + sizeof(icm_bound_params), icm_bound_params); + msgb_tv_put(msg, OM2K_DEI_TTA, 10); /* Timer for Time Alignment */ + if (ts->pchan == GSM_PCHAN_TCH_H) + msgb_tv_put(msg, OM2K_DEI_ICM_CHAN_RATE, 1); /* TCH/H */ + else + msgb_tv_put(msg, OM2K_DEI_ICM_CHAN_RATE, 0); /* TCH/F */ + msgb_tv_put(msg, OM2K_DEI_LSC, 1); /* enabled */ + msgb_tv_put(msg, OM2K_DEI_LSC_FILT_TIME, 10); /* units of 100ms */ + msgb_tv_put(msg, OM2K_DEI_CALL_SUPV_TIME, 8); + msgb_tv_put(msg, OM2K_DEI_ENCR_ALG, 0x00); + /* Not sure what those below mean */ + msgb_tv_put(msg, 0x9e, 0x00); + msgb_tv_put(msg, 0x9f, 0x37); + msgb_tv_put(msg, 0xa0, 0x01); + break; + } + + DEBUGP(DNM, "Tx MO=%s %s\n", + om2k_mo_name(&mo), + get_value_string(om2k_msgcode_vals, OM2K_MSGT_TS_CONF_REQ)); + + return abis_om2k_sendmsg(ts->trx->bts, msg); +} + + +/*********************************************************************** + * OM2000 Managed Object (MO) FSM + ***********************************************************************/ + +#define S(x) (1 << (x)) + +enum om2k_event_name { + OM2K_MO_EVT_START, + OM2K_MO_EVT_RX_CONN_COMPL, + OM2K_MO_EVT_RX_RESET_COMPL, + OM2K_MO_EVT_RX_START_REQ_ACCEPT, + OM2K_MO_EVT_RX_START_RES, + OM2K_MO_EVT_RX_CFG_REQ_ACCEPT, + OM2K_MO_EVT_RX_CFG_RES, + OM2K_MO_EVT_RX_ENA_REQ_ACCEPT, + OM2K_MO_EVT_RX_ENA_RES, + OM2K_MO_EVT_RX_OPINFO_ACC, +}; + +static const struct value_string om2k_event_names[] = { + { OM2K_MO_EVT_START, "START" }, + { OM2K_MO_EVT_RX_CONN_COMPL, "RX-CONN-COMPL" }, + { OM2K_MO_EVT_RX_RESET_COMPL, "RX-RESET-COMPL" }, + { OM2K_MO_EVT_RX_START_REQ_ACCEPT, "RX-RESET-REQ-ACCEPT" }, + { OM2K_MO_EVT_RX_START_RES, "RX-START-RESULT" }, + { OM2K_MO_EVT_RX_CFG_REQ_ACCEPT, "RX-CFG-REQ-ACCEPT" }, + { OM2K_MO_EVT_RX_CFG_RES, "RX-CFG-RESULT" }, + { OM2K_MO_EVT_RX_ENA_REQ_ACCEPT, "RX-ENABLE-REQ-ACCEPT" }, + { OM2K_MO_EVT_RX_ENA_RES, "RX-ENABLE-RESULT" }, + { OM2K_MO_EVT_RX_OPINFO_ACC, "RX-OPINFO-ACCEPT" }, + { 0, NULL } +}; + +enum om2k_mo_fsm_state { + OM2K_ST_INIT, + OM2K_ST_WAIT_CONN_COMPL, + OM2K_ST_WAIT_RES_COMPL, + OM2K_ST_WAIT_START_ACCEPT, + OM2K_ST_WAIT_START_RES, + OM2K_ST_WAIT_CFG_ACCEPT, + OM2K_ST_WAIT_CFG_RES, + OM2K_ST_WAIT_ENABLE_ACCEPT, + OM2K_ST_WAIT_ENABLE_RES, + OM2K_ST_WAIT_OPINFO_ACCEPT, + OM2K_ST_DONE, + OM2K_ST_ERROR, +}; + +struct om2k_mo_fsm_priv { + struct gsm_bts_trx *trx; + struct om2k_mo *mo; + uint8_t ts_nr; +}; + +static void om2k_mo_st_init(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct om2k_mo_fsm_priv *omfp = fi->priv; + + OSMO_ASSERT(event == OM2K_MO_EVT_START); + + switch (omfp->mo->addr.class) { + case OM2K_MO_CLS_CF: + /* no Connect required, is always connected */ + osmo_fsm_inst_state_chg(fi, OM2K_ST_WAIT_START_ACCEPT, + OM2K_TIMEOUT, 0); + abis_om2k_tx_start_req(omfp->trx->bts, &omfp->mo->addr); + break; + case OM2K_MO_CLS_TRXC: + /* no Connect required, start with Reset */ + osmo_fsm_inst_state_chg(fi, OM2K_ST_WAIT_RES_COMPL, + OM2K_TIMEOUT, 0); + abis_om2k_tx_reset_cmd(omfp->trx->bts, &omfp->mo->addr); + break; + default: + /* start with Connect */ + osmo_fsm_inst_state_chg(fi, OM2K_ST_WAIT_CONN_COMPL, + OM2K_TIMEOUT, 0); + abis_om2k_tx_connect_cmd(omfp->trx->bts, &omfp->mo->addr); + break; + } +} + +static void om2k_mo_st_wait_conn_compl(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct om2k_mo_fsm_priv *omfp = fi->priv; + + switch (omfp->mo->addr.class) { +#if 0 + case OM2K_MO_CLS_TF: + /* skip the reset, hope that helps */ + osmo_fsm_inst_state_chg(fi, OM2K_ST_WAIT_START_ACCEPT, + OM2K_TIMEOUT, 0); + abis_om2k_tx_start_req(omfp->trx->bts, &omfp->mo->addr); + break; +#endif + default: + osmo_fsm_inst_state_chg(fi, OM2K_ST_WAIT_RES_COMPL, + OM2K_TIMEOUT, 0); + abis_om2k_tx_reset_cmd(omfp->trx->bts, &omfp->mo->addr); + break; + } +} + +static void om2k_mo_st_wait_res_compl(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct om2k_mo_fsm_priv *omfp = fi->priv; + + osmo_fsm_inst_state_chg(fi, OM2K_ST_WAIT_START_ACCEPT, + OM2K_TIMEOUT, 0); + abis_om2k_tx_start_req(omfp->trx->bts, &omfp->mo->addr); +} + +static void om2k_mo_st_wait_start_accept(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct om2k_decoded_msg *omd = data; + + switch (omd->msg_type) { + case OM2K_MSGT_START_REQ_ACK: + osmo_fsm_inst_state_chg(fi, OM2K_ST_WAIT_START_RES, + OM2K_TIMEOUT, 0); + break; + case OM2K_MSGT_START_REQ_REJ: + osmo_fsm_inst_state_chg(fi, OM2K_ST_ERROR, 0, 0); + break; + } +} + +static void om2k_mo_st_wait_start_res(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct om2k_mo_fsm_priv *omfp = fi->priv; + struct gsm_bts_trx_ts *ts; + + switch (omfp->mo->addr.class) { + case OM2K_MO_CLS_CF: + case OM2K_MO_CLS_TRXC: + /* Transition directly to Operational Info */ + osmo_fsm_inst_state_chg(fi, OM2K_ST_WAIT_OPINFO_ACCEPT, + OM2K_TIMEOUT, 0); + abis_om2k_tx_op_info(omfp->trx->bts, &omfp->mo->addr, 1); + return; + case OM2K_MO_CLS_DP: + /* Transition directoy to WAIT_ENABLE_ACCEPT */ + osmo_fsm_inst_state_chg(fi, OM2K_ST_WAIT_ENABLE_ACCEPT, + OM2K_TIMEOUT, 0); + abis_om2k_tx_enable_req(omfp->trx->bts, &omfp->mo->addr); + return; +#if 0 + case OM2K_MO_CLS_TF: + /* skip the config, hope that helps speeding things up */ + osmo_fsm_inst_state_chg(fi, OM2K_ST_WAIT_ENABLE_ACCEPT, + OM2K_TIMEOUT, 0); + abis_om2k_tx_enable_req(omfp->trx->bts, &omfp->mo->addr); + return; +#endif + } + + osmo_fsm_inst_state_chg(fi, OM2K_ST_WAIT_CFG_ACCEPT, + OM2K_TIMEOUT, 0); + switch (omfp->mo->addr.class) { + case OM2K_MO_CLS_TF: + abis_om2k_tx_tf_conf_req(omfp->trx->bts); + break; + case OM2K_MO_CLS_IS: + abis_om2k_tx_is_conf_req(omfp->trx->bts); + break; + case OM2K_MO_CLS_CON: + abis_om2k_tx_con_conf_req(omfp->trx->bts); + break; + case OM2K_MO_CLS_TX: + abis_om2k_tx_tx_conf_req(omfp->trx); + break; + case OM2K_MO_CLS_RX: + abis_om2k_tx_rx_conf_req(omfp->trx); + break; + case OM2K_MO_CLS_TS: + ts = mo2obj(omfp->trx->bts, &omfp->mo->addr); + abis_om2k_tx_ts_conf_req(ts); + break; + } +} + +static void om2k_mo_st_wait_cfg_accept(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct om2k_mo_fsm_priv *omfp = fi->priv; + uint32_t timeout = OM2K_TIMEOUT; + + if (omfp->mo->addr.class == OM2K_MO_CLS_TF) + timeout = 600; + + osmo_fsm_inst_state_chg(fi, OM2K_ST_WAIT_CFG_RES, timeout, 0); +} + +static void om2k_mo_st_wait_cfg_res(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct om2k_mo_fsm_priv *omfp = fi->priv; + struct om2k_decoded_msg *omd = data; + uint8_t accordance; + + if (!TLVP_PRESENT(&omd->tp, OM2K_DEI_ACCORDANCE_IND)) { + osmo_fsm_inst_state_chg(fi, OM2K_ST_ERROR, 0, 0); + return; + } + accordance = *TLVP_VAL(&omd->tp, OM2K_DEI_ACCORDANCE_IND); + + if (accordance != 0) { + /* accordance not OK */ + osmo_fsm_inst_state_chg(fi, OM2K_ST_ERROR, 0, 0); + return; + } + + osmo_fsm_inst_state_chg(fi, OM2K_ST_WAIT_ENABLE_ACCEPT, + OM2K_TIMEOUT, 0); + abis_om2k_tx_enable_req(omfp->trx->bts, &omfp->mo->addr); +} + +static void om2k_mo_st_wait_enable_accept(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct om2k_mo_fsm_priv *omfp = fi->priv; + struct om2k_decoded_msg *omd = data; + + switch (omd->msg_type) { + case OM2K_MSGT_ENABLE_REQ_REJ: + osmo_fsm_inst_state_chg(fi, OM2K_ST_ERROR, 0, 0); + break; + case OM2K_MSGT_ENABLE_REQ_ACK: + if (omfp->mo->addr.class == OM2K_MO_CLS_IS && + omfp->trx->bts->rbs2000.use_superchannel) + e1inp_ericsson_set_altc(omfp->trx->bts->oml_link->ts->line, 1); + osmo_fsm_inst_state_chg(fi, OM2K_ST_WAIT_ENABLE_RES, + OM2K_TIMEOUT, 0); + } +} + +static void om2k_mo_st_wait_enable_res(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct om2k_mo_fsm_priv *omfp = fi->priv; + //struct om2k_decoded_msg *omd = data; + /* TODO: check if state is actually enabled now? */ + + osmo_fsm_inst_state_chg(fi, OM2K_ST_WAIT_OPINFO_ACCEPT, + OM2K_TIMEOUT, 0); + abis_om2k_tx_op_info(omfp->trx->bts, &omfp->mo->addr, 1); +} + +static void om2k_mo_st_wait_opinfo_accept(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + osmo_fsm_inst_state_chg(fi, OM2K_ST_DONE, 0, 0); +} + +static void om2k_mo_s_done_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct om2k_mo_fsm_priv *omfp = fi->priv; + omfp->mo->fsm = NULL; + osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL); +} + +static void om2k_mo_s_error_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct om2k_mo_fsm_priv *omfp = fi->priv; + + omfp->mo->fsm = NULL; + osmo_fsm_inst_term(fi, OSMO_FSM_TERM_ERROR, NULL); +} + +static const struct osmo_fsm_state om2k_is_states[] = { + [OM2K_ST_INIT] = { + .name = "INIT", + .in_event_mask = S(OM2K_MO_EVT_START), + .out_state_mask = S(OM2K_ST_DONE) | + S(OM2K_ST_ERROR) | + S(OM2K_ST_WAIT_CONN_COMPL) | + S(OM2K_ST_WAIT_START_ACCEPT) | + S(OM2K_ST_WAIT_RES_COMPL), + .action = om2k_mo_st_init, + }, + [OM2K_ST_WAIT_CONN_COMPL] = { + .name = "WAIT-CONN-COMPL", + .in_event_mask = S(OM2K_MO_EVT_RX_CONN_COMPL), + .out_state_mask = S(OM2K_ST_DONE) | + S(OM2K_ST_ERROR) | + S(OM2K_ST_WAIT_START_ACCEPT) | + S(OM2K_ST_WAIT_RES_COMPL), + .action = om2k_mo_st_wait_conn_compl, + }, + [OM2K_ST_WAIT_RES_COMPL] = { + .name = "WAIT-RES-COMPL", + .in_event_mask = S(OM2K_MO_EVT_RX_RESET_COMPL), + .out_state_mask = S(OM2K_ST_DONE) | + S(OM2K_ST_ERROR) | + S(OM2K_ST_WAIT_START_ACCEPT), + .action = om2k_mo_st_wait_res_compl, + }, + [OM2K_ST_WAIT_START_ACCEPT] = { + .name = "WAIT-START-ACCEPT", + .in_event_mask = S(OM2K_MO_EVT_RX_START_REQ_ACCEPT), + .out_state_mask = S(OM2K_ST_DONE) | + S(OM2K_ST_ERROR) | + S(OM2K_ST_WAIT_START_RES), + .action =om2k_mo_st_wait_start_accept, + }, + [OM2K_ST_WAIT_START_RES] = { + .name = "WAIT-START-RES", + .in_event_mask = S(OM2K_MO_EVT_RX_START_RES), + .out_state_mask = S(OM2K_ST_DONE) | + S(OM2K_ST_ERROR) | + S(OM2K_ST_WAIT_CFG_ACCEPT) | + S(OM2K_ST_WAIT_OPINFO_ACCEPT), + .action = om2k_mo_st_wait_start_res, + }, + [OM2K_ST_WAIT_CFG_ACCEPT] = { + .name = "WAIT-CFG-ACCEPT", + .in_event_mask = S(OM2K_MO_EVT_RX_CFG_REQ_ACCEPT), + .out_state_mask = S(OM2K_ST_DONE) | + S(OM2K_ST_ERROR) | + S(OM2K_ST_WAIT_CFG_RES), + .action = om2k_mo_st_wait_cfg_accept, + }, + [OM2K_ST_WAIT_CFG_RES] = { + .name = "WAIT-CFG-RES", + .in_event_mask = S(OM2K_MO_EVT_RX_CFG_RES), + .out_state_mask = S(OM2K_ST_DONE) | + S(OM2K_ST_ERROR) | + S(OM2K_ST_WAIT_ENABLE_ACCEPT), + .action = om2k_mo_st_wait_cfg_res, + }, + [OM2K_ST_WAIT_ENABLE_ACCEPT] = { + .name = "WAIT-ENABLE-ACCEPT", + .in_event_mask = S(OM2K_MO_EVT_RX_ENA_REQ_ACCEPT), + .out_state_mask = S(OM2K_ST_DONE) | + S(OM2K_ST_ERROR) | + S(OM2K_ST_WAIT_ENABLE_RES), + .action = om2k_mo_st_wait_enable_accept, + }, + [OM2K_ST_WAIT_ENABLE_RES] = { + .name = "WAIT-ENABLE-RES", + .in_event_mask = S(OM2K_MO_EVT_RX_ENA_RES), + .out_state_mask = S(OM2K_ST_DONE) | + S(OM2K_ST_ERROR) | + S(OM2K_ST_WAIT_OPINFO_ACCEPT), + .action = om2k_mo_st_wait_enable_res, + }, + [OM2K_ST_WAIT_OPINFO_ACCEPT] = { + .name = "WAIT-OPINFO-ACCEPT", + .in_event_mask = S(OM2K_MO_EVT_RX_OPINFO_ACC), + .out_state_mask = S(OM2K_ST_DONE) | + S(OM2K_ST_ERROR), + .action = om2k_mo_st_wait_opinfo_accept, + }, + [OM2K_ST_DONE] = { + .name = "DONE", + .in_event_mask = 0, + .out_state_mask = 0, + .onenter = om2k_mo_s_done_onenter, + }, + [OM2K_ST_ERROR] = { + .name = "ERROR", + .in_event_mask = 0, + .out_state_mask = 0, + .onenter = om2k_mo_s_error_onenter, + }, + +}; + +static int om2k_mo_timer_cb(struct osmo_fsm_inst *fi) +{ + osmo_fsm_inst_state_chg(fi, OM2K_ST_ERROR, 0, 0); + return 0; +} + +static struct osmo_fsm om2k_mo_fsm = { + .name = "OM2000-MO", + .states = om2k_is_states, + .num_states = ARRAY_SIZE(om2k_is_states), + .log_subsys = DNM, + .event_names = om2k_event_names, + .timer_cb = om2k_mo_timer_cb, +}; + +struct osmo_fsm_inst *om2k_mo_fsm_start(struct osmo_fsm_inst *parent, + uint32_t term_event, + struct gsm_bts_trx *trx, struct om2k_mo *mo) +{ + struct osmo_fsm_inst *fi; + struct om2k_mo_fsm_priv *omfp; + char idbuf[64]; + + snprintf(idbuf, sizeof(idbuf), "%s-%s", parent->id, + om2k_mo_name(&mo->addr)); + + fi = osmo_fsm_inst_alloc_child_id(&om2k_mo_fsm, parent, + term_event, idbuf); + if (!fi) + return NULL; + + mo->fsm = fi; + omfp = talloc_zero(fi, struct om2k_mo_fsm_priv); + omfp->mo = mo; + omfp->trx = trx; + fi->priv = omfp; + + osmo_fsm_inst_dispatch(fi, OM2K_MO_EVT_START, NULL); + + return fi; +} + +int om2k_mo_fsm_recvmsg(struct gsm_bts *bts, struct om2k_mo *mo, + struct om2k_decoded_msg *odm) +{ + switch (odm->msg_type) { + case OM2K_MSGT_CONNECT_COMPL: + case OM2K_MSGT_CONNECT_REJ: + osmo_fsm_inst_dispatch(mo->fsm, + OM2K_MO_EVT_RX_CONN_COMPL, odm); + break; + + case OM2K_MSGT_RESET_COMPL: + case OM2K_MSGT_RESET_REJ: + osmo_fsm_inst_dispatch(mo->fsm, + OM2K_MO_EVT_RX_RESET_COMPL, odm); + break; + + case OM2K_MSGT_START_REQ_ACK: + case OM2K_MSGT_START_REQ_REJ: + osmo_fsm_inst_dispatch(mo->fsm, + OM2K_MO_EVT_RX_START_REQ_ACCEPT, odm); + break; + + case OM2K_MSGT_START_RES: + osmo_fsm_inst_dispatch(mo->fsm, + OM2K_MO_EVT_RX_START_RES, odm); + break; + + case OM2K_MSGT_CON_CONF_REQ_ACK: + case OM2K_MSGT_IS_CONF_REQ_ACK: + case OM2K_MSGT_RX_CONF_REQ_ACK: + case OM2K_MSGT_TF_CONF_REQ_ACK: + case OM2K_MSGT_TS_CONF_REQ_ACK: + case OM2K_MSGT_TX_CONF_REQ_ACK: + osmo_fsm_inst_dispatch(mo->fsm, + OM2K_MO_EVT_RX_CFG_REQ_ACCEPT, odm); + break; + + case OM2K_MSGT_CON_CONF_RES: + case OM2K_MSGT_IS_CONF_RES: + case OM2K_MSGT_RX_CONF_RES: + case OM2K_MSGT_TF_CONF_RES: + case OM2K_MSGT_TS_CONF_RES: + case OM2K_MSGT_TX_CONF_RES: + osmo_fsm_inst_dispatch(mo->fsm, + OM2K_MO_EVT_RX_CFG_RES, odm); + break; + + case OM2K_MSGT_ENABLE_REQ_ACK: + case OM2K_MSGT_ENABLE_REQ_REJ: + osmo_fsm_inst_dispatch(mo->fsm, + OM2K_MO_EVT_RX_ENA_REQ_ACCEPT, odm); + break; + case OM2K_MSGT_ENABLE_RES: + osmo_fsm_inst_dispatch(mo->fsm, + OM2K_MO_EVT_RX_ENA_RES, odm); + break; + + case OM2K_MSGT_OP_INFO_ACK: + case OM2K_MSGT_OP_INFO_REJ: + osmo_fsm_inst_dispatch(mo->fsm, + OM2K_MO_EVT_RX_OPINFO_ACC, odm); + break; + default: + return -1; + } + + return 0; +} + +/*********************************************************************** + * OM2000 TRX Finite State Machine, initializes TRXC and all siblings + ***********************************************************************/ + +enum om2k_trx_event { + OM2K_TRX_EVT_START, + OM2K_TRX_EVT_TRXC_DONE, + OM2K_TRX_EVT_TX_DONE, + OM2K_TRX_EVT_RX_DONE, + OM2K_TRX_EVT_TS_DONE, + OM2K_TRX_EVT_STOP, +}; + +static struct value_string om2k_trx_events[] = { + { OM2K_TRX_EVT_START, "START" }, + { OM2K_TRX_EVT_TRXC_DONE, "TRXC-DONE" }, + { OM2K_TRX_EVT_TX_DONE, "TX-DONE" }, + { OM2K_TRX_EVT_RX_DONE, "RX-DONE" }, + { OM2K_TRX_EVT_TS_DONE, "TS-DONE" }, + { OM2K_TRX_EVT_STOP, "STOP" }, + { 0, NULL } +}; + +enum om2k_trx_state { + OM2K_TRX_S_INIT, + OM2K_TRX_S_WAIT_TRXC, + OM2K_TRX_S_WAIT_TX, + OM2K_TRX_S_WAIT_RX, + OM2K_TRX_S_WAIT_TS, + OM2K_TRX_S_DONE, + OM2K_TRX_S_ERROR +}; + +struct om2k_trx_fsm_priv { + struct gsm_bts_trx *trx; + uint8_t next_ts_nr; +}; + +static void om2k_trx_s_init(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct om2k_trx_fsm_priv *otfp = fi->priv; + + /* First initialize TRXC */ + osmo_fsm_inst_state_chg(fi, OM2K_TRX_S_WAIT_TRXC, + TRX_FSM_TIMEOUT, 0); + om2k_mo_fsm_start(fi, OM2K_TRX_EVT_TRXC_DONE, otfp->trx, + &otfp->trx->rbs2000.trxc.om2k_mo); +} + +static void om2k_trx_s_wait_trxc(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct om2k_trx_fsm_priv *otfp = fi->priv; + + /* Initialize TX after TRXC */ + osmo_fsm_inst_state_chg(fi, OM2K_TRX_S_WAIT_TX, + TRX_FSM_TIMEOUT, 0); + om2k_mo_fsm_start(fi, OM2K_TRX_EVT_TX_DONE, otfp->trx, + &otfp->trx->rbs2000.tx.om2k_mo); +} + +static void om2k_trx_s_wait_tx(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct om2k_trx_fsm_priv *otfp = fi->priv; + + /* Initialize RX after TX */ + osmo_fsm_inst_state_chg(fi, OM2K_TRX_S_WAIT_RX, + TRX_FSM_TIMEOUT, 0); + om2k_mo_fsm_start(fi, OM2K_TRX_EVT_RX_DONE, otfp->trx, + &otfp->trx->rbs2000.rx.om2k_mo); +} + +static void om2k_trx_s_wait_rx(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct om2k_trx_fsm_priv *otfp = fi->priv; + struct gsm_bts_trx_ts *ts; + + /* Initialize Timeslots after TX */ + osmo_fsm_inst_state_chg(fi, OM2K_TRX_S_WAIT_TS, + TRX_FSM_TIMEOUT, 0); + otfp->next_ts_nr = 0; + ts = &otfp->trx->ts[otfp->next_ts_nr++]; + om2k_mo_fsm_start(fi, OM2K_TRX_EVT_TS_DONE, otfp->trx, + &ts->rbs2000.om2k_mo); +} + +static void om2k_trx_s_wait_ts(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct om2k_trx_fsm_priv *otfp = fi->priv; + struct gsm_bts_trx_ts *ts; + + if (otfp->next_ts_nr < 8) { + /* iterate to the next timeslot */ + ts = &otfp->trx->ts[otfp->next_ts_nr++]; + om2k_mo_fsm_start(fi, OM2K_TRX_EVT_TS_DONE, otfp->trx, + &ts->rbs2000.om2k_mo); + } else { + /* only after all 8 TS */ + osmo_fsm_inst_state_chg(fi, OM2K_TRX_S_DONE, 0, 0); + } +} + +static void om2k_trx_s_done_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct om2k_trx_fsm_priv *otfp = fi->priv; + gsm_bts_trx_set_system_infos(otfp->trx); + osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL); +} + +static const struct osmo_fsm_state om2k_trx_states[] = { + [OM2K_TRX_S_INIT] = { + .in_event_mask = S(OM2K_TRX_EVT_START), + .out_state_mask = S(OM2K_TRX_S_WAIT_TRXC), + .name = "INIT", + .action = om2k_trx_s_init, + }, + [OM2K_TRX_S_WAIT_TRXC] = { + .in_event_mask = S(OM2K_TRX_EVT_TRXC_DONE), + .out_state_mask = S(OM2K_TRX_S_ERROR) | + S(OM2K_TRX_S_WAIT_TX), + .name = "WAIT-TRXC", + .action = om2k_trx_s_wait_trxc, + }, + [OM2K_TRX_S_WAIT_TX] = { + .in_event_mask = S(OM2K_TRX_EVT_TX_DONE), + .out_state_mask = S(OM2K_TRX_S_ERROR) | + S(OM2K_TRX_S_WAIT_RX), + .name = "WAIT-TX", + .action = om2k_trx_s_wait_tx, + }, + [OM2K_TRX_S_WAIT_RX] = { + .in_event_mask = S(OM2K_TRX_EVT_RX_DONE), + .out_state_mask = S(OM2K_TRX_S_ERROR) | + S(OM2K_TRX_S_WAIT_TS), + .name = "WAIT-RX", + .action = om2k_trx_s_wait_rx, + }, + [OM2K_TRX_S_WAIT_TS] = { + .in_event_mask = S(OM2K_TRX_EVT_TS_DONE), + .out_state_mask = S(OM2K_TRX_S_ERROR) | + S(OM2K_TRX_S_DONE), + .name = "WAIT-TS", + .action = om2k_trx_s_wait_ts, + }, + [OM2K_TRX_S_DONE] = { + .name = "DONE", + .onenter = om2k_trx_s_done_onenter, + }, + [OM2K_TRX_S_ERROR] = { + .name = "ERROR", + }, +}; + +static int om2k_trx_timer_cb(struct osmo_fsm_inst *fi) +{ + osmo_fsm_inst_state_chg(fi, OM2K_TRX_S_ERROR, 0, 0); + return 0; +} + +static struct osmo_fsm om2k_trx_fsm = { + .name = "OM2000-TRX", + .states = om2k_trx_states, + .num_states = ARRAY_SIZE(om2k_trx_states), + .log_subsys = DNM, + .event_names = om2k_trx_events, + .timer_cb = om2k_trx_timer_cb, +}; + +struct osmo_fsm_inst *om2k_trx_fsm_start(struct osmo_fsm_inst *parent, + struct gsm_bts_trx *trx, + uint32_t term_event) +{ + struct osmo_fsm_inst *fi; + struct om2k_trx_fsm_priv *otfp; + char idbuf[32]; + + snprintf(idbuf, sizeof(idbuf), "%u/%u", trx->bts->nr, trx->nr); + + fi = osmo_fsm_inst_alloc_child_id(&om2k_trx_fsm, parent, term_event, + idbuf); + if (!fi) + return NULL; + + otfp = talloc_zero(fi, struct om2k_trx_fsm_priv); + otfp->trx = trx; + fi->priv = otfp; + + osmo_fsm_inst_dispatch(fi, OM2K_TRX_EVT_START, NULL); + + return fi; +} + + +/*********************************************************************** + * OM2000 BTS Finite State Machine, initializes CF and all siblings + ***********************************************************************/ + +enum om2k_bts_event { + OM2K_BTS_EVT_START, + OM2K_BTS_EVT_CF_DONE, + OM2K_BTS_EVT_IS_DONE, + OM2K_BTS_EVT_CON_DONE, + OM2K_BTS_EVT_TF_DONE, + OM2K_BTS_EVT_TRX_DONE, + OM2K_BTS_EVT_STOP, +}; + +static const struct value_string om2k_bts_events[] = { + { OM2K_BTS_EVT_START, "START" }, + { OM2K_BTS_EVT_CF_DONE, "CF-DONE" }, + { OM2K_BTS_EVT_IS_DONE, "IS-DONE" }, + { OM2K_BTS_EVT_CON_DONE, "CON-DONE" }, + { OM2K_BTS_EVT_TF_DONE, "TF-DONE" }, + { OM2K_BTS_EVT_TRX_DONE, "TRX-DONE" }, + { OM2K_BTS_EVT_STOP, "STOP" }, + { 0, NULL } +}; + +enum om2k_bts_state { + OM2K_BTS_S_INIT, + OM2K_BTS_S_WAIT_CF, + OM2K_BTS_S_WAIT_IS, + OM2K_BTS_S_WAIT_CON, + OM2K_BTS_S_WAIT_TF, + OM2K_BTS_S_WAIT_TRX, + OM2K_BTS_S_DONE, + OM2K_BTS_S_ERROR, +}; + +struct om2k_bts_fsm_priv { + struct gsm_bts *bts; + uint8_t next_trx_nr; +}; + +static void om2k_bts_s_init(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct om2k_bts_fsm_priv *obfp = fi->priv; + struct gsm_bts *bts = obfp->bts; + + OSMO_ASSERT(event == OM2K_BTS_EVT_START); + osmo_fsm_inst_state_chg(fi, OM2K_BTS_S_WAIT_CF, + BTS_FSM_TIMEOUT, 0); + om2k_mo_fsm_start(fi, OM2K_BTS_EVT_CF_DONE, bts->c0, + &bts->rbs2000.cf.om2k_mo); +} + +static void om2k_bts_s_wait_cf(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct om2k_bts_fsm_priv *obfp = fi->priv; + struct gsm_bts *bts = obfp->bts; + + OSMO_ASSERT(event == OM2K_BTS_EVT_CF_DONE); + /* TF can take a long time to initialize, wait for 10min */ + osmo_fsm_inst_state_chg(fi, OM2K_BTS_S_WAIT_TF, 600, 0); + om2k_mo_fsm_start(fi, OM2K_BTS_EVT_TF_DONE, bts->c0, + &bts->rbs2000.tf.om2k_mo); +} + +static void om2k_bts_s_wait_tf(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct om2k_bts_fsm_priv *obfp = fi->priv; + struct gsm_bts *bts = obfp->bts; + + OSMO_ASSERT(event == OM2K_BTS_EVT_TF_DONE); + + osmo_fsm_inst_state_chg(fi, OM2K_BTS_S_WAIT_CON, + BTS_FSM_TIMEOUT, 0); + om2k_mo_fsm_start(fi, OM2K_BTS_EVT_CON_DONE, bts->c0, + &bts->rbs2000.con.om2k_mo); +} + +static void om2k_bts_s_wait_con(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct om2k_bts_fsm_priv *obfp = fi->priv; + struct gsm_bts *bts = obfp->bts; + + OSMO_ASSERT(event == OM2K_BTS_EVT_CON_DONE); + + osmo_fsm_inst_state_chg(fi, OM2K_BTS_S_WAIT_IS, + BTS_FSM_TIMEOUT, 0); + om2k_mo_fsm_start(fi, OM2K_BTS_EVT_IS_DONE, bts->c0, + &bts->rbs2000.is.om2k_mo); +} + +static void om2k_bts_s_wait_is(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct om2k_bts_fsm_priv *obfp = fi->priv; + struct gsm_bts_trx *trx; + + OSMO_ASSERT(event == OM2K_BTS_EVT_IS_DONE); + + osmo_fsm_inst_state_chg(fi, OM2K_BTS_S_WAIT_TRX, + BTS_FSM_TIMEOUT, 0); + obfp->next_trx_nr = 0; + trx = gsm_bts_trx_num(obfp->bts, obfp->next_trx_nr++); + om2k_trx_fsm_start(fi, trx, OM2K_BTS_EVT_TRX_DONE); +} + +static void om2k_bts_s_wait_trx(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct om2k_bts_fsm_priv *obfp = fi->priv; + + OSMO_ASSERT(event == OM2K_BTS_EVT_TRX_DONE); + + if (obfp->next_trx_nr < obfp->bts->num_trx) { + struct gsm_bts_trx *trx; + trx = gsm_bts_trx_num(obfp->bts, obfp->next_trx_nr++); + om2k_trx_fsm_start(fi, trx, OM2K_BTS_EVT_TRX_DONE); + } else { + osmo_fsm_inst_state_chg(fi, OM2K_BTS_S_DONE, 0, 0); + } +} + +static void om2k_bts_s_done_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL); +} + +static const struct osmo_fsm_state om2k_bts_states[] = { + [OM2K_BTS_S_INIT] = { + .in_event_mask = S(OM2K_BTS_EVT_START), + .out_state_mask = S(OM2K_BTS_S_WAIT_CF), + .name = "INIT", + .action = om2k_bts_s_init, + }, + [OM2K_BTS_S_WAIT_CF] = { + .in_event_mask = S(OM2K_BTS_EVT_CF_DONE), + .out_state_mask = S(OM2K_BTS_S_ERROR) | + S(OM2K_BTS_S_WAIT_TF), + .name = "WAIT-CF", + .action = om2k_bts_s_wait_cf, + }, + [OM2K_BTS_S_WAIT_TF] = { + .in_event_mask = S(OM2K_BTS_EVT_TF_DONE), + .out_state_mask = S(OM2K_BTS_S_ERROR) | + S(OM2K_BTS_S_WAIT_CON), + .name = "WAIT-TF", + .action = om2k_bts_s_wait_tf, + }, + [OM2K_BTS_S_WAIT_CON] = { + .in_event_mask = S(OM2K_BTS_EVT_CON_DONE), + .out_state_mask = S(OM2K_BTS_S_ERROR) | + S(OM2K_BTS_S_WAIT_IS), + .name = "WAIT-CON", + .action = om2k_bts_s_wait_con, + }, + [OM2K_BTS_S_WAIT_IS] = { + .in_event_mask = S(OM2K_BTS_EVT_IS_DONE), + .out_state_mask = S(OM2K_BTS_S_ERROR) | + S(OM2K_BTS_S_WAIT_TRX), + .name = "WAIT-IS", + .action = om2k_bts_s_wait_is, + }, + [OM2K_BTS_S_WAIT_TRX] = { + .in_event_mask = S(OM2K_BTS_EVT_TRX_DONE), + .out_state_mask = S(OM2K_BTS_S_ERROR) | + S(OM2K_BTS_S_DONE), + .name = "WAIT-TRX", + .action = om2k_bts_s_wait_trx, + }, + [OM2K_BTS_S_DONE] = { + .name = "DONE", + .onenter = om2k_bts_s_done_onenter, + }, + [OM2K_BTS_S_ERROR] = { + .name = "ERROR", + }, +}; + +static int om2k_bts_timer_cb(struct osmo_fsm_inst *fi) +{ + osmo_fsm_inst_state_chg(fi, OM2K_BTS_S_ERROR, 0, 0); + return 0; +} + +static struct osmo_fsm om2k_bts_fsm = { + .name = "OM2000-BTS", + .states = om2k_bts_states, + .num_states = ARRAY_SIZE(om2k_bts_states), + .log_subsys = DNM, + .event_names = om2k_bts_events, + .timer_cb = om2k_bts_timer_cb, +}; + +struct osmo_fsm_inst * +om2k_bts_fsm_start(struct gsm_bts *bts) +{ + struct osmo_fsm_inst *fi; + struct om2k_bts_fsm_priv *obfp; + char idbuf[16]; + + snprintf(idbuf, sizeof(idbuf), "%u", bts->nr); + + fi = osmo_fsm_inst_alloc(&om2k_bts_fsm, bts, NULL, + LOGL_DEBUG, idbuf); + if (!fi) + return NULL; + fi->priv = obfp = talloc_zero(fi, struct om2k_bts_fsm_priv); + obfp->bts = bts; + + osmo_fsm_inst_dispatch(fi, OM2K_BTS_EVT_START, NULL); + + return fi; +} + + +/*********************************************************************** + * OM2000 Negotiation + ***********************************************************************/ + +static int abis_om2k_tx_negot_req_ack(struct gsm_bts *bts, const struct abis_om2k_mo *mo, + uint8_t *data, unsigned int len) +{ + struct msgb *msg = om2k_msgb_alloc(); + struct abis_om2k_hdr *o2k; + + o2k = (struct abis_om2k_hdr *) msgb_put(msg, sizeof(*o2k)); + fill_om2k_hdr(o2k, mo, OM2K_MSGT_NEGOT_REQ_ACK); + + msgb_tlv_put(msg, OM2K_DEI_NEGOT_REC2, len, data); + + DEBUGP(DNM, "Tx MO=%s %s\n", om2k_mo_name(mo), + get_value_string(om2k_msgcode_vals, OM2K_MSGT_NEGOT_REQ_ACK)); + + return abis_om2k_sendmsg(bts, msg); +} + +struct iwd_version { + uint8_t gen_char[3+1]; + uint8_t rev_char[3+1]; +}; + +struct iwd_type { + uint8_t num_vers; + struct iwd_version v[8]; +}; + +static int om2k_rx_negot_req(struct msgb *msg) +{ + struct e1inp_sign_link *sign_link = (struct e1inp_sign_link *)msg->dst; + struct abis_om2k_hdr *o2h = msgb_l2(msg); + struct iwd_type iwd_types[16]; + uint8_t num_iwd_types = o2h->data[2]; + uint8_t *cur = o2h->data+3; + unsigned int i, v; + + uint8_t out_buf[1024]; + uint8_t *out_cur = out_buf+1; + uint8_t out_num_types = 0; + + memset(iwd_types, 0, sizeof(iwd_types)); + + /* Parse the RBS-supported IWD versions into iwd_types array */ + for (i = 0; i < num_iwd_types; i++) { + uint8_t num_versions = *cur++; + uint8_t iwd_type = *cur++; + + iwd_types[iwd_type].num_vers = num_versions; + + for (v = 0; v < num_versions; v++) { + struct iwd_version *iwd_v = &iwd_types[iwd_type].v[v]; + + memcpy(iwd_v->gen_char, cur, 3); + cur += 3; + memcpy(iwd_v->rev_char, cur, 3); + cur += 3; + + DEBUGP(DNM, "\tIWD Type %u Gen %s Rev %s\n", iwd_type, + iwd_v->gen_char, iwd_v->rev_char); + } + } + + /* Select the last version for each IWD type */ + for (i = 0; i < ARRAY_SIZE(iwd_types); i++) { + struct iwd_type *type = &iwd_types[i]; + struct iwd_version *last_v; + + if (type->num_vers == 0) + continue; + + out_num_types++; + + last_v = &type->v[type->num_vers-1]; + + *out_cur++ = i; + memcpy(out_cur, last_v->gen_char, 3); + out_cur += 3; + memcpy(out_cur, last_v->rev_char, 3); + out_cur += 3; + } + + out_buf[0] = out_num_types; + + return abis_om2k_tx_negot_req_ack(sign_link->trx->bts, &o2h->mo, out_buf, out_cur - out_buf); +} + + +/*********************************************************************** + * OM2000 Receive Message Handler + ***********************************************************************/ + +static int om2k_rx_nack(struct msgb *msg) +{ + struct abis_om2k_hdr *o2h = msgb_l2(msg); + uint16_t msg_type = ntohs(o2h->msg_type); + struct tlv_parsed tp; + + LOGP(DNM, LOGL_ERROR, "Rx MO=%s %s", om2k_mo_name(&o2h->mo), + get_value_string(om2k_msgcode_vals, msg_type)); + + abis_om2k_msg_tlv_parse(&tp, o2h); + if (TLVP_PRESENT(&tp, OM2K_DEI_REASON_CODE)) + LOGPC(DNM, LOGL_ERROR, ", Reason 0x%02x", + *TLVP_VAL(&tp, OM2K_DEI_REASON_CODE)); + + if (TLVP_PRESENT(&tp, OM2K_DEI_RESULT_CODE)) + LOGPC(DNM, LOGL_ERROR, ", Result %s", + get_value_string(om2k_result_strings, + *TLVP_VAL(&tp, OM2K_DEI_RESULT_CODE))); + LOGPC(DNM, LOGL_ERROR, "\n"); + + return 0; +} + +static int process_mo_state(struct gsm_bts *bts, struct om2k_decoded_msg *odm) +{ + uint8_t mo_state; + + if (!TLVP_PRESENT(&odm->tp, OM2K_DEI_MO_STATE)) + return -EIO; + mo_state = *TLVP_VAL(&odm->tp, OM2K_DEI_MO_STATE); + + LOGP(DNM, LOGL_DEBUG, "Rx MO=%s %s, MO State: %s\n", + om2k_mo_name(&odm->o2h.mo), + get_value_string(om2k_msgcode_vals, odm->msg_type), + get_value_string(om2k_mostate_vals, mo_state)); + + /* Throw error message in case we see an enable rsponse that does + * not yield an enabled mo-state */ + if (odm->msg_type == OM2K_MSGT_ENABLE_RES + && mo_state != OM2K_MO_S_ENABLED) { + LOGP(DNM, LOGL_ERROR, + "Rx MO=%s %s Failed to enable MO State!\n", + om2k_mo_name(&odm->o2h.mo), + get_value_string(om2k_msgcode_vals, odm->msg_type)); + } + + update_mo_state(bts, &odm->o2h.mo, mo_state); + + return 0; +} + +/* Display fault report bits (helper function of display_fault_maps()) */ +static bool display_fault_bits(const uint8_t *vect, uint16_t len, + uint8_t dei, const struct abis_om2k_mo *mo) +{ + uint16_t i; + int k; + bool faults_present = false; + int first = 1; + char string[255]; + + /* Check if errors are present at all */ + for (i = 0; i < len; i++) + if (vect[i]) + faults_present = true; + if (!faults_present) + return false; + + sprintf(string, "Fault Report: %s (", + get_value_string(om2k_attr_vals, dei)); + + for (i = 0; i < len; i++) { + for (k = 0; k < 8; k++) { + if ((vect[i] >> k) & 1) { + if (!first) + sprintf(string + strlen(string), ","); + sprintf(string + strlen(string), "%d", k + i*8); + first = 0; + } + } + } + + sprintf(string + strlen(string), ")\n"); + DEBUGP(DNM, "Rx MO=%s %s", om2k_mo_name(mo), string); + + return true; +} + +/* Display fault report maps */ +static void display_fault_maps(const uint8_t *src, unsigned int src_len, + const struct abis_om2k_mo *mo) +{ + uint8_t tag; + uint16_t tag_len; + const uint8_t *val; + int src_pos = 0; + int rc; + int tlv_count = 0; + uint16_t msg_code; + bool faults_present = false; + + /* Chop off header */ + src+=4; + src_len-=4; + + /* Check message type */ + msg_code = (*src & 0xff) << 8; + src++; + src_len--; + msg_code |= (*src & 0xff); + src++; + src_len--; + if (msg_code != OM2K_MSGT_FAULT_REP) { + LOGP(DNM, LOGL_ERROR, "Rx MO=%s Fault report: invalid message code!\n", + om2k_mo_name(mo)); + return; + } + + /* Chop off mo-interface */ + src += 4; + src_len -= 4; + + /* Iterate over each TLV element */ + while (1) { + + /* Bail if an the maximum number of TLV fields + * have been parsed */ + if (tlv_count >= 11) { + LOGP(DNM, LOGL_ERROR, + "Rx MO=%s Fault Report: too many tlv elements!\n", + om2k_mo_name(mo)); + return; + } + + /* Parse TLV field */ + rc = tlv_parse_one(&tag, &tag_len, &val, &om2k_att_tlvdef, + src + src_pos, src_len - src_pos); + if (rc > 0) + src_pos += rc; + else { + LOGP(DNM, LOGL_ERROR, + "Rx MO=%s Fault Report: invalid tlv element!\n", + om2k_mo_name(mo)); + return; + } + + switch (tag) { + case OM2K_DEI_INT_FAULT_MAP_1A: + case OM2K_DEI_INT_FAULT_MAP_1B: + case OM2K_DEI_INT_FAULT_MAP_2A: + case OM2K_DEI_EXT_COND_MAP_1: + case OM2K_DEI_EXT_COND_MAP_2: + case OM2K_DEI_REPL_UNIT_MAP: + case OM2K_DEI_INT_FAULT_MAP_2A_EXT: + case OM2K_DEI_EXT_COND_MAP_2_EXT: + case OM2K_DEI_REPL_UNIT_MAP_EXT: + faults_present |= display_fault_bits(val, tag_len, + tag, mo); + break; + } + + /* Stop when no further TLV elements can be expected */ + if (src_len - src_pos < 2) + break; + + tlv_count++; + } + + if (!faults_present) { + DEBUGP(DNM, "Rx MO=%s Fault Report: All faults ceased!\n", + om2k_mo_name(mo)); + } +} + +int abis_om2k_rcvmsg(struct msgb *msg) +{ + struct e1inp_sign_link *sign_link = (struct e1inp_sign_link *)msg->dst; + struct gsm_bts *bts = sign_link->trx->bts; + struct abis_om2k_hdr *o2h = msgb_l2(msg); + struct abis_om_hdr *oh = &o2h->om; + uint16_t msg_type = ntohs(o2h->msg_type); + struct om2k_decoded_msg odm; + struct om2k_mo *mo; + int rc = 0; + + /* Various consistency checks */ + if (oh->placement != ABIS_OM_PLACEMENT_ONLY) { + LOGP(DNM, LOGL_ERROR, "ABIS OML placement 0x%x not supported\n", + oh->placement); + if (oh->placement != ABIS_OM_PLACEMENT_FIRST) + return -EINVAL; + } + if (oh->sequence != 0) { + LOGP(DNM, LOGL_ERROR, "ABIS OML sequence 0x%x != 0x00\n", + oh->sequence); + return -EINVAL; + } + + msg->l3h = (unsigned char *)o2h + sizeof(*o2h); + + if (oh->mdisc != ABIS_OM_MDISC_FOM) { + LOGP(DNM, LOGL_ERROR, "unknown ABIS OM2000 message discriminator 0x%x\n", + oh->mdisc); + return -EINVAL; + } + + DEBUGP(DNM, "Rx MO=%s %s (%s)\n", om2k_mo_name(&o2h->mo), + get_value_string(om2k_msgcode_vals, msg_type), + osmo_hexdump(msg->l2h, msgb_l2len(msg))); + + om2k_decode_msg(&odm, msg); + + process_mo_state(bts, &odm); + + switch (msg_type) { + case OM2K_MSGT_CAL_TIME_REQ: + rc = abis_om2k_cal_time_resp(bts); + break; + case OM2K_MSGT_FAULT_REP: + display_fault_maps(msg->l2h, msgb_l2len(msg), &o2h->mo); + rc = abis_om2k_tx_simple(bts, &o2h->mo, OM2K_MSGT_FAULT_REP_ACK); + break; + case OM2K_MSGT_NEGOT_REQ: + rc = om2k_rx_negot_req(msg); + break; + case OM2K_MSGT_START_RES: + /* common processing here */ + rc = abis_om2k_tx_simple(bts, &o2h->mo, OM2K_MSGT_START_RES_ACK); + /* below we dispatch into MO */ + break; + case OM2K_MSGT_IS_CONF_RES: + rc = abis_om2k_tx_simple(bts, &o2h->mo, OM2K_MSGT_IS_CONF_RES_ACK); + break; + case OM2K_MSGT_CON_CONF_RES: + rc = abis_om2k_tx_simple(bts, &o2h->mo, OM2K_MSGT_CON_CONF_RES_ACK); + break; + case OM2K_MSGT_TX_CONF_RES: + rc = abis_om2k_tx_simple(bts, &o2h->mo, OM2K_MSGT_TX_CONF_RES_ACK); + break; + case OM2K_MSGT_RX_CONF_RES: + rc = abis_om2k_tx_simple(bts, &o2h->mo, OM2K_MSGT_RX_CONF_RES_ACK); + break; + case OM2K_MSGT_TS_CONF_RES: + rc = abis_om2k_tx_simple(bts, &o2h->mo, OM2K_MSGT_TS_CONF_RES_ACK); + break; + case OM2K_MSGT_TF_CONF_RES: + rc = abis_om2k_tx_simple(bts, &o2h->mo, OM2K_MSGT_TF_CONF_RES_ACK); + break; + case OM2K_MSGT_ENABLE_RES: + rc = abis_om2k_tx_simple(bts, &o2h->mo, OM2K_MSGT_ENABLE_RES_ACK); + break; + case OM2K_MSGT_DISABLE_RES: + rc = abis_om2k_tx_simple(bts, &o2h->mo, OM2K_MSGT_DISABLE_RES_ACK); + break; + case OM2K_MSGT_TEST_RES: + rc = abis_om2k_tx_simple(bts, &o2h->mo, OM2K_MSGT_TEST_RES_ACK); + break; + case OM2K_MSGT_CAPA_RES: + rc = abis_om2k_tx_simple(bts, &o2h->mo, OM2K_MSGT_CAPA_RES_ACK); + break; + /* ERrors */ + case OM2K_MSGT_START_REQ_REJ: + case OM2K_MSGT_CONNECT_REJ: + case OM2K_MSGT_OP_INFO_REJ: + case OM2K_MSGT_DISCONNECT_REJ: + case OM2K_MSGT_TEST_REQ_REJ: + case OM2K_MSGT_CON_CONF_REQ_REJ: + case OM2K_MSGT_IS_CONF_REQ_REJ: + case OM2K_MSGT_TX_CONF_REQ_REJ: + case OM2K_MSGT_RX_CONF_REQ_REJ: + case OM2K_MSGT_TS_CONF_REQ_REJ: + case OM2K_MSGT_TF_CONF_REQ_REJ: + case OM2K_MSGT_ENABLE_REQ_REJ: + case OM2K_MSGT_ALARM_STATUS_REQ_REJ: + case OM2K_MSGT_DISABLE_REQ_REJ: + rc = om2k_rx_nack(msg); + break; + } + + /* Resolve the MO for this message */ + mo = get_om2k_mo(bts, &o2h->mo); + if (!mo) { + LOGP(DNM, LOGL_ERROR, "Couldn't resolve MO for OM2K msg " + "%s: %s\n", get_value_string(om2k_msgcode_vals, msg_type), + msgb_hexdump(msg)); + return 0; + } + if (!mo->fsm) { + LOGP(DNM, LOGL_ERROR, "MO object should not generate any message. fsm == NULL " + "%s: %s\n", get_value_string(om2k_msgcode_vals, msg_type), + msgb_hexdump(msg)); + return 0; + } + + /* Dispatch message to that MO */ + om2k_mo_fsm_recvmsg(bts, mo, &odm); + + msgb_free(msg); + return rc; +} + +static void om2k_mo_init(struct om2k_mo *mo, uint8_t class, + uint8_t bts_nr, uint8_t assoc_so, uint8_t inst) +{ + mo->addr.class = class; + mo->addr.bts = bts_nr; + mo->addr.assoc_so = assoc_so; + mo->addr.inst = inst; +} + +/* initialize the OM2K_MO members of gsm_bts_trx and its timeslots */ +void abis_om2k_trx_init(struct gsm_bts_trx *trx) +{ + struct gsm_bts *bts = trx->bts; + unsigned int i; + + OSMO_ASSERT(bts->type == GSM_BTS_TYPE_RBS2000); + + om2k_mo_init(&trx->rbs2000.trxc.om2k_mo, OM2K_MO_CLS_TRXC, + bts->nr, 255, trx->nr); + om2k_mo_init(&trx->rbs2000.tx.om2k_mo, OM2K_MO_CLS_TX, + bts->nr, 255, trx->nr); + om2k_mo_init(&trx->rbs2000.rx.om2k_mo, OM2K_MO_CLS_RX, + bts->nr, 255, trx->nr); + + for (i = 0; i < ARRAY_SIZE(trx->ts); i++) { + struct gsm_bts_trx_ts *ts = &trx->ts[i]; + om2k_mo_init(&ts->rbs2000.om2k_mo, OM2K_MO_CLS_TS, + bts->nr, trx->nr, i); + gsm_ts_check_init(ts); + } +} + +/* initialize the OM2K_MO members of gsm_bts */ +void abis_om2k_bts_init(struct gsm_bts *bts) +{ + OSMO_ASSERT(bts->type == GSM_BTS_TYPE_RBS2000); + + om2k_mo_init(&bts->rbs2000.cf.om2k_mo, OM2K_MO_CLS_CF, + bts->nr, 0xFF, 0); + om2k_mo_init(&bts->rbs2000.is.om2k_mo, OM2K_MO_CLS_IS, + bts->nr, 0xFF, 0); + om2k_mo_init(&bts->rbs2000.con.om2k_mo, OM2K_MO_CLS_CON, + bts->nr, 0xFF, 0); + om2k_mo_init(&bts->rbs2000.dp.om2k_mo, OM2K_MO_CLS_DP, + bts->nr, 0xFF, 0); + om2k_mo_init(&bts->rbs2000.tf.om2k_mo, OM2K_MO_CLS_TF, + bts->nr, 0xFF, 0); +} + +static __attribute__((constructor)) void abis_om2k_init(void) +{ + osmo_fsm_register(&om2k_mo_fsm); + osmo_fsm_register(&om2k_bts_fsm); + osmo_fsm_register(&om2k_trx_fsm); +} diff --git a/src/osmo-bsc/abis_om2000_vty.c b/src/osmo-bsc/abis_om2000_vty.c new file mode 100644 index 000000000..faf39c106 --- /dev/null +++ b/src/osmo-bsc/abis_om2000_vty.c @@ -0,0 +1,604 @@ +/* VTY interface for A-bis OM2000 */ + +/* (C) 2010-2018 by Harald Welte <laforge@gnumonks.org> + * + * 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 <stdlib.h> +#include <unistd.h> +#include <errno.h> +#include <stdint.h> + +#include <arpa/inet.h> + +#include <osmocom/bsc/gsm_data.h> +#include <osmocom/core/msgb.h> +#include <osmocom/gsm/tlv.h> +#include <osmocom/core/talloc.h> +#include <osmocom/bsc/debug.h> +#include <osmocom/bsc/signal.h> +#include <osmocom/bsc/abis_om2000.h> +#include <osmocom/bsc/vty.h> + +#include <osmocom/vty/vty.h> +#include <osmocom/vty/command.h> +#include <osmocom/vty/logging.h> +#include <osmocom/vty/telnet_interface.h> + +static struct cmd_node om2k_node = { + OM2K_NODE, + "%s(om2k)# ", + 1, +}; + +static struct cmd_node om2k_con_group_node = { + OM2K_CON_GROUP_NODE, + "%s(om2k-con-group)# ", + 1, +}; + +struct con_group; + +struct oml_node_state { + struct gsm_bts *bts; + struct abis_om2k_mo mo; + struct con_group *cg; +}; + +static int dummy_config_write(struct vty *v) +{ + return CMD_SUCCESS; +} + +/* FIXME: auto-generate those strings from the value_string lists */ +#define OM2K_OBJCLASS_VTY "(trxc|ts|tf|is|con|dp|cf|tx|rx)" +#define OM2K_OBJCLASS_VTY_HELP "TRX Controller\n" \ + "Timeslot\n" \ + "Timing Function\n" \ + "Interface Switch\n" \ + "Abis Concentrator\n" \ + "Digital Path\n" \ + "Central Function\n" \ + "Transmitter\n" \ + "Receiver\n" + +DEFUN(om2k_class_inst, om2k_class_inst_cmd, + "bts <0-255> om2000 class " OM2K_OBJCLASS_VTY + " <0-255> <0-255> <0-255>", + "BTS related commands\n" "BTS Number\n" + "Manipulate the OM2000 managed objects\n" + "Object Class\n" OM2K_OBJCLASS_VTY_HELP + "BTS Number\n" "Associated SO Instance\n" "Instance Number\n") +{ + struct gsm_bts *bts; + struct oml_node_state *oms; + int bts_nr = atoi(argv[0]); + + bts = gsm_bts_num(gsmnet_from_vty(vty), bts_nr); + if (!bts) { + vty_out(vty, "%% No such BTS (%d)%s", bts_nr, VTY_NEWLINE); + return CMD_WARNING; + } + + if (bts->type != GSM_BTS_TYPE_RBS2000) { + vty_out(vty, "%% BTS %d not an Ericsson RBS%s", + bts_nr, VTY_NEWLINE); + return CMD_WARNING; + } + + oms = talloc_zero(tall_bsc_ctx, struct oml_node_state); + if (!oms) + return CMD_WARNING; + + oms->bts = bts; + oms->mo.class = get_string_value(om2k_mo_class_short_vals, argv[1]); + oms->mo.bts = atoi(argv[2]); + oms->mo.assoc_so = atoi(argv[3]); + oms->mo.inst = atoi(argv[4]); + + vty->index = oms; + vty->node = OM2K_NODE; + + return CMD_SUCCESS; + +} + +DEFUN(om2k_classnum_inst, om2k_classnum_inst_cmd, + "bts <0-255> om2000 class <0-255> <0-255> <0-255> <0-255>", + "BTS related commands\n" "BTS Number\n" + "Manipulate the OML managed objects\n" + "Object Class\n" "Object Class\n" + "BTS Number\n" "Associated SO Instance\n" "Instance Number\n") +{ + struct gsm_bts *bts; + struct oml_node_state *oms; + int bts_nr = atoi(argv[0]); + + bts = gsm_bts_num(gsmnet_from_vty(vty), bts_nr); + if (!bts) { + vty_out(vty, "%% No such BTS (%d)%s", bts_nr, VTY_NEWLINE); + return CMD_WARNING; + } + + oms = talloc_zero(tall_bsc_ctx, struct oml_node_state); + if (!oms) + return CMD_WARNING; + + oms->bts = bts; + oms->mo.class = atoi(argv[1]); + oms->mo.bts = atoi(argv[2]); + oms->mo.assoc_so = atoi(argv[3]); + oms->mo.inst = atoi(argv[4]); + + vty->index = oms; + vty->node = OM2K_NODE; + + return CMD_SUCCESS; +} + +DEFUN(om2k_reset, om2k_reset_cmd, + "reset-command", + "Reset the MO\n") +{ + struct oml_node_state *oms = vty->index; + + abis_om2k_tx_reset_cmd(oms->bts, &oms->mo); + return CMD_SUCCESS; +} + +DEFUN(om2k_start, om2k_start_cmd, + "start-request", + "Start the MO\n") +{ + struct oml_node_state *oms = vty->index; + + abis_om2k_tx_start_req(oms->bts, &oms->mo); + return CMD_SUCCESS; +} + +DEFUN(om2k_status, om2k_status_cmd, + "status-request", + "Get the MO Status\n") +{ + struct oml_node_state *oms = vty->index; + + abis_om2k_tx_status_req(oms->bts, &oms->mo); + return CMD_SUCCESS; +} + +DEFUN(om2k_connect, om2k_connect_cmd, + "connect-command", + "Connect the MO\n") +{ + struct oml_node_state *oms = vty->index; + + abis_om2k_tx_connect_cmd(oms->bts, &oms->mo); + return CMD_SUCCESS; +} + +DEFUN(om2k_disconnect, om2k_disconnect_cmd, + "disconnect-command", + "Disconnect the MO\n") +{ + struct oml_node_state *oms = vty->index; + + abis_om2k_tx_disconnect_cmd(oms->bts, &oms->mo); + return CMD_SUCCESS; +} + +DEFUN(om2k_enable, om2k_enable_cmd, + "enable-request", + "Enable the MO\n") +{ + struct oml_node_state *oms = vty->index; + + abis_om2k_tx_enable_req(oms->bts, &oms->mo); + return CMD_SUCCESS; +} + +DEFUN(om2k_disable, om2k_disable_cmd, + "disable-request", + "Disable the MO\n") +{ + struct oml_node_state *oms = vty->index; + + abis_om2k_tx_disable_req(oms->bts, &oms->mo); + return CMD_SUCCESS; +} + +DEFUN(om2k_op_info, om2k_op_info_cmd, + "operational-info <0-1>", + "Set operational information\n" + "Set operational info to 0 or 1\n") +{ + struct oml_node_state *oms = vty->index; + int oper = atoi(argv[0]); + + abis_om2k_tx_op_info(oms->bts, &oms->mo, oper); + return CMD_SUCCESS; +} + +DEFUN(om2k_test, om2k_test_cmd, + "test-request", + "Test the MO\n") +{ + struct oml_node_state *oms = vty->index; + + abis_om2k_tx_test_req(oms->bts, &oms->mo); + return CMD_SUCCESS; +} + +DEFUN(om2k_cap_req, om2k_cap_req_cmd, + "capabilities-request", + "Request MO capabilities\n") +{ + struct oml_node_state *oms = vty->index; + + abis_om2k_tx_cap_req(oms->bts, &oms->mo); + return CMD_SUCCESS; +} + +static struct con_group *con_group_find_or_create(struct gsm_bts *bts, uint8_t cg) +{ + struct con_group *ent; + + llist_for_each_entry(ent, &bts->rbs2000.con.conn_groups, list) { + if (ent->cg == cg) + return ent; + } + + ent = talloc_zero(bts, struct con_group); + ent->bts = bts; + ent->cg = cg; + INIT_LLIST_HEAD(&ent->paths); + llist_add_tail(&ent->list, &bts->rbs2000.con.conn_groups); + + return ent; +} + +static int con_group_del(struct gsm_bts *bts, uint8_t cg_id) +{ + struct con_group *cg, *cg2; + + llist_for_each_entry_safe(cg, cg2, &bts->rbs2000.con.conn_groups, list) { + if (cg->cg == cg_id) { + llist_del(&cg->list); + talloc_free(cg); + return 0; + }; + } + return -ENOENT; +} + +static void con_group_add_path(struct con_group *cg, uint16_t ccp, + uint8_t ci, uint8_t tag, uint8_t tei) +{ + struct con_path *cp = talloc_zero(cg, struct con_path); + + cp->ccp = ccp; + cp->ci = ci; + cp->tag = tag; + cp->tei = tei; + llist_add(&cp->list, &cg->paths); +} + +static int con_group_del_path(struct con_group *cg, uint16_t ccp, + uint8_t ci, uint8_t tag, uint8_t tei) +{ + struct con_path *cp, *cp2; + llist_for_each_entry_safe(cp, cp2, &cg->paths, list) { + if (cp->ccp == ccp && cp->ci == ci && cp->tag == tag && + cp->tei == tei) { + llist_del(&cp->list); + talloc_free(cp); + return 0; + } + } + return -ENOENT; +} + +DEFUN(cfg_om2k_con_group, cfg_om2k_con_group_cmd, + "con-connection-group <1-31>", + "Configure a CON (Concentrator) Connection Group\n" + "CON Connection Group Number\n") +{ + struct gsm_bts *bts = vty->index; + struct con_group *cg; + uint8_t cgid = atoi(argv[0]); + + if (bts->type != GSM_BTS_TYPE_RBS2000) { + vty_out(vty, "%% CON MO only exists in RBS2000%s", + VTY_NEWLINE); + return CMD_WARNING; + } + + cg = con_group_find_or_create(bts, cgid); + if (!cg) { + vty_out(vty, "%% Cannot create CON Group%s", + VTY_NEWLINE); + return CMD_WARNING; + } + + vty->node = OM2K_CON_GROUP_NODE; + vty->index = cg; + + return CMD_SUCCESS; +} + +DEFUN(del_om2k_con_group, del_om2k_con_group_cmd, + "del-connection-group <1-31>", + "Delete a CON (Concentrator) Connection Group\n" + "CON Connection Group Number\n") +{ + struct gsm_bts *bts = vty->index; + int rc; + uint8_t cgid = atoi(argv[0]); + + if (bts->type != GSM_BTS_TYPE_RBS2000) { + vty_out(vty, "%% CON MO only exists in RBS2000%s", + VTY_NEWLINE); + return CMD_WARNING; + } + + rc = con_group_del(bts, cgid); + if (rc != 0) { + vty_out(vty, "%% Cannot delete CON Group%s", + VTY_NEWLINE); + return CMD_WARNING; + } + + return CMD_SUCCESS; +} + +#define CON_PATH_HELP "CON Path (In/Out)\n" \ + "Add CON Path to Concentration Group\n" \ + "Delete CON Path from Concentration Group\n" \ + "CON Conection Point\n" \ + "Contiguity Index\n" \ + +DEFUN(cfg_om2k_con_path_dec, cfg_om2k_con_path_dec_cmd, + "con-path (add|del) <0-2047> <0-255> deconcentrated <0-63>", + CON_PATH_HELP "De-concentrated in/outlet\n" "TEI Value\n") +{ + struct con_group *cg = vty->index; + uint16_t ccp = atoi(argv[1]); + uint8_t ci = atoi(argv[2]); + uint8_t tei = atoi(argv[3]); + + if (!strcmp(argv[0], "add")) + con_group_add_path(cg, ccp, ci, 0, tei); + else { + if (con_group_del_path(cg, ccp, ci, 0, tei) < 0) { + vty_out(vty, "%% No matching CON Path%s", + VTY_NEWLINE); + return CMD_WARNING; + } + } + + return CMD_SUCCESS; +} + +DEFUN(cfg_om2k_con_path_conc, cfg_om2k_con_path_conc_cmd, + "con-path (add|del) <0-2047> <0-255> concentrated <1-16>", + CON_PATH_HELP "Concentrated in/outlet\n" "Tag Number\n") +{ + struct con_group *cg = vty->index; + uint16_t ccp = atoi(argv[1]); + uint8_t ci = atoi(argv[2]); + uint8_t tag = atoi(argv[3]); + + if (!strcmp(argv[0], "add")) + con_group_add_path(cg, ccp, ci, tag, 0xff); + else { + if (con_group_del_path(cg, ccp, ci, tag, 0xff) < 0) { + vty_out(vty, "%% No matching CON list entry%s", + VTY_NEWLINE); + return CMD_WARNING; + } + } + + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_alt_mode, cfg_bts_alt_mode_cmd, + "abis-lower-transport (single-timeslot|super-channel)", + "Configure thee Abis Lower Transport\n" + "Single Timeslot (classic Abis)\n" + "SuperChannel (Packet Abis)\n") +{ + struct gsm_bts *bts = vty->index; + + if (bts->type != GSM_BTS_TYPE_RBS2000) { + vty_out(vty, "%% Command only works for RBS2000%s", + VTY_NEWLINE); + return CMD_WARNING; + } + + if (!strcmp(argv[0], "super-channel")) + bts->rbs2000.use_superchannel = 1; + else + bts->rbs2000.use_superchannel = 0; + + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_is_conn_list, cfg_bts_is_conn_list_cmd, + "is-connection-list (add|del) <0-2047> <0-2047> <0-255>", + "Interface Switch Connection List\n" + "Add to IS list\n" "Delete from IS list\n" + "ICP1\n" "ICP2\n" "Contiguity Index\n") +{ + struct gsm_bts *bts = vty->index; + uint16_t icp1 = atoi(argv[1]); + uint16_t icp2 = atoi(argv[2]); + uint8_t ci = atoi(argv[3]); + struct is_conn_group *grp, *grp2; + + if (bts->type != GSM_BTS_TYPE_RBS2000) { + vty_out(vty, "%% IS MO only exists in RBS2000%s", + VTY_NEWLINE); + return CMD_WARNING; + } + + if (!strcmp(argv[0], "add")) { + grp = talloc_zero(bts, struct is_conn_group); + grp->icp1 = icp1; + grp->icp2 = icp2; + grp->ci = ci; + llist_add_tail(&grp->list, &bts->rbs2000.is.conn_groups); + } else { + llist_for_each_entry_safe(grp, grp2, &bts->rbs2000.is.conn_groups, list) { + if (grp->icp1 == icp1 && grp->icp2 == icp2 + && grp->ci == ci) { + llist_del(&grp->list); + talloc_free(grp); + return CMD_SUCCESS; + } + } + vty_out(vty, "%% No matching IS Conn Group found!%s", + VTY_NEWLINE); + return CMD_WARNING; + } + + return CMD_SUCCESS; +} + + +DEFUN(om2k_conf_req, om2k_conf_req_cmd, + "configuration-request", + "Send the configuration request for current MO\n") +{ + struct oml_node_state *oms = vty->index; + struct gsm_bts *bts = oms->bts; + struct gsm_bts_trx *trx = NULL; + struct gsm_bts_trx_ts *ts = NULL; + + switch (oms->mo.class) { + case OM2K_MO_CLS_IS: + abis_om2k_tx_is_conf_req(bts); + break; + case OM2K_MO_CLS_TS: + trx = gsm_bts_trx_by_nr(bts, oms->mo.assoc_so); + if (!trx) { + vty_out(vty, "%% BTS %u has no TRX %u%s", bts->nr, + oms->mo.assoc_so, VTY_NEWLINE); + return CMD_WARNING; + } + if (oms->mo.inst >= ARRAY_SIZE(trx->ts)) { + vty_out(vty, "%% Timeslot %u out of range%s", + oms->mo.inst, VTY_NEWLINE); + return CMD_WARNING; + } + ts = &trx->ts[oms->mo.inst]; + abis_om2k_tx_ts_conf_req(ts); + break; + case OM2K_MO_CLS_RX: + case OM2K_MO_CLS_TX: + case OM2K_MO_CLS_TRXC: + trx = gsm_bts_trx_by_nr(bts, oms->mo.inst); + if (!trx) { + vty_out(vty, "%% BTS %u has no TRX %u%s", bts->nr, + oms->mo.inst, VTY_NEWLINE); + return CMD_WARNING; + } + switch (oms->mo.class) { + case OM2K_MO_CLS_RX: + abis_om2k_tx_rx_conf_req(trx); + break; + case OM2K_MO_CLS_TX: + abis_om2k_tx_tx_conf_req(trx); + break; + default: + break; + } + break; + case OM2K_MO_CLS_TF: + abis_om2k_tx_tf_conf_req(bts); + break; + default: + vty_out(vty, "%% Don't know how to configure MO%s", + VTY_NEWLINE); + } + + return CMD_SUCCESS; +} + +static void dump_con_group(struct vty *vty, struct con_group *cg) +{ + struct con_path *cp; + + llist_for_each_entry(cp, &cg->paths, list) { + vty_out(vty, " con-path add %u %u ", cp->ccp, cp->ci); + if (cp->tei == 0xff) { + vty_out(vty, "concentrated %u%s", cp->tag, + VTY_NEWLINE); + } else { + vty_out(vty, "deconcentrated %u%s", cp->tei, + VTY_NEWLINE); + } + } +} + +void abis_om2k_config_write_bts(struct vty *vty, struct gsm_bts *bts) +{ + struct is_conn_group *igrp; + struct con_group *cgrp; + + llist_for_each_entry(igrp, &bts->rbs2000.is.conn_groups, list) + vty_out(vty, " is-connection-list add %u %u %u%s", + igrp->icp1, igrp->icp2, igrp->ci, VTY_NEWLINE); + + llist_for_each_entry(cgrp, &bts->rbs2000.con.conn_groups, list) { + vty_out(vty, " con-connection-group %u%s", cgrp->cg, + VTY_NEWLINE); + dump_con_group(vty, cgrp); + } + if (bts->rbs2000.use_superchannel) + vty_out(vty, " abis-lower-transport super-channel%s", + VTY_NEWLINE); +} + +int abis_om2k_vty_init(void) +{ + install_element(ENABLE_NODE, &om2k_class_inst_cmd); + install_element(ENABLE_NODE, &om2k_classnum_inst_cmd); + install_node(&om2k_node, dummy_config_write); + + install_element(OM2K_NODE, &om2k_reset_cmd); + install_element(OM2K_NODE, &om2k_start_cmd); + install_element(OM2K_NODE, &om2k_status_cmd); + install_element(OM2K_NODE, &om2k_connect_cmd); + install_element(OM2K_NODE, &om2k_disconnect_cmd); + install_element(OM2K_NODE, &om2k_enable_cmd); + install_element(OM2K_NODE, &om2k_disable_cmd); + install_element(OM2K_NODE, &om2k_op_info_cmd); + install_element(OM2K_NODE, &om2k_test_cmd); + install_element(OM2K_NODE, &om2k_cap_req_cmd); + install_element(OM2K_NODE, &om2k_conf_req_cmd); + + install_node(&om2k_con_group_node, dummy_config_write); + install_element(OM2K_CON_GROUP_NODE, &cfg_om2k_con_path_dec_cmd); + install_element(OM2K_CON_GROUP_NODE, &cfg_om2k_con_path_conc_cmd); + + install_element(BTS_NODE, &cfg_bts_is_conn_list_cmd); + install_element(BTS_NODE, &cfg_bts_alt_mode_cmd); + install_element(BTS_NODE, &cfg_om2k_con_group_cmd); + install_element(BTS_NODE, &del_om2k_con_group_cmd); + + return 0; +} diff --git a/src/osmo-bsc/abis_rsl.c b/src/osmo-bsc/abis_rsl.c new file mode 100644 index 000000000..7bbde47d7 --- /dev/null +++ b/src/osmo-bsc/abis_rsl.c @@ -0,0 +1,3040 @@ +/* GSM Radio Signalling Link messages on the A-bis interface + * 3GPP TS 08.58 version 8.6.0 Release 1999 / ETSI TS 100 596 V8.6.0 */ + +/* (C) 2008-2010 by Harald Welte <laforge@gnumonks.org> + * (C) 2012 by Holger Hans Peter Freyther + * + * 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 <stdio.h> +#include <stdlib.h> +#include <errno.h> +#include <netinet/in.h> +#include <arpa/inet.h> + +#include <osmocom/bsc/gsm_data.h> +#include <osmocom/bsc/gsm_04_08_utils.h> +#include <osmocom/gsm/gsm_utils.h> +#include <osmocom/bsc/abis_rsl.h> +#include <osmocom/bsc/chan_alloc.h> +#include <osmocom/bsc/bsc_rll.h> +#include <osmocom/bsc/debug.h> +#include <osmocom/gsm/tlv.h> +#include <osmocom/gsm/protocol/gsm_04_08.h> +#include <osmocom/gsm/protocol/gsm_08_58.h> +#include <osmocom/bsc/paging.h> +#include <osmocom/bsc/signal.h> +#include <osmocom/bsc/meas_rep.h> +#include <osmocom/abis/e1_input.h> +#include <osmocom/gsm/rsl.h> +#include <osmocom/core/talloc.h> +#include <osmocom/bsc/pcu_if.h> +#include <osmocom/bsc/bsc_api.h> +#include <osmocom/bsc/bsc_subscr_conn_fsm.h> + +#define RSL_ALLOC_SIZE 1024 +#define RSL_ALLOC_HEADROOM 128 + +#define RTP_PT_GSM_FULL 3 +#define RTP_PT_GSM_HALF 96 +#define RTP_PT_GSM_EFR 97 +#define RTP_PT_AMR 98 + +enum sacch_deact { + SACCH_NONE, + SACCH_DEACTIVATE, +}; + +static int rsl_send_imm_assignment(struct gsm_lchan *lchan); +static void error_timeout_cb(void *data); +static int dyn_ts_switchover_continue(struct gsm_bts_trx_ts *ts); +static int dyn_ts_switchover_failed(struct gsm_bts_trx_ts *ts, int rc); +static void dyn_ts_switchover_complete(struct gsm_lchan *lchan); + +static void send_lchan_signal(int sig_no, struct gsm_lchan *lchan, + struct gsm_meas_rep *resp) +{ + struct lchan_signal_data sig; + sig.lchan = lchan; + sig.mr = resp; + osmo_signal_dispatch(SS_LCHAN, sig_no, &sig); +} + +static void do_lchan_free(struct gsm_lchan *lchan) +{ + /* We start the error timer to make the channel available again */ + if (lchan->state == LCHAN_S_REL_ERR) { + osmo_timer_setup(&lchan->error_timer, error_timeout_cb, lchan); + osmo_timer_schedule(&lchan->error_timer, + lchan->ts->trx->bts->network->T3111 + 2, 0); + } else { + rsl_lchan_set_state(lchan, LCHAN_S_NONE); + } + lchan_free(lchan); +} + +static void count_codecs(struct gsm_bts *bts, struct gsm_lchan *lchan) +{ + OSMO_ASSERT(bts); + + if (lchan->type == GSM_LCHAN_TCH_H) { + switch (lchan->tch_mode) { + case GSM48_CMODE_SPEECH_AMR: + rate_ctr_inc(&bts->bts_ctrs->ctr[BTS_CTR_CODEC_AMR_H]); + break; + case GSM48_CMODE_SPEECH_V1: + rate_ctr_inc(&bts->bts_ctrs->ctr[BTS_CTR_CODEC_V1_HR]); + break; + default: + break; + } + } else if (lchan->type == GSM_LCHAN_TCH_F) { + switch (lchan->tch_mode) { + case GSM48_CMODE_SPEECH_AMR: + rate_ctr_inc(&bts->bts_ctrs->ctr[BTS_CTR_CODEC_AMR_F]); + break; + case GSM48_CMODE_SPEECH_V1: + rate_ctr_inc(&bts->bts_ctrs->ctr[BTS_CTR_CODEC_V1_FR]); + break; + case GSM48_CMODE_SPEECH_EFR: + rate_ctr_inc(&bts->bts_ctrs->ctr[BTS_CTR_CODEC_EFR]); + break; + default: + break; + } + } +} + +static uint8_t mdisc_by_msgtype(uint8_t msg_type) +{ + /* mask off the transparent bit ? */ + msg_type &= 0xfe; + + if ((msg_type & 0xf0) == 0x00) + return ABIS_RSL_MDISC_RLL; + if ((msg_type & 0xf0) == 0x10) { + if (msg_type >= 0x19 && msg_type <= 0x22) + return ABIS_RSL_MDISC_TRX; + else + return ABIS_RSL_MDISC_COM_CHAN; + } + if ((msg_type & 0xe0) == 0x20) + return ABIS_RSL_MDISC_DED_CHAN; + + return ABIS_RSL_MDISC_LOC; +} + +static inline void init_dchan_hdr(struct abis_rsl_dchan_hdr *dh, + uint8_t msg_type) +{ + dh->c.msg_discr = mdisc_by_msgtype(msg_type); + dh->c.msg_type = msg_type; + dh->ie_chan = RSL_IE_CHAN_NR; +} + +/* call rsl_lchan_lookup and set the log context */ +static struct gsm_lchan *lchan_lookup(struct gsm_bts_trx *trx, uint8_t chan_nr, + const char *log_name) +{ + int rc; + struct gsm_lchan *lchan = rsl_lchan_lookup(trx, chan_nr, &rc); + + if (!lchan) { + LOGP(DRSL, LOGL_ERROR, "%sunknown chan_nr=0x%02x\n", + log_name, chan_nr); + return NULL; + } + + if (rc < 0) + LOGP(DRSL, LOGL_ERROR, "%s %smismatching chan_nr=0x%02x\n", + gsm_ts_and_pchan_name(lchan->ts), log_name, chan_nr); + + return lchan; +} + +/* As per TS 03.03 Section 2.2, the IMSI has 'not more than 15 digits' */ +uint64_t str_to_imsi(const char *imsi_str) +{ + uint64_t ret; + + ret = strtoull(imsi_str, NULL, 10); + + return ret; +} + +static struct msgb *rsl_msgb_alloc(void) +{ + return msgb_alloc_headroom(RSL_ALLOC_SIZE, RSL_ALLOC_HEADROOM, + "RSL"); +} + +static void pad_macblock(uint8_t *out, const uint8_t *in, int len) +{ + memcpy(out, in, len); + + if (len < GSM_MACBLOCK_LEN) + memset(out+len, 0x2b, GSM_MACBLOCK_LEN - len); +} + +/* Chapter 9.3.7: Encryption Information */ +static int build_encr_info(uint8_t *out, struct gsm_lchan *lchan) +{ + *out++ = lchan->encr.alg_id & 0xff; + if (lchan->encr.key_len) + memcpy(out, lchan->encr.key, lchan->encr.key_len); + return lchan->encr.key_len + 1; +} + +static void print_rsl_cause(int lvl, const uint8_t *cause_v, uint8_t cause_len) +{ + int i; + + LOGPC(DRSL, lvl, "CAUSE=0x%02x(%s) ", + cause_v[0], rsl_err_name(cause_v[0])); + for (i = 1; i < cause_len-1; i++) + LOGPC(DRSL, lvl, "%02x ", cause_v[i]); +} + +static void lchan_act_tmr_cb(void *data) +{ + struct gsm_lchan *lchan = data; + + rsl_lchan_mark_broken(lchan, "activation timeout"); + lchan_free(lchan); +} + +static void lchan_deact_tmr_cb(void *data) +{ + struct gsm_lchan *lchan = data; + + rsl_lchan_mark_broken(lchan, "de-activation timeout"); + lchan_free(lchan); +} + + +/* Send a BCCH_INFO message as per Chapter 8.5.1 */ +int rsl_bcch_info(const struct gsm_bts_trx *trx, enum osmo_sysinfo_type si_type, const uint8_t *data, int len) +{ + struct abis_rsl_dchan_hdr *dh; + const struct gsm_bts *bts = trx->bts; + struct msgb *msg = rsl_msgb_alloc(); + uint8_t type = osmo_sitype2rsl(si_type); + + if (bts->c0 != trx) + LOGP(DRR, LOGL_ERROR, "Attempting to set BCCH SI%s on wrong BTS%u/TRX%u\n", + get_value_string(osmo_sitype_strs, si_type), bts->nr, trx->nr); + + dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof*dh); + init_dchan_hdr(dh, RSL_MT_BCCH_INFO); + dh->chan_nr = RSL_CHAN_BCCH; + + if (trx->bts->type == GSM_BTS_TYPE_RBS2000 + && type == RSL_SYSTEM_INFO_13) { + /* Ericsson proprietary encoding of SI13 */ + msgb_tv_put(msg, RSL_IE_SYSINFO_TYPE, RSL_ERIC_SYSTEM_INFO_13); + if (data) + msgb_tlv_put(msg, RSL_IE_FULL_BCCH_INFO, len, data); + msgb_tv_put(msg, RSL_IE_ERIC_BCCH_MAPPING, 0x00); + } else { + /* Normal encoding */ + msgb_tv_put(msg, RSL_IE_SYSINFO_TYPE, type); + if (data) + msgb_tlv_put(msg, RSL_IE_FULL_BCCH_INFO, len, data); + } + + msg->dst = trx->rsl_link; + + return abis_rsl_sendmsg(msg); +} + +int rsl_sacch_filling(struct gsm_bts_trx *trx, uint8_t type, + const uint8_t *data, int len) +{ + struct abis_rsl_common_hdr *ch; + struct msgb *msg = rsl_msgb_alloc(); + + ch = (struct abis_rsl_common_hdr *) msgb_put(msg, sizeof(*ch)); + ch->msg_discr = ABIS_RSL_MDISC_TRX; + ch->msg_type = RSL_MT_SACCH_FILL; + + msgb_tv_put(msg, RSL_IE_SYSINFO_TYPE, type); + if (data) + msgb_tl16v_put(msg, RSL_IE_L3_INFO, len, data); + + msg->dst = trx->rsl_link; + + return abis_rsl_sendmsg(msg); +} + +int rsl_sacch_info_modify(struct gsm_lchan *lchan, uint8_t type, + const uint8_t *data, int len) +{ + struct abis_rsl_dchan_hdr *dh; + struct msgb *msg = rsl_msgb_alloc(); + uint8_t chan_nr = gsm_lchan2chan_nr(lchan); + + dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh)); + init_dchan_hdr(dh, RSL_MT_SACCH_INFO_MODIFY); + dh->chan_nr = chan_nr; + + msgb_tv_put(msg, RSL_IE_SYSINFO_TYPE, type); + if (data) + msgb_tl16v_put(msg, RSL_IE_L3_INFO, len, data); + + msg->dst = lchan->ts->trx->rsl_link; + + return abis_rsl_sendmsg(msg); +} + +int rsl_chan_bs_power_ctrl(struct gsm_lchan *lchan, unsigned int fpc, int db) +{ + struct abis_rsl_dchan_hdr *dh; + struct msgb *msg; + uint8_t chan_nr = gsm_lchan2chan_nr(lchan); + + db = abs(db); + if (db > 30) + return -EINVAL; + + msg = rsl_msgb_alloc(); + + lchan->bs_power = db/2; + if (fpc) + lchan->bs_power |= 0x10; + + dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh)); + init_dchan_hdr(dh, RSL_MT_BS_POWER_CONTROL); + dh->chan_nr = chan_nr; + + msgb_tv_put(msg, RSL_IE_BS_POWER, lchan->bs_power); + + msg->dst = lchan->ts->trx->rsl_link; + + return abis_rsl_sendmsg(msg); +} + +int rsl_chan_ms_power_ctrl(struct gsm_lchan *lchan, unsigned int fpc, int dbm) +{ + struct abis_rsl_dchan_hdr *dh; + struct msgb *msg; + uint8_t chan_nr = gsm_lchan2chan_nr(lchan); + int ctl_lvl; + + ctl_lvl = ms_pwr_ctl_lvl(lchan->ts->trx->bts->band, dbm); + if (ctl_lvl < 0) + return ctl_lvl; + + msg = rsl_msgb_alloc(); + + lchan->ms_power = ctl_lvl; + + if (fpc) + lchan->ms_power |= 0x20; + + dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh)); + init_dchan_hdr(dh, RSL_MT_MS_POWER_CONTROL); + dh->chan_nr = chan_nr; + + msgb_tv_put(msg, RSL_IE_MS_POWER, lchan->ms_power); + + msg->dst = lchan->ts->trx->rsl_link; + + return abis_rsl_sendmsg(msg); +} + +static int channel_mode_from_lchan(struct rsl_ie_chan_mode *cm, + struct gsm_lchan *lchan) +{ + memset(cm, 0, sizeof(*cm)); + + /* FIXME: what to do with data calls ? */ + cm->dtx_dtu = 0; + if (lchan->ts->trx->bts->dtxu != GSM48_DTX_SHALL_NOT_BE_USED) + cm->dtx_dtu |= RSL_CMOD_DTXu; + if (lchan->ts->trx->bts->dtxd) + cm->dtx_dtu |= RSL_CMOD_DTXd; + + /* set TCH Speech/Data */ + cm->spd_ind = lchan->rsl_cmode; + + if (lchan->rsl_cmode == RSL_CMOD_SPD_SIGN && + lchan->tch_mode != GSM48_CMODE_SIGN) + LOGP(DRSL, LOGL_ERROR, "unsupported: rsl_mode == signalling, " + "but tch_mode != signalling\n"); + + switch (lchan->type) { + case GSM_LCHAN_SDCCH: + cm->chan_rt = RSL_CMOD_CRT_SDCCH; + break; + case GSM_LCHAN_TCH_F: + cm->chan_rt = RSL_CMOD_CRT_TCH_Bm; + break; + case GSM_LCHAN_TCH_H: + cm->chan_rt = RSL_CMOD_CRT_TCH_Lm; + break; + case GSM_LCHAN_NONE: + case GSM_LCHAN_UNKNOWN: + default: + LOGP(DRSL, LOGL_ERROR, + "unsupported activation lchan->type %u %s\n", + lchan->type, gsm_lchant_name(lchan->type)); + return -EINVAL; + } + + switch (lchan->tch_mode) { + case GSM48_CMODE_SIGN: + cm->chan_rate = 0; + break; + case GSM48_CMODE_SPEECH_V1: + cm->chan_rate = RSL_CMOD_SP_GSM1; + break; + case GSM48_CMODE_SPEECH_EFR: + cm->chan_rate = RSL_CMOD_SP_GSM2; + break; + case GSM48_CMODE_SPEECH_AMR: + cm->chan_rate = RSL_CMOD_SP_GSM3; + break; + case GSM48_CMODE_DATA_14k5: + case GSM48_CMODE_DATA_12k0: + case GSM48_CMODE_DATA_6k0: + switch (lchan->csd_mode) { + case LCHAN_CSD_M_NT: + /* non-transparent CSD with RLP */ + switch (lchan->tch_mode) { + case GSM48_CMODE_DATA_14k5: + cm->chan_rate = RSL_CMOD_SP_NT_14k5; + break; + case GSM48_CMODE_DATA_12k0: + cm->chan_rate = RSL_CMOD_SP_NT_12k0; + break; + case GSM48_CMODE_DATA_6k0: + cm->chan_rate = RSL_CMOD_SP_NT_6k0; + break; + default: + LOGP(DRSL, LOGL_ERROR, + "unsupported lchan->tch_mode %u\n", + lchan->tch_mode); + return -EINVAL; + } + break; + /* transparent data services below */ + case LCHAN_CSD_M_T_1200_75: + cm->chan_rate = RSL_CMOD_CSD_T_1200_75; + break; + case LCHAN_CSD_M_T_600: + cm->chan_rate = RSL_CMOD_CSD_T_600; + break; + case LCHAN_CSD_M_T_1200: + cm->chan_rate = RSL_CMOD_CSD_T_1200; + break; + case LCHAN_CSD_M_T_2400: + cm->chan_rate = RSL_CMOD_CSD_T_2400; + break; + case LCHAN_CSD_M_T_9600: + cm->chan_rate = RSL_CMOD_CSD_T_9600; + break; + case LCHAN_CSD_M_T_14400: + cm->chan_rate = RSL_CMOD_CSD_T_14400; + break; + case LCHAN_CSD_M_T_29000: + cm->chan_rate = RSL_CMOD_CSD_T_29000; + break; + case LCHAN_CSD_M_T_32000: + cm->chan_rate = RSL_CMOD_CSD_T_32000; + break; + default: + LOGP(DRSL, LOGL_ERROR, + "unsupported lchan->csd_mode %u\n", + lchan->csd_mode); + return -EINVAL; + } + break; + default: + LOGP(DRSL, LOGL_ERROR, + "unsupported lchan->tch_mode %u\n", + lchan->tch_mode); + return -EINVAL; + } + + return 0; +} + +static void mr_config_for_bts(struct gsm_lchan *lchan, struct msgb *msg) +{ + if (lchan->tch_mode == GSM48_CMODE_SPEECH_AMR) + msgb_tlv_put(msg, RSL_IE_MR_CONFIG, lchan->mr_bts_lv[0], + lchan->mr_bts_lv + 1); +} + +static enum gsm_phys_chan_config pchan_for_lchant(enum gsm_chan_t type) +{ + switch (type) { + case GSM_LCHAN_TCH_F: + return GSM_PCHAN_TCH_F; + case GSM_LCHAN_TCH_H: + return GSM_PCHAN_TCH_H; + case GSM_LCHAN_NONE: + case GSM_LCHAN_PDTCH: + /* TODO: so far lchan->type is NONE in PDCH mode. PDTCH is only + * used in osmo-bts. Maybe set PDTCH and drop the NONE case + * here. */ + return GSM_PCHAN_PDCH; + default: + return GSM_PCHAN_UNKNOWN; + } +} + +/*! Tx simplified channel activation message for non-standard PDCH type. */ +static int rsl_chan_activate_lchan_as_pdch(struct gsm_lchan *lchan) +{ + struct msgb *msg; + struct abis_rsl_dchan_hdr *dh; + + /* This might be called after release of the second lchan of a TCH/H, + * but PDCH activation must always happen on the first lchan. Make sure + * the calling code passes the correct lchan. */ + OSMO_ASSERT(lchan == lchan->ts->lchan); + + rsl_lchan_set_state(lchan, LCHAN_S_ACT_REQ); + + msg = rsl_msgb_alloc(); + dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh)); + init_dchan_hdr(dh, RSL_MT_CHAN_ACTIV); + dh->chan_nr = gsm_lchan_as_pchan2chan_nr(lchan, GSM_PCHAN_PDCH); + + msgb_tv_put(msg, RSL_IE_ACT_TYPE, RSL_ACT_OSMO_PDCH); + + if (lchan->ts->trx->bts->type == GSM_BTS_TYPE_RBS2000 && + lchan->ts->trx->bts->rbs2000.use_superchannel) { + const uint8_t eric_pgsl_tmr[] = { 30, 1 }; + msgb_tv_fixed_put(msg, RSL_IE_ERIC_PGSL_TIMERS, + sizeof(eric_pgsl_tmr), eric_pgsl_tmr); + } + + msg->dst = lchan->ts->trx->rsl_link; + + return abis_rsl_sendmsg(msg); +} + +/* Chapter 8.4.1 */ +int rsl_chan_activate_lchan(struct gsm_lchan *lchan, uint8_t act_type, + uint8_t ho_ref) +{ + struct abis_rsl_dchan_hdr *dh; + struct msgb *msg; + int rc; + uint8_t *len; + uint8_t ta; + + struct rsl_ie_chan_mode cm; + struct gsm48_chan_desc cd; + + /* If a TCH_F/PDCH TS is in PDCH mode, deactivate PDCH first. */ + if (lchan->ts->pchan == GSM_PCHAN_TCH_F_PDCH + && (lchan->ts->flags & TS_F_PDCH_ACTIVE)) { + /* store activation type and handover reference */ + lchan->dyn.act_type = act_type; + lchan->dyn.ho_ref = ho_ref; + return rsl_ipacc_pdch_activate(lchan->ts, 0); + } + + /* + * If necessary, release PDCH on dynamic TS. Note that sending a + * release here is only necessary when in PDCH mode; for TCH types, an + * RSL RF Chan Release is initiated by the BTS when a voice call ends, + * so when we reach this, it will already be released. If a dyn TS is + * in PDCH mode, it is still active and we need to initiate a release + * from the BSC side here. + * + * If pchan_is != pchan_want, the PDCH has already been taken down and + * the switchover now needs to enable the TCH lchan. + * + * To switch a dyn TS between TCH/H and TCH/F, it is sufficient to send + * a chan activ with the new lchan type, because it will already be + * released. + */ + if (lchan->ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH + && lchan->ts->dyn.pchan_is == lchan->ts->dyn.pchan_want) { + enum gsm_phys_chan_config pchan_want; + pchan_want = pchan_for_lchant(lchan->type); + if (lchan->ts->dyn.pchan_is != pchan_want) { + /* + * Make sure to record on lchan[0] so that we'll find + * it after the PDCH release. + */ + struct gsm_lchan *lchan0 = lchan->ts->lchan; + lchan0->dyn.act_type = act_type, + lchan0->dyn.ho_ref = ho_ref; + lchan0->dyn.rqd_ref = lchan->rqd_ref; + lchan0->dyn.rqd_ta = lchan->rqd_ta; + lchan->rqd_ref = NULL; + lchan->rqd_ta = 0; + DEBUGP(DRSL, "%s saved rqd_ref=%p ta=%u\n", + gsm_lchan_name(lchan0), lchan0->rqd_ref, + lchan0->rqd_ta); + return dyn_ts_switchover_start(lchan->ts, pchan_want); + } + } + + DEBUGP(DRSL, "%s Tx RSL Channel Activate with act_type=%s\n", + gsm_ts_and_pchan_name(lchan->ts), + rsl_act_type_name(act_type)); + + if (act_type == RSL_ACT_OSMO_PDCH) { + if (lchan->ts->pchan != GSM_PCHAN_TCH_F_TCH_H_PDCH) { + LOGP(DRSL, LOGL_ERROR, + "%s PDCH channel activation only allowed on %s\n", + gsm_ts_and_pchan_name(lchan->ts), + gsm_pchan_name(GSM_PCHAN_TCH_F_TCH_H_PDCH)); + return -EINVAL; + } + return rsl_chan_activate_lchan_as_pdch(lchan); + } + + if (lchan->ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH + && lchan->ts->dyn.pchan_want == GSM_PCHAN_PDCH) { + LOGP(DRSL, LOGL_ERROR, + "%s Expected PDCH activation kind\n", + gsm_ts_and_pchan_name(lchan->ts)); + return -EINVAL; + } + + rc = channel_mode_from_lchan(&cm, lchan); + if (rc < 0) { + LOGP(DRSL, LOGL_ERROR, + "%s Cannot find channel mode from lchan type\n", + gsm_ts_and_pchan_name(lchan->ts)); + return rc; + } + + rsl_lchan_set_state(lchan, LCHAN_S_ACT_REQ); + + ta = lchan->rqd_ta; + + /* BS11 requires TA shifted by 2 bits */ + if (lchan->ts->trx->bts->type == GSM_BTS_TYPE_BS11) + ta <<= 2; + + memset(&cd, 0, sizeof(cd)); + gsm48_lchan2chan_desc(&cd, lchan); + + msg = rsl_msgb_alloc(); + dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh)); + init_dchan_hdr(dh, RSL_MT_CHAN_ACTIV); + + if (lchan->ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH) + dh->chan_nr = gsm_lchan_as_pchan2chan_nr( + lchan, lchan->ts->dyn.pchan_want); + else + dh->chan_nr = gsm_lchan2chan_nr(lchan); + + msgb_tv_put(msg, RSL_IE_ACT_TYPE, act_type); + msgb_tlv_put(msg, RSL_IE_CHAN_MODE, sizeof(cm), + (uint8_t *) &cm); + + /* + * The Channel Identification is needed for Phase1 phones + * and it contains the GSM48 Channel Description and the + * Mobile Allocation. The GSM 08.58 asks for the Mobile + * Allocation to have a length of zero. We are using the + * msgb_l3len to calculate the length of both messages. + */ + msgb_v_put(msg, RSL_IE_CHAN_IDENT); + len = msgb_put(msg, 1); + msgb_tv_fixed_put(msg, GSM48_IE_CHANDESC_2, sizeof(cd), (const uint8_t *) &cd); + + if (lchan->ts->hopping.enabled) + msgb_tlv_put(msg, GSM48_IE_MA_AFTER, lchan->ts->hopping.ma_len, + lchan->ts->hopping.ma_data); + else + msgb_tlv_put(msg, GSM48_IE_MA_AFTER, 0, NULL); + + /* update the calculated size */ + msg->l3h = len + 1; + *len = msgb_l3len(msg); + + if (lchan->encr.alg_id > RSL_ENC_ALG_A5(0)) { + uint8_t encr_info[MAX_A5_KEY_LEN+2]; + rc = build_encr_info(encr_info, lchan); + if (rc > 0) + msgb_tlv_put(msg, RSL_IE_ENCR_INFO, rc, encr_info); + } + + switch (act_type) { + case RSL_ACT_INTER_ASYNC: + case RSL_ACT_INTER_SYNC: + msgb_tv_put(msg, RSL_IE_HANDO_REF, ho_ref); + break; + default: + break; + } + + msgb_tv_put(msg, RSL_IE_BS_POWER, lchan->bs_power); + msgb_tv_put(msg, RSL_IE_MS_POWER, lchan->ms_power); + msgb_tv_put(msg, RSL_IE_TIMING_ADVANCE, ta); + mr_config_for_bts(lchan, msg); + + msg->dst = lchan->ts->trx->rsl_link; + + rate_ctr_inc(&lchan->ts->trx->bts->bts_ctrs->ctr[BTS_CTR_CHAN_ACT_TOTAL]); + + rc = abis_rsl_sendmsg(msg); + if (!rc) + rsl_lchan_set_state(lchan, LCHAN_S_ACT_REQ); + return rc; +} + +/* Chapter 8.4.9: Modify channel mode on BTS side */ +int rsl_chan_mode_modify_req(struct gsm_lchan *lchan) +{ + struct abis_rsl_dchan_hdr *dh; + struct msgb *msg; + int rc; + + uint8_t chan_nr = gsm_lchan2chan_nr(lchan); + struct rsl_ie_chan_mode cm; + + rc = channel_mode_from_lchan(&cm, lchan); + if (rc < 0) + return rc; + + msg = rsl_msgb_alloc(); + dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh)); + init_dchan_hdr(dh, RSL_MT_MODE_MODIFY_REQ); + dh->chan_nr = chan_nr; + + msgb_tlv_put(msg, RSL_IE_CHAN_MODE, sizeof(cm), + (uint8_t *) &cm); + + if (lchan->encr.alg_id > RSL_ENC_ALG_A5(0)) { + uint8_t encr_info[MAX_A5_KEY_LEN+2]; + rc = build_encr_info(encr_info, lchan); + if (rc > 0) + msgb_tlv_put(msg, RSL_IE_ENCR_INFO, rc, encr_info); + } + + mr_config_for_bts(lchan, msg); + + msg->dst = lchan->ts->trx->rsl_link; + + return abis_rsl_sendmsg(msg); +} + +/* Chapter 8.4.6: Send the encryption command with given L3 info */ +int rsl_encryption_cmd(struct msgb *msg) +{ + struct abis_rsl_dchan_hdr *dh; + struct gsm_lchan *lchan = msg->lchan; + uint8_t chan_nr = gsm_lchan2chan_nr(lchan); + uint8_t encr_info[MAX_A5_KEY_LEN+2]; + uint8_t l3_len = msg->len; + int rc; + + /* First push the L3 IE tag and length */ + msgb_tv16_push(msg, RSL_IE_L3_INFO, l3_len); + + /* then the link identifier (SAPI0, main sign link) */ + msgb_tv_push(msg, RSL_IE_LINK_IDENT, 0); + + /* then encryption information */ + rc = build_encr_info(encr_info, lchan); + if (rc <= 0) + return rc; + msgb_tlv_push(msg, RSL_IE_ENCR_INFO, rc, encr_info); + + /* and finally the DCHAN header */ + dh = (struct abis_rsl_dchan_hdr *) msgb_push(msg, sizeof(*dh)); + init_dchan_hdr(dh, RSL_MT_ENCR_CMD); + dh->chan_nr = chan_nr; + + msg->dst = lchan->ts->trx->rsl_link; + + return abis_rsl_sendmsg(msg); +} + +/* Chapter 8.4.5 / 4.6: Deactivate the SACCH after 04.08 RR CHAN RELEASE */ +int rsl_deact_sacch(struct gsm_lchan *lchan) +{ + struct abis_rsl_dchan_hdr *dh; + struct msgb *msg = rsl_msgb_alloc(); + + dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh)); + init_dchan_hdr(dh, RSL_MT_DEACTIVATE_SACCH); + dh->chan_nr = gsm_lchan2chan_nr(lchan); + + msg->lchan = lchan; + msg->dst = lchan->ts->trx->rsl_link; + + DEBUGP(DRSL, "%s DEACTivate SACCH CMD\n", gsm_lchan_name(lchan)); + + return abis_rsl_sendmsg(msg); +} + +static bool dyn_ts_should_switch_to_pdch(struct gsm_bts_trx_ts *ts) +{ + int ss; + + if (ts->pchan != GSM_PCHAN_TCH_F_TCH_H_PDCH) + return false; + + if (ts->trx->bts->gprs.mode == BTS_GPRS_NONE) + return false; + + /* Already in PDCH mode? */ + if (ts->dyn.pchan_is == GSM_PCHAN_PDCH) + return false; + + /* See if all lchans are released. */ + for (ss = 0; ss < ts_subslots(ts); ss++) { + struct gsm_lchan *lc = &ts->lchan[ss]; + if (lc->state != LCHAN_S_NONE) { + DEBUGP(DRSL, "%s lchan %u still in use" + " (type=%s,state=%s)\n", + gsm_ts_and_pchan_name(ts), lc->nr, + gsm_lchant_name(lc->type), + gsm_lchans_name(lc->state)); + /* An lchan is still used. */ + return false; + } + } + + /* All channels are released, go to PDCH mode. */ + DEBUGP(DRSL, "%s back to PDCH\n", + gsm_ts_and_pchan_name(ts)); + return true; +} + +static void error_timeout_cb(void *data) +{ + struct gsm_lchan *lchan = data; + if (lchan->state != LCHAN_S_REL_ERR) { + LOGP(DRSL, LOGL_ERROR, "%s error timeout but not in error state: %d\n", + gsm_lchan_name(lchan), lchan->state); + return; + } + + /* go back to the none state */ + LOGP(DRSL, LOGL_INFO, "%s is back in operation.\n", gsm_lchan_name(lchan)); + rsl_lchan_set_state(lchan, LCHAN_S_NONE); + + /* Put PDCH channel back into PDCH mode, if GPRS is enabled */ + if (lchan->ts->pchan == GSM_PCHAN_TCH_F_PDCH + && lchan->ts->trx->bts->gprs.mode != BTS_GPRS_NONE) + rsl_ipacc_pdch_activate(lchan->ts, 1); + + if (dyn_ts_should_switch_to_pdch(lchan->ts)) + dyn_ts_switchover_start(lchan->ts, GSM_PCHAN_PDCH); +} + +static int rsl_rx_rf_chan_rel_ack(struct gsm_lchan *lchan); + +/* Chapter 8.4.14 / 4.7: Tell BTS to release the radio channel */ +static int rsl_rf_chan_release(struct gsm_lchan *lchan, int error, + enum sacch_deact deact_sacch) +{ + struct abis_rsl_dchan_hdr *dh; + struct msgb *msg; + int rc; + + /* Stop timers that should lead to a channel release */ + osmo_timer_del(&lchan->T3109); + + if (lchan->state == LCHAN_S_REL_ERR) { + LOGP(DRSL, LOGL_NOTICE, "%s is in error state, not sending release.\n", + gsm_lchan_name(lchan)); + return -1; + } + + msg = rsl_msgb_alloc(); + dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh)); + init_dchan_hdr(dh, RSL_MT_RF_CHAN_REL); + dh->chan_nr = gsm_lchan2chan_nr(lchan); + + msg->lchan = lchan; + msg->dst = lchan->ts->trx->rsl_link; + + if (error) + DEBUGP(DRSL, "%s RF Channel Release due to error: %d\n", + gsm_lchan_name(lchan), error); + else + DEBUGP(DRSL, "%s RF Channel Release\n", gsm_lchan_name(lchan)); + + if (error) { + /* + * FIXME: GSM 04.08 gives us two options for the abnormal + * chanel release. This can be either like in the non-existent + * sub-lcuase 3.5.1 or for the main signalling link deactivate + * the SACCH, start timer T3109 and consider the channel as + * released. + * + * This code is doing the later for all raido links and not + * only the main link. Right now all SAPIs are released on the + * local end, the SACCH will be de-activated and right now the + * T3111 will be started. First T3109 should be started and then + * the T3111. + * + * TODO: Move this out of the function. + */ + + /* + * sacch de-activate and "local end release" + */ + if (deact_sacch == SACCH_DEACTIVATE) + rsl_deact_sacch(lchan); + rsl_release_sapis_from(lchan, 0, RSL_REL_LOCAL_END); + + /* + * TODO: start T3109 now. + */ + rsl_lchan_set_state(lchan, LCHAN_S_REL_ERR); + } + + /* Start another timer or assume the BTS sends a ACK/NACK? */ + osmo_timer_setup(&lchan->act_timer, lchan_deact_tmr_cb, lchan); + osmo_timer_schedule(&lchan->act_timer, 4, 0); + + rc = abis_rsl_sendmsg(msg); + + /* BTS will respond by RF CHAN REL ACK */ + return rc; +} + +/* + * Special handling for channel releases in the error case. + */ +static int rsl_rf_chan_release_err(struct gsm_lchan *lchan) +{ + enum sacch_deact sacch_deact; + if (lchan->state != LCHAN_S_ACTIVE) + return 0; + switch (ts_pchan(lchan->ts)) { + case GSM_PCHAN_TCH_F: + case GSM_PCHAN_TCH_H: + case GSM_PCHAN_CCCH_SDCCH4: + case GSM_PCHAN_CCCH_SDCCH4_CBCH: + case GSM_PCHAN_SDCCH8_SACCH8C: + case GSM_PCHAN_SDCCH8_SACCH8C_CBCH: + sacch_deact = SACCH_DEACTIVATE; + break; + default: + sacch_deact = SACCH_NONE; + break; + } + return rsl_rf_chan_release(lchan, 1, sacch_deact); +} + +static int rsl_rx_rf_chan_rel_ack(struct gsm_lchan *lchan) +{ + struct gsm_bts_trx_ts *ts = lchan->ts; + + DEBUGP(DRSL, "%s RF CHANNEL RELEASE ACK\n", gsm_lchan_name(lchan)); + + /* Stop all pending timers */ + osmo_timer_del(&lchan->act_timer); + osmo_timer_del(&lchan->T3111); + + /* + * The BTS didn't respond within the timeout to our channel + * release request and we have marked the channel as broken. + * Now we do receive an ACK and let's be conservative. If it + * is a sysmoBTS we know that only one RF Channel Release ACK + * will be sent. So let's "repair" the channel. + */ + if (lchan->state == LCHAN_S_BROKEN) { + int do_free = is_sysmobts_v2(ts->trx->bts); + LOGP(DRSL, LOGL_NOTICE, + "%s CHAN REL ACK for broken channel. %s.\n", + gsm_lchan_name(lchan), + do_free ? "Releasing it" : "Keeping it broken"); + if (do_free) + do_lchan_free(lchan); + if (dyn_ts_should_switch_to_pdch(lchan->ts)) + dyn_ts_switchover_start(lchan->ts, GSM_PCHAN_PDCH); + return 0; + } + + if (lchan->state != LCHAN_S_REL_REQ && lchan->state != LCHAN_S_REL_ERR) + LOGP(DRSL, LOGL_NOTICE, "%s CHAN REL ACK but state %s\n", + gsm_lchan_name(lchan), + gsm_lchans_name(lchan->state)); + + do_lchan_free(lchan); + + /* + * Check Osmocom RSL CHAN ACT style dynamic TCH/F_TCH/H_PDCH TS for pending + * transitions in these cases: + * + * a) after PDCH was released due to switchover request, activate TCH. + * BSC initiated this switchover, so dyn.pchan_is != pchan_want and + * lchan->type has been set to the desired GSM_LCHAN_*. + * + * b) Voice call ended and a TCH is released. If the TS is now unused, + * switch to PDCH. Here still dyn.pchan_is == dyn.pchan_want because + * we're only just notified and may decide to switch to PDCH now. + */ + if (ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH) { + DEBUGP(DRSL, "%s Rx RSL Channel Release ack for lchan %u\n", + gsm_ts_and_pchan_name(ts), lchan->nr); + + /* (a) */ + if (ts->dyn.pchan_is != ts->dyn.pchan_want) + return dyn_ts_switchover_continue(ts); + + /* (b) */ + if (dyn_ts_should_switch_to_pdch(ts)) + return dyn_ts_switchover_start(ts, GSM_PCHAN_PDCH); + } + + /* + * Put a dynamic TCH/F_PDCH channel back to PDCH mode iff it was + * released successfully. If in error, the PDCH ACT will follow after + * T3111 in error_timeout_cb(). + * + * Any state other than LCHAN_S_REL_ERR became LCHAN_S_NONE after above + * do_lchan_free(). Assert this, because that's what ensures a PDCH ACT + * on a TCH/F_PDCH TS in all cases. + * + * If GPRS is disabled, always skip the PDCH ACT. + */ + OSMO_ASSERT(lchan->state == LCHAN_S_NONE + || lchan->state == LCHAN_S_REL_ERR); + if (ts->trx->bts->gprs.mode == BTS_GPRS_NONE) + return 0; + if (ts->pchan == GSM_PCHAN_TCH_F_PDCH + && lchan->state == LCHAN_S_NONE) + return rsl_ipacc_pdch_activate(ts, 1); + return 0; +} + +int rsl_paging_cmd(struct gsm_bts *bts, uint8_t paging_group, uint8_t len, + uint8_t *ms_ident, uint8_t chan_needed, bool is_gprs) +{ + struct abis_rsl_dchan_hdr *dh; + struct msgb *msg = rsl_msgb_alloc(); + + dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh)); + init_dchan_hdr(dh, RSL_MT_PAGING_CMD); + dh->chan_nr = RSL_CHAN_PCH_AGCH; + + msgb_tv_put(msg, RSL_IE_PAGING_GROUP, paging_group); + msgb_tlv_put(msg, RSL_IE_MS_IDENTITY, len-2, ms_ident+2); + msgb_tv_put(msg, RSL_IE_CHAN_NEEDED, chan_needed); + + /* Ericsson wants to have this IE in case a paging message + * relates to packet paging */ + if (bts->type == GSM_BTS_TYPE_RBS2000 && is_gprs) + msgb_tv_put(msg, RSL_IE_ERIC_PACKET_PAG_IND, 0); + + msg->dst = bts->c0->rsl_link; + + return abis_rsl_sendmsg(msg); +} + +int imsi_str2bcd(uint8_t *bcd_out, const char *str_in) +{ + int i, len = strlen(str_in); + + for (i = 0; i < len; i++) { + int num = str_in[i] - 0x30; + if (num < 0 || num > 9) + return -1; + if (i % 2 == 0) + bcd_out[i/2] = num; + else + bcd_out[i/2] |= (num << 4); + } + + return 0; +} + +/* Chapter 8.5.6 */ +struct msgb *rsl_imm_assign_cmd_common(struct gsm_bts *bts, uint8_t len, uint8_t *val) +{ + struct msgb *msg = rsl_msgb_alloc(); + struct abis_rsl_dchan_hdr *dh; + uint8_t buf[GSM_MACBLOCK_LEN]; + + dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh)); + init_dchan_hdr(dh, RSL_MT_IMMEDIATE_ASSIGN_CMD); + dh->chan_nr = RSL_CHAN_PCH_AGCH; + + switch (bts->type) { + case GSM_BTS_TYPE_BS11: + msgb_tlv_put(msg, RSL_IE_IMM_ASS_INFO, len, val); + break; + default: + /* If phase 2, construct a FULL_IMM_ASS_INFO */ + pad_macblock(buf, val, len); + msgb_tlv_put(msg, RSL_IE_FULL_IMM_ASS_INFO, GSM_MACBLOCK_LEN, + buf); + break; + } + + msg->dst = bts->c0->rsl_link; + return msg; +} + +/* Chapter 8.5.6 */ +int rsl_imm_assign_cmd(struct gsm_bts *bts, uint8_t len, uint8_t *val) +{ + struct msgb *msg = rsl_imm_assign_cmd_common(bts, len, val); + if (!msg) + return 1; + return abis_rsl_sendmsg(msg); +} + +/* Chapter 8.5.6 */ +int rsl_ericsson_imm_assign_cmd(struct gsm_bts *bts, uint32_t tlli, uint8_t len, uint8_t *val) +{ + struct msgb *msg = rsl_imm_assign_cmd_common(bts, len, val); + if (!msg) + return 1; + + /* ericsson can handle a reference at the end of the message which is used in + * the confirm message. The confirm message is only sent if the trailer is present */ + msgb_put_u8(msg, RSL_IE_ERIC_MOBILE_ID); + msgb_put_u32(msg, tlli); + + return abis_rsl_sendmsg(msg); +} + +/* Send Siemens specific MS RF Power Capability Indication */ +int rsl_siemens_mrpci(struct gsm_lchan *lchan, struct rsl_mrpci *mrpci) +{ + struct msgb *msg = rsl_msgb_alloc(); + struct abis_rsl_dchan_hdr *dh; + + dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh)); + init_dchan_hdr(dh, RSL_MT_SIEMENS_MRPCI); + dh->c.msg_discr = ABIS_RSL_MDISC_DED_CHAN; + dh->chan_nr = gsm_lchan2chan_nr(lchan); + msgb_tv_put(msg, RSL_IE_SIEMENS_MRPCI, *(uint8_t *)mrpci); + + DEBUGP(DRSL, "%s TX Siemens MRPCI 0x%02x\n", + gsm_lchan_name(lchan), *(uint8_t *)mrpci); + + msg->dst = lchan->ts->trx->rsl_link; + + return abis_rsl_sendmsg(msg); +} + + +/* Send "DATA REQUEST" message with given L3 Info payload */ +/* Chapter 8.3.1 */ +int rsl_data_request(struct msgb *msg, uint8_t link_id) +{ + if (msg->lchan == NULL) { + LOGP(DRSL, LOGL_ERROR, "cannot send DATA REQUEST to unknown lchan\n"); + return -EINVAL; + } + + rsl_rll_push_l3(msg, RSL_MT_DATA_REQ, gsm_lchan2chan_nr(msg->lchan), + link_id, 1); + + msg->dst = msg->lchan->ts->trx->rsl_link; + + return abis_rsl_sendmsg(msg); +} + +/* Send "ESTABLISH REQUEST" message with given L3 Info payload */ +/* Chapter 8.3.1 */ +int rsl_establish_request(struct gsm_lchan *lchan, uint8_t link_id) +{ + struct msgb *msg; + + msg = rsl_rll_simple(RSL_MT_EST_REQ, gsm_lchan2chan_nr(lchan), + link_id, 0); + msg->dst = lchan->ts->trx->rsl_link; + + DEBUGP(DRLL, "%s RSL RLL ESTABLISH REQ (link_id=0x%02x)\n", + gsm_lchan_name(lchan), link_id); + + return abis_rsl_sendmsg(msg); +} + +static void rsl_handle_release(struct gsm_lchan *lchan); + +/* Special work handler to handle missing RSL_MT_REL_CONF message from + * Nokia InSite BTS */ +static void lchan_rel_work_cb(void *data) +{ + struct gsm_lchan *lchan = data; + int sapi; + + for (sapi = 0; sapi < ARRAY_SIZE(lchan->sapis); ++sapi) { + if (lchan->sapis[sapi] == LCHAN_SAPI_REL) + lchan->sapis[sapi] = LCHAN_SAPI_UNUSED; + } + rsl_handle_release(lchan); +} + +/* Chapter 8.3.7 Request the release of multiframe mode of RLL connection. + This is what higher layers should call. The BTS then responds with + RELEASE CONFIRM, which we in turn use to trigger RSL CHANNEL RELEASE, + which in turn is acknowledged by RSL CHANNEL RELEASE ACK, which calls + lchan_free() */ +int rsl_release_request(struct gsm_lchan *lchan, uint8_t link_id, + enum rsl_rel_mode release_mode) +{ + + struct msgb *msg; + + msg = rsl_rll_simple(RSL_MT_REL_REQ, gsm_lchan2chan_nr(lchan), + link_id, 0); + /* 0 is normal release, 1 is local end */ + msgb_tv_put(msg, RSL_IE_RELEASE_MODE, release_mode); + + /* FIXME: start some timer in case we don't receive a REL ACK ? */ + + msg->dst = lchan->ts->trx->rsl_link; + + DEBUGP(DRLL, "%s RSL RLL RELEASE REQ (link_id=0x%02x, reason=%u)\n", + gsm_lchan_name(lchan), link_id, release_mode); + + abis_rsl_sendmsg(msg); + + /* Do not wait for Nokia BTS to send the confirm. */ + if (is_nokia_bts(lchan->ts->trx->bts) + && lchan->ts->trx->bts->nokia.no_loc_rel_cnf + && release_mode == RSL_REL_LOCAL_END) { + DEBUGP(DRLL, "Scheduling release, becasuse Nokia InSite BTS does not send a RELease CONFirm.\n"); + lchan->sapis[link_id & 0x7] = LCHAN_SAPI_REL; + osmo_timer_setup(&lchan->rel_work, lchan_rel_work_cb, lchan); + osmo_timer_schedule(&lchan->rel_work, 0, 0); + } + + return 0; +} + +int rsl_lchan_mark_broken(struct gsm_lchan *lchan, const char *reason) +{ + LOGP(DRSL, LOGL_ERROR, "%s %s lchan broken: %s\n", + gsm_lchan_name(lchan), gsm_lchant_name(lchan->type), reason); + rsl_lchan_set_state(lchan, LCHAN_S_BROKEN); + lchan->broken_reason = reason; + return 0; +} + +int rsl_lchan_set_state_with_log(struct gsm_lchan *lchan, enum gsm_lchan_state state, const char *file, unsigned line) +{ + if (lchan->state != state) + LOGPSRC(DRSL, LOGL_DEBUG, file, line, "%s state %s -> %s\n", + gsm_lchan_name(lchan), gsm_lchans_name(lchan->state), gsm_lchans_name(state)); + + lchan->state = state; + return 0; +} + +/* Chapter 8.4.2: Channel Activate Acknowledge */ +static int rsl_rx_chan_act_ack(struct msgb *msg) +{ + struct abis_rsl_dchan_hdr *rslh = msgb_l2(msg); + struct gsm_lchan *lchan = msg->lchan; + struct gsm_bts_trx_ts *ts = lchan->ts; + + /* BTS has confirmed channel activation, we now need + * to assign the activated channel to the MS */ + if (rslh->ie_chan != RSL_IE_CHAN_NR) + return -EINVAL; + + osmo_timer_del(&lchan->act_timer); + + if (lchan->state == LCHAN_S_BROKEN) { + int do_release = is_sysmobts_v2(ts->trx->bts); + LOGP(DRSL, LOGL_NOTICE, "%s CHAN ACT ACK for broken channel. %s\n", + gsm_lchan_name(lchan), + do_release ? "Releasing it" : "Keeping it broken"); + if (do_release) { + talloc_free(lchan->rqd_ref); + lchan->rqd_ref = NULL; + lchan->rqd_ta = 0; + rsl_lchan_set_state(msg->lchan, LCHAN_S_ACTIVE); + if (ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH) { + /* + * lchan_act_tmr_cb() already called + * lchan_free() and cleared the lchan->type, so + * calling dyn_ts_switchover_complete() here + * would not have the desired effect of + * mimicking an activated lchan that we can + * release. Instead hack the dyn ts state to + * make sure that rsl_rx_rf_chan_rel_ack() will + * switch back to PDCH, i.e. have pchan_is == + * pchan_want, both != GSM_PCHAN_PDCH: + */ + ts->dyn.pchan_is = GSM_PCHAN_NONE; + ts->dyn.pchan_want = GSM_PCHAN_NONE; + } + rsl_rf_chan_release(msg->lchan, 0, SACCH_NONE); + } + return 0; + } + + if (lchan->state != LCHAN_S_ACT_REQ) + LOGP(DRSL, LOGL_NOTICE, "%s CHAN ACT ACK, but state %s\n", + gsm_lchan_name(lchan), + gsm_lchans_name(lchan->state)); + rsl_lchan_set_state(lchan, LCHAN_S_ACTIVE); + + if (ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH) + dyn_ts_switchover_complete(lchan); + + if (lchan->rqd_ref) { + rsl_send_imm_assignment(lchan); + talloc_free(lchan->rqd_ref); + lchan->rqd_ref = NULL; + lchan->rqd_ta = 0; + } + + send_lchan_signal(S_LCHAN_ACTIVATE_ACK, lchan, NULL); + + /* Update bts attributes inside the PCU */ + if (ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH || + ts->pchan == GSM_PCHAN_TCH_F_PDCH || + ts->pchan == GSM_PCHAN_PDCH) + pcu_info_update(ts->trx->bts); + + return 0; +} + +/* Chapter 8.4.3: Channel Activate NACK */ +static int rsl_rx_chan_act_nack(struct msgb *msg) +{ + struct abis_rsl_dchan_hdr *dh = msgb_l2(msg); + struct tlv_parsed tp; + + osmo_timer_del(&msg->lchan->act_timer); + + rate_ctr_inc(&msg->lchan->ts->trx->bts->bts_ctrs->ctr[BTS_CTR_CHAN_ACT_NACK]); + + if (msg->lchan->state == LCHAN_S_BROKEN) { + LOGP(DRSL, LOGL_ERROR, + "%s CHANNEL ACTIVATE NACK for broken channel.\n", + gsm_lchan_name(msg->lchan)); + return -1; + } + + LOGP(DRSL, LOGL_ERROR, "%s CHANNEL ACTIVATE NACK ", + gsm_lchan_name(msg->lchan)); + + /* BTS has rejected channel activation ?!? */ + if (dh->ie_chan != RSL_IE_CHAN_NR) + return -EINVAL; + + rsl_tlv_parse(&tp, dh->data, msgb_l2len(msg)-sizeof(*dh)); + if (TLVP_PRESENT(&tp, RSL_IE_CAUSE)) { + const uint8_t *cause = TLVP_VAL(&tp, RSL_IE_CAUSE); + print_rsl_cause(LOGL_ERROR, cause, + TLVP_LEN(&tp, RSL_IE_CAUSE)); + msg->lchan->error_cause = *cause; + if (*cause != RSL_ERR_RCH_ALR_ACTV_ALLOC) { + rsl_lchan_mark_broken(msg->lchan, "NACK on activation"); + } else + rsl_rf_chan_release(msg->lchan, 1, SACCH_DEACTIVATE); + + } else { + rsl_lchan_mark_broken(msg->lchan, "NACK on activation no IE"); + } + + LOGPC(DRSL, LOGL_ERROR, "\n"); + + send_lchan_signal(S_LCHAN_ACTIVATE_NACK, msg->lchan, NULL); + return 0; +} + +/* Chapter 8.4.4: Connection Failure Indication */ +static int rsl_rx_conn_fail(struct msgb *msg) +{ + struct abis_rsl_dchan_hdr *dh = msgb_l2(msg); + struct gsm_lchan *lchan = msg->lchan; + struct tlv_parsed tp; + uint8_t cause = 0; + + LOGP(DRSL, LOGL_NOTICE, "%s CONNECTION FAIL in state %s ", + gsm_lchan_name(msg->lchan), + gsm_lchans_name(msg->lchan->state)); + + rsl_tlv_parse(&tp, dh->data, msgb_l2len(msg)-sizeof(*dh)); + + if (TLVP_PRESENT(&tp, RSL_IE_CAUSE)) { + print_rsl_cause(LOGL_NOTICE, TLVP_VAL(&tp, RSL_IE_CAUSE), + TLVP_LEN(&tp, RSL_IE_CAUSE)); + cause = *TLVP_VAL(&tp, RSL_IE_CAUSE); + } + + LOGPC(DRSL, LOGL_NOTICE, "\n"); + rate_ctr_inc(&lchan->ts->trx->bts->bts_ctrs->ctr[BTS_CTR_CHAN_RF_FAIL]); + + osmo_fsm_inst_dispatch(lchan->conn->fi, GSCON_EV_RSL_CONN_FAIL, &cause); + + return 0; +} + +static void print_meas_rep_uni(struct gsm_meas_rep_unidir *mru, + const char *prefix) +{ + DEBUGPC(DMEAS, "RXL-FULL-%s=%3ddBm RXL-SUB-%s=%3ddBm ", + prefix, rxlev2dbm(mru->full.rx_lev), + prefix, rxlev2dbm(mru->sub.rx_lev)); + DEBUGPC(DMEAS, "RXQ-FULL-%s=%d RXQ-SUB-%s=%d ", + prefix, mru->full.rx_qual, prefix, mru->sub.rx_qual); +} + +static void print_meas_rep(struct gsm_lchan *lchan, struct gsm_meas_rep *mr) +{ + int i; + const char *name = ""; + + if (lchan && lchan->conn) { + if (lchan->conn->bsub) + name = bsc_subscr_name(lchan->conn->bsub); + else + name = lchan->name; + } + + DEBUGP(DMEAS, "[%s] MEASUREMENT RESULT NR=%d ", name, mr->nr); + + if (mr->flags & MEAS_REP_F_DL_DTX) + DEBUGPC(DMEAS, "DTXd "); + + print_meas_rep_uni(&mr->ul, "ul"); + DEBUGPC(DMEAS, "BS_POWER=%d ", mr->bs_power); + + if (mr->flags & MEAS_REP_F_MS_TO) + DEBUGPC(DMEAS, "MS_TO=%d ", mr->ms_timing_offset); + + if (mr->flags & MEAS_REP_F_MS_L1) { + DEBUGPC(DMEAS, "L1_MS_PWR=%3ddBm ", mr->ms_l1.pwr); + DEBUGPC(DMEAS, "L1_FPC=%u ", + mr->flags & MEAS_REP_F_FPC ? 1 : 0); + DEBUGPC(DMEAS, "L1_TA=%u ", mr->ms_l1.ta); + } + + if (mr->flags & MEAS_REP_F_UL_DTX) + DEBUGPC(DMEAS, "DTXu "); + if (mr->flags & MEAS_REP_F_BA1) + DEBUGPC(DMEAS, "BA1 "); + if (!(mr->flags & MEAS_REP_F_DL_VALID)) + DEBUGPC(DMEAS, "NOT VALID "); + else + print_meas_rep_uni(&mr->dl, "dl"); + + DEBUGPC(DMEAS, "NUM_NEIGH=%u\n", mr->num_cell); + if (mr->num_cell == 7) + return; + for (i = 0; i < mr->num_cell; i++) { + struct gsm_meas_rep_cell *mrc = &mr->cell[i]; + DEBUGP(DMEAS, "IDX=%u ARFCN=%u BSIC=%u => %d dBm\n", + mrc->neigh_idx, mrc->arfcn, mrc->bsic, rxlev2dbm(mrc->rxlev)); + } +} + +static struct gsm_meas_rep *lchan_next_meas_rep(struct gsm_lchan *lchan) +{ + struct gsm_meas_rep *meas_rep; + + meas_rep = &lchan->meas_rep[lchan->meas_rep_idx]; + memset(meas_rep, 0, sizeof(*meas_rep)); + meas_rep->lchan = lchan; + lchan->meas_rep_idx = (lchan->meas_rep_idx + 1) + % ARRAY_SIZE(lchan->meas_rep); + + return meas_rep; +} + +static int rsl_rx_meas_res(struct msgb *msg) +{ + struct abis_rsl_dchan_hdr *dh = msgb_l2(msg); + struct tlv_parsed tp; + struct gsm_meas_rep *mr = lchan_next_meas_rep(msg->lchan); + uint8_t len; + const uint8_t *val; + int rc; + + /* check if this channel is actually active */ + /* FIXME: maybe this check should be way more generic/centralized */ + if (msg->lchan->state != LCHAN_S_ACTIVE) { + LOGP(DRSL, LOGL_DEBUG, "%s: MEAS RES for inactive channel\n", + gsm_lchan_name(msg->lchan)); + return 0; + } + + memset(mr, 0, sizeof(*mr)); + mr->lchan = msg->lchan; + + rsl_tlv_parse(&tp, dh->data, msgb_l2len(msg)-sizeof(*dh)); + + if (!TLVP_PRESENT(&tp, RSL_IE_MEAS_RES_NR) || + !TLVP_PRESENT(&tp, RSL_IE_UPLINK_MEAS) || + !TLVP_PRESENT(&tp, RSL_IE_BS_POWER)) { + LOGP(DRSL, LOGL_ERROR, "%s Measurement Report lacks mandatory IEs\n", + gsm_lchan_name(mr->lchan)); + return -EIO; + } + + /* Mandatory Parts */ + mr->nr = *TLVP_VAL(&tp, RSL_IE_MEAS_RES_NR); + + len = TLVP_LEN(&tp, RSL_IE_UPLINK_MEAS); + val = TLVP_VAL(&tp, RSL_IE_UPLINK_MEAS); + if (len >= 3) { + if (val[0] & 0x40) + mr->flags |= MEAS_REP_F_DL_DTX; + mr->ul.full.rx_lev = val[0] & 0x3f; + mr->ul.sub.rx_lev = val[1] & 0x3f; + mr->ul.full.rx_qual = val[2]>>3 & 0x7; + mr->ul.sub.rx_qual = val[2] & 0x7; + } + + mr->bs_power = *TLVP_VAL(&tp, RSL_IE_BS_POWER); + + /* Optional Parts */ + if (TLVP_PRESENT(&tp, RSL_IE_MS_TIMING_OFFSET)) { + /* According to 3GPP TS 48.058 § MS Timing Offset = Timing Offset field - 63 */ + mr->ms_timing_offset = *TLVP_VAL(&tp, RSL_IE_MS_TIMING_OFFSET) - 63; + mr->flags |= MEAS_REP_F_MS_TO; + } + + if (TLVP_PRESENT(&tp, RSL_IE_L1_INFO)) { + struct e1inp_sign_link *sign_link = msg->dst; + + val = TLVP_VAL(&tp, RSL_IE_L1_INFO); + mr->flags |= MEAS_REP_F_MS_L1; + mr->ms_l1.pwr = ms_pwr_dbm(sign_link->trx->bts->band, val[0] >> 3); + if (val[0] & 0x04) + mr->flags |= MEAS_REP_F_FPC; + mr->ms_l1.ta = val[1]; + /* BS11 and Nokia reports TA shifted by 2 bits */ + if (msg->lchan->ts->trx->bts->type == GSM_BTS_TYPE_BS11 + || msg->lchan->ts->trx->bts->type == GSM_BTS_TYPE_NOKIA_SITE) + mr->ms_l1.ta >>= 2; + /* store TA for next assignment/handover */ + mr->lchan->rqd_ta = mr->ms_l1.ta; + } + if (TLVP_PRESENT(&tp, RSL_IE_L3_INFO)) { + msg->l3h = (uint8_t *) TLVP_VAL(&tp, RSL_IE_L3_INFO); + rc = gsm48_parse_meas_rep(mr, msg); + if (rc < 0) + return rc; + } + + mr->lchan->meas_rep_count++; + mr->lchan->meas_rep_last_seen_nr = mr->nr; + LOGP(DRSL, LOGL_DEBUG, "%s: meas_rep_count++=%d meas_rep_last_seen_nr=%u\n", + gsm_lchan_name(mr->lchan), mr->lchan->meas_rep_count, mr->lchan->meas_rep_last_seen_nr); + + print_meas_rep(msg->lchan, mr); + + send_lchan_signal(S_LCHAN_MEAS_REP, msg->lchan, mr); + + return 0; +} + +/* Chapter 8.4.7 */ +static int rsl_rx_hando_det(struct msgb *msg) +{ + struct abis_rsl_dchan_hdr *dh = msgb_l2(msg); + struct tlv_parsed tp; + + DEBUGP(DRSL, "%s HANDOVER DETECT ", gsm_lchan_name(msg->lchan)); + + rsl_tlv_parse(&tp, dh->data, msgb_l2len(msg)-sizeof(*dh)); + + if (TLVP_PRESENT(&tp, RSL_IE_ACCESS_DELAY)) + DEBUGPC(DRSL, "access delay = %u\n", + *TLVP_VAL(&tp, RSL_IE_ACCESS_DELAY)); + else + DEBUGPC(DRSL, "\n"); + + send_lchan_signal(S_LCHAN_HANDOVER_DETECT, msg->lchan, NULL); + + return 0; +} + +static bool lchan_may_change_pdch(struct gsm_lchan *lchan, bool pdch_act) +{ + struct gsm_bts_trx_ts *ts; + + OSMO_ASSERT(lchan); + + ts = lchan->ts; + OSMO_ASSERT(ts); + OSMO_ASSERT(ts->trx); + OSMO_ASSERT(ts->trx->bts); + + if (lchan->ts->pchan != GSM_PCHAN_TCH_F_PDCH) { + LOGP(DRSL, LOGL_ERROR, "%s pchan=%s Rx PDCH %s ACK" + " for channel that is no TCH/F_PDCH\n", + gsm_lchan_name(lchan), + gsm_pchan_name(ts->pchan), + pdch_act? "ACT" : "DEACT"); + return false; + } + + if (lchan->state != LCHAN_S_NONE) { + LOGP(DRSL, LOGL_ERROR, "%s pchan=%s Rx PDCH %s ACK" + " in unexpected state: %s\n", + gsm_lchan_name(lchan), + gsm_pchan_name(ts->pchan), + pdch_act? "ACT" : "DEACT", + gsm_lchans_name(lchan->state)); + return false; + } + return true; +} + +static int rsl_rx_pdch_act_ack(struct msgb *msg) +{ + if (!lchan_may_change_pdch(msg->lchan, true)) + return -EINVAL; + + msg->lchan->ts->flags |= TS_F_PDCH_ACTIVE; + msg->lchan->ts->flags &= ~TS_F_PDCH_ACT_PENDING; + + return 0; +} + +static int rsl_rx_pdch_deact_ack(struct msgb *msg) +{ + if (!lchan_may_change_pdch(msg->lchan, false)) + return -EINVAL; + + msg->lchan->ts->flags &= ~TS_F_PDCH_ACTIVE; + msg->lchan->ts->flags &= ~TS_F_PDCH_DEACT_PENDING; + + rsl_chan_activate_lchan(msg->lchan, msg->lchan->dyn.act_type, + msg->lchan->dyn.ho_ref); + + return 0; +} + +static int abis_rsl_rx_dchan(struct msgb *msg) +{ + struct abis_rsl_dchan_hdr *rslh = msgb_l2(msg); + int rc = 0; + char *ts_name; + struct e1inp_sign_link *sign_link = msg->dst; + + msg->lchan = lchan_lookup(sign_link->trx, rslh->chan_nr, + "Abis RSL rx DCHAN: "); + if (!msg->lchan) + return -1; + ts_name = gsm_lchan_name(msg->lchan); + + switch (rslh->c.msg_type) { + case RSL_MT_CHAN_ACTIV_ACK: + DEBUGP(DRSL, "%s CHANNEL ACTIVATE ACK\n", ts_name); + rc = rsl_rx_chan_act_ack(msg); + count_codecs(sign_link->trx->bts, msg->lchan); + break; + case RSL_MT_CHAN_ACTIV_NACK: + rc = rsl_rx_chan_act_nack(msg); + break; + case RSL_MT_CONN_FAIL: + rc = rsl_rx_conn_fail(msg); + break; + case RSL_MT_MEAS_RES: + rc = rsl_rx_meas_res(msg); + break; + case RSL_MT_HANDO_DET: + rc = rsl_rx_hando_det(msg); + break; + case RSL_MT_RF_CHAN_REL_ACK: + rc = rsl_rx_rf_chan_rel_ack(msg->lchan); + break; + case RSL_MT_MODE_MODIFY_ACK: + count_codecs(sign_link->trx->bts, msg->lchan); + DEBUGP(DRSL, "%s CHANNEL MODE MODIFY ACK\n", ts_name); + break; + case RSL_MT_MODE_MODIFY_NACK: + LOGP(DRSL, LOGL_ERROR, "%s CHANNEL MODE MODIFY NACK\n", ts_name); + rate_ctr_inc(&sign_link->trx->bts->bts_ctrs->ctr[BTS_CTR_MODE_MODIFY_NACK]); + break; + case RSL_MT_IPAC_PDCH_ACT_ACK: + DEBUGP(DRSL, "%s IPAC PDCH ACT ACK\n", ts_name); + rc = rsl_rx_pdch_act_ack(msg); + break; + case RSL_MT_IPAC_PDCH_ACT_NACK: + LOGP(DRSL, LOGL_ERROR, "%s IPAC PDCH ACT NACK\n", ts_name); + rate_ctr_inc(&sign_link->trx->bts->bts_ctrs->ctr[BTS_CTR_RSL_IPA_NACK]); + break; + case RSL_MT_IPAC_PDCH_DEACT_ACK: + DEBUGP(DRSL, "%s IPAC PDCH DEACT ACK\n", ts_name); + rc = rsl_rx_pdch_deact_ack(msg); + break; + case RSL_MT_IPAC_PDCH_DEACT_NACK: + LOGP(DRSL, LOGL_ERROR, "%s IPAC PDCH DEACT NACK\n", ts_name); + rate_ctr_inc(&sign_link->trx->bts->bts_ctrs->ctr[BTS_CTR_RSL_IPA_NACK]); + break; + case RSL_MT_PHY_CONTEXT_CONF: + case RSL_MT_PREPROC_MEAS_RES: + case RSL_MT_TALKER_DET: + case RSL_MT_LISTENER_DET: + case RSL_MT_REMOTE_CODEC_CONF_REP: + case RSL_MT_MR_CODEC_MOD_ACK: + case RSL_MT_MR_CODEC_MOD_NACK: + case RSL_MT_MR_CODEC_MOD_PER: + LOGP(DRSL, LOGL_NOTICE, "%s Unimplemented Abis RSL DChan " + "msg 0x%02x\n", ts_name, rslh->c.msg_type); + rate_ctr_inc(&sign_link->trx->bts->bts_ctrs->ctr[BTS_CTR_RSL_UNKNOWN]); + break; + default: + LOGP(DRSL, LOGL_NOTICE, "%s unknown Abis RSL DChan msg 0x%02x\n", + ts_name, rslh->c.msg_type); + rate_ctr_inc(&sign_link->trx->bts->bts_ctrs->ctr[BTS_CTR_RSL_UNKNOWN]); + return -EINVAL; + } + + return rc; +} + +static int rsl_rx_error_rep(struct msgb *msg) +{ + struct abis_rsl_common_hdr *rslh = msgb_l2(msg); + struct tlv_parsed tp; + struct e1inp_sign_link *sign_link = msg->dst; + + LOGP(DRSL, LOGL_ERROR, "%s ERROR REPORT ", gsm_trx_name(sign_link->trx)); + + rsl_tlv_parse(&tp, rslh->data, msgb_l2len(msg)-sizeof(*rslh)); + + if (TLVP_PRESENT(&tp, RSL_IE_CAUSE)) + print_rsl_cause(LOGL_ERROR, TLVP_VAL(&tp, RSL_IE_CAUSE), + TLVP_LEN(&tp, RSL_IE_CAUSE)); + + LOGPC(DRSL, LOGL_ERROR, "\n"); + + return 0; +} + +static int abis_rsl_rx_trx(struct msgb *msg) +{ + struct abis_rsl_common_hdr *rslh = msgb_l2(msg); + struct e1inp_sign_link *sign_link = msg->dst; + int rc = 0; + + switch (rslh->msg_type) { + case RSL_MT_ERROR_REPORT: + rc = rsl_rx_error_rep(msg); + break; + case RSL_MT_RF_RES_IND: + /* interference on idle channels of TRX */ + //DEBUGP(DRSL, "%s RF Resource Indication\n", gsm_trx_name(sign_link->trx)); + break; + case RSL_MT_OVERLOAD: + /* indicate CCCH / ACCH / processor overload */ + LOGP(DRSL, LOGL_ERROR, "%s CCCH/ACCH/CPU Overload\n", + gsm_trx_name(sign_link->trx)); + break; + case 0x42: /* Nokia specific: SI End ACK */ + LOGP(DRSL, LOGL_INFO, "Nokia SI End ACK\n"); + break; + case 0x43: /* Nokia specific: SI End NACK */ + LOGP(DRSL, LOGL_INFO, "Nokia SI End NACK\n"); + break; + default: + LOGP(DRSL, LOGL_NOTICE, "%s Unknown Abis RSL TRX message " + "type 0x%02x\n", gsm_trx_name(sign_link->trx), rslh->msg_type); + rate_ctr_inc(&sign_link->trx->bts->bts_ctrs->ctr[BTS_CTR_RSL_UNKNOWN]); + return -EINVAL; + } + return rc; +} + +/* If T3101 expires, we never received a response to IMMEDIATE ASSIGN */ +static void t3101_expired(void *data) +{ + struct gsm_lchan *lchan = data; + LOGP(DRSL, LOGL_NOTICE, + "%s T3101 expired: no response to IMMEDIATE ASSIGN\n", + gsm_lchan_name(lchan)); + rsl_rf_chan_release(lchan, 1, SACCH_DEACTIVATE); +} + +/* If T3111 expires, we will send the RF Channel Request */ +static void t3111_expired(void *data) +{ + struct gsm_lchan *lchan = data; + LOGP(DRSL, LOGL_NOTICE, + "%s T3111 expired: releasing RF Channel\n", + gsm_lchan_name(lchan)); + rsl_rf_chan_release(lchan, 0, SACCH_NONE); +} + +/* If T3109 expires the MS has not send a UA/UM do the error release */ +static void t3109_expired(void *data) +{ + struct gsm_lchan *lchan = data; + + LOGP(DRSL, LOGL_ERROR, + "%s SACCH deactivation timeout.\n", gsm_lchan_name(lchan)); + rsl_rf_chan_release(lchan, 1, SACCH_NONE); +} + +/* Format an IMM ASS REJ according to 04.08 Chapter 9.1.20 */ +static int rsl_send_imm_ass_rej(struct gsm_bts *bts, + struct gsm48_req_ref *rqd_ref, + uint8_t wait_ind) +{ + uint8_t buf[GSM_MACBLOCK_LEN]; + struct gsm48_imm_ass_rej *iar = (struct gsm48_imm_ass_rej *)buf; + + /* create IMMEDIATE ASSIGN REJECT 04.08 message */ + memset(iar, 0, sizeof(*iar)); + iar->proto_discr = GSM48_PDISC_RR; + iar->msg_type = GSM48_MT_RR_IMM_ASS_REJ; + iar->page_mode = GSM48_PM_SAME; + + /* + * Set all request references and wait indications to the same value. + * 3GPP TS 44.018 v4.5.0 release 4 (section 9.1.20.2) requires that + * we duplicate reference and wait indication to fill the message. + * The BTS will aggregate up to 4 of our ASS REJ messages if possible. + */ + memcpy(&iar->req_ref1, rqd_ref, sizeof(iar->req_ref1)); + iar->wait_ind1 = wait_ind; + memcpy(&iar->req_ref2, rqd_ref, sizeof(iar->req_ref2)); + iar->wait_ind2 = wait_ind; + memcpy(&iar->req_ref3, rqd_ref, sizeof(iar->req_ref3)); + iar->wait_ind3 = wait_ind; + memcpy(&iar->req_ref4, rqd_ref, sizeof(iar->req_ref4)); + iar->wait_ind4 = wait_ind; + + /* we need to subtract 1 byte from sizeof(*iar) since ia includes the l2_plen field */ + iar->l2_plen = GSM48_LEN2PLEN((sizeof(*iar)-1)); + + return rsl_imm_assign_cmd(bts, sizeof(*iar), (uint8_t *) iar); +} + +/* Handle packet channel rach requests */ +static int rsl_rx_pchan_rqd(struct msgb *msg, struct gsm_bts *bts) +{ + struct gsm48_req_ref *rqd_ref; + struct abis_rsl_dchan_hdr *rqd_hdr = msgb_l2(msg); + rqd_ref = (struct gsm48_req_ref *) &rqd_hdr->data[1]; + uint8_t ra = rqd_ref->ra; + uint8_t t1, t2, t3; + uint32_t fn; + uint8_t rqd_ta; + uint8_t is_11bit; + + /* Process rach request and forward contained information to PCU */ + if (ra == 0x7F) { + is_11bit = 1; + + /* FIXME: Also handle 11 bit rach requests */ + LOGP(DRSL, LOGL_ERROR, "BTS %d eleven bit access burst not supported yet!\n",bts->nr); + return -EINVAL; + } else { + is_11bit = 0; + t1 = rqd_ref->t1; + t2 = rqd_ref->t2; + t3 = rqd_ref->t3_low | (rqd_ref->t3_high << 3); + fn = (51 * ((t3-t2) % 26) + t3 + 51 * 26 * t1); + + rqd_ta = rqd_hdr->data[sizeof(struct gsm48_req_ref)+2]; + } + + return pcu_tx_rach_ind(bts, rqd_ta, ra, fn, is_11bit, + GSM_L1_BURST_TYPE_ACCESS_0); +} + +/* MS has requested a channel on the RACH */ +static int rsl_rx_chan_rqd(struct msgb *msg) +{ + struct e1inp_sign_link *sign_link = msg->dst; + struct gsm_bts *bts = sign_link->trx->bts; + struct abis_rsl_dchan_hdr *rqd_hdr = msgb_l2(msg); + struct gsm48_req_ref *rqd_ref; + enum gsm_chan_t lctype; + enum gsm_chreq_reason_t chreq_reason; + struct gsm_lchan *lchan; + uint8_t rqd_ta; + + uint16_t arfcn; + uint8_t subch; + + /* parse request reference to be used in immediate assign */ + if (rqd_hdr->data[0] != RSL_IE_REQ_REFERENCE) + return -EINVAL; + + rqd_ref = (struct gsm48_req_ref *) &rqd_hdr->data[1]; + + /* parse access delay and use as TA */ + if (rqd_hdr->data[sizeof(struct gsm48_req_ref)+1] != RSL_IE_ACCESS_DELAY) + return -EINVAL; + rqd_ta = rqd_hdr->data[sizeof(struct gsm48_req_ref)+2]; + + /* Determine channel request cause code */ + chreq_reason = get_reason_by_chreq(rqd_ref->ra, bts->network->neci); + LOGP(DRSL, LOGL_NOTICE, "(bts=%d) CHAN RQD: reason: %s (ra=0x%02x, neci=0x%02x, chreq_reason=0x%02x)\n", + msg->lchan->ts->trx->bts->nr, + get_value_string(gsm_chreq_descs, chreq_reason), + rqd_ref->ra, bts->network->neci, chreq_reason); + + /* Handle PDCH related rach requests (in case of BSC-co-located-PCU */ + if (chreq_reason == GSM_CHREQ_REASON_PDCH) + return rsl_rx_pchan_rqd(msg, bts); + + /* determine channel type (SDCCH/TCH_F/TCH_H) based on + * request reference RA */ + lctype = get_ctype_by_chreq(bts->network, rqd_ref->ra); + + rate_ctr_inc(&bts->bts_ctrs->ctr[BTS_CTR_CHREQ_TOTAL]); + + /* check availability / allocate channel + * + * - First try to allocate SDCCH. + * - If SDCCH is not available, try whatever MS requested, if not SDCCH. + * - If there is still no channel available, reject channel request. + * + * lchan_alloc() possibly tries to allocate larger lchans. + * + * Note: If the MS requests not TCH/H, we don't know if the phone + * supports TCH/H, so we must assign TCH/F or SDCCH. + */ + lchan = lchan_alloc(bts, GSM_LCHAN_SDCCH, 0); + if (!lchan && lctype != GSM_LCHAN_SDCCH) { + LOGP(DRSL, LOGL_NOTICE, "(bts=%d) CHAN RQD: no resources for %s " + "0x%x, retrying with %s\n", + msg->lchan->ts->trx->bts->nr, + gsm_lchant_name(GSM_LCHAN_SDCCH), rqd_ref->ra, + gsm_lchant_name(lctype)); + lchan = lchan_alloc(bts, lctype, 0); + } + if (!lchan) { + uint8_t wait_ind; + LOGP(DRSL, LOGL_NOTICE, "(bts=%d) CHAN RQD: no resources for %s 0x%x\n", + msg->lchan->ts->trx->bts->nr, gsm_lchant_name(lctype), rqd_ref->ra); + rate_ctr_inc(&bts->bts_ctrs->ctr[BTS_CTR_CHREQ_NO_CHANNEL]); + if (bts->T3122) + wait_ind = bts->T3122; + else if (bts->network->T3122) + wait_ind = bts->network->T3122 & 0xff; + else + wait_ind = GSM_T3122_DEFAULT; + /* The BTS will gather multiple CHAN RQD and reject up to 4 MS at the same time. */ + rsl_send_imm_ass_rej(bts, rqd_ref, wait_ind); + return 0; + } + + /* + * Expecting lchan state to be NONE, except for dyn TS in PDCH mode. + * Those are expected to be ACTIVE: the PDCH release will be sent from + * rsl_chan_activate_lchan() below. + */ + if (lchan->state != LCHAN_S_NONE + && !(lchan->ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH + && lchan->ts->dyn.pchan_is == GSM_PCHAN_PDCH + && lchan->state == LCHAN_S_ACTIVE)) + LOGP(DRSL, LOGL_NOTICE, "%s lchan_alloc() returned channel " + "in state %s\n", gsm_lchan_name(lchan), + gsm_lchans_name(lchan->state)); + + /* save the RACH data as we need it after the CHAN ACT ACK */ + lchan->rqd_ref = talloc_zero(bts, struct gsm48_req_ref); + if (!lchan->rqd_ref) { + LOGP(DRSL, LOGL_ERROR, "Failed to allocate gsm48_req_ref.\n"); + lchan_free(lchan); + return -ENOMEM; + } + + memcpy(lchan->rqd_ref, rqd_ref, sizeof(*rqd_ref)); + lchan->rqd_ta = rqd_ta; + + arfcn = lchan->ts->trx->arfcn; + subch = lchan->nr; + + lchan->encr.alg_id = RSL_ENC_ALG_A5(0); /* no encryption */ + lchan->ms_power = ms_pwr_ctl_lvl(bts->band, bts->ms_max_power); + lchan->bs_power = 0; /* 0dB reduction, output power = Pn */ + lchan->rsl_cmode = RSL_CMOD_SPD_SIGN; + lchan->tch_mode = GSM48_CMODE_SIGN; + + /* Start another timer or assume the BTS sends a ACK/NACK? */ + osmo_timer_setup(&lchan->act_timer, lchan_act_tmr_cb, lchan); + osmo_timer_schedule(&lchan->act_timer, 4, 0); + + DEBUGP(DRSL, "%s Activating ARFCN(%u) SS(%u) lctype %s " + "r=%s ra=0x%02x ta=%d\n", gsm_lchan_name(lchan), arfcn, subch, + gsm_lchant_name(lchan->type), gsm_chreq_name(chreq_reason), + rqd_ref->ra, rqd_ta); + + rsl_chan_activate_lchan(lchan, RSL_ACT_INTRA_IMM_ASS, 0); + + return 0; +} + +static int rsl_send_imm_assignment(struct gsm_lchan *lchan) +{ + struct gsm_bts *bts = lchan->ts->trx->bts; + uint8_t buf[GSM_MACBLOCK_LEN]; + struct gsm48_imm_ass *ia = (struct gsm48_imm_ass *) buf; + + /* create IMMEDIATE ASSIGN 04.08 messge */ + memset(ia, 0, sizeof(*ia)); + /* we set ia->l2_plen once we know the length of the MA below */ + ia->proto_discr = GSM48_PDISC_RR; + ia->msg_type = GSM48_MT_RR_IMM_ASS; + ia->page_mode = GSM48_PM_SAME; + gsm48_lchan2chan_desc(&ia->chan_desc, lchan); + + /* use request reference extracted from CHAN_RQD */ + memcpy(&ia->req_ref, lchan->rqd_ref, sizeof(ia->req_ref)); + ia->timing_advance = lchan->rqd_ta; + if (!lchan->ts->hopping.enabled) { + ia->mob_alloc_len = 0; + } else { + ia->mob_alloc_len = lchan->ts->hopping.ma_len; + memcpy(ia->mob_alloc, lchan->ts->hopping.ma_data, ia->mob_alloc_len); + } + /* we need to subtract 1 byte from sizeof(*ia) since ia includes the l2_plen field */ + ia->l2_plen = GSM48_LEN2PLEN((sizeof(*ia)-1) + ia->mob_alloc_len); + + /* Start timer T3101 to wait for GSM48_MT_RR_PAG_RESP */ + osmo_timer_setup(&lchan->T3101, t3101_expired, lchan); + osmo_timer_schedule(&lchan->T3101, bts->network->T3101, 0); + + /* send IMMEDIATE ASSIGN CMD on RSL to BTS (to send on CCCH to MS) */ + return rsl_imm_assign_cmd(bts, sizeof(*ia)+ia->mob_alloc_len, (uint8_t *) ia); +} + +/* current load on the CCCH */ +static int rsl_rx_ccch_load(struct msgb *msg) +{ + struct e1inp_sign_link *sign_link = msg->dst; + struct abis_rsl_dchan_hdr *rslh = msgb_l2(msg); + struct ccch_signal_data sd; + + sd.bts = sign_link->trx->bts; + sd.rach_slot_count = -1; + sd.rach_busy_count = -1; + sd.rach_access_count = -1; + + switch (rslh->data[0]) { + case RSL_IE_PAGING_LOAD: + sd.pg_buf_space = rslh->data[1] << 8 | rslh->data[2]; + if (is_ipaccess_bts(sign_link->trx->bts) && sd.pg_buf_space == 0xffff) { + /* paging load below configured threshold, use 50 as default */ + sd.pg_buf_space = 50; + } + paging_update_buffer_space(sign_link->trx->bts, sd.pg_buf_space); + osmo_signal_dispatch(SS_CCCH, S_CCCH_PAGING_LOAD, &sd); + break; + case RSL_IE_RACH_LOAD: + if (msg->data_len >= 7) { + sd.rach_slot_count = rslh->data[2] << 8 | rslh->data[3]; + sd.rach_busy_count = rslh->data[4] << 8 | rslh->data[5]; + sd.rach_access_count = rslh->data[6] << 8 | rslh->data[7]; + osmo_signal_dispatch(SS_CCCH, S_CCCH_RACH_LOAD, &sd); + } + break; + default: + break; + } + + return 0; +} + +/* Ericsson specific: Immediate Assign Sent */ +static int rsl_rx_ericsson_imm_assign_sent(struct msgb *msg) +{ + struct e1inp_sign_link *sign_link = msg->dst; + struct abis_rsl_dchan_hdr *dh = msgb_l2(msg); + uint32_t tlli; + + LOGP(DRSL, LOGL_INFO, "IMM.ass sent\n"); + msgb_pull(msg, sizeof(*dh)); + + /* FIXME: Move to TLV once we support defining TV types with V having len != 1 byte */ + if(msg->len < 5) + LOGP(DRSL, LOGL_ERROR, "short IMM.ass sent message!\n"); + else if(msg->data[0] != RSL_IE_ERIC_MOBILE_ID) + LOGP(DRSL, LOGL_ERROR, "unsupported IMM.ass message format! (please fix)\n"); + else { + msgb_pull(msg, 1); /* drop previous data to use msg_pull_u32 */ + tlli = msgb_pull_u32(msg); + pcu_tx_imm_ass_sent(sign_link->trx->bts, tlli); + } + return 0; +} + +static int abis_rsl_rx_cchan(struct msgb *msg) +{ + struct e1inp_sign_link *sign_link = msg->dst; + struct abis_rsl_dchan_hdr *rslh = msgb_l2(msg); + int rc = 0; + + msg->lchan = lchan_lookup(sign_link->trx, rslh->chan_nr, + "Abis RSL rx CCHAN: "); + + switch (rslh->c.msg_type) { + case RSL_MT_CHAN_RQD: + /* MS has requested a channel on the RACH */ + rc = rsl_rx_chan_rqd(msg); + break; + case RSL_MT_CCCH_LOAD_IND: + /* current load on the CCCH */ + rc = rsl_rx_ccch_load(msg); + break; + case RSL_MT_DELETE_IND: + /* CCCH overloaded, IMM_ASSIGN was dropped */ + case RSL_MT_CBCH_LOAD_IND: + /* current load on the CBCH */ + LOGP(DRSL, LOGL_NOTICE, "Unimplemented Abis RSL TRX message " + "type %s\n", rsl_msg_name(rslh->c.msg_type)); + break; + case RSL_MT_ERICSSON_IMM_ASS_SENT: + rc = rsl_rx_ericsson_imm_assign_sent(msg); + break; + default: + LOGP(DRSL, LOGL_NOTICE, "Unknown Abis RSL TRX message type " + "0x%02x\n", rslh->c.msg_type); + rate_ctr_inc(&sign_link->trx->bts->bts_ctrs->ctr[BTS_CTR_RSL_UNKNOWN]); + return -EINVAL; + } + + return rc; +} + +static int rsl_rx_rll_err_ind(struct msgb *msg) +{ + struct tlv_parsed tp; + struct abis_rsl_rll_hdr *rllh = msgb_l2(msg); + uint8_t rlm_cause; + + rsl_tlv_parse(&tp, rllh->data, msgb_l2len(msg) - sizeof(*rllh)); + if (!TLVP_PRESENT(&tp, RSL_IE_RLM_CAUSE)) { + LOGP(DRLL, LOGL_ERROR, + "%s ERROR INDICATION without mandantory cause.\n", + gsm_lchan_name(msg->lchan)); + return -1; + } + + rlm_cause = *TLVP_VAL(&tp, RSL_IE_RLM_CAUSE); + LOGP(DRLL, LOGL_ERROR, "%s ERROR INDICATION cause=%s in state=%s\n", + gsm_lchan_name(msg->lchan), + rsl_rlm_cause_name(rlm_cause), + gsm_lchans_name(msg->lchan->state)); + + rll_indication(msg->lchan, rllh->link_id, BSC_RLLR_IND_ERR_IND); + + if (rlm_cause == RLL_CAUSE_T200_EXPIRED) { + rate_ctr_inc(&msg->lchan->ts->trx->bts->bts_ctrs->ctr[BTS_CTR_CHAN_RLL_ERR]); + return rsl_rf_chan_release_err(msg->lchan); + } + + return 0; +} + +static void rsl_handle_release(struct gsm_lchan *lchan) +{ + int sapi; + struct gsm_bts *bts; + + /* + * Maybe only one link/SAPI was releasd or the error handling + * was activated. Just return now and let the other code handle + * it. + */ + if (lchan->state != LCHAN_S_REL_REQ) + return; + + for (sapi = 0; sapi < ARRAY_SIZE(lchan->sapis); ++sapi) { + if (lchan->sapis[sapi] == LCHAN_SAPI_UNUSED) + continue; + LOGP(DRSL, LOGL_DEBUG, "%s waiting for SAPI=%d to be released.\n", + gsm_lchan_name(lchan), sapi); + return; + } + + + /* Stop T3109 and wait for T3111 before re-using the channel */ + osmo_timer_del(&lchan->T3109); + osmo_timer_setup(&lchan->T3111, t3111_expired, lchan); + bts = lchan->ts->trx->bts; + osmo_timer_schedule(&lchan->T3111, bts->network->T3111, 0); +} + +/* ESTABLISH INDICATION, LOCATION AREA UPDATE REQUEST + 0x02, 0x06, + 0x01, 0x20, + 0x02, 0x00, + 0x0b, 0x00, 0x0f, 0x05, 0x08, ... */ + +static int abis_rsl_rx_rll(struct msgb *msg) +{ + struct e1inp_sign_link *sign_link = msg->dst; + struct abis_rsl_rll_hdr *rllh = msgb_l2(msg); + int rc = 0; + char *ts_name; + uint8_t sapi = rllh->link_id & 7; + + msg->lchan = lchan_lookup(sign_link->trx, rllh->chan_nr, + "Abis RSL rx RLL: "); + ts_name = gsm_lchan_name(msg->lchan); + DEBUGP(DRLL, "%s SAPI=%u ", ts_name, sapi); + + switch (rllh->c.msg_type) { + case RSL_MT_DATA_IND: + DEBUGPC(DRLL, "DATA INDICATION\n"); + if (msgb_l2len(msg) > + sizeof(struct abis_rsl_common_hdr) + sizeof(*rllh) && + rllh->data[0] == RSL_IE_L3_INFO) { + msg->l3h = &rllh->data[3]; + return gsm0408_rcvmsg(msg, rllh->link_id); + } + break; + case RSL_MT_EST_IND: + DEBUGPC(DRLL, "ESTABLISH INDICATION\n"); + /* lchan is established, stop T3101 */ + + /* Note: By definition the first Establish Indication must + * happen first on SAPI 0, once the connection on SAPI 0 is + * made, parallel connections on other SAPIs are permitted */ + if (sapi != 0 && msg->lchan->sapis[0] != LCHAN_SAPI_MS) { + LOGP(DRLL, LOGL_NOTICE, "MS attempted to establish DCCH on SAPI=%d (expected SAPI=0)\n", + rllh->link_id & 0x7); + + /* Note: We do not need to close the channel, + * since we might still get a proper Establish Ind. + * If not, T3101 will close the channel on timeout. */ + break; + } + + /* Note: Check for MF SACCH on SAPI=0 (not permitted). By + * definition we establish a link in multiframe (MF) mode. + * (see also 3GPP TS 48.058, chapter 3.1. However, on SAPI=0 + * SACCH is only allowed in UL mode, not in MF mode. + * (see also 3GPP TS 44.005, figure 5) So we have to drop such + * Establish Indications */ + if (sapi == 0 && (rllh->link_id >> 6 & 0x03) == 1) { + LOGP(DRLL, LOGL_NOTICE, "MS attempted to establish an SACCH in MF mode on SAPI=0 (not permitted)\n"); + + /* Note: We do not need to close the channel, + * since we might still get a proper Establish Ind. + * If not, T3101 will close the channel on timeout. */ + break; + } + + msg->lchan->sapis[rllh->link_id & 0x7] = LCHAN_SAPI_MS; + osmo_timer_del(&msg->lchan->T3101); + if (msgb_l2len(msg) > + sizeof(struct abis_rsl_common_hdr) + sizeof(*rllh) && + rllh->data[0] == RSL_IE_L3_INFO) { + msg->l3h = &rllh->data[3]; + return gsm0408_rcvmsg(msg, rllh->link_id); + } + break; + case RSL_MT_EST_CONF: + DEBUGPC(DRLL, "ESTABLISH CONFIRM\n"); + msg->lchan->sapis[rllh->link_id & 0x7] = LCHAN_SAPI_NET; + rll_indication(msg->lchan, rllh->link_id, + BSC_RLLR_IND_EST_CONF); + break; + case RSL_MT_REL_IND: + /* BTS informs us of having received DISC from MS */ + DEBUGPC(DRLL, "RELEASE INDICATION\n"); + msg->lchan->sapis[rllh->link_id & 0x7] = LCHAN_SAPI_UNUSED; + rll_indication(msg->lchan, rllh->link_id, + BSC_RLLR_IND_REL_IND); + rsl_handle_release(msg->lchan); + /* if it was the main signalling link, let the subscr_conn_fsm know */ + if (msg->lchan->conn && sapi == 0 && (rllh->link_id >> 6) == 0) + osmo_fsm_inst_dispatch(msg->lchan->conn->fi, GSCON_EV_RLL_REL_IND, msg); + break; + case RSL_MT_REL_CONF: + /* BTS informs us of having received UA from MS, + * in response to DISC that we've sent earlier */ + DEBUGPC(DRLL, "RELEASE CONFIRMATION\n"); + msg->lchan->sapis[rllh->link_id & 0x7] = LCHAN_SAPI_UNUSED; + rsl_handle_release(msg->lchan); + break; + case RSL_MT_ERROR_IND: + DEBUGPC(DRLL, "ERROR INDICATION\n"); + rc = rsl_rx_rll_err_ind(msg); + break; + case RSL_MT_UNIT_DATA_IND: + DEBUGPC(DRLL, "UNIT DATA INDICATION\n"); + LOGP(DRLL, LOGL_NOTICE, "unimplemented Abis RLL message " + "type 0x%02x\n", rllh->c.msg_type); + break; + default: + DEBUGPC(DRLL, "UNKNOWN\n"); + LOGP(DRLL, LOGL_NOTICE, "unknown Abis RLL message " + "type 0x%02x\n", rllh->c.msg_type); + rate_ctr_inc(&sign_link->trx->bts->bts_ctrs->ctr[BTS_CTR_RSL_UNKNOWN]); + } + return rc; +} + +static uint8_t ipa_smod_s_for_lchan(struct gsm_lchan *lchan) +{ + switch (lchan->tch_mode) { + case GSM48_CMODE_SPEECH_V1: + switch (lchan->type) { + case GSM_LCHAN_TCH_F: + return 0x00; + case GSM_LCHAN_TCH_H: + return 0x03; + default: + break; + } + break; + case GSM48_CMODE_SPEECH_EFR: + switch (lchan->type) { + case GSM_LCHAN_TCH_F: + return 0x01; + /* there's no half-rate EFR */ + default: + break; + } + break; + case GSM48_CMODE_SPEECH_AMR: + switch (lchan->type) { + case GSM_LCHAN_TCH_F: + return 0x02; + case GSM_LCHAN_TCH_H: + return 0x05; + default: + break; + } + break; + default: + break; + } + LOGP(DRSL, LOGL_ERROR, "Cannot determine ip.access speech mode for " + "tch_mode == 0x%02x\n", lchan->tch_mode); + return 0; +} + +static uint8_t ipa_rtp_pt_for_lchan(struct gsm_lchan *lchan) +{ + switch (lchan->tch_mode) { + case GSM48_CMODE_SPEECH_V1: + switch (lchan->type) { + case GSM_LCHAN_TCH_F: + return RTP_PT_GSM_FULL; + case GSM_LCHAN_TCH_H: + return RTP_PT_GSM_HALF; + default: + break; + } + break; + case GSM48_CMODE_SPEECH_EFR: + switch (lchan->type) { + case GSM_LCHAN_TCH_F: + return RTP_PT_GSM_EFR; + /* there's no half-rate EFR */ + default: + break; + } + break; + case GSM48_CMODE_SPEECH_AMR: + switch (lchan->type) { + case GSM_LCHAN_TCH_F: + case GSM_LCHAN_TCH_H: + return RTP_PT_AMR; + default: + break; + } + break; + default: + break; + } + LOGP(DRSL, LOGL_ERROR, "Cannot determine ip.access rtp payload type for " + "tch_mode == 0x%02x\n & lchan_type == %d", + lchan->tch_mode, lchan->type); + return 0; +} + +/* ip.access specific RSL extensions */ +static void ipac_parse_rtp(struct gsm_lchan *lchan, struct tlv_parsed *tv) +{ + struct in_addr ip; + uint16_t port, conn_id; + + if (TLVP_PRESENT(tv, RSL_IE_IPAC_LOCAL_IP)) { + ip.s_addr = tlvp_val32_unal(tv, RSL_IE_IPAC_LOCAL_IP); + DEBUGPC(DRSL, "LOCAL_IP=%s ", inet_ntoa(ip)); + lchan->abis_ip.bound_ip = ntohl(ip.s_addr); + } + + if (TLVP_PRESENT(tv, RSL_IE_IPAC_LOCAL_PORT)) { + port = tlvp_val16_unal(tv, RSL_IE_IPAC_LOCAL_PORT); + port = ntohs(port); + DEBUGPC(DRSL, "LOCAL_PORT=%u ", port); + lchan->abis_ip.bound_port = port; + } + + if (TLVP_PRESENT(tv, RSL_IE_IPAC_CONN_ID)) { + conn_id = tlvp_val16_unal(tv, RSL_IE_IPAC_CONN_ID); + conn_id = ntohs(conn_id); + DEBUGPC(DRSL, "CON_ID=%u ", conn_id); + lchan->abis_ip.conn_id = conn_id; + } + + if (TLVP_PRESENT(tv, RSL_IE_IPAC_RTP_PAYLOAD2)) { + lchan->abis_ip.rtp_payload2 = + *TLVP_VAL(tv, RSL_IE_IPAC_RTP_PAYLOAD2); + DEBUGPC(DRSL, "RTP_PAYLOAD2=0x%02x ", + lchan->abis_ip.rtp_payload2); + } + + if (TLVP_PRESENT(tv, RSL_IE_IPAC_SPEECH_MODE)) { + lchan->abis_ip.speech_mode = + *TLVP_VAL(tv, RSL_IE_IPAC_SPEECH_MODE); + DEBUGPC(DRSL, "speech_mode=0x%02x ", + lchan->abis_ip.speech_mode); + } + + if (TLVP_PRESENT(tv, RSL_IE_IPAC_REMOTE_IP)) { + ip.s_addr = tlvp_val32_unal(tv, RSL_IE_IPAC_REMOTE_IP); + DEBUGPC(DRSL, "REMOTE_IP=%s ", inet_ntoa(ip)); + lchan->abis_ip.connect_ip = ntohl(ip.s_addr); + } + + if (TLVP_PRESENT(tv, RSL_IE_IPAC_REMOTE_PORT)) { + port = tlvp_val16_unal(tv, RSL_IE_IPAC_REMOTE_PORT); + port = ntohs(port); + DEBUGPC(DRSL, "REMOTE_PORT=%u ", port); + lchan->abis_ip.connect_port = port; + } + + DEBUGPC(DRSL, "\n"); +} + +/*! \brief Issue IPA RSL CRCX to configure RTP on BTS side + * \param[in] lchan Logical Channel for which we issue CRCX + */ +int rsl_ipacc_crcx(struct gsm_lchan *lchan) +{ + struct msgb *msg = rsl_msgb_alloc(); + struct abis_rsl_dchan_hdr *dh; + + dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh)); + init_dchan_hdr(dh, RSL_MT_IPAC_CRCX); + dh->c.msg_discr = ABIS_RSL_MDISC_IPACCESS; + dh->chan_nr = gsm_lchan2chan_nr(lchan); + + /* 0x1- == receive-only, 0x-1 == EFR codec */ + lchan->abis_ip.speech_mode = 0x10 | ipa_smod_s_for_lchan(lchan); + lchan->abis_ip.rtp_payload = ipa_rtp_pt_for_lchan(lchan); + msgb_tv_put(msg, RSL_IE_IPAC_SPEECH_MODE, lchan->abis_ip.speech_mode); + msgb_tv_put(msg, RSL_IE_IPAC_RTP_PAYLOAD, lchan->abis_ip.rtp_payload); + + DEBUGP(DRSL, "%s IPAC_BIND speech_mode=0x%02x RTP_PAYLOAD=%d\n", + gsm_lchan_name(lchan), lchan->abis_ip.speech_mode, + lchan->abis_ip.rtp_payload); + + msg->dst = lchan->ts->trx->rsl_link; + + return abis_rsl_sendmsg(msg); +} + +/*! \brief Issue IPA RSL MDCX to configure MGW-side of RTP + * \param[in] lchan Logical Channel for which we issue MDCX + * \param[in] ip Remote (MGW) IP address for RTP + * \param[in] port Remote (MGW) UDP port number for RTP + * \param[in] rtp_payload2 Contents of RTP PAYLOAD 2 IE + */ +int rsl_ipacc_mdcx(struct gsm_lchan *lchan, uint32_t ip, uint16_t port, + uint8_t rtp_payload2) +{ + struct msgb *msg = rsl_msgb_alloc(); + struct abis_rsl_dchan_hdr *dh; + uint32_t *att_ip; + struct in_addr ia; + + dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh)); + init_dchan_hdr(dh, RSL_MT_IPAC_MDCX); + dh->c.msg_discr = ABIS_RSL_MDISC_IPACCESS; + dh->chan_nr = gsm_lchan2chan_nr(lchan); + + /* we need to store these now as MDCX_ACK does not return them :( */ + lchan->abis_ip.rtp_payload2 = rtp_payload2; + lchan->abis_ip.connect_port = port; + lchan->abis_ip.connect_ip = ip; + + /* 0x0- == both directions, 0x-1 == EFR codec */ + lchan->abis_ip.speech_mode = 0x00 | ipa_smod_s_for_lchan(lchan); + lchan->abis_ip.rtp_payload = ipa_rtp_pt_for_lchan(lchan); + + ia.s_addr = htonl(ip); + DEBUGP(DRSL, "%s IPAC_MDCX IP=%s PORT=%d RTP_PAYLOAD=%d RTP_PAYLOAD2=%d " + "CONN_ID=%d speech_mode=0x%02x\n", gsm_lchan_name(lchan), + inet_ntoa(ia), port, lchan->abis_ip.rtp_payload, rtp_payload2, + lchan->abis_ip.conn_id, lchan->abis_ip.speech_mode); + + msgb_tv16_put(msg, RSL_IE_IPAC_CONN_ID, lchan->abis_ip.conn_id); + msgb_v_put(msg, RSL_IE_IPAC_REMOTE_IP); + att_ip = (uint32_t *) msgb_put(msg, sizeof(ip)); + *att_ip = ia.s_addr; + msgb_tv16_put(msg, RSL_IE_IPAC_REMOTE_PORT, port); + msgb_tv_put(msg, RSL_IE_IPAC_SPEECH_MODE, lchan->abis_ip.speech_mode); + msgb_tv_put(msg, RSL_IE_IPAC_RTP_PAYLOAD, lchan->abis_ip.rtp_payload); + if (rtp_payload2) + msgb_tv_put(msg, RSL_IE_IPAC_RTP_PAYLOAD2, rtp_payload2); + + msg->dst = lchan->ts->trx->rsl_link; + + return abis_rsl_sendmsg(msg); +} + +static bool check_gprs_enabled(struct gsm_bts_trx_ts *ts) +{ + if (ts->trx->bts->gprs.mode == BTS_GPRS_NONE) { + LOGP(DRSL, LOGL_NOTICE, "%s: GPRS mode is 'none': not activating PDCH.\n", + gsm_ts_and_pchan_name(ts)); + return false; + } + return true; +} + +int rsl_ipacc_pdch_activate(struct gsm_bts_trx_ts *ts, int act) +{ + struct msgb *msg = rsl_msgb_alloc(); + struct abis_rsl_dchan_hdr *dh; + uint8_t msg_type; + + if (ts->flags & TS_F_PDCH_PENDING_MASK) { + LOGP(DRSL, LOGL_ERROR, + "%s PDCH %s requested, but a PDCH%s%s is still pending\n", + gsm_ts_name(ts), + act ? "ACT" : "DEACT", + ts->flags & TS_F_PDCH_ACT_PENDING? " ACT" : "", + ts->flags & TS_F_PDCH_DEACT_PENDING? " DEACT" : ""); + return -EINVAL; + } + + if (act){ + if (!check_gprs_enabled(ts)) + return -ENOTSUP; + + msg_type = RSL_MT_IPAC_PDCH_ACT; + ts->flags |= TS_F_PDCH_ACT_PENDING; + } else { + msg_type = RSL_MT_IPAC_PDCH_DEACT; + ts->flags |= TS_F_PDCH_DEACT_PENDING; + } + /* TODO add timeout to cancel PDCH DE/ACT */ + + dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh)); + init_dchan_hdr(dh, msg_type); + dh->c.msg_discr = ABIS_RSL_MDISC_DED_CHAN; + dh->chan_nr = gsm_pchan2chan_nr(GSM_PCHAN_TCH_F, ts->nr, 0); + + DEBUGP(DRSL, "%s IPAC PDCH %sACT\n", gsm_ts_name(ts), + act ? "" : "DE"); + + msg->dst = ts->trx->rsl_link; + + return abis_rsl_sendmsg(msg); +} + +static int abis_rsl_rx_ipacc_crcx_ack(struct msgb *msg) +{ + struct abis_rsl_dchan_hdr *dh = msgb_l2(msg); + struct tlv_parsed tv; + struct gsm_lchan *lchan = msg->lchan; + + /* the BTS has acknowledged a local bind, it now tells us the IP + * address and port number to which it has bound the given logical + * channel */ + + rsl_tlv_parse(&tv, dh->data, msgb_l2len(msg)-sizeof(*dh)); + if (!TLVP_PRESENT(&tv, RSL_IE_IPAC_LOCAL_PORT) || + !TLVP_PRESENT(&tv, RSL_IE_IPAC_LOCAL_IP) || + !TLVP_PRESENT(&tv, RSL_IE_IPAC_CONN_ID)) { + LOGP(DRSL, LOGL_NOTICE, "mandatory IE missing"); + return -EINVAL; + } + + ipac_parse_rtp(lchan, &tv); + + osmo_signal_dispatch(SS_ABISIP, S_ABISIP_CRCX_ACK, msg->lchan); + + return 0; +} + +static int abis_rsl_rx_ipacc_mdcx_ack(struct msgb *msg) +{ + struct abis_rsl_dchan_hdr *dh = msgb_l2(msg); + struct tlv_parsed tv; + struct gsm_lchan *lchan = msg->lchan; + + /* the BTS has acknowledged a remote connect request and + * it now tells us the IP address and port number to which it has + * connected the given logical channel */ + + rsl_tlv_parse(&tv, dh->data, msgb_l2len(msg)-sizeof(*dh)); + ipac_parse_rtp(lchan, &tv); + osmo_signal_dispatch(SS_ABISIP, S_ABISIP_MDCX_ACK, msg->lchan); + + return 0; +} + +static int abis_rsl_rx_ipacc_dlcx_ind(struct msgb *msg) +{ + struct abis_rsl_dchan_hdr *dh = msgb_l2(msg); + struct tlv_parsed tv; + + rsl_tlv_parse(&tv, dh->data, msgb_l2len(msg)-sizeof(*dh)); + + if (TLVP_PRESENT(&tv, RSL_IE_CAUSE)) + print_rsl_cause(LOGL_DEBUG, TLVP_VAL(&tv, RSL_IE_CAUSE), + TLVP_LEN(&tv, RSL_IE_CAUSE)); + + osmo_signal_dispatch(SS_ABISIP, S_ABISIP_DLCX_IND, msg->lchan); + + return 0; +} + +static int abis_rsl_rx_ipacc(struct msgb *msg) +{ + struct e1inp_sign_link *sign_link = msg->dst; + struct abis_rsl_rll_hdr *rllh = msgb_l2(msg); + char *ts_name; + int rc = 0; + + msg->lchan = lchan_lookup(sign_link->trx, rllh->chan_nr, + "Abis RSL rx IPACC: "); + ts_name = gsm_lchan_name(msg->lchan); + + switch (rllh->c.msg_type) { + case RSL_MT_IPAC_CRCX_ACK: + DEBUGP(DRSL, "%s IPAC_CRCX_ACK ", ts_name); + rc = abis_rsl_rx_ipacc_crcx_ack(msg); + break; + case RSL_MT_IPAC_CRCX_NACK: + /* somehow the BTS was unable to bind the lchan to its local + * port?!? */ + LOGP(DRSL, LOGL_ERROR, "%s IPAC_CRCX_NACK\n", ts_name); + rate_ctr_inc(&sign_link->trx->bts->bts_ctrs->ctr[BTS_CTR_RSL_IPA_NACK]); + break; + case RSL_MT_IPAC_MDCX_ACK: + /* the BTS tells us that a connect operation was successful */ + DEBUGP(DRSL, "%s IPAC_MDCX_ACK ", ts_name); + rc = abis_rsl_rx_ipacc_mdcx_ack(msg); + break; + case RSL_MT_IPAC_MDCX_NACK: + /* somehow the BTS was unable to connect the lchan to a remote + * port */ + LOGP(DRSL, LOGL_ERROR, "%s IPAC_MDCX_NACK\n", ts_name); + rate_ctr_inc(&sign_link->trx->bts->bts_ctrs->ctr[BTS_CTR_RSL_IPA_NACK]); + break; + case RSL_MT_IPAC_DLCX_IND: + DEBUGP(DRSL, "%s IPAC_DLCX_IND ", ts_name); + rc = abis_rsl_rx_ipacc_dlcx_ind(msg); + break; + default: + LOGP(DRSL, LOGL_NOTICE, "Unknown ip.access msg_type 0x%02x\n", + rllh->c.msg_type); + rate_ctr_inc(&sign_link->trx->bts->bts_ctrs->ctr[BTS_CTR_RSL_UNKNOWN]); + break; + } + + return rc; +} + +int dyn_ts_switchover_start(struct gsm_bts_trx_ts *ts, + enum gsm_phys_chan_config to_pchan) +{ + int ss; + int rc = -EIO; + + OSMO_ASSERT(ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH); + + if (ts->dyn.pchan_is != ts->dyn.pchan_want) { + LOGP(DRSL, LOGL_ERROR, + "%s: Attempt to switch dynamic channel to %s," + " but is already in switchover.\n", + gsm_ts_and_pchan_name(ts), + gsm_pchan_name(to_pchan)); + return ts->dyn.pchan_want == to_pchan? 0 : -EAGAIN; + } + + if (ts->dyn.pchan_is == to_pchan) { + LOGP(DRSL, LOGL_INFO, + "%s %s Already is in %s mode, cannot switchover.\n", + gsm_ts_name(ts), gsm_pchan_name(ts->pchan), + gsm_pchan_name(to_pchan)); + return -EINVAL; + } + + /* Paranoia: let's make sure all is indeed released. */ + for (ss = 0; ss < ts_subslots(ts); ss++) { + struct gsm_lchan *lc = &ts->lchan[ss]; + if (lc->state != LCHAN_S_NONE) { + LOGP(DRSL, LOGL_ERROR, + "%s Attempt to switch dynamic channel to %s," + " but is not fully released.\n", + gsm_ts_and_pchan_name(ts), + gsm_pchan_name(to_pchan)); + return -EAGAIN; + } + } + + if (to_pchan == GSM_PCHAN_PDCH && !check_gprs_enabled(ts)) + return -ENOTSUP; + + DEBUGP(DRSL, "%s starting switchover to %s\n", + gsm_ts_and_pchan_name(ts), gsm_pchan_name(to_pchan)); + + /* Record that we're busy switching. */ + ts->dyn.pchan_want = to_pchan; + + /* + * To switch from PDCH, we need to initiate the release from the BSC + * side. dyn_ts_switchover_continue() will be called from + * rsl_rx_rf_chan_rel_ack(). PDCH is always on lchan[0]. + */ + if (ts->dyn.pchan_is == GSM_PCHAN_PDCH) { + rsl_lchan_set_state(ts->lchan, LCHAN_S_REL_REQ); + rc = rsl_rf_chan_release(ts->lchan, 0, SACCH_NONE); + if (rc) { + LOGP(DRSL, LOGL_ERROR, + "%s RSL RF Chan Release failed\n", + gsm_ts_and_pchan_name(ts)); + return dyn_ts_switchover_failed(ts, rc); + } + return 0; + } + + /* + * To switch from TCH/F and TCH/H pchans, this has been called from + * rsl_rx_rf_chan_rel_ack(), i.e. release is complete. Go ahead and + * activate as new type. This will always be PDCH. + */ + return dyn_ts_switchover_continue(ts); +} + +static int dyn_ts_switchover_continue(struct gsm_bts_trx_ts *ts) +{ + int rc; + uint8_t act_type; + uint8_t ho_ref; + int ss; + struct gsm_lchan *lchan; + + OSMO_ASSERT(ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH); + DEBUGP(DRSL, "%s switchover: release complete," + " activating new pchan type\n", + gsm_ts_and_pchan_name(ts)); + + if (ts->dyn.pchan_is == ts->dyn.pchan_want) { + LOGP(DRSL, LOGL_ERROR, + "%s Requested to switchover dynamic channel to the" + " same type it is already in.\n", + gsm_ts_and_pchan_name(ts)); + return 0; + } + + for (ss = 0; ss < ts_subslots(ts); ss++) { + lchan = &ts->lchan[ss]; + if (lchan->rqd_ref) { + LOGP(DRSL, LOGL_ERROR, + "%s During dyn TS switchover, expecting no" + " Request Reference to be pending. Discarding!\n", + gsm_lchan_name(lchan)); + talloc_free(lchan->rqd_ref); + lchan->rqd_ref = NULL; + } + } + + /* + * When switching pchan modes, all lchans are unused. So always + * activate whatever wants to be activated on the first lchan. (We + * wouldn't remember to use lchan[1] across e.g. a PDCH deact anyway) + */ + lchan = ts->lchan; + + /* + * For TCH/x, the lchan->type has been set in lchan_alloc(), but it may + * have been lost during channel release due to dynamic switchover. + * + * For PDCH, the lchan->type will actually remain NONE. + * TODO: set GSM_LCHAN_PDTCH? + */ + switch (ts->dyn.pchan_want) { + case GSM_PCHAN_TCH_F: + lchan->type = GSM_LCHAN_TCH_F; + break; + case GSM_PCHAN_TCH_H: + lchan->type = GSM_LCHAN_TCH_H; + break; + case GSM_PCHAN_PDCH: + lchan->type = GSM_LCHAN_NONE; + break; + default: + LOGP(DRSL, LOGL_ERROR, + "%s Invalid target pchan for dynamic TS\n", + gsm_ts_and_pchan_name(ts)); + } + + act_type = (ts->dyn.pchan_want == GSM_PCHAN_PDCH) + ? RSL_ACT_OSMO_PDCH + : lchan->dyn.act_type; + ho_ref = (ts->dyn.pchan_want == GSM_PCHAN_PDCH) + ? 0 + : lchan->dyn.ho_ref; + + /* Fetch the rqd_ref back from before switchover started. */ + lchan->rqd_ref = lchan->dyn.rqd_ref; + lchan->rqd_ta = lchan->dyn.rqd_ta; + lchan->dyn.rqd_ref = NULL; + lchan->dyn.rqd_ta = 0; + + /* During switchover, we have received a release ack, which means that + * the act_timer has been stopped. Start the timer again so we mark + * this channel broken if the activation ack comes too late. */ + osmo_timer_setup(&lchan->act_timer, lchan_act_tmr_cb, lchan); + osmo_timer_schedule(&lchan->act_timer, 4, 0); + + rc = rsl_chan_activate_lchan(lchan, act_type, ho_ref); + if (rc) { + LOGP(DRSL, LOGL_ERROR, + "%s RSL Chan Activate failed\n", + gsm_ts_and_pchan_name(ts)); + return dyn_ts_switchover_failed(ts, rc); + } + return 0; +} + +static int dyn_ts_switchover_failed(struct gsm_bts_trx_ts *ts, int rc) +{ + ts->dyn.pchan_want = ts->dyn.pchan_is; + LOGP(DRSL, LOGL_ERROR, "%s Error %d during dynamic channel switchover." + " Going back to previous pchan.\n", gsm_ts_and_pchan_name(ts), + rc); + return rc; +} + +static void dyn_ts_switchover_complete(struct gsm_lchan *lchan) +{ + enum gsm_phys_chan_config pchan_act; + enum gsm_phys_chan_config pchan_was; + struct gsm_bts_trx_ts *ts = lchan->ts; + + OSMO_ASSERT(ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH); + + pchan_act = pchan_for_lchant(lchan->type); + /* + * Paranoia: do the types match? + * In case of errors: we've received an act ack already, so what to do + * about it? Logging the error should suffice for now. + */ + if (pchan_act != ts->dyn.pchan_want) + LOGP(DRSL, LOGL_ERROR, + "%s Requested transition does not match lchan type %s\n", + gsm_ts_and_pchan_name(ts), + gsm_lchant_name(lchan->type)); + + pchan_was = ts->dyn.pchan_is; + ts->dyn.pchan_is = ts->dyn.pchan_want = pchan_act; + + if (pchan_was != ts->dyn.pchan_is) + LOGP(DRSL, LOGL_INFO, "%s switchover from %s complete.\n", + gsm_ts_and_pchan_name(ts), gsm_pchan_name(pchan_was)); +} + +/* Entry-point where L2 RSL from BTS enters */ +int abis_rsl_rcvmsg(struct msgb *msg) +{ + struct e1inp_sign_link *sign_link; + struct abis_rsl_common_hdr *rslh; + int rc = 0; + + if (!msg) { + DEBUGP(DRSL, "Empty RSL msg?..\n"); + return -1; + } + + if (msgb_l2len(msg) < sizeof(*rslh)) { + DEBUGP(DRSL, "Truncated RSL message with l2len: %u\n", msgb_l2len(msg)); + msgb_free(msg); + return -1; + } + + sign_link = msg->dst; + rslh = msgb_l2(msg); + + switch (rslh->msg_discr & 0xfe) { + case ABIS_RSL_MDISC_RLL: + rc = abis_rsl_rx_rll(msg); + break; + case ABIS_RSL_MDISC_DED_CHAN: + rc = abis_rsl_rx_dchan(msg); + break; + case ABIS_RSL_MDISC_COM_CHAN: + rc = abis_rsl_rx_cchan(msg); + break; + case ABIS_RSL_MDISC_TRX: + rc = abis_rsl_rx_trx(msg); + break; + case ABIS_RSL_MDISC_LOC: + LOGP(DRSL, LOGL_NOTICE, "unimplemented RSL msg disc 0x%02x\n", + rslh->msg_discr); + break; + case ABIS_RSL_MDISC_IPACCESS: + rc = abis_rsl_rx_ipacc(msg); + break; + default: + LOGP(DRSL, LOGL_NOTICE, "unknown RSL message discriminator " + "0x%02x\n", rslh->msg_discr); + rate_ctr_inc(&sign_link->trx->bts->bts_ctrs->ctr[BTS_CTR_RSL_UNKNOWN]); + rc = -EINVAL; + } + msgb_free(msg); + return rc; +} + +int rsl_sms_cb_command(struct gsm_bts *bts, uint8_t chan_number, + struct rsl_ie_cb_cmd_type cb_command, + const uint8_t *data, int len) +{ + struct abis_rsl_dchan_hdr *dh; + struct msgb *cb_cmd; + + cb_cmd = rsl_msgb_alloc(); + if (!cb_cmd) + return -1; + + dh = (struct abis_rsl_dchan_hdr *) msgb_put(cb_cmd, sizeof(*dh)); + init_dchan_hdr(dh, RSL_MT_SMS_BC_CMD); + dh->c.msg_discr = ABIS_RSL_MDISC_COM_CHAN; + dh->chan_nr = chan_number; /* TODO: check the chan config */ + + msgb_tv_put(cb_cmd, RSL_IE_CB_CMD_TYPE, *(uint8_t*)&cb_command); + msgb_tlv_put(cb_cmd, RSL_IE_SMSCB_MSG, len, data); + + cb_cmd->dst = bts->c0->rsl_link; + + return abis_rsl_sendmsg(cb_cmd); +} + +int rsl_nokia_si_begin(struct gsm_bts_trx *trx) +{ + struct abis_rsl_common_hdr *ch; + struct msgb *msg = rsl_msgb_alloc(); + + ch = (struct abis_rsl_common_hdr *) msgb_put(msg, sizeof(*ch)); + ch->msg_discr = ABIS_RSL_MDISC_TRX; + ch->msg_type = 0x40; /* Nokia SI Begin */ + + msg->dst = trx->rsl_link; + + return abis_rsl_sendmsg(msg); +} + +int rsl_nokia_si_end(struct gsm_bts_trx *trx) +{ + struct abis_rsl_common_hdr *ch; + struct msgb *msg = rsl_msgb_alloc(); + + ch = (struct abis_rsl_common_hdr *) msgb_put(msg, sizeof(*ch)); + ch->msg_discr = ABIS_RSL_MDISC_TRX; + ch->msg_type = 0x41; /* Nokia SI End */ + + msgb_tv_put(msg, 0xFD, 0x00); /* Nokia Pagemode Info, No paging reorganisation required */ + + msg->dst = trx->rsl_link; + + return abis_rsl_sendmsg(msg); +} + +int rsl_bs_power_control(struct gsm_bts_trx *trx, uint8_t channel, uint8_t reduction) +{ + struct abis_rsl_common_hdr *ch; + struct msgb *msg = rsl_msgb_alloc(); + + ch = (struct abis_rsl_common_hdr *) msgb_put(msg, sizeof(*ch)); + ch->msg_discr = ABIS_RSL_MDISC_DED_CHAN; + ch->msg_type = RSL_MT_BS_POWER_CONTROL; + + msgb_tv_put(msg, RSL_IE_CHAN_NR, channel); + msgb_tv_put(msg, RSL_IE_BS_POWER, reduction); /* reduction in 2dB steps */ + + msg->dst = trx->rsl_link; + + return abis_rsl_sendmsg(msg); +} + +/** + * Release all allocated SAPIs starting from @param start and + * release them with the given release mode. Once the release + * confirmation arrives it will be attempted to release the + * the RF channel. + */ +int rsl_release_sapis_from(struct gsm_lchan *lchan, int start, + enum rsl_rel_mode release_mode) +{ + int no_sapi = 1; + int sapi; + + for (sapi = start; sapi < ARRAY_SIZE(lchan->sapis); ++sapi) { + uint8_t link_id; + if (lchan->sapis[sapi] == LCHAN_SAPI_UNUSED) + continue; + + link_id = sapi; + if (lchan->type == GSM_LCHAN_TCH_F || lchan->type == GSM_LCHAN_TCH_H) + link_id |= 0x40; + rsl_release_request(lchan, link_id, release_mode); + no_sapi = 0; + } + + return no_sapi; +} + +int rsl_start_t3109(struct gsm_lchan *lchan) +{ + struct gsm_bts *bts = lchan->ts->trx->bts; + + osmo_timer_setup(&lchan->T3109, t3109_expired, lchan); + osmo_timer_schedule(&lchan->T3109, bts->network->T3109, 0); + return 0; +} + +/** + * \brief directly RF Channel Release the lchan + * + * When no SAPI was allocated, directly release the logical channel. This + * should only be called from chan_alloc.c on channel release handling. In + * case no SAPI was established the RF Channel can be directly released, + */ +int rsl_direct_rf_release(struct gsm_lchan *lchan) +{ + int i; + for (i = 0; i < ARRAY_SIZE(lchan->sapis); ++i) { + if (lchan->sapis[i] != LCHAN_SAPI_UNUSED) { + LOGP(DRSL, LOGL_ERROR, "%s SAPI(%d) still allocated.\n", + gsm_lchan_name(lchan), i); + return -1; + } + } + + /* Now release it */ + return rsl_rf_chan_release(lchan, 0, SACCH_NONE); +} + +/* Initial timeslot actions when a timeslot first comes into operation. */ +bool on_gsm_ts_init(struct gsm_bts_trx_ts *ts) +{ + dyn_ts_init(ts); + return true; +} diff --git a/src/osmo-bsc/acc_ramp.c b/src/osmo-bsc/acc_ramp.c new file mode 100644 index 000000000..ac9f02da1 --- /dev/null +++ b/src/osmo-bsc/acc_ramp.c @@ -0,0 +1,363 @@ +/* (C) 2018 by sysmocom s.f.m.c. GmbH <info@sysmocom.de> + * + * Author: Stefan Sperling <ssperling@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 <strings.h> +#include <errno.h> +#include <stdbool.h> + +#include <osmocom/bsc/debug.h> +#include <osmocom/bsc/acc_ramp.h> +#include <osmocom/bsc/gsm_data.h> +#include <osmocom/bsc/chan_alloc.h> +#include <osmocom/bsc/signal.h> +#include <osmocom/bsc/abis_nm.h> + +/* + * Check if an ACC has been permanently barred for a BTS, + * e.g. with the 'rach access-control-class' VTY command. + */ +static bool acc_is_permanently_barred(struct gsm_bts *bts, unsigned int acc) +{ + OSMO_ASSERT(acc >= 0 && acc <= 9); + if (acc == 8 || acc == 9) + return (bts->si_common.rach_control.t2 & (1 << (acc - 8))); + return (bts->si_common.rach_control.t3 & (1 << (acc))); +} + +static void allow_one_acc(struct acc_ramp *acc_ramp, unsigned int acc) +{ + OSMO_ASSERT(acc >= 0 && acc <= 9); + if (acc_ramp->barred_accs & (1 << acc)) + LOGP(DRSL, LOGL_NOTICE, "(bts=%d) ACC RAMP: allowing Access Control Class %u\n", acc_ramp->bts->nr, acc); + acc_ramp->barred_accs &= ~(1 << acc); +} + +static void barr_one_acc(struct acc_ramp *acc_ramp, unsigned int acc) +{ + OSMO_ASSERT(acc >= 0 && acc <= 9); + if ((acc_ramp->barred_accs & (1 << acc)) == 0) + LOGP(DRSL, LOGL_NOTICE, "(bts=%d) ACC RAMP: barring Access Control Class %u\n", acc_ramp->bts->nr, acc); + acc_ramp->barred_accs |= (1 << acc); +} + +static void barr_all_accs(struct acc_ramp *acc_ramp) +{ + unsigned int acc; + for (acc = 0; acc < 10; acc++) { + if (!acc_is_permanently_barred(acc_ramp->bts, acc)) + barr_one_acc(acc_ramp, acc); + } +} + +static void allow_all_accs(struct acc_ramp *acc_ramp) +{ + unsigned int acc; + for (acc = 0; acc < 10; acc++) { + if (!acc_is_permanently_barred(acc_ramp->bts, acc)) + allow_one_acc(acc_ramp, acc); + } +} + +static unsigned int get_next_step_interval(struct acc_ramp *acc_ramp) +{ + struct gsm_bts *bts = acc_ramp->bts; + uint64_t load; + + if (acc_ramp->step_interval_is_fixed) + return acc_ramp->step_interval_sec; + + /* Scale the step interval to current channel load average. */ + load = (bts->chan_load_avg << 8); /* convert to fixed-point */ + acc_ramp->step_interval_sec = ((load * ACC_RAMP_STEP_INTERVAL_MAX) / 100) >> 8; + if (acc_ramp->step_interval_sec < ACC_RAMP_STEP_SIZE_MIN) + acc_ramp->step_interval_sec = ACC_RAMP_STEP_INTERVAL_MIN; + else if (acc_ramp->step_interval_sec > ACC_RAMP_STEP_INTERVAL_MAX) + acc_ramp->step_interval_sec = ACC_RAMP_STEP_INTERVAL_MAX; + + LOGP(DRSL, LOGL_DEBUG, "(bts=%d) ACC RAMP: step interval set to %u seconds based on %u%% channel load average\n", + bts->nr, acc_ramp->step_interval_sec, bts->chan_load_avg); + return acc_ramp->step_interval_sec; +} + +static void do_acc_ramping_step(void *data) +{ + struct acc_ramp *acc_ramp = data; + int i; + + /* Shortcut in case we only do one ramping step. */ + if (acc_ramp->step_size == ACC_RAMP_STEP_SIZE_MAX) { + allow_all_accs(acc_ramp); + gsm_bts_set_system_infos(acc_ramp->bts); + return; + } + + /* Allow 'step_size' ACCs, starting from ACC0. ACC9 will be allowed last. */ + for (i = 0; i < acc_ramp->step_size; i++) { + int idx = ffs(acc_ramp_get_barred_t3(acc_ramp)); + if (idx > 0) { + /* One of ACC0-ACC7 is still bared. */ + unsigned int acc = idx - 1; + if (!acc_is_permanently_barred(acc_ramp->bts, acc)) + allow_one_acc(acc_ramp, acc); + } else { + idx = ffs(acc_ramp_get_barred_t2(acc_ramp)); + if (idx == 1 || idx == 2) { + /* ACC8 or ACC9 is still barred. */ + unsigned int acc = idx - 1 + 8; + if (!acc_is_permanently_barred(acc_ramp->bts, acc)) + allow_one_acc(acc_ramp, acc); + } else { + /* All ACCs are now allowed. */ + break; + } + } + } + + gsm_bts_set_system_infos(acc_ramp->bts); + + /* If we have not allowed all ACCs yet, schedule another ramping step. */ + if (acc_ramp_get_barred_t2(acc_ramp) != 0x00 || + acc_ramp_get_barred_t3(acc_ramp) != 0x00) + osmo_timer_schedule(&acc_ramp->step_timer, get_next_step_interval(acc_ramp), 0); +} + +/* Implements osmo_signal_cbfn() -- trigger or abort ACC ramping upon changes RF lock state. */ +static int acc_ramp_nm_sig_cb(unsigned int subsys, unsigned int signal, void *handler_data, void *signal_data) +{ + struct nm_statechg_signal_data *nsd = signal_data; + struct acc_ramp *acc_ramp = handler_data; + struct gsm_bts_trx *trx = NULL; + bool trigger_ramping = false, abort_ramping = false; + + /* Handled signals map to an Administrative State Change ACK, or a State Changed Event Report. */ + if (signal != S_NM_STATECHG_ADM && signal != S_NM_STATECHG_OPER) + return 0; + + if (nsd->obj_class != NM_OC_RADIO_CARRIER) + return 0; + + trx = nsd->obj; + + LOGP(DRSL, LOGL_DEBUG, "(bts=%d,trx=%d) ACC RAMP: administrative state %s -> %s\n", + acc_ramp->bts->nr, trx->nr, + get_value_string(abis_nm_adm_state_names, nsd->old_state->administrative), + get_value_string(abis_nm_adm_state_names, nsd->new_state->administrative)); + LOGP(DRSL, LOGL_DEBUG, "(bts=%d,trx=%d) ACC RAMP: operational state %s -> %s\n", + acc_ramp->bts->nr, trx->nr, + abis_nm_opstate_name(nsd->old_state->operational), + abis_nm_opstate_name(nsd->new_state->operational)); + + /* We only care about state changes of the first TRX. */ + if (trx->nr != 0) + return 0; + + /* RSL must already be up. We cannot send RACH system information to the BTS otherwise. */ + if (trx->rsl_link == NULL) { + LOGP(DRSL, LOGL_DEBUG, "(bts=%d,trx=%d) ACC RAMP: ignoring state change because RSL link is down\n", + acc_ramp->bts->nr, trx->nr); + return 0; + } + + /* Trigger or abort ACC ramping based on the new state of this TRX. */ + if (nsd->old_state->administrative != nsd->new_state->administrative) { + switch (nsd->new_state->administrative) { + case NM_STATE_UNLOCKED: + if (nsd->old_state->operational != nsd->new_state->operational) { + /* + * Administrative and operational state have both changed. + * Trigger ramping only if TRX 0 will be both enabled and unlocked. + */ + if (nsd->new_state->operational == NM_OPSTATE_ENABLED) + trigger_ramping = true; + else + LOGP(DRSL, LOGL_DEBUG, "(bts=%d,trx=%d) ACC RAMP: ignoring state change " + "because TRX is transitioning into operational state '%s'\n", + acc_ramp->bts->nr, trx->nr, + abis_nm_opstate_name(nsd->new_state->operational)); + } else { + /* + * Operational state has not changed. + * Trigger ramping only if TRX 0 is already usable. + */ + if (trx_is_usable(trx)) + trigger_ramping = true; + else + LOGP(DRSL, LOGL_DEBUG, "(bts=%d,trx=%d) ACC RAMP: ignoring state change " + "because TRX is not usable\n", acc_ramp->bts->nr, trx->nr); + } + break; + case NM_STATE_LOCKED: + case NM_STATE_SHUTDOWN: + abort_ramping = true; + break; + case NM_STATE_NULL: + default: + LOGP(DRSL, LOGL_ERROR, "(bts=%d) ACC RAMP: unrecognized administrative state '0x%x' " + "reported for TRX 0\n", acc_ramp->bts->nr, nsd->new_state->administrative); + break; + } + } + if (nsd->old_state->operational != nsd->new_state->operational) { + switch (nsd->new_state->operational) { + case NM_OPSTATE_ENABLED: + if (nsd->old_state->administrative != nsd->new_state->administrative) { + /* + * Administrative and operational state have both changed. + * Trigger ramping only if TRX 0 will be both enabled and unlocked. + */ + if (nsd->new_state->administrative == NM_STATE_UNLOCKED) + trigger_ramping = true; + else + LOGP(DRSL, LOGL_DEBUG, "(bts=%d,trx=%d) ACC RAMP: ignoring state change " + "because TRX is transitioning into administrative state '%s'\n", + acc_ramp->bts->nr, trx->nr, + get_value_string(abis_nm_adm_state_names, nsd->new_state->administrative)); + } else { + /* + * Administrative state has not changed. + * Trigger ramping only if TRX 0 is already unlocked. + */ + if (trx->mo.nm_state.administrative == NM_STATE_UNLOCKED) + trigger_ramping = true; + else + LOGP(DRSL, LOGL_DEBUG, "(bts=%d,trx=%d) ACC RAMP: ignoring state change " + "because TRX is in administrative state '%s'\n", + acc_ramp->bts->nr, trx->nr, + get_value_string(abis_nm_adm_state_names, trx->mo.nm_state.administrative)); + } + break; + case NM_OPSTATE_DISABLED: + abort_ramping = true; + break; + case NM_OPSTATE_NULL: + default: + LOGP(DRSL, LOGL_ERROR, "(bts=%d) ACC RAMP: unrecognized operational state '0x%x' " + "reported for TRX 0\n", acc_ramp->bts->nr, nsd->new_state->administrative); + break; + } + } + + if (trigger_ramping) + acc_ramp_trigger(acc_ramp); + else if (abort_ramping) + acc_ramp_abort(acc_ramp); + + return 0; +} + +/*! + * Initialize an acc_ramp data structure. + * Storage for this structure must be provided by the caller. + * + * By default, ACC ramping is disabled and all ACCs are allowed. + * + * \param[in] acc_ramp Pointer to acc_ramp structure to be initialized. + * \param[in] bts BTS which uses this ACC ramp data structure. + */ +void acc_ramp_init(struct acc_ramp *acc_ramp, struct gsm_bts *bts) +{ + acc_ramp->bts = bts; + acc_ramp_set_enabled(acc_ramp, false); + acc_ramp->step_size = ACC_RAMP_STEP_SIZE_DEFAULT; + acc_ramp->step_interval_sec = ACC_RAMP_STEP_INTERVAL_MIN; + acc_ramp->step_interval_is_fixed = false; + allow_all_accs(acc_ramp); + osmo_timer_setup(&acc_ramp->step_timer, do_acc_ramping_step, acc_ramp); + osmo_signal_register_handler(SS_NM, acc_ramp_nm_sig_cb, acc_ramp); +} + +/*! + * Change the ramping step size which controls how many ACCs will be allowed per ramping step. + * Returns negative on error (step_size out of range), else zero. + * \param[in] acc_ramp Pointer to acc_ramp structure. + * \param[in] step_size The new step size value. + */ +int acc_ramp_set_step_size(struct acc_ramp *acc_ramp, unsigned int step_size) +{ + if (step_size < ACC_RAMP_STEP_SIZE_MIN || step_size > ACC_RAMP_STEP_SIZE_MAX) + return -ERANGE; + + acc_ramp->step_size = step_size; + LOGP(DRSL, LOGL_DEBUG, "(bts=%d) ACC RAMP: ramping step size set to %u\n", acc_ramp->bts->nr, step_size); + return 0; +} + +/*! + * Change the ramping step interval to a fixed value. Unless this function is called, + * the interval is automatically scaled to the BTS channel load average. + * \param[in] acc_ramp Pointer to acc_ramp structure. + * \param[in] step_interval The new fixed step interval in seconds. + */ +int acc_ramp_set_step_interval(struct acc_ramp *acc_ramp, unsigned int step_interval) +{ + if (step_interval < ACC_RAMP_STEP_INTERVAL_MIN || step_interval > ACC_RAMP_STEP_INTERVAL_MAX) + return -ERANGE; + + acc_ramp->step_interval_sec = step_interval; + acc_ramp->step_interval_is_fixed = true; + LOGP(DRSL, LOGL_DEBUG, "(bts=%d) ACC RAMP: ramping step interval set to %u seconds\n", + acc_ramp->bts->nr, step_interval); + return 0; +} + +/*! + * Clear a previously set fixed ramping step interval, so that the interval + * is again automatically scaled to the BTS channel load average. + * \param[in] acc_ramp Pointer to acc_ramp structure. + */ +void acc_ramp_set_step_interval_dynamic(struct acc_ramp *acc_ramp) +{ + acc_ramp->step_interval_is_fixed = false; + LOGP(DRSL, LOGL_DEBUG, "(bts=%d) ACC RAMP: ramping step interval set to 'dynamic'\n", + acc_ramp->bts->nr); +} + +/*! + * Determine if ACC ramping should be started according to configuration, and + * begin the ramping process if the necessary conditions are present. + * Perform at least one ramping step to allow 'step_size' ACCs. + * If 'step_size' is ACC_RAMP_STEP_SIZE_MAX, or if ACC ramping is disabled, + * all ACCs will be allowed immediately. + * \param[in] acc_ramp Pointer to acc_ramp structure. + */ +void acc_ramp_trigger(struct acc_ramp *acc_ramp) +{ + /* Abort any previously running ramping process and allow all available ACCs. */ + acc_ramp_abort(acc_ramp); + + if (acc_ramp_is_enabled(acc_ramp)) { + /* Set all available ACCs to barred and start ramping up. */ + barr_all_accs(acc_ramp); + do_acc_ramping_step(acc_ramp); + } +} + +/*! + * Abort the ramping process and allow all available ACCs immediately. + * \param[in] acc_ramp Pointer to acc_ramp structure. + */ +void acc_ramp_abort(struct acc_ramp *acc_ramp) +{ + if (osmo_timer_pending(&acc_ramp->step_timer)) + osmo_timer_del(&acc_ramp->step_timer); + + allow_all_accs(acc_ramp); +} diff --git a/src/osmo-bsc/arfcn_range_encode.c b/src/osmo-bsc/arfcn_range_encode.c new file mode 100644 index 000000000..84f9f635f --- /dev/null +++ b/src/osmo-bsc/arfcn_range_encode.c @@ -0,0 +1,336 @@ +/* gsm 04.08 system information (si) encoding and decoding + * 3gpp ts 04.08 version 7.21.0 release 1998 / etsi ts 100 940 v7.21.0 */ + +/* + * (C) 2012 Holger Hans Peter Freyther + * (C) 2012 by On-Waves + * 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 <osmocom/bsc/arfcn_range_encode.h> +#include <osmocom/bsc/debug.h> + +#include <osmocom/gsm/protocol/gsm_04_08.h> + +#include <osmocom/core/utils.h> + +#include <errno.h> + +static inline int greatest_power_of_2_lesser_or_equal_to(int index) +{ + int power_of_2 = 1; + + do { + power_of_2 *= 2; + } while (power_of_2 <= index); + + /* now go back one step */ + return power_of_2 / 2; +} + +static inline int mod(int data, int range) +{ + int res = data % range; + while (res < 0) + res += range; + return res; +} + +/** + * Determine at which index to split the ARFCNs to create an + * equally size partition for the given range. Return -1 if + * no such partition exists. + */ +int range_enc_find_index(enum gsm48_range range, const int *freqs, const int size) +{ + int i, j, n; + + const int RANGE_DELTA = (range - 1) / 2; + + for (i = 0; i < size; ++i) { + n = 0; + for (j = 0; j < size; ++j) { + if (mod(freqs[j] - freqs[i], range) <= RANGE_DELTA) + n += 1; + } + + if (n - 1 == (size - 1) / 2) + return i; + } + + return -1; +} + +/* Worker for range_enc_arfcns(), do not call directly. */ +int _range_enc_arfcns(enum gsm48_range range, + const int *arfcns, int size, int *out, + const int index) +{ + int split_at; + int i; + + /* + * The below is a GNU extension and we can remove it when + * we move to a quicksort like in-situ swap with the pivot. + */ + int arfcns_left[size / 2]; + int arfcns_right[size / 2]; + int l_size; + int r_size; + int l_origin; + int r_origin; + + /* Now do the processing */ + split_at = range_enc_find_index(range, arfcns, size); + if (split_at < 0) + return -EINVAL; + + /* we now know where to split */ + out[index] = 1 + arfcns[split_at]; + + /* calculate the work that needs to be done for the leafs */ + l_origin = mod(arfcns[split_at] + ((range - 1) / 2) + 1, range); + r_origin = mod(arfcns[split_at] + 1, range); + for (i = 0, l_size = 0, r_size = 0; i < size; ++i) { + if (mod(arfcns[i] - l_origin, range) < range / 2) + arfcns_left[l_size++] = mod(arfcns[i] - l_origin, range); + if (mod(arfcns[i] - r_origin, range) < range / 2) + arfcns_right[r_size++] = mod(arfcns[i] - r_origin, range); + } + + /* + * Now recurse and we need to make this iterative... but as the + * tree is balanced the stack will not be too deep. + */ + if (l_size) + range_enc_arfcns(range / 2, arfcns_left, l_size, + out, index + greatest_power_of_2_lesser_or_equal_to(index + 1)); + if (r_size) + range_enc_arfcns((range - 1) / 2, arfcns_right, r_size, + out, index + (2 * greatest_power_of_2_lesser_or_equal_to(index + 1))); + return 0; +} + +/** + * Range encode the ARFCN list. + * \param range The range to use. + * \param arfcns The list of ARFCNs + * \param size The size of the list of ARFCNs + * \param out Place to store the W(i) output. + */ +int range_enc_arfcns(enum gsm48_range range, + const int *arfcns, int size, int *out, + const int index) +{ + if (size <= 0) + return 0; + + if (size == 1) { + out[index] = 1 + arfcns[0]; + return 0; + } + + return _range_enc_arfcns(range, arfcns, size, out, index); +} + +/* + * The easiest is to use f0 == arfcns[0]. This means that under certain + * circumstances we can encode less ARFCNs than possible with an optimal f0. + * + * TODO: Solve the optimisation problem and pick f0 so that the max distance + * is the smallest. Taking into account the modulo operation. I think picking + * size/2 will be the optimal arfcn. + */ +/** + * This implements the range determination as described in GSM 04.08 J4. The + * result will be a base frequency f0 and the range to use. Note that for range + * 1024 encoding f0 always refers to ARFCN 0 even if it is not an element of + * the arfcns list. + * + * \param[in] arfcns The input frequencies, they must be sorted, lowest number first + * \param[in] size The length of the array + * \param[out] f0 The selected F0 base frequency. It might not be inside the list + */ +int range_enc_determine_range(const int *arfcns, const int size, int *f0) +{ + int max = 0; + + /* + * Go for the easiest. And pick arfcns[0] == f0. + */ + max = arfcns[size - 1] - arfcns[0]; + *f0 = arfcns[0]; + + if (max < 128 && size <= 29) + return ARFCN_RANGE_128; + if (max < 256 && size <= 22) + return ARFCN_RANGE_256; + if (max < 512 && size <= 18) + return ARFCN_RANGE_512; + if (max < 1024 && size <= 17) { + *f0 = 0; + return ARFCN_RANGE_1024; + } + + return ARFCN_RANGE_INVALID; +} + +static void write_orig_arfcn(uint8_t *chan_list, int f0) +{ + chan_list[0] |= (f0 >> 9) & 1; + chan_list[1] = (f0 >> 1); + chan_list[2] = (f0 & 1) << 7; +} + +static void write_all_wn(uint8_t *chan_list, int bit_offs, + int *w, int w_size, int w1_len) +{ + int octet_offs = 0; /* offset into chan_list */ + int wk_len = w1_len; /* encoding size in bits of w[k] */ + int k; /* 1 based */ + int level = 0; /* tree level, top level = 0 */ + int lvl_left = 1; /* nodes per tree level */ + + /* W(2^i) to W(2^(i+1)-1) are on w1_len-i bits when present */ + + for (k = 1; k <= w_size; k++) { + int wk_left = wk_len; + DEBUGP(DRR, + "k=%d, wk_len=%d, offs=%d:%d, level=%d, " + "lvl_left=%d\n", + k, wk_len, octet_offs, bit_offs, level, lvl_left); + + while (wk_left > 0) { + int cur_bits = 8 - bit_offs; + int cur_mask; + int wk_slice; + + if (cur_bits > wk_left) + cur_bits = wk_left; + + cur_mask = ((1 << cur_bits) - 1); + + DEBUGP(DRR, + " wk_left=%d, cur_bits=%d, offs=%d:%d\n", + wk_left, cur_bits, octet_offs, bit_offs); + + /* advance */ + wk_left -= cur_bits; + bit_offs += cur_bits; + + /* right aligned wk data for current out octet */ + wk_slice = (w[k-1] >> wk_left) & cur_mask; + + /* cur_bits now contains the number of bits + * that are to be copied from wk to the chan_list. + * wk_left is set to the number of bits that must + * not yet be copied. + * bit_offs points after the bit area that is going to + * be overwritten: + * + * wk_left + * | + * v + * wk: WWWWWWWWWWW + * |||||<-- wk_slice, cur_bits=5 + * --WWWWW- + * ^ + * | + * bit_offs + */ + + DEBUGP(DRR, + " wk=%02x, slice=%02x/%02x, cl=%02x\n", + w[k-1], wk_slice, cur_mask, wk_slice << (8 - bit_offs)); + + chan_list[octet_offs] &= ~(cur_mask << (8 - bit_offs)); + chan_list[octet_offs] |= wk_slice << (8 - bit_offs); + + /* adjust output */ + if (bit_offs == 8) { + bit_offs = 0; + octet_offs += 1; + } + } + + /* adjust bit sizes */ + lvl_left -= 1; + if (!lvl_left) { + /* completed tree level, advance to next */ + level += 1; + lvl_left = 1 << level; + wk_len -= 1; + } + } +} + +int range_enc_range128(uint8_t *chan_list, int f0, int *w) +{ + chan_list[0] = 0x8C; + write_orig_arfcn(chan_list, f0); + + write_all_wn(&chan_list[2], 1, w, 28, 7); + return 0; +} + +int range_enc_range256(uint8_t *chan_list, int f0, int *w) +{ + chan_list[0] = 0x8A; + write_orig_arfcn(chan_list, f0); + + write_all_wn(&chan_list[2], 1, w, 21, 8); + return 0; +} + +int range_enc_range512(uint8_t *chan_list, int f0, int *w) +{ + chan_list[0] = 0x88; + write_orig_arfcn(chan_list, f0); + + write_all_wn(&chan_list[2], 1, w, 17, 9); + return 0; +} + +int range_enc_range1024(uint8_t *chan_list, int f0, int f0_included, int *w) +{ + chan_list[0] = 0x80 | (f0_included << 2); + + write_all_wn(&chan_list[0], 6, w, 16, 10); + return 0; +} + +int range_enc_filter_arfcns(int *arfcns, + const int size, const int f0, int *f0_included) +{ + int i, j = 0; + *f0_included = 0; + + for (i = 0; i < size; ++i) { + /* + * Appendix J.4 says the following: + * All frequencies except F(0), minus F(0) + 1. + * I assume we need to exclude it here. + */ + if (arfcns[i] == f0) { + *f0_included = 1; + continue; + } + + arfcns[j++] = mod(arfcns[i] - (f0 + 1), 1024); + } + + return j; +} diff --git a/src/osmo-bsc/bsc_api.c b/src/osmo-bsc/bsc_api.c new file mode 100644 index 000000000..8ae781e96 --- /dev/null +++ b/src/osmo-bsc/bsc_api.c @@ -0,0 +1,841 @@ +/* GSM 08.08 like API for OpenBSC. The bridge from MSC to BSC */ + +/* (C) 2010-2011 by Holger Hans Peter Freyther + * (C) 2010-2011 by On-Waves + * (C) 2009,2017 by Harald Welte <laforge@gnumonks.org> + * + * 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 <osmocom/bsc/bsc_api.h> +#include <osmocom/bsc/bsc_rll.h> +#include <osmocom/bsc/gsm_data.h> +#include <osmocom/bsc/signal.h> +#include <osmocom/bsc/abis_rsl.h> +#include <osmocom/bsc/chan_alloc.h> +#include <osmocom/bsc/handover.h> +#include <osmocom/bsc/debug.h> +#include <osmocom/bsc/gsm_04_08_utils.h> +#include <osmocom/bsc/bsc_subscriber.h> +#include <osmocom/bsc/penalty_timers.h> +#include <osmocom/bsc/osmo_bsc_sigtran.h> + +#include <osmocom/gsm/protocol/gsm_08_08.h> +#include <osmocom/gsm/gsm48.h> + +#include <osmocom/core/talloc.h> + +#define GSM0808_T10_VALUE 6, 0 + +#define HO_DTAP_CACHE_MSGB_CB_LINK_ID 0 +#define HO_DTAP_CACHE_MSGB_CB_ALLOW_SACCH 1 + +static void rll_ind_cb(struct gsm_lchan *, uint8_t, void *, enum bsc_rllr_ind); +static void send_sapi_reject(struct gsm_subscriber_connection *conn, int link_id); +static void handle_release(struct gsm_subscriber_connection *conn, struct bsc_api *bsc, struct gsm_lchan *lchan); +static void handle_chan_ack(struct gsm_subscriber_connection *conn, struct bsc_api *bsc, struct gsm_lchan *lchan); +static void handle_chan_nack(struct gsm_subscriber_connection *conn, struct bsc_api *bsc, struct gsm_lchan *lchan); + +/*! \brief Determine and apply AMR multi-rate configuration to lchan + * Determine which AMR multi-rate configuration to use and apply it to + * the lchan (so it can be communicated to BTS and MS during channel + * activation. + * \param[in] conn subscriber connection (used to resolve bsc_api) + * \param[out] lchan logical channel to which to apply mr config + * \param[in] full_rate whether to use full-rate (1) or half-rate (0) config + */ +static void handle_mr_config(struct gsm_subscriber_connection *conn, + struct gsm_lchan *lchan, int full_rate) +{ + struct bsc_api *api; + api = conn->network->bsc_api; + struct amr_multirate_conf *mr; + struct gsm48_multi_rate_conf *mr_conf; + + /* BSC api override for this method, used in OsmoBSC mode with + * bsc_mr_config() to use MSC-specific/specified configuration */ + if (api->mr_config) + return api->mr_config(conn, lchan, full_rate); + + /* NITB case: use the BTS-specic multi-rate configuration from + * the vty/configuration file */ + if (full_rate) + mr = &lchan->ts->trx->bts->mr_full; + else + mr = &lchan->ts->trx->bts->mr_half; + + mr_conf = (struct gsm48_multi_rate_conf *) mr->gsm48_ie; + mr_conf->ver = 1; + + /* default, if no AMR codec defined */ + if (!mr->gsm48_ie[1]) { + mr_conf->icmi = 1; + mr_conf->m5_90 = 1; + } + /* store encoded MR config IE lchan for both MS (uplink) and BTS + * (downlink) directions */ + gsm48_multirate_config(lchan->mr_ms_lv, mr, mr->ms_mode); + gsm48_multirate_config(lchan->mr_bts_lv, mr, mr->bts_mode); +} + +/* + * Start a new assignment and make sure that it is completed within T10 either + * positively, negatively or by the timeout. + * + * 1.) allocate a new lchan + * 2.) copy the encryption key and other data from the + * old to the new channel. + * 3.) RSL Channel Activate this channel and wait + * + * -> Signal handler for the LCHAN + * 4.) Send GSM 04.08 assignment command to the MS + * + * -> Assignment Complete/Assignment Failure + * 5.) Release the SDCCH, continue signalling on the new link + */ +static int handle_new_assignment(struct gsm_subscriber_connection *conn, int chan_mode, int full_rate) +{ + struct gsm_lchan *new_lchan; + enum gsm_chan_t chan_type; + + chan_type = full_rate ? GSM_LCHAN_TCH_F : GSM_LCHAN_TCH_H; + + new_lchan = lchan_alloc(conn_get_bts(conn), chan_type, 0); + + if (!new_lchan) { + LOGP(DMSC, LOGL_NOTICE, "%s No free channel for %s\n", + bsc_subscr_name(conn->bsub), gsm_lchant_name(chan_type)); + return -1; + } + + /* check if we are on TCH/F and requested TCH/H, but got TCH/F */ + if (conn->lchan->type == new_lchan->type + && chan_type != new_lchan->type) { + LOGPLCHAN(conn->lchan, DHO, LOGL_NOTICE, + "-> %s Will not re-assign to identical channel type, %s was requested\n", + gsm_lchan_name(new_lchan), gsm_lchant_name(chan_type)); + lchan_free(new_lchan); + return -1; + } + + /* copy old data to the new channel */ + memcpy(&new_lchan->encr, &conn->lchan->encr, sizeof(new_lchan->encr)); + new_lchan->ms_power = conn->lchan->ms_power; + new_lchan->bs_power = conn->lchan->bs_power; + new_lchan->rqd_ta = conn->lchan->rqd_ta; + + /* copy new data to it */ + new_lchan->tch_mode = chan_mode; + new_lchan->rsl_cmode = (chan_mode == GSM48_CMODE_SIGN) ? + RSL_CMOD_SPD_SIGN : RSL_CMOD_SPD_SPEECH; + + /* handle AMR correctly */ + if (chan_mode == GSM48_CMODE_SPEECH_AMR) + handle_mr_config(conn, new_lchan, full_rate); + + if (rsl_chan_activate_lchan(new_lchan, 0x1, 0) < 0) { + LOGPLCHAN(new_lchan, DHO, LOGL_ERROR, "could not activate channel\n"); + lchan_free(new_lchan); + return -1; + } + + /* remember that we have the channel */ + conn->secondary_lchan = new_lchan; + new_lchan->conn = conn; + return 0; +} + +static void ho_dtap_cache_add(struct gsm_subscriber_connection *conn, struct msgb *msg, + int link_id, bool allow_sacch) +{ + if (conn->ho_dtap_cache_len >= 23) { + LOGP(DHO, LOGL_ERROR, "%s: Cannot cache more DTAP messages," + " already reached sane maximum of %u cached messages\n", + bsc_subscr_name(conn->bsub), conn->ho_dtap_cache_len); + msgb_free(msg); + return; + } + conn->ho_dtap_cache_len ++; + LOGP(DHO, LOGL_DEBUG, "%s: Caching DTAP message during ho/ass (%u)\n", + bsc_subscr_name(conn->bsub), conn->ho_dtap_cache_len); + msg->cb[HO_DTAP_CACHE_MSGB_CB_LINK_ID] = (unsigned long)link_id; + msg->cb[HO_DTAP_CACHE_MSGB_CB_ALLOW_SACCH] = allow_sacch ? 1 : 0; + msgb_enqueue(&conn->ho_dtap_cache, msg); +} + +void ho_dtap_cache_flush(struct gsm_subscriber_connection *conn, int send) +{ + struct msgb *msg; + unsigned int flushed_count = 0; + + if (conn->secondary_lchan || conn->ho) { + LOGP(DHO, LOGL_ERROR, "%s: Cannot send cached DTAP messages, handover/assignment is still ongoing\n", + bsc_subscr_name(conn->bsub)); + send = 0; + } + + while ((msg = msgb_dequeue(&conn->ho_dtap_cache))) { + conn->ho_dtap_cache_len --; + flushed_count ++; + if (send) { + int link_id = (int)msg->cb[HO_DTAP_CACHE_MSGB_CB_LINK_ID]; + bool allow_sacch = !!msg->cb[HO_DTAP_CACHE_MSGB_CB_ALLOW_SACCH]; + LOGP(DHO, LOGL_DEBUG, "%s: Sending cached DTAP message after handover/assignment (%u/%u)\n", + bsc_subscr_name(conn->bsub), flushed_count, conn->ho_dtap_cache_len); + gsm0808_submit_dtap(conn, msg, link_id, allow_sacch); + } else + msgb_free(msg); + } +} + +int bsc_api_init(struct gsm_network *network, struct bsc_api *api) +{ + network->bsc_api = api; + return 0; +} + +/*! \brief process incoming 08.08 DTAP from MSC (send via BTS to MS) */ +int gsm0808_submit_dtap(struct gsm_subscriber_connection *conn, + struct msgb *msg, int link_id, int allow_sacch) +{ + uint8_t sapi; + + + if (!conn->lchan) { + LOGP(DMSC, LOGL_ERROR, + "%s Called submit dtap without an lchan.\n", + bsc_subscr_name(conn->bsub)); + msgb_free(msg); + return -1; + } + + /* buffer message during assignment / handover */ + if (conn->secondary_lchan || conn->ho) { + ho_dtap_cache_add(conn, msg, link_id, !! allow_sacch); + return 0; + } + + sapi = link_id & 0x7; + msg->lchan = conn->lchan; + msg->dst = msg->lchan->ts->trx->rsl_link; + + /* If we are on a TCH and need to submit a SMS (on SAPI=3) we need to use the SACH */ + if (allow_sacch && sapi != 0) { + if (conn->lchan->type == GSM_LCHAN_TCH_F || conn->lchan->type == GSM_LCHAN_TCH_H) + link_id |= 0x40; + } + + msg->l3h = msg->data; + /* is requested SAPI already up? */ + if (conn->lchan->sapis[sapi] == LCHAN_SAPI_UNUSED) { + /* Establish L2 for additional SAPI */ + OBSC_LINKID_CB(msg) = link_id; + if (rll_establish(msg->lchan, sapi, rll_ind_cb, msg) != 0) { + msgb_free(msg); + send_sapi_reject(conn, link_id); + return -1; + } + return 0; + } else { + /* Directly forward via RLL/RSL to BTS */ + return rsl_data_request(msg, link_id); + } +} + +/* + * \brief Check if the given channel is compatible with the mode/fullrate + */ +static int chan_compat_with_mode(struct gsm_lchan *lchan, int chan_mode, int full_rate) +{ + switch (chan_mode) { + case GSM48_CMODE_SIGN: + switch (lchan->type) { + case GSM_LCHAN_TCH_F: + case GSM_LCHAN_TCH_H: + case GSM_LCHAN_SDCCH: + return 1; + default: + return 0; + } + case GSM48_CMODE_SPEECH_V1: + case GSM48_CMODE_SPEECH_AMR: + case GSM48_CMODE_DATA_3k6: + case GSM48_CMODE_DATA_6k0: + /* these services can all run on TCH/H, but we may have + * an explicit override by the 'full_rate' argument */ + switch (lchan->type) { + case GSM_LCHAN_TCH_F: + return full_rate ? 1 : 0; + case GSM_LCHAN_TCH_H: + return full_rate ? 0 : 1; + default: + return 0; + } + case GSM48_CMODE_DATA_12k0: + case GSM48_CMODE_DATA_14k5: + case GSM48_CMODE_SPEECH_EFR: + /* these services all explicitly require a TCH/F */ + return (lchan->type == GSM_LCHAN_TCH_F) ? 1 : 0; + default: + return 0; + } +} + +/*! Send a GSM08.08 Assignment Request. Right now this does not contain the + * audio codec type or the allowed rates for the config. In case the current + * channel does not allow the selected mode a new one will be allocated. + * \param[out] conn related subscriber connection + * \param[in] chan_mode mode of the channel (see enum gsm48_chan_mode) + * \param[in] full_rate select full rate or half rate channel + * \returns 0 on success, 1 when no operation is neccessary, -1 on failure */ +int gsm0808_assign_req(struct gsm_subscriber_connection *conn, int chan_mode, int full_rate) +{ + /* TODO: Add multirate configuration, make it work for more than audio. */ + + struct bsc_api *api; + api = conn->network->bsc_api; + + if (!chan_compat_with_mode(conn->lchan, chan_mode, full_rate)) { + if (handle_new_assignment(conn, chan_mode, full_rate) != 0) + goto error; + } else { + /* Check if the channel is already in the requested mode, if + * yes, we skip unnecessary channel mode modify operations. */ + if (conn->lchan->tch_mode == chan_mode) + return 1; + + if (chan_mode == GSM48_CMODE_SPEECH_AMR) + handle_mr_config(conn, conn->lchan, full_rate); + + LOGPLCHAN(conn->lchan, DMSC, LOGL_NOTICE, + "Sending ChanModify for speech: %s\n", + get_value_string(gsm48_chan_mode_names, chan_mode)); + gsm48_lchan_modify(conn->lchan, chan_mode); + } + + /* we expect the caller will manage T10 */ + return 0; + +error: + api->assign_fail(conn, 0, NULL); + return -1; +} + +int gsm0808_page(struct gsm_bts *bts, unsigned int page_group, unsigned int mi_len, + uint8_t *mi, int chan_type) +{ + return rsl_paging_cmd(bts, page_group, mi_len, mi, chan_type, false); +} + +static void handle_ass_compl(struct gsm_subscriber_connection *conn, + struct msgb *msg) +{ + struct gsm48_hdr *gh = msgb_l3(msg); + struct bsc_api *api = conn->network->bsc_api; + enum gsm48_rr_cause cause; + + /* Expecting gsm48_hdr + cause value */ + if (msgb_l3len(msg) != sizeof(*gh) + 1) { + LOGPLCHAN(msg->lchan, DRR, LOGL_ERROR, + "RR Assignment Complete: length invalid: %u, expected %zu\n", + msgb_l3len(msg), sizeof(*gh) + 1); + return; + } + + cause = gh->data[0]; + + LOGPLCHAN(msg->lchan, DRR, LOGL_DEBUG, "ASSIGNMENT COMPLETE cause = %s\n", + rr_cause_name(cause)); + + if (conn->ho) { + struct lchan_signal_data sig = { + .lchan = msg->lchan, + }; + osmo_signal_dispatch(SS_LCHAN, S_LCHAN_ASSIGNMENT_COMPL, &sig); + /* FIXME: release old channel */ + + /* send pending messages, if any */ + ho_dtap_cache_flush(conn, 1); + + return; + } + + if (conn->secondary_lchan != msg->lchan) { + LOGPLCHAN(msg->lchan, DRR, LOGL_ERROR, + "RR Assignment Complete does not match conn's secondary lchan.\n"); + return; + } + + /* swap channels */ + osmo_timer_del(&conn->T10); + + lchan_release(conn->lchan, 0, RSL_REL_LOCAL_END); + conn->lchan = conn->secondary_lchan; + conn->secondary_lchan = NULL; + + /* send pending messages, if any */ + ho_dtap_cache_flush(conn, 1); + + if (is_ipaccess_bts(conn_get_bts(conn)) && conn->lchan->tch_mode != GSM48_CMODE_SIGN) + rsl_ipacc_crcx(conn->lchan); + + api->assign_compl(conn, cause); +} + +static void handle_ass_fail(struct gsm_subscriber_connection *conn, + struct msgb *msg) +{ + struct bsc_api *api = conn->network->bsc_api; + uint8_t *rr_failure; + struct gsm48_hdr *gh; + + if (conn->ho) { + struct lchan_signal_data sig; + struct gsm48_hdr *gh = msgb_l3(msg); + + LOGPLCHAN(msg->lchan, DRR, LOGL_DEBUG, "ASSIGNMENT FAILED cause = %s\n", + rr_cause_name(gh->data[0])); + + sig.lchan = msg->lchan; + sig.mr = NULL; + osmo_signal_dispatch(SS_LCHAN, S_LCHAN_ASSIGNMENT_FAIL, &sig); + /* FIXME: release allocated new channel */ + + /* send pending messages, if any */ + ho_dtap_cache_flush(conn, 1); + + return; + } + + if (conn->lchan != msg->lchan) { + LOGPLCHAN(msg->lchan, DMSC, LOGL_ERROR, + "Assignment failure should occur on primary lchan.\n"); + return; + } + + /* stop the timer and release it */ + osmo_timer_del(&conn->T10); + if (conn->secondary_lchan) { + lchan_release(conn->secondary_lchan, 0, RSL_REL_LOCAL_END); + conn->secondary_lchan = NULL; + } + + /* send pending messages, if any */ + ho_dtap_cache_flush(conn, 1); + + gh = msgb_l3(msg); + if (msgb_l3len(msg) - sizeof(*gh) != 1) { + LOGPLCHAN(conn->lchan, DMSC, LOGL_ERROR, "assignment failure unhandled: %zu\n", + msgb_l3len(msg) - sizeof(*gh)); + rr_failure = NULL; + } else { + rr_failure = &gh->data[0]; + } + + api->assign_fail(conn, + GSM0808_CAUSE_RADIO_INTERFACE_MESSAGE_FAILURE, + rr_failure); +} + +static void handle_classmark_chg(struct gsm_subscriber_connection *conn, + struct msgb *msg) +{ + struct bsc_api *api = msg->lchan->ts->trx->bts->network->bsc_api; + struct gsm48_hdr *gh = msgb_l3(msg); + unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh); + uint8_t cm2_len, cm3_len = 0; + uint8_t *cm2, *cm3 = NULL; + + LOGPLCHAN(msg->lchan, DRR, LOGL_DEBUG, "CLASSMARK CHANGE "); + + /* classmark 2 */ + cm2_len = gh->data[0]; + cm2 = &gh->data[1]; + DEBUGPC(DRR, "CM2(len=%u) ", cm2_len); + + if (payload_len > cm2_len + 1) { + /* we must have a classmark3 */ + if (gh->data[cm2_len+1] != 0x20) { + DEBUGPC(DRR, "ERR CM3 TAG\n"); + return; + } + if (cm2_len > 3) { + DEBUGPC(DRR, "CM2 too long!\n"); + return; + } + + cm3_len = gh->data[cm2_len+2]; + cm3 = &gh->data[cm2_len+3]; + if (cm3_len > 14) { + DEBUGPC(DRR, "CM3 len %u too long!\n", cm3_len); + return; + } + DEBUGPC(DRR, "CM3(len=%u)\n", cm3_len); + } + api->classmark_chg(conn, cm2, cm2_len, cm3, cm3_len); +} + +/* Chapter 9.1.16 Handover complete */ +static void handle_rr_ho_compl(struct msgb *msg) +{ + struct lchan_signal_data sig; + struct gsm48_hdr *gh = msgb_l3(msg); + + LOGPLCHAN(msg->lchan, DRR, LOGL_DEBUG, + "HANDOVER COMPLETE cause = %s\n", rr_cause_name(gh->data[0])); + + sig.lchan = msg->lchan; + sig.mr = NULL; + osmo_signal_dispatch(SS_LCHAN, S_LCHAN_HANDOVER_COMPL, &sig); + /* FIXME: release old channel */ + + /* send pending messages, if any */ + ho_dtap_cache_flush(msg->lchan->conn, 1); +} + +/* Chapter 9.1.17 Handover Failure */ +static void handle_rr_ho_fail(struct msgb *msg) +{ + struct lchan_signal_data sig; + struct gsm48_hdr *gh = msgb_l3(msg); + + /* Log on both RR and HO categories: it is an RR message, but is still quite important when + * filtering on HO. */ + LOGPLCHAN(msg->lchan, DRR, LOGL_DEBUG, + "HANDOVER FAILED cause = %s\n", rr_cause_name(gh->data[0])); + LOGPLCHAN(msg->lchan, DHO, LOGL_DEBUG, + "HANDOVER FAILED cause = %s\n", rr_cause_name(gh->data[0])); + + sig.lchan = msg->lchan; + sig.mr = NULL; + osmo_signal_dispatch(SS_LCHAN, S_LCHAN_HANDOVER_FAIL, &sig); + /* FIXME: release allocated new channel */ + + /* send pending messages, if any */ + ho_dtap_cache_flush(msg->lchan->conn, 1); +} + + +static void dispatch_dtap(struct gsm_subscriber_connection *conn, + uint8_t link_id, struct msgb *msg) +{ + struct bsc_api *api = msg->lchan->ts->trx->bts->network->bsc_api; + struct gsm48_hdr *gh; + uint8_t pdisc; + uint8_t msg_type; + int rc; + + if (msgb_l3len(msg) < sizeof(*gh)) { + LOGP(DMSC, LOGL_ERROR, "(%s) Message too short for a GSM48 header.\n", + bsc_subscr_name(conn->bsub)); + return; + } + + gh = msgb_l3(msg); + pdisc = gsm48_hdr_pdisc(gh); + msg_type = gsm48_hdr_msg_type(gh); + + /* the idea is to handle all RR messages here, and only hand + * MM/CC/SMS-CP/LCS up to the MSC. Some messages like PAGING + * RESPONSE or CM SERVICE REQUEST will not be covered here, as + * they are only possible in the first L3 message of each L2 + * channel, i.e. 'conn' will not exist and gsm0408_rcvmsg() + * will call api->compl_l3() for it */ + switch (pdisc) { + case GSM48_PDISC_RR: + switch (msg_type) { + case GSM48_MT_RR_GPRS_SUSP_REQ: + LOGPLCHAN(msg->lchan, DRR, LOGL_DEBUG, + "%s\n", gsm48_rr_msg_name(GSM48_MT_RR_GPRS_SUSP_REQ)); + break; + case GSM48_MT_RR_STATUS: + LOGPLCHAN(msg->lchan, DRR, LOGL_NOTICE, + "%s (cause: %s)\n", gsm48_rr_msg_name(GSM48_MT_RR_STATUS), + rr_cause_name(gh->data[0])); + break; + case GSM48_MT_RR_MEAS_REP: + /* This shouldn't actually end up here, as RSL treats + * L3 Info of 08.58 MEASUREMENT REPORT different by calling + * directly into gsm48_parse_meas_rep */ + LOGPLCHAN(msg->lchan, DMEAS, LOGL_ERROR, + "DIRECT GSM48 MEASUREMENT REPORT ?!?\n"); + gsm48_tx_rr_status(conn, GSM48_RR_CAUSE_MSG_TYPE_N_COMPAT); + break; + case GSM48_MT_RR_HANDO_COMPL: + handle_rr_ho_compl(msg); + break; + case GSM48_MT_RR_HANDO_FAIL: + handle_rr_ho_fail(msg); + break; + case GSM48_MT_RR_CIPH_M_COMPL: + if (api->cipher_mode_compl) + api->cipher_mode_compl(conn, msg, + conn->lchan->encr.alg_id); + break; + case GSM48_MT_RR_ASS_COMPL: + handle_ass_compl(conn, msg); + break; + case GSM48_MT_RR_ASS_FAIL: + handle_ass_fail(conn, msg); + break; + case GSM48_MT_RR_CHAN_MODE_MODIF_ACK: + osmo_timer_del(&conn->T10); + rc = gsm48_rx_rr_modif_ack(msg); + if (rc < 0) { + api->assign_fail(conn, + GSM0808_CAUSE_NO_RADIO_RESOURCE_AVAILABLE, + NULL); + } else if (rc >= 0) { + api->assign_compl(conn, 0); + } + break; + case GSM48_MT_RR_CLSM_CHG: + handle_classmark_chg(conn, msg); + break; + case GSM48_MT_RR_APP_INFO: + /* Passing RR APP INFO to MSC, not quite + * according to spec */ + if (api->dtap) + api->dtap(conn, link_id, msg); + break; + default: + /* Drop unknown RR message */ + LOGPLCHAN(msg->lchan, DRR, LOGL_NOTICE, + "Dropping %s 04.08 RR message\n", gsm48_rr_msg_name(msg_type)); + gsm48_tx_rr_status(conn, GSM48_RR_CAUSE_MSG_TYPE_N); + break; + } + break; + default: + if (api->dtap) + api->dtap(conn, link_id, msg); + break; + } +} + +/*! \brief RSL has received a DATA INDICATION with L3 from MS */ +int gsm0408_rcvmsg(struct msgb *msg, uint8_t link_id) +{ + int rc; + struct bsc_api *api = msg->lchan->ts->trx->bts->network->bsc_api; + struct gsm_lchan *lchan; + + lchan = msg->lchan; + if (lchan->state != LCHAN_S_ACTIVE) { + LOGPLCHAN(msg->lchan, DRSL, LOGL_INFO, "Got data in non active state, discarding.\n"); + return -1; + } + + + if (lchan->conn) { + /* if we already have a connection, forward via DTAP to + * MSC */ + dispatch_dtap(lchan->conn, link_id, msg); + } else { + /* allocate a new connection */ + rc = BSC_API_CONN_POL_REJECT; + lchan->conn = bsc_subscr_con_allocate(msg->lchan->ts->trx->bts->network); + if (!lchan->conn) { + lchan_release(lchan, 1, RSL_REL_NORMAL); + return -1; + } + lchan->conn->lchan = lchan; + + /* fwd via bsc_api to send COMPLETE L3 INFO to MSC */ + rc = api->compl_l3(lchan->conn, msg, 0); + + if (rc != BSC_API_CONN_POL_ACCEPT) { + //osmo_fsm_inst_dispatch(lchan->conn->fi, FIXME, NULL); + } + } + + return 0; +} + +/*! \brief We received a GSM 08.08 CIPHER MODE from the MSC */ +int gsm0808_cipher_mode(struct gsm_subscriber_connection *conn, int cipher, + const uint8_t *key, int len, int include_imeisv) +{ + if (cipher > 0 && key == NULL) { + LOGP(DRSL, LOGL_ERROR, "%s: Need to have an encryption key.\n", + bsc_subscr_name(conn->bsub)); + return -1; + } + + if (len > MAX_A5_KEY_LEN) { + LOGP(DRSL, LOGL_ERROR, "%s: The key is too long: %d\n", + bsc_subscr_name(conn->bsub), len); + return -1; + } + + LOGP(DRSL, LOGL_DEBUG, "(subscr %s) Cipher Mode: cipher=%d key=%s include_imeisv=%d\n", + bsc_subscr_name(conn->bsub), cipher, osmo_hexdump_nospc(key, len), include_imeisv); + + conn->lchan->encr.alg_id = RSL_ENC_ALG_A5(cipher); + if (key) { + conn->lchan->encr.key_len = len; + memcpy(conn->lchan->encr.key, key, len); + } + + return gsm48_send_rr_ciph_mode(conn->lchan, include_imeisv); +} + +/* + * Release all occupied RF Channels but stay around for more. + */ +int gsm0808_clear(struct gsm_subscriber_connection *conn) +{ + if (conn->ho) + bsc_clear_handover(conn, 1); + + if (conn->secondary_lchan) + lchan_release(conn->secondary_lchan, 0, RSL_REL_LOCAL_END); + + if (conn->lchan) + lchan_release(conn->lchan, 1, RSL_REL_NORMAL); + + conn->lchan = NULL; + conn->secondary_lchan = NULL; + + osmo_timer_del(&conn->T10); + + return 0; +} + +static void send_sapi_reject(struct gsm_subscriber_connection *conn, int link_id) +{ + struct bsc_api *api; + + if (!conn) + return; + + api = conn->network->bsc_api; + if (!api || !api->sapi_n_reject) + return; + + api->sapi_n_reject(conn, link_id); +} + +static void rll_ind_cb(struct gsm_lchan *lchan, uint8_t link_id, void *_data, enum bsc_rllr_ind rllr_ind) +{ + struct msgb *msg = _data; + + /* + * There seems to be a small window that the RLL timer can + * fire after a lchan_release call and before the S_CHALLOC_FREED + * is called. Check if a conn is set before proceeding. + */ + if (!lchan->conn) + return; + + switch (rllr_ind) { + case BSC_RLLR_IND_EST_CONF: + rsl_data_request(msg, OBSC_LINKID_CB(msg)); + break; + case BSC_RLLR_IND_REL_IND: + case BSC_RLLR_IND_ERR_IND: + case BSC_RLLR_IND_TIMEOUT: + send_sapi_reject(lchan->conn, OBSC_LINKID_CB(msg)); + msgb_free(msg); + break; + } +} + +static int bsc_handle_lchan_signal(unsigned int subsys, unsigned int signal, + void *handler_data, void *signal_data) +{ + struct bsc_api *bsc; + struct gsm_lchan *lchan; + struct lchan_signal_data *lchan_data; + + if (subsys != SS_LCHAN) + return 0; + + + lchan_data = signal_data; + if (!lchan_data->lchan || !lchan_data->lchan->conn) + return 0; + + lchan = lchan_data->lchan; + bsc = lchan->ts->trx->bts->network->bsc_api; + if (!bsc) + return 0; + + switch (signal) { + case S_LCHAN_UNEXPECTED_RELEASE: + handle_release(lchan->conn, bsc, lchan); + break; + case S_LCHAN_ACTIVATE_ACK: + handle_chan_ack(lchan->conn, bsc, lchan); + break; + case S_LCHAN_ACTIVATE_NACK: + handle_chan_nack(lchan->conn, bsc, lchan); + break; + } + + return 0; +} + +static void handle_release(struct gsm_subscriber_connection *conn, + struct bsc_api *bsc, struct gsm_lchan *lchan) +{ + if (conn->secondary_lchan == lchan) { + osmo_timer_del(&conn->T10); + conn->secondary_lchan = NULL; + + bsc->assign_fail(conn, + GSM0808_CAUSE_RADIO_INTERFACE_FAILURE, + NULL); + } + + /* clear the connection now */ + if (bsc->clear_request) + bsc->clear_request(conn, 0); + + /* now give up all channels */ + if (conn->lchan == lchan) + conn->lchan = NULL; + if (conn->ho && conn->ho->new_lchan == lchan) + bsc_clear_handover(conn, 0); + lchan->conn = NULL; +} + +static void handle_chan_ack(struct gsm_subscriber_connection *conn, + struct bsc_api *api, struct gsm_lchan *lchan) +{ + if (conn->secondary_lchan != lchan) + return; + + LOGPLCHAN(lchan, DMSC, LOGL_NOTICE, "Sending RR Assignment\n"); + gsm48_send_rr_ass_cmd(conn->lchan, lchan, lchan->ms_power); +} + +static void handle_chan_nack(struct gsm_subscriber_connection *conn, + struct bsc_api *api, struct gsm_lchan *lchan) +{ + if (conn->secondary_lchan != lchan) + return; + + LOGPLCHAN(lchan, DMSC, LOGL_ERROR, "Channel activation failed. Waiting for timeout now\n"); + conn->secondary_lchan->conn = NULL; + conn->secondary_lchan = NULL; +} + +static __attribute__((constructor)) void on_dso_load_bsc(void) +{ + osmo_signal_register_handler(SS_LCHAN, bsc_handle_lchan_signal, NULL); +} diff --git a/src/osmo-bsc/bsc_ctrl_commands.c b/src/osmo-bsc/bsc_ctrl_commands.c new file mode 100644 index 000000000..171feaff0 --- /dev/null +++ b/src/osmo-bsc/bsc_ctrl_commands.c @@ -0,0 +1,500 @@ +/* + * (C) 2013-2015 by Holger Hans Peter Freyther + * (C) 2013-2015 by sysmocom s.f.m.c. GmbH + * + * 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 <errno.h> +#include <time.h> + +#include <osmocom/ctrl/control_cmd.h> +#include <osmocom/gsm/gsm48.h> +#include <osmocom/bsc/ipaccess.h> +#include <osmocom/bsc/gsm_data.h> +#include <osmocom/bsc/abis_nm.h> +#include <osmocom/bsc/debug.h> +#include <osmocom/bsc/chan_alloc.h> +#include <osmocom/bsc/osmo_bsc_rf.h> +#include <osmocom/bsc/bsc_msc_data.h> + +CTRL_CMD_DEFINE(net_mcc, "mcc"); +static int get_net_mcc(struct ctrl_cmd *cmd, void *_data) +{ + struct gsm_network *net = cmd->node; + cmd->reply = talloc_asprintf(cmd, "%s", osmo_mcc_name(net->plmn.mcc)); + if (!cmd->reply) { + cmd->reply = "OOM"; + return CTRL_CMD_ERROR; + } + return CTRL_CMD_REPLY; +} +static int set_net_mcc(struct ctrl_cmd *cmd, void *_data) +{ + struct gsm_network *net = cmd->node; + uint16_t mcc; + if (osmo_mcc_from_str(cmd->value, &mcc)) + return -1; + net->plmn.mcc = mcc; + return get_net_mcc(cmd, _data); +} +static int verify_net_mcc(struct ctrl_cmd *cmd, const char *value, void *_data) +{ + if (osmo_mcc_from_str(value, NULL)) + return -1; + return 0; +} + +CTRL_CMD_DEFINE(net_mnc, "mnc"); +static int get_net_mnc(struct ctrl_cmd *cmd, void *_data) +{ + struct gsm_network *net = cmd->node; + cmd->reply = talloc_asprintf(cmd, "%s", osmo_mnc_name(net->plmn.mnc, net->plmn.mnc_3_digits)); + if (!cmd->reply) { + cmd->reply = "OOM"; + return CTRL_CMD_ERROR; + } + return CTRL_CMD_REPLY; +} +static int set_net_mnc(struct ctrl_cmd *cmd, void *_data) +{ + struct gsm_network *net = cmd->node; + struct osmo_plmn_id plmn = net->plmn; + if (osmo_mnc_from_str(cmd->value, &plmn.mnc, &plmn.mnc_3_digits)) { + cmd->reply = "Error while decoding MNC"; + return CTRL_CMD_ERROR; + } + net->plmn = plmn; + return get_net_mnc(cmd, _data); +} +static int verify_net_mnc(struct ctrl_cmd *cmd, const char *value, void *_data) +{ + if (osmo_mnc_from_str(value, NULL, NULL)) + return -1; + return 0; +} + +static int set_net_apply_config(struct ctrl_cmd *cmd, void *data) +{ + struct gsm_network *net = cmd->node; + struct gsm_bts *bts; + + llist_for_each_entry(bts, &net->bts_list, list) { + if (!is_ipaccess_bts(bts)) + continue; + + /* + * The ip.access nanoBTS seems to be unrelaible on BSSGP + * so let's us just reboot it. For the sysmoBTS we can just + * restart the process as all state is gone. + */ + if (!is_sysmobts_v2(bts) && strcmp(cmd->value, "restart") == 0) { + struct gsm_bts_trx *trx; + llist_for_each_entry_reverse(trx, &bts->trx_list, list) + abis_nm_ipaccess_restart(trx); + } else + ipaccess_drop_oml(bts); + } + + cmd->reply = "Tried to drop the BTS"; + return CTRL_CMD_REPLY; +} + +CTRL_CMD_DEFINE_WO_NOVRF(net_apply_config, "apply-configuration"); + +static int verify_net_mcc_mnc_apply(struct ctrl_cmd *cmd, const char *value, void *d) +{ + char *tmp, *saveptr, *mcc, *mnc; + int rc = 0; + + tmp = talloc_strdup(cmd, value); + if (!tmp) + return 1; + + mcc = strtok_r(tmp, ",", &saveptr); + mnc = strtok_r(NULL, ",", &saveptr); + + if (osmo_mcc_from_str(mcc, NULL) || osmo_mnc_from_str(mnc, NULL, NULL)) + rc = -1; + + talloc_free(tmp); + return rc; +} + +static int set_net_mcc_mnc_apply(struct ctrl_cmd *cmd, void *data) +{ + struct gsm_network *net = cmd->node; + char *tmp, *saveptr, *mcc_str, *mnc_str; + struct osmo_plmn_id plmn; + + tmp = talloc_strdup(cmd, cmd->value); + if (!tmp) + goto oom; + + mcc_str = strtok_r(tmp, ",", &saveptr); + mnc_str = strtok_r(NULL, ",", &saveptr); + + if (osmo_mcc_from_str(mcc_str, &plmn.mcc)) { + cmd->reply = "Error while decoding MCC"; + talloc_free(tmp); + return CTRL_CMD_ERROR; + } + + if (osmo_mnc_from_str(mnc_str, &plmn.mnc, &plmn.mnc_3_digits)) { + cmd->reply = "Error while decoding MNC"; + talloc_free(tmp); + return CTRL_CMD_ERROR; + } + + talloc_free(tmp); + + if (!osmo_plmn_cmp(&net->plmn, &plmn)) { + cmd->reply = "Nothing changed"; + return CTRL_CMD_REPLY; + } + + net->plmn = plmn; + + return set_net_apply_config(cmd, data); + +oom: + cmd->reply = "OOM"; + return CTRL_CMD_ERROR; +} +CTRL_CMD_DEFINE_WO(net_mcc_mnc_apply, "mcc-mnc-apply"); + +/* BTS related commands below */ +CTRL_CMD_DEFINE_RANGE(bts_lac, "location-area-code", struct gsm_bts, location_area_code, 0, 65535); +CTRL_CMD_DEFINE_RANGE(bts_ci, "cell-identity", struct gsm_bts, cell_identity, 0, 65535); + +static int set_bts_apply_config(struct ctrl_cmd *cmd, void *data) +{ + struct gsm_bts *bts = cmd->node; + + if (!is_ipaccess_bts(bts)) { + cmd->reply = "BTS is not IP based"; + return CTRL_CMD_ERROR; + } + + ipaccess_drop_oml(bts); + cmd->reply = "Tried to drop the BTS"; + return CTRL_CMD_REPLY; +} + +CTRL_CMD_DEFINE_WO_NOVRF(bts_apply_config, "apply-configuration"); + +static int set_bts_si(struct ctrl_cmd *cmd, void *data) +{ + struct gsm_bts *bts = cmd->node; + int rc; + + rc = gsm_bts_set_system_infos(bts); + if (rc != 0) { + cmd->reply = "Failed to generate SI"; + return CTRL_CMD_ERROR; + } + + cmd->reply = "Generated new System Information"; + return CTRL_CMD_REPLY; +} +CTRL_CMD_DEFINE_WO_NOVRF(bts_si, "send-new-system-informations"); + +static int get_bts_chan_load(struct ctrl_cmd *cmd, void *data) +{ + int i; + struct pchan_load pl; + struct gsm_bts *bts; + const char *space = ""; + + bts = cmd->node; + memset(&pl, 0, sizeof(pl)); + bts_chan_load(&pl, bts); + + cmd->reply = talloc_strdup(cmd, ""); + + for (i = 0; i < ARRAY_SIZE(pl.pchan); ++i) { + const struct load_counter *lc = &pl.pchan[i]; + + /* These can never have user load */ + if (i == GSM_PCHAN_NONE) + continue; + if (i == GSM_PCHAN_CCCH) + continue; + if (i == GSM_PCHAN_PDCH) + continue; + if (i == GSM_PCHAN_UNKNOWN) + continue; + + cmd->reply = talloc_asprintf_append(cmd->reply, + "%s%s,%u,%u", + space, gsm_pchan_name(i), lc->used, lc->total); + if (!cmd->reply) + goto error; + space = " "; + } + + return CTRL_CMD_REPLY; + +error: + cmd->reply = "Memory allocation failure"; + return CTRL_CMD_ERROR; +} + +CTRL_CMD_DEFINE_RO(bts_chan_load, "channel-load"); + +static int get_bts_oml_conn(struct ctrl_cmd *cmd, void *data) +{ + const struct gsm_bts *bts = cmd->node; + + cmd->reply = get_model_oml_status(bts); + + return CTRL_CMD_REPLY; +} + +CTRL_CMD_DEFINE_RO(bts_oml_conn, "oml-connection-state"); + +static int get_bts_oml_up(struct ctrl_cmd *cmd, void *data) +{ + const struct gsm_bts *bts = cmd->node; + + cmd->reply = talloc_asprintf(cmd, "%llu", bts_uptime(bts)); + if (!cmd->reply) { + cmd->reply = "OOM"; + return CTRL_CMD_ERROR; + } + + return CTRL_CMD_REPLY; +} + +CTRL_CMD_DEFINE_RO(bts_oml_up, "oml-uptime"); + +static int verify_bts_gprs_mode(struct ctrl_cmd *cmd, const char *value, void *_data) +{ + int valid; + enum bts_gprs_mode mode; + struct gsm_bts *bts = cmd->node; + + mode = bts_gprs_mode_parse(value, &valid); + if (!valid) { + cmd->reply = "Mode is not known"; + return 1; + } + + if (!bts_gprs_mode_is_compat(bts, mode)) { + cmd->reply = "bts does not support this mode"; + return 1; + } + + return 0; +} + +static int get_bts_gprs_mode(struct ctrl_cmd *cmd, void *data) +{ + struct gsm_bts *bts = cmd->node; + + cmd->reply = talloc_strdup(cmd, bts_gprs_mode_name(bts->gprs.mode)); + return CTRL_CMD_REPLY; +} + +static int set_bts_gprs_mode(struct ctrl_cmd *cmd, void *data) +{ + struct gsm_bts *bts = cmd->node; + + bts->gprs.mode = bts_gprs_mode_parse(cmd->value, NULL); + return get_bts_gprs_mode(cmd, data); +} + +CTRL_CMD_DEFINE(bts_gprs_mode, "gprs-mode"); + +static int get_bts_rf_state(struct ctrl_cmd *cmd, void *data) +{ + const char *oper, *admin, *policy; + struct gsm_bts *bts = cmd->node; + + if (!bts) { + cmd->reply = "bts not found."; + return CTRL_CMD_ERROR; + } + + oper = osmo_bsc_rf_get_opstate_name(osmo_bsc_rf_get_opstate_by_bts(bts)); + admin = osmo_bsc_rf_get_adminstate_name(osmo_bsc_rf_get_adminstate_by_bts(bts)); + policy = osmo_bsc_rf_get_policy_name(osmo_bsc_rf_get_policy_by_bts(bts)); + + cmd->reply = talloc_asprintf(cmd, "%s,%s,%s", oper, admin, policy); + if (!cmd->reply) { + cmd->reply = "OOM."; + return CTRL_CMD_ERROR; + } + + return CTRL_CMD_REPLY; +} +CTRL_CMD_DEFINE_RO(bts_rf_state, "rf_state"); + +static int get_net_rf_lock(struct ctrl_cmd *cmd, void *data) +{ + struct gsm_network *net = cmd->node; + struct gsm_bts *bts; + const char *policy_name; + + policy_name = osmo_bsc_rf_get_policy_name(net->bsc_data->rf_ctrl->policy); + + llist_for_each_entry(bts, &net->bts_list, list) { + struct gsm_bts_trx *trx; + + /* Exclude the BTS from the global lock */ + if (bts->excl_from_rf_lock) + continue; + + llist_for_each_entry(trx, &bts->trx_list, list) { + if (trx->mo.nm_state.availability == NM_AVSTATE_OK && + trx->mo.nm_state.operational != NM_OPSTATE_DISABLED) { + cmd->reply = talloc_asprintf(cmd, + "state=on,policy=%s,bts=%u,trx=%u", + policy_name, bts->nr, trx->nr); + return CTRL_CMD_REPLY; + } + } + } + + cmd->reply = talloc_asprintf(cmd, "state=off,policy=%s", + policy_name); + return CTRL_CMD_REPLY; +} + +#define TIME_FORMAT_RFC2822 "%a, %d %b %Y %T %z" + +static int set_net_rf_lock(struct ctrl_cmd *cmd, void *data) +{ + int locked = atoi(cmd->value); + struct gsm_network *net = cmd->node; + time_t now = time(NULL); + char now_buf[64]; + struct osmo_bsc_rf *rf; + + if (!net) { + cmd->reply = "net not found."; + return CTRL_CMD_ERROR; + } + + rf = net->bsc_data->rf_ctrl; + + if (!rf) { + cmd->reply = "RF Ctrl is not enabled in the BSC Configuration"; + return CTRL_CMD_ERROR; + } + + talloc_free(rf->last_rf_lock_ctrl_command); + strftime(now_buf, sizeof(now_buf), TIME_FORMAT_RFC2822, gmtime(&now)); + rf->last_rf_lock_ctrl_command = + talloc_asprintf(rf, "rf_locked %u (%s)", locked, now_buf); + + osmo_bsc_rf_schedule_lock(rf, locked == 1 ? '0' : '1'); + + cmd->reply = talloc_asprintf(cmd, "%u", locked); + if (!cmd->reply) { + cmd->reply = "OOM."; + return CTRL_CMD_ERROR; + } + + return CTRL_CMD_REPLY; +} + +static int verify_net_rf_lock(struct ctrl_cmd *cmd, const char *value, void *data) +{ + int locked = atoi(cmd->value); + + if ((locked != 0) && (locked != 1)) + return 1; + + return 0; +} +CTRL_CMD_DEFINE(net_rf_lock, "rf_locked"); + +static int get_net_bts_num(struct ctrl_cmd *cmd, void *data) +{ + struct gsm_network *net = cmd->node; + + cmd->reply = talloc_asprintf(cmd, "%u", net->num_bts); + return CTRL_CMD_REPLY; +} +CTRL_CMD_DEFINE_RO(net_bts_num, "number-of-bts"); + +/* TRX related commands below here */ +CTRL_HELPER_GET_INT(trx_max_power, struct gsm_bts_trx, max_power_red); +static int verify_trx_max_power(struct ctrl_cmd *cmd, const char *value, void *_data) +{ + int tmp = atoi(value); + + if (tmp < 0 || tmp > 22) { + cmd->reply = "Value must be between 0 and 22"; + return -1; + } + + if (tmp & 1) { + cmd->reply = "Value must be even"; + return -1; + } + + return 0; +} +CTRL_CMD_DEFINE_RANGE(trx_arfcn, "arfcn", struct gsm_bts_trx, arfcn, 0, 1023); + +static int set_trx_max_power(struct ctrl_cmd *cmd, void *_data) +{ + struct gsm_bts_trx *trx = cmd->node; + int old_power; + + /* remember the old value, set the new one */ + old_power = trx->max_power_red; + trx->max_power_red = atoi(cmd->value); + + /* Maybe update the value */ + if (old_power != trx->max_power_red) { + LOGP(DCTRL, LOGL_NOTICE, + "%s updating max_pwr_red(%d)\n", + gsm_trx_name(trx), trx->max_power_red); + abis_nm_update_max_power_red(trx); + } + + return get_trx_max_power(cmd, _data); +} +CTRL_CMD_DEFINE(trx_max_power, "max-power-reduction"); + +int bsc_base_ctrl_cmds_install(void) +{ + int rc = 0; + rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_net_mnc); + rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_net_mcc); + rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_net_apply_config); + rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_net_mcc_mnc_apply); + rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_net_rf_lock); + rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_net_bts_num); + + rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_lac); + rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_ci); + rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_apply_config); + rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_si); + rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_chan_load); + rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_oml_conn); + rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_oml_up); + rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_gprs_mode); + rc |= ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_rf_state); + + rc |= ctrl_cmd_install(CTRL_NODE_TRX, &cmd_trx_max_power); + rc |= ctrl_cmd_install(CTRL_NODE_TRX, &cmd_trx_arfcn); + + return rc; +} diff --git a/src/osmo-bsc/bsc_ctrl_lookup.c b/src/osmo-bsc/bsc_ctrl_lookup.c new file mode 100644 index 000000000..38d1ba4ea --- /dev/null +++ b/src/osmo-bsc/bsc_ctrl_lookup.c @@ -0,0 +1,123 @@ +/* SNMP-like status interface. Look-up of BTS/TRX/MSC + * + * (C) 2010-2011 by Daniel Willmann <daniel@totalueberwachung.de> + * (C) 2010-2011 by On-Waves + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 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 General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#include <errno.h> + +#include <osmocom/vty/command.h> +#include <osmocom/ctrl/control_if.h> +#include <osmocom/bsc/ctrl.h> +#include <osmocom/bsc/debug.h> +#include <osmocom/bsc/gsm_data.h> +#include <osmocom/bsc/bsc_msc_data.h> + +extern vector ctrl_node_vec; + +/*! \brief control interface lookup function for bsc/bts/msc gsm_data + * \param[in] data Private data passed to controlif_setup() + * \param[in] vline Vector of the line holding the command string + * \param[out] node_type type (CTRL_NODE_) that was determined + * \param[out] node_data private dta of node that was determined + * \param i Current index into vline, up to which it is parsed + */ +static int bsc_ctrl_node_lookup(void *data, vector vline, int *node_type, + void **node_data, int *i) +{ + struct gsm_network *net = data; + struct gsm_bts *bts = NULL; + struct gsm_bts_trx *trx = NULL; + struct gsm_bts_trx_ts *ts = NULL; + struct bsc_msc_data *msc = NULL; + char *token = vector_slot(vline, *i); + long num; + + /* TODO: We need to make sure that the following chars are digits + * and/or use strtol to check if number conversion was successful + * Right now something like net.bts_stats will not work */ + if (!strcmp(token, "bts")) { + if (*node_type != CTRL_NODE_ROOT || !net) + goto err_missing; + (*i)++; + if (!ctrl_parse_get_num(vline, *i, &num)) + goto err_index; + + bts = gsm_bts_num(net, num); + if (!bts) + goto err_missing; + *node_data = bts; + *node_type = CTRL_NODE_BTS; + } else if (!strcmp(token, "trx")) { + if (*node_type != CTRL_NODE_BTS || !*node_data) + goto err_missing; + bts = *node_data; + (*i)++; + if (!ctrl_parse_get_num(vline, *i, &num)) + goto err_index; + + trx = gsm_bts_trx_num(bts, num); + if (!trx) + goto err_missing; + *node_data = trx; + *node_type = CTRL_NODE_TRX; + } else if (!strcmp(token, "ts")) { + if (*node_type != CTRL_NODE_TRX || !*node_data) + goto err_missing; + trx = *node_data; + (*i)++; + if (!ctrl_parse_get_num(vline, *i, &num)) + goto err_index; + + if ((num >= 0) && (num < TRX_NR_TS)) + ts = &trx->ts[num]; + if (!ts) + goto err_missing; + *node_data = ts; + *node_type = CTRL_NODE_TS; + } else if (!strcmp(token, "msc")) { + if (*node_type != CTRL_NODE_ROOT || !net) + goto err_missing; + (*i)++; + if (!ctrl_parse_get_num(vline, *i, &num)) + goto err_index; + + msc = osmo_msc_data_find(net, num); + if (!msc) + goto err_missing; + *node_data = msc; + *node_type = CTRL_NODE_MSC; + } else + return 0; + + return 1; +err_missing: + return -ENODEV; +err_index: + return -ERANGE; +} + +struct ctrl_handle *bsc_controlif_setup(struct gsm_network *net, + const char *bind_addr, uint16_t port) +{ + return ctrl_interface_setup_dynip2(net, bind_addr, port, + bsc_ctrl_node_lookup, + _LAST_CTRL_NODE_BSC); +} diff --git a/src/osmo-bsc/bsc_dyn_ts.c b/src/osmo-bsc/bsc_dyn_ts.c new file mode 100644 index 000000000..ed7caed7f --- /dev/null +++ b/src/osmo-bsc/bsc_dyn_ts.c @@ -0,0 +1,60 @@ +/* Dynamic PDCH initialisation implementation shared across NM and RSL */ + +/* (C) 2016 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 <osmocom/core/logging.h> +#include <osmocom/bsc/debug.h> +#include <osmocom/bsc/gsm_data.h> +#include <osmocom/bsc/abis_rsl.h> + +static void tchf_pdch_ts_init(struct gsm_bts_trx_ts *ts) +{ + int rc; + + rc = rsl_ipacc_pdch_activate(ts, 1); + if (rc != 0 && rc != -ENOTSUP) + LOGP(DRSL, LOGL_ERROR, "%s %s: PDCH ACT failed\n", + gsm_ts_name(ts), gsm_pchan_name(ts->pchan)); +} + +static void tchf_tchh_pdch_ts_init(struct gsm_bts_trx_ts *ts) +{ + dyn_ts_switchover_start(ts, GSM_PCHAN_PDCH); +} + +void dyn_ts_init(struct gsm_bts_trx_ts *ts) +{ + /* Clear all TCH/F_PDCH flags */ + ts->flags &= ~(TS_F_PDCH_PENDING_MASK | TS_F_PDCH_ACTIVE); + + /* Clear TCH/F_TCH/H_PDCH state */ + ts->dyn.pchan_is = ts->dyn.pchan_want = GSM_PCHAN_NONE; + ts->dyn.pending_chan_activ = NULL; + + switch (ts->pchan) { + case GSM_PCHAN_TCH_F_PDCH: + tchf_pdch_ts_init(ts); + break; + case GSM_PCHAN_TCH_F_TCH_H_PDCH: + tchf_tchh_pdch_ts_init(ts); + break; + default: + break; + } +} diff --git a/src/osmo-bsc/bsc_init.c b/src/osmo-bsc/bsc_init.c new file mode 100644 index 000000000..b6bd41025 --- /dev/null +++ b/src/osmo-bsc/bsc_init.c @@ -0,0 +1,288 @@ +/* A hackish minimal BSC (+MSC +HLR) implementation */ + +/* (C) 2008-2018 by Harald Welte <laforge@gnumonks.org> + * (C) 2009 by Holger Hans Peter Freyther <zecke@selfish.org> + * 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 <osmocom/bsc/gsm_data.h> +#include <osmocom/gsm/gsm_utils.h> +#include <osmocom/bsc/abis_rsl.h> +#include <osmocom/bsc/debug.h> +#include <osmocom/bsc/misdn.h> +#include <osmocom/bsc/system_information.h> +#include <osmocom/bsc/paging.h> +#include <osmocom/bsc/signal.h> +#include <osmocom/bsc/chan_alloc.h> +#include <osmocom/core/talloc.h> +#include <osmocom/bsc/ipaccess.h> +#include <osmocom/gsm/sysinfo.h> +#include <osmocom/bsc/pcu_if.h> +#include <osmocom/bsc/bsc_msc_data.h> +#include <osmocom/bsc/handover_cfg.h> +#include <osmocom/bsc/gsm_04_08_utils.h> + +#include <time.h> +#include <limits.h> +#include <stdbool.h> + +int bsc_shutdown_net(struct gsm_network *net) +{ + struct gsm_bts *bts; + + llist_for_each_entry(bts, &net->bts_list, list) { + LOGP(DNM, LOGL_NOTICE, "shutting down OML for BTS %u\n", bts->nr); + osmo_signal_dispatch(SS_L_GLOBAL, S_GLOBAL_BTS_CLOSE_OM, bts); + } + + return 0; +} + +unsigned long long bts_uptime(const struct gsm_bts *bts) +{ + struct timespec tp; + + if (!bts->uptime || !bts->oml_link) { + LOGP(DNM, LOGL_ERROR, "BTS %u OML link uptime unavailable\n", bts->nr); + return 0; + } + + if (clock_gettime(CLOCK_MONOTONIC, &tp) != 0) { + LOGP(DNM, LOGL_ERROR, "BTS %u uptime computation failure: %s\n", bts->nr, strerror(errno)); + return 0; + } + + /* monotonic clock helps to ensure that the conversion is valid */ + return difftime(tp.tv_sec, bts->uptime); +} + +static int rsl_si(struct gsm_bts_trx *trx, enum osmo_sysinfo_type i, int si_len) +{ + struct gsm_bts *bts = trx->bts; + int rc, j; + + if (si_len) { + DEBUGP(DRR, "SI%s: %s\n", get_value_string(osmo_sitype_strs, i), + osmo_hexdump(GSM_BTS_SI(bts, i), GSM_MACBLOCK_LEN)); + } else + DEBUGP(DRR, "SI%s: OFF\n", get_value_string(osmo_sitype_strs, i)); + + switch (i) { + case SYSINFO_TYPE_5: + case SYSINFO_TYPE_5bis: + case SYSINFO_TYPE_5ter: + case SYSINFO_TYPE_6: + rc = rsl_sacch_filling(trx, osmo_sitype2rsl(i), + si_len ? GSM_BTS_SI(bts, i) : NULL, si_len); + break; + case SYSINFO_TYPE_2quater: + if (si_len == 0) { + rc = rsl_bcch_info(trx, i, NULL, 0); + break; + } + rc = 0; + for (j = 0; j <= bts->si2q_count; j++) + rc = rsl_bcch_info(trx, i, (const uint8_t *)GSM_BTS_SI2Q(bts, j), GSM_MACBLOCK_LEN); + break; + default: + rc = rsl_bcch_info(trx, i, si_len ? GSM_BTS_SI(bts, i) : NULL, si_len); + break; + } + + return rc; +} + +/* set all system information types for a TRX */ +int gsm_bts_trx_set_system_infos(struct gsm_bts_trx *trx) +{ + int i, rc; + struct gsm_bts *bts = trx->bts; + uint8_t gen_si[_MAX_SYSINFO_TYPE], n_si = 0, n; + int si_len[_MAX_SYSINFO_TYPE]; + + bts->si_common.cell_sel_par.ms_txpwr_max_ccch = + ms_pwr_ctl_lvl(bts->band, bts->ms_max_power); + bts->si_common.cell_sel_par.neci = bts->network->neci; + + /* Zero/forget the state of the dynamically computed SIs, leeping the static ones */ + bts->si_valid = bts->si_mode_static; + + /* First, we determine which of the SI messages we actually need */ + + if (trx == bts->c0) { + /* 1...4 are always present on a C0 TRX */ + gen_si[n_si++] = SYSINFO_TYPE_1; + gen_si[n_si++] = SYSINFO_TYPE_2; + gen_si[n_si++] = SYSINFO_TYPE_2bis; + gen_si[n_si++] = SYSINFO_TYPE_2ter; + gen_si[n_si++] = SYSINFO_TYPE_2quater; + gen_si[n_si++] = SYSINFO_TYPE_3; + gen_si[n_si++] = SYSINFO_TYPE_4; + + /* 13 is always present on a C0 TRX of a GPRS BTS */ + if (bts->gprs.mode != BTS_GPRS_NONE) + gen_si[n_si++] = SYSINFO_TYPE_13; + } + + /* 5 and 6 are always present on every TRX */ + gen_si[n_si++] = SYSINFO_TYPE_5; + gen_si[n_si++] = SYSINFO_TYPE_5bis; + gen_si[n_si++] = SYSINFO_TYPE_5ter; + gen_si[n_si++] = SYSINFO_TYPE_6; + + /* Second, we generate the selected SI via RSL */ + + for (n = 0; n < n_si; n++) { + i = gen_si[n]; + /* Only generate SI if this SI is not in "static" (user-defined) mode */ + if (!(bts->si_mode_static & (1 << i))) { + /* Set SI as being valid. gsm_generate_si() might unset + * it, if SI is not required. */ + bts->si_valid |= (1 << i); + rc = gsm_generate_si(bts, i); + if (rc < 0) + goto err_out; + si_len[i] = rc; + } else { + if (i == SYSINFO_TYPE_5 || i == SYSINFO_TYPE_5bis + || i == SYSINFO_TYPE_5ter) + si_len[i] = 18; + else if (i == SYSINFO_TYPE_6) + si_len[i] = 11; + else + si_len[i] = 23; + } + } + + /* Third, we send the selected SI via RSL */ + + for (n = 0; n < n_si; n++) { + i = gen_si[n]; + /* if we don't currently have this SI, we send a zero-length + * RSL BCCH FILLING / SACCH FILLING * in order to deactivate + * the SI, in case it might have previously been active */ + if (!GSM_BTS_HAS_SI(bts, i)) + rc = rsl_si(trx, i, 0); + else + rc = rsl_si(trx, i, si_len[i]); + if (rc < 0) + return rc; + } + + /* Make sure the PCU is aware (in case anything GPRS related has + * changed in SI */ + pcu_info_update(bts); + + return 0; +err_out: + LOGP(DRR, LOGL_ERROR, "Cannot generate SI%s for BTS %u: error <%s>, " + "most likely a problem with neighbor cell list generation\n", + get_value_string(osmo_sitype_strs, i), bts->nr, strerror(-rc)); + return rc; +} + +/* set all system information types for a BTS */ +int gsm_bts_set_system_infos(struct gsm_bts *bts) +{ + struct gsm_bts_trx *trx; + + /* Generate a new ID */ + bts->bcch_change_mark += 1; + bts->bcch_change_mark %= 0x7; + + llist_for_each_entry(trx, &bts->trx_list, list) { + int rc; + + rc = gsm_bts_trx_set_system_infos(trx); + if (rc != 0) + return rc; + } + + return 0; +} + +/* XXX hard-coded for now */ +#define T3122_CHAN_LOAD_SAMPLE_INTERVAL 1 /* in seconds */ + +static void update_t3122_chan_load_timer(void *data) +{ + struct gsm_network *net = data; + struct gsm_bts *bts; + + llist_for_each_entry(bts, &net->bts_list, list) + bts_update_t3122_chan_load(bts); + + /* Keep this timer ticking. */ + osmo_timer_schedule(&net->t3122_chan_load_timer, T3122_CHAN_LOAD_SAMPLE_INTERVAL, 0); +} + +static struct gsm_network *bsc_network_init(void *ctx) +{ + struct gsm_network *net = gsm_network_init(ctx); + + net->bsc_data = talloc_zero(net, struct osmo_bsc_data); + if (!net->bsc_data) { + talloc_free(net); + return NULL; + } + + /* Init back pointer */ + net->bsc_data->auto_off_timeout = -1; + net->bsc_data->network = net; + INIT_LLIST_HEAD(&net->bsc_data->mscs); + + net->ho = ho_cfg_init(net, NULL); + net->hodec2.congestion_check_interval_s = HO_CFG_CONGESTION_CHECK_DEFAULT; + + /* init statistics */ + net->bsc_ctrs = rate_ctr_group_alloc(net, &bsc_ctrg_desc, 0); + if (!net->bsc_ctrs) { + talloc_free(net); + return NULL; + } + + gsm_net_update_ctype(net); + + /* + * At present all BTS in the network share one channel load timeout. + * If this becomes a problem for networks with a lot of BTS, this + * code could be refactored to run the timeout individually per BTS. + */ + osmo_timer_setup(&net->t3122_chan_load_timer, update_t3122_chan_load_timer, net); + osmo_timer_schedule(&net->t3122_chan_load_timer, T3122_CHAN_LOAD_SAMPLE_INTERVAL, 0); + + return net; +} + +int bsc_network_alloc(void) +{ + /* initialize our data structures */ + bsc_gsmnet = bsc_network_init(tall_bsc_ctx); + if (!bsc_gsmnet) + return -ENOMEM; + + return 0; +} + +struct gsm_bts *bsc_bts_alloc_register(struct gsm_network *net, enum gsm_bts_type type, uint8_t bsic) +{ + struct gsm_bts *bts = gsm_bts_alloc_register(net, type, bsic); + + bts->ho = ho_cfg_init(bts, net->ho); + + return bts; +} diff --git a/src/osmo-bsc/bsc_rf_ctrl.c b/src/osmo-bsc/bsc_rf_ctrl.c new file mode 100644 index 000000000..f4a21b53a --- /dev/null +++ b/src/osmo-bsc/bsc_rf_ctrl.c @@ -0,0 +1,534 @@ +/* RF Ctl handling socket */ + +/* (C) 2010 by Harald Welte <laforge@gnumonks.org> + * (C) 2010-2014 by Holger Hans Peter Freyther <zecke@selfish.org> + * (C) 2010-2014 by On-Waves + * 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 <osmocom/bsc/osmo_bsc_rf.h> +#include <osmocom/bsc/debug.h> +#include <osmocom/bsc/gsm_data.h> +#include <osmocom/bsc/signal.h> +#include <osmocom/bsc/bsc_msc_data.h> +#include <osmocom/bsc/ipaccess.h> + +#include <osmocom/core/talloc.h> +#include <osmocom/core/utils.h> +#include <osmocom/gsm/protocol/gsm_12_21.h> + +#include <sys/socket.h> +#include <sys/un.h> + +#include <errno.h> +#include <unistd.h> + +#define RF_CMD_QUERY '?' +#define RF_CMD_OFF '0' +#define RF_CMD_ON '1' +#define RF_CMD_D_OFF 'd' +#define RF_CMD_ON_G 'g' + +static const struct value_string opstate_names[] = { + { OSMO_BSC_RF_OPSTATE_INOPERATIONAL, "inoperational" }, + { OSMO_BSC_RF_OPSTATE_OPERATIONAL, "operational" }, + { 0, NULL } +}; + +static const struct value_string adminstate_names[] = { + { OSMO_BSC_RF_ADMINSTATE_UNLOCKED, "unlocked" }, + { OSMO_BSC_RF_ADMINSTATE_LOCKED, "locked" }, + { 0, NULL } +}; + +static const struct value_string policy_names[] = { + { OSMO_BSC_RF_POLICY_OFF, "off" }, + { OSMO_BSC_RF_POLICY_ON, "on" }, + { OSMO_BSC_RF_POLICY_GRACE, "grace" }, + { OSMO_BSC_RF_POLICY_UNKNOWN, "unknown" }, + { 0, NULL } +}; + +const char *osmo_bsc_rf_get_opstate_name(enum osmo_bsc_rf_opstate opstate) +{ + return get_value_string(opstate_names, opstate); +} + +const char *osmo_bsc_rf_get_adminstate_name(enum osmo_bsc_rf_adminstate adminstate) +{ + return get_value_string(adminstate_names, adminstate); +} + +const char *osmo_bsc_rf_get_policy_name(enum osmo_bsc_rf_policy policy) +{ + return get_value_string(policy_names, policy); +} + +enum osmo_bsc_rf_opstate osmo_bsc_rf_get_opstate_by_bts(struct gsm_bts *bts) +{ + struct gsm_bts_trx *trx; + + llist_for_each_entry(trx, &bts->trx_list, list) { + if (trx->mo.nm_state.operational == NM_OPSTATE_ENABLED) + return OSMO_BSC_RF_OPSTATE_OPERATIONAL; + } + + /* No trx were active, so this bts is disabled */ + return OSMO_BSC_RF_OPSTATE_INOPERATIONAL; +} + +enum osmo_bsc_rf_adminstate osmo_bsc_rf_get_adminstate_by_bts(struct gsm_bts *bts) +{ + struct gsm_bts_trx *trx; + + llist_for_each_entry(trx, &bts->trx_list, list) { + if (trx->mo.nm_state.administrative == NM_STATE_UNLOCKED) + return OSMO_BSC_RF_ADMINSTATE_UNLOCKED; + } + + /* All trx administrative states were locked */ + return OSMO_BSC_RF_ADMINSTATE_LOCKED; +} + +enum osmo_bsc_rf_policy osmo_bsc_rf_get_policy_by_bts(struct gsm_bts *bts) +{ + struct osmo_bsc_data *bsc_data = bts->network->bsc_data; + + if (!bsc_data) + return OSMO_BSC_RF_POLICY_UNKNOWN; + + switch (bsc_data->rf_ctrl->policy) { + case S_RF_ON: + return OSMO_BSC_RF_POLICY_ON; + case S_RF_OFF: + return OSMO_BSC_RF_POLICY_OFF; + case S_RF_GRACE: + return OSMO_BSC_RF_POLICY_GRACE; + default: + return OSMO_BSC_RF_POLICY_UNKNOWN; + } +} + +static int lock_each_trx(struct gsm_network *net, bool lock) +{ + struct gsm_bts *bts; + + llist_for_each_entry(bts, &net->bts_list, list) { + struct gsm_bts_trx *trx; + + /* Exclude the BTS from the global lock */ + if (bts->excl_from_rf_lock) { + LOGP(DLINP, LOGL_DEBUG, + "Excluding BTS(%d) from trx lock.\n", bts->nr); + continue; + } + + llist_for_each_entry(trx, &bts->trx_list, list) { + gsm_trx_lock_rf(trx, lock, "ctrl"); + } + } + + return 0; +} + +static void send_resp(struct osmo_bsc_rf_conn *conn, char send) +{ + struct msgb *msg; + + msg = msgb_alloc(10, "RF Query"); + if (!msg) { + LOGP(DLINP, LOGL_ERROR, "Failed to allocate response msg.\n"); + return; + } + + msg->l2h = msgb_put(msg, 1); + msg->l2h[0] = send; + + if (osmo_wqueue_enqueue(&conn->queue, msg) != 0) { + LOGP(DLINP, LOGL_ERROR, "Failed to enqueue the answer.\n"); + msgb_free(msg); + return; + } + + return; +} + + +/* + * Send a + * 'g' when we are in grace mode + * '1' when one TRX is online, + * '0' otherwise + */ +static void handle_query(struct osmo_bsc_rf_conn *conn) +{ + struct gsm_bts *bts; + char send = RF_CMD_OFF; + + if (conn->rf->policy == S_RF_GRACE) + return send_resp(conn, RF_CMD_ON_G); + + llist_for_each_entry(bts, &conn->rf->gsm_network->bts_list, list) { + struct gsm_bts_trx *trx; + + /* Exclude the BTS from the global lock */ + if (bts->excl_from_rf_lock) { + LOGP(DLINP, LOGL_DEBUG, + "Excluding BTS(%d) from query.\n", bts->nr); + continue; + } + llist_for_each_entry(trx, &bts->trx_list, list) { + if (trx->mo.nm_state.availability == NM_AVSTATE_OK && + trx->mo.nm_state.operational != NM_OPSTATE_DISABLED) { + send = RF_CMD_ON; + break; + } + } + } + + send_resp(conn, send); +} + +static void rf_check_cb(void *_data) +{ + struct gsm_bts *bts; + struct osmo_bsc_rf *rf = _data; + + llist_for_each_entry(bts, &rf->gsm_network->bts_list, list) { + struct gsm_bts_trx *trx; + + /* don't bother to check a booting or missing BTS */ + if (!bts->oml_link || !is_ipaccess_bts(bts)) + continue; + + /* Exclude the BTS from the global lock */ + if (bts->excl_from_rf_lock) { + LOGP(DLINP, LOGL_DEBUG, + "Excluding BTS(%d) from query.\n", bts->nr); + continue; + } + + llist_for_each_entry(trx, &bts->trx_list, list) { + if (trx->mo.nm_state.availability != NM_AVSTATE_OK || + trx->mo.nm_state.operational != NM_OPSTATE_ENABLED || + trx->mo.nm_state.administrative != NM_STATE_UNLOCKED) { + LOGP(DNM, LOGL_ERROR, "RF activation failed. Starting again.\n"); + ipaccess_drop_oml(bts); + break; + } + } + } +} + +static void send_signal(struct osmo_bsc_rf *rf, int val) +{ + struct rf_signal_data sig; + sig.net = rf->gsm_network; + + rf->policy = val; + osmo_signal_dispatch(SS_RF, val, &sig); +} + +static int switch_rf_off(struct osmo_bsc_rf *rf) +{ + lock_each_trx(rf->gsm_network, true); + send_signal(rf, S_RF_OFF); + + return 0; +} + +static void grace_timeout(void *_data) +{ + struct osmo_bsc_rf *rf = (struct osmo_bsc_rf *) _data; + + LOGP(DLINP, LOGL_NOTICE, "Grace timeout. Going to disable all BTS/TRX.\n"); + switch_rf_off(rf); +} + +static int enter_grace(struct osmo_bsc_rf *rf) +{ + if (osmo_timer_pending(&rf->grace_timeout)) { + LOGP(DLINP, LOGL_NOTICE, "RF Grace timer is pending. Not restarting.\n"); + return 0; + } + + osmo_timer_setup(&rf->grace_timeout, grace_timeout, rf); + osmo_timer_schedule(&rf->grace_timeout, rf->gsm_network->bsc_data->mid_call_timeout, 0); + LOGP(DLINP, LOGL_NOTICE, "Going to switch RF off in %d seconds.\n", + rf->gsm_network->bsc_data->mid_call_timeout); + + send_signal(rf, S_RF_GRACE); + return 0; +} + +static void rf_delay_cmd_cb(void *data) +{ + struct osmo_bsc_rf *rf = data; + + switch (rf->last_request) { + case RF_CMD_D_OFF: + rf->last_state_command = "RF Direct Off"; + osmo_timer_del(&rf->rf_check); + osmo_timer_del(&rf->grace_timeout); + switch_rf_off(rf); + break; + case RF_CMD_ON: + rf->last_state_command = "RF Direct On"; + osmo_timer_del(&rf->grace_timeout); + lock_each_trx(rf->gsm_network, false); + send_signal(rf, S_RF_ON); + osmo_timer_schedule(&rf->rf_check, 3, 0); + break; + case RF_CMD_OFF: + rf->last_state_command = "RF Scheduled Off"; + osmo_timer_del(&rf->rf_check); + enter_grace(rf); + break; + } +} + +static int rf_read_cmd(struct osmo_fd *fd) +{ + struct osmo_bsc_rf_conn *conn = fd->data; + char buf[1]; + int rc; + + rc = read(fd->fd, buf, sizeof(buf)); + if (rc != sizeof(buf)) { + LOGP(DLINP, LOGL_ERROR, "Short read %d/%s\n", errno, strerror(errno)); + osmo_fd_unregister(fd); + close(fd->fd); + osmo_wqueue_clear(&conn->queue); + talloc_free(conn); + return -1; + } + + switch (buf[0]) { + case RF_CMD_QUERY: + handle_query(conn); + break; + case RF_CMD_D_OFF: + case RF_CMD_ON: + case RF_CMD_OFF: + osmo_bsc_rf_schedule_lock(conn->rf, buf[0]); + break; + default: + conn->rf->last_state_command = "Unknown command"; + LOGP(DLINP, LOGL_ERROR, "Unknown command %d\n", buf[0]); + break; + } + + return 0; +} + +static int rf_write_cmd(struct osmo_fd *fd, struct msgb *msg) +{ + int rc; + + rc = write(fd->fd, msg->data, msg->len); + if (rc != msg->len) { + LOGP(DLINP, LOGL_ERROR, "Short write %d/%s\n", errno, strerror(errno)); + return -1; + } + + return 0; +} + +static int rf_ctrl_accept(struct osmo_fd *bfd, unsigned int what) +{ + struct osmo_bsc_rf_conn *conn; + struct osmo_bsc_rf *rf = bfd->data; + struct sockaddr_un addr; + socklen_t len = sizeof(addr); + int fd; + + fd = accept(bfd->fd, (struct sockaddr *) &addr, &len); + if (fd < 0) { + LOGP(DLINP, LOGL_ERROR, "Failed to accept. errno: %d/%s\n", + errno, strerror(errno)); + return -1; + } + + conn = talloc_zero(rf, struct osmo_bsc_rf_conn); + if (!conn) { + LOGP(DLINP, LOGL_ERROR, "Failed to allocate mem.\n"); + close(fd); + return -1; + } + + osmo_wqueue_init(&conn->queue, 10); + conn->queue.bfd.data = conn; + conn->queue.bfd.fd = fd; + conn->queue.bfd.when = BSC_FD_READ | BSC_FD_WRITE; + conn->queue.read_cb = rf_read_cmd; + conn->queue.write_cb = rf_write_cmd; + conn->rf = rf; + + if (osmo_fd_register(&conn->queue.bfd) != 0) { + close(fd); + talloc_free(conn); + return -1; + } + + return 0; +} + +static void rf_auto_off_cb(void *_timer) +{ + struct osmo_bsc_rf *rf = _timer; + + LOGP(DLINP, LOGL_NOTICE, + "Going to switch off RF due lack of a MSC connection.\n"); + osmo_bsc_rf_schedule_lock(rf, RF_CMD_D_OFF); +} + +static int msc_signal_handler(unsigned int subsys, unsigned int signal, + void *handler_data, void *signal_data) +{ + struct gsm_network *net; + struct msc_signal_data *msc; + struct osmo_bsc_rf *rf; + + /* check if we want to handle this signal */ + if (subsys != SS_MSC) + return 0; + + net = handler_data; + msc = signal_data; + + /* check if we have the needed information */ + if (!net->bsc_data) + return 0; + if (msc->data->type != MSC_CON_TYPE_NORMAL) + return 0; + + rf = net->bsc_data->rf_ctrl; + switch (signal) { + case S_MSC_LOST: + if (net->bsc_data->auto_off_timeout < 0) + return 0; + if (osmo_timer_pending(&rf->auto_off_timer)) + return 0; + osmo_timer_schedule(&rf->auto_off_timer, + net->bsc_data->auto_off_timeout, 0); + break; + case S_MSC_CONNECTED: + osmo_timer_del(&rf->auto_off_timer); + break; + } + + return 0; +} + +static int rf_create_socket(struct osmo_bsc_rf *rf, const char *path) +{ + unsigned int namelen; + struct sockaddr_un local; + struct osmo_fd *bfd; + int rc; + + bfd = &rf->listen; + bfd->fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (bfd->fd < 0) { + LOGP(DLINP, LOGL_ERROR, "Can not create socket. %d/%s\n", + errno, strerror(errno)); + return -1; + } + + local.sun_family = AF_UNIX; + osmo_strlcpy(local.sun_path, path, sizeof(local.sun_path)); + unlink(local.sun_path); + + /* we use the same magic that X11 uses in Xtranssock.c for + * calculating the proper length of the sockaddr */ +#if defined(BSD44SOCKETS) || defined(__UNIXWARE__) + local.sun_len = strlen(local.sun_path); +#endif +#if defined(BSD44SOCKETS) || defined(SUN_LEN) + namelen = SUN_LEN(&local); +#else + namelen = strlen(local.sun_path) + + offsetof(struct sockaddr_un, sun_path); +#endif + + rc = bind(bfd->fd, (struct sockaddr *) &local, namelen); + if (rc != 0) { + LOGP(DLINP, LOGL_ERROR, "Failed to bind '%s' errno: %d/%s\n", + local.sun_path, errno, strerror(errno)); + close(bfd->fd); + return -1; + } + + if (listen(bfd->fd, 0) != 0) { + LOGP(DLINP, LOGL_ERROR, "Failed to listen: %d/%s\n", errno, strerror(errno)); + close(bfd->fd); + return -1; + } + + bfd->when = BSC_FD_READ; + bfd->cb = rf_ctrl_accept; + bfd->data = rf; + + if (osmo_fd_register(bfd) != 0) { + LOGP(DLINP, LOGL_ERROR, "Failed to register bfd.\n"); + close(bfd->fd); + return -1; + } + + return 0; +} + +struct osmo_bsc_rf *osmo_bsc_rf_create(const char *path, struct gsm_network *net) +{ + struct osmo_bsc_rf *rf; + + rf = talloc_zero(NULL, struct osmo_bsc_rf); + if (!rf) { + LOGP(DLINP, LOGL_ERROR, "Failed to create osmo_bsc_rf.\n"); + return NULL; + } + + if (path && rf_create_socket(rf, path) != 0) { + talloc_free(rf); + return NULL; + } + + rf->gsm_network = net; + rf->policy = S_RF_ON; + rf->last_state_command = ""; + rf->last_rf_lock_ctrl_command = talloc_strdup(rf, ""); + + /* check the rf state */ + osmo_timer_setup(&rf->rf_check, rf_check_cb, rf); + + /* delay cmd handling */ + osmo_timer_setup(&rf->delay_cmd, rf_delay_cmd_cb, rf); + + osmo_timer_setup(&rf->auto_off_timer, rf_auto_off_cb, rf); + + /* listen to RF signals */ + osmo_signal_register_handler(SS_MSC, msc_signal_handler, net); + + return rf; +} + +void osmo_bsc_rf_schedule_lock(struct osmo_bsc_rf *rf, char cmd) +{ + rf->last_request = cmd; + if (!osmo_timer_pending(&rf->delay_cmd)) + osmo_timer_schedule(&rf->delay_cmd, 1, 0); +} diff --git a/src/osmo-bsc/bsc_rll.c b/src/osmo-bsc/bsc_rll.c new file mode 100644 index 000000000..ebf9b8856 --- /dev/null +++ b/src/osmo-bsc/bsc_rll.c @@ -0,0 +1,139 @@ +/* GSM BSC Radio Link Layer API + * 3GPP TS 08.58 version 8.6.0 Release 1999 / ETSI TS 100 596 V8.6.0 */ + +/* (C) 2009 by Harald Welte <laforge@gnumonks.org> + * + * 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 <errno.h> + +#include <osmocom/bsc/debug.h> +#include <osmocom/core/talloc.h> +#include <osmocom/core/timer.h> +#include <osmocom/core/linuxlist.h> +#include <osmocom/bsc/bsc_rll.h> +#include <osmocom/bsc/gsm_data.h> +#include <osmocom/bsc/chan_alloc.h> +#include <osmocom/bsc/abis_rsl.h> +#include <osmocom/bsc/signal.h> + +struct bsc_rll_req { + struct llist_head list; + struct osmo_timer_list timer; + + struct gsm_lchan *lchan; + uint8_t link_id; + + void (*cb)(struct gsm_lchan *lchan, uint8_t link_id, + void *data, enum bsc_rllr_ind); + void *data; +}; + +/* we only compare C1, C2 and SAPI */ +#define LINKID_MASK 0xC7 + +static LLIST_HEAD(bsc_rll_reqs); + +static void complete_rllr(struct bsc_rll_req *rllr, enum bsc_rllr_ind type) +{ + llist_del(&rllr->list); + rllr->cb(rllr->lchan, rllr->link_id, rllr->data, type); + talloc_free(rllr); +} + +static void timer_cb(void *_rllr) +{ + struct bsc_rll_req *rllr = _rllr; + + complete_rllr(rllr, BSC_RLLR_IND_TIMEOUT); +} + +/* establish a RLL connection with given SAPI / priority */ +int rll_establish(struct gsm_lchan *lchan, uint8_t sapi, + void (*cb)(struct gsm_lchan *, uint8_t, void *, + enum bsc_rllr_ind), + void *data) +{ + struct bsc_rll_req *rllr = talloc_zero(tall_bsc_ctx, struct bsc_rll_req); + uint8_t link_id; + if (!rllr) + return -ENOMEM; + + link_id = sapi; + + /* If we are a TCH and not in signalling mode, we need to + * indicate that the new RLL connection is to be made on the SACCH */ + if ((lchan->type == GSM_LCHAN_TCH_F || + lchan->type == GSM_LCHAN_TCH_H) && sapi != 0) + link_id |= 0x40; + + rllr->lchan = lchan; + rllr->link_id = link_id; + rllr->cb = cb; + rllr->data = data; + + llist_add(&rllr->list, &bsc_rll_reqs); + + osmo_timer_setup(&rllr->timer, timer_cb, rllr); + osmo_timer_schedule(&rllr->timer, 7, 0); + + /* send the RSL RLL ESTablish REQuest */ + return rsl_establish_request(rllr->lchan, rllr->link_id); +} + +/* Called from RSL code in case we have received an indication regarding + * any RLL link */ +void rll_indication(struct gsm_lchan *lchan, uint8_t link_id, uint8_t type) +{ + struct bsc_rll_req *rllr, *rllr2; + + llist_for_each_entry_safe(rllr, rllr2, &bsc_rll_reqs, list) { + if (rllr->lchan == lchan && + (rllr->link_id & LINKID_MASK) == (link_id & LINKID_MASK)) { + osmo_timer_del(&rllr->timer); + complete_rllr(rllr, type); + return; + } + } +} + +static int rll_lchan_signal(unsigned int subsys, unsigned int signal, + void *handler_data, void *signal_data) +{ + struct challoc_signal_data *challoc; + struct bsc_rll_req *rllr, *rllr2; + + if (subsys != SS_CHALLOC || signal != S_CHALLOC_FREED) + return 0; + + challoc = (struct challoc_signal_data *) signal_data; + + llist_for_each_entry_safe(rllr, rllr2, &bsc_rll_reqs, list) { + if (rllr->lchan == challoc->lchan) { + osmo_timer_del(&rllr->timer); + complete_rllr(rllr, BSC_RLLR_IND_ERR_IND); + } + } + + return 0; +} + +static __attribute__((constructor)) void on_dso_load_rll(void) +{ + osmo_signal_register_handler(SS_CHALLOC, rll_lchan_signal, NULL); +} diff --git a/src/osmo-bsc/bsc_subscr_conn_fsm.c b/src/osmo-bsc/bsc_subscr_conn_fsm.c new file mode 100644 index 000000000..bafe14589 --- /dev/null +++ b/src/osmo-bsc/bsc_subscr_conn_fsm.c @@ -0,0 +1,1204 @@ +/* (C) 2017 by Harald Welte <laforge@gnumonks.org> + * 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 <osmocom/core/fsm.h> +#include <osmocom/core/logging.h> +#include <osmocom/gsm/gsm0808.h> +#include <osmocom/sigtran/sccp_sap.h> +#include <osmocom/gsm/gsm0808_utils.h> + +#include <osmocom/bsc/debug.h> +#include <osmocom/bsc/a_reset.h> +#include <osmocom/bsc/bsc_api.h> +#include <osmocom/bsc/gsm_data.h> +#include <osmocom/bsc/handover.h> +#include <osmocom/bsc/chan_alloc.h> +#include <osmocom/bsc/bsc_subscriber.h> +#include <osmocom/bsc/osmo_bsc_sigtran.h> +#include <osmocom/bsc/osmo_bsc_lcls.h> +#include <osmocom/bsc/bsc_subscr_conn_fsm.h> +#include <osmocom/bsc/osmo_bsc.h> +#include <osmocom/bsc/penalty_timers.h> +#include <osmocom/mgcp_client/mgcp_client_fsm.h> +#include <osmocom/core/byteswap.h> + +#define S(x) (1 << (x)) + +#define MGCP_MGW_TIMEOUT 4 /* in seconds */ +#define MGCP_MGW_TIMEOUT_TIMER_NR 1 + +#define MGCP_MGW_HO_TIMEOUT 4 /* in seconds */ +#define MGCP_MGW_HO_TIMEOUT_TIMER_NR 2 + +#define GSM0808_T10_TIMER_NR 10 +#define GSM0808_T10_VALUE 6 + +#define ENDPOINT_ID "rtpbridge/*@mgw" + +enum gscon_fsm_states { + ST_INIT, + /* waiting for CC from MSC */ + ST_WAIT_CC, + /* active connection */ + ST_ACTIVE, + /* during assignment; waiting for ASS_CMPL */ + ST_WAIT_ASS_CMPL, + /* BSSMAP CLEAR has been received */ + ST_CLEARING, + +/* MGW handling */ + /* during assignment; waiting for MGW response to CRCX for BTS */ + ST_WAIT_CRCX_BTS, + /* during assignment; waiting for MGW response to MDCX for BTS */ + ST_WAIT_MDCX_BTS, + /* during assignment; waiting for MGW response to CRCX for MSC */ + ST_WAIT_CRCX_MSC, + +/* MT (inbound) handover */ + /* Wait for Handover Access from MS/BTS */ + ST_WAIT_MT_HO_ACC, + /* Wait for RR Handover Complete from MS/BTS */ + ST_WAIT_MT_HO_COMPL, + +/* MO (outbound) handover */ + /* Wait for Handover Command / Handover Required Reject from MSC */ + ST_WAIT_MO_HO_CMD, + /* Wait for Clear Command from MSC */ + ST_MO_HO_PROCEEDING, + +/* Internal HO handling */ + /* Wait for the handover logic to complete the handover */ + ST_WAIT_HO_COMPL, + /* during handover; waiting for MGW response to MDCX for BTS */ + ST_WAIT_MDCX_BTS_HO, +}; + +static const struct value_string gscon_fsm_event_names[] = { + {GSCON_EV_A_CONN_IND, "MT-CONNECT.ind"}, + {GSCON_EV_A_CONN_REQ, "MO-CONNECT.req"}, + {GSCON_EV_A_CONN_CFM, "MO-CONNECT.cfm"}, + {GSCON_EV_A_ASSIGNMENT_CMD, "ASSIGNMENT_CMD"}, + {GSCON_EV_A_CLEAR_CMD, "CLEAR_CMD"}, + {GSCON_EV_A_DISC_IND, "DISCONNET.ind"}, + {GSCON_EV_A_HO_REQ, "HANDOVER_REQUEST"}, + + {GSCON_EV_RR_ASS_COMPL, "RR_ASSIGN_COMPL"}, + {GSCON_EV_RR_ASS_FAIL, "RR_ASSIGN_FAIL"}, + {GSCON_EV_RLL_REL_IND, "RLL_RELEASE.ind"}, + {GSCON_EV_RSL_CONN_FAIL, "RSL_CONN_FAIL.ind"}, + {GSCON_EV_RSL_CLEAR_COMPL, "RSL_CLEAR_COMPLETE"}, + + {GSCON_EV_MO_DTAP, "MO-DTAP"}, + {GSCON_EV_MT_DTAP, "MT-DTAP"}, + {GSCON_EV_TX_SCCP, "TX_SCCP"}, + + {GSCON_EV_MGW_FAIL_BTS, "MGW_FAILURE_BTS"}, + {GSCON_EV_MGW_FAIL_MSC, "MGW_FAILURE_MSC"}, + {GSCON_EV_MGW_CRCX_RESP_BTS, "MGW_CRCX_RESPONSE_BTS"}, + {GSCON_EV_MGW_MDCX_RESP_BTS, "MGW_MDCX_RESPONSE_BTS"}, + {GSCON_EV_MGW_CRCX_RESP_MSC, "MGW_CRCX_RESPONSE_MSC"}, + {GSCON_EV_MGW_MDCX_RESP_MSC, "MGW_MDCX_RESPONSE_MSC"}, + + {GSCON_EV_HO_START, "HO_START"}, + {GSCON_EV_HO_TIMEOUT, "HO_TIMEOUT"}, + {GSCON_EV_HO_FAIL, "HO_FAIL"}, + {GSCON_EV_HO_COMPL, "HO_COMPL"}, + + {0, NULL} +}; + +/* Send data SCCP message through SCCP connection. All sigtran messages + * that are send from this FSM must use this function. Never use + * osmo_bsc_sigtran_send() directly since this would defeat the checks + * provided by this function. */ +static void sigtran_send(struct gsm_subscriber_connection *conn, struct msgb *msg, struct osmo_fsm_inst *fi) +{ + int rc; + + /* Make sure that we only attempt to send SCCP messages if we have + * a life SCCP connection. Otherwise drop the message. */ + if (fi->state == ST_INIT || fi->state == ST_WAIT_CC) { + LOGPFSML(fi, LOGL_ERROR, "No active SCCP connection, dropping message!\n"); + msgb_free(msg); + return; + } + + rc = osmo_bsc_sigtran_send(conn, msg); + if (rc < 0) + LOGPFSML(fi, LOGL_ERROR, "Unable to deliver SCCP message!\n"); +} + + +/* See TS 48.008 3.2.2.11 Channel Type Octet 5 */ +static int bssap_speech_from_lchan(const struct gsm_lchan *lchan) +{ + switch (lchan->type) { + case GSM_LCHAN_TCH_H: + switch (lchan->tch_mode) { + case GSM48_CMODE_SPEECH_V1: + return 0x05; + case GSM48_CMODE_SPEECH_AMR: + return 0x25; + default: + return -1; + } + break; + case GSM_LCHAN_TCH_F: + switch (lchan->tch_mode) { + case GSM48_CMODE_SPEECH_V1: + return 0x01; + case GSM48_CMODE_SPEECH_EFR: + return 0x11; + case GSM48_CMODE_SPEECH_AMR: + return 0x21; + default: + return -1; + } + break; + default: + return -1; + } +} + +/* GSM 08.08 3.2.2.33 */ +static uint8_t lchan_to_chosen_channel(struct gsm_lchan *lchan) +{ + uint8_t channel_mode = 0, channel = 0; + + switch (lchan->tch_mode) { + case GSM48_CMODE_SPEECH_V1: + case GSM48_CMODE_SPEECH_EFR: + case GSM48_CMODE_SPEECH_AMR: + channel_mode = 0x9; + break; + case GSM48_CMODE_SIGN: + channel_mode = 0x8; + break; + case GSM48_CMODE_DATA_14k5: + channel_mode = 0xe; + break; + case GSM48_CMODE_DATA_12k0: + channel_mode = 0xb; + break; + case GSM48_CMODE_DATA_6k0: + channel_mode = 0xc; + break; + case GSM48_CMODE_DATA_3k6: + channel_mode = 0xd; + break; + } + + switch (lchan->type) { + case GSM_LCHAN_NONE: + channel = 0x0; + break; + case GSM_LCHAN_SDCCH: + channel = 0x1; + break; + case GSM_LCHAN_TCH_F: + channel = 0x8; + break; + case GSM_LCHAN_TCH_H: + channel = 0x9; + break; + case GSM_LCHAN_UNKNOWN: + default: + LOGP(DMSC, LOGL_ERROR, "Unknown lchan type: %p\n", lchan); + break; + } + + return channel_mode << 4 | channel; +} + +/* Add the LCLS BSS Status IE to a BSSMAP message. We assume this is + * called on a msgb that was returned by gsm0808_create_ass_compl() */ +static void bssmap_add_lcls_status(struct msgb *msg, enum gsm0808_lcls_status status) +{ + OSMO_ASSERT(msg->l3h[0] == BSSAP_MSG_BSS_MANAGEMENT); + OSMO_ASSERT(msg->l3h[2] == BSS_MAP_MSG_ASSIGMENT_COMPLETE || + msg->l3h[2] == BSS_MAP_MSG_HANDOVER_RQST_ACKNOWLEDGE || + msg->l3h[2] == BSS_MAP_MSG_HANDOVER_COMPLETE || + msg->l3h[2] == BSS_MAP_MSG_HANDOVER_PERFORMED); + OSMO_ASSERT(msgb_tailroom(msg) >= 2); + + /* append IE to end of message */ + msgb_tv_put(msg, GSM0808_IE_LCLS_BSS_STATUS, status); + /* increment the "length" byte in the BSSAP header */ + msg->l3h[1] += 2; +} + +/* Add (append) the LCLS BSS Status IE to a BSSMAP message, if there is any LCLS + * active on the given \a conn */ +static void bssmap_add_lcls_status_if_needed(struct gsm_subscriber_connection *conn, + struct msgb *msg) +{ + enum gsm0808_lcls_status status = lcls_get_status(conn); + if (status != 0xff) { + LOGPFSM(conn->fi, "Adding LCLS BSS-Status (%s) to %s\n", + gsm0808_lcls_status_name(status), + gsm0808_bssmap_name(msg->l3h[2])); + bssmap_add_lcls_status(msg, status); + } +} + +/* Generate and send assignment complete message */ +static void send_ass_compl(struct gsm_lchan *lchan, struct osmo_fsm_inst *fi, bool voice) +{ + struct msgb *resp; + struct gsm0808_speech_codec sc; + struct gsm0808_speech_codec *sc_ptr = NULL; + struct gsm_subscriber_connection *conn; + struct sockaddr_storage *addr_local = NULL; + int perm_spch = 0; + + conn = lchan->conn; + OSMO_ASSERT(conn); + + /* apply LCLS configuration (if any) */ + lcls_apply_config(conn); + + LOGPFSML(fi, LOGL_DEBUG, "Sending assignment complete message... (id=%i)\n", conn->sccp.conn_id); + + /* Generate voice related fields */ + if (voice) { + perm_spch = bssap_speech_from_lchan(lchan); + switch (conn->sccp.msc->a.asp_proto) { + case OSMO_SS7_ASP_PROT_IPA: + /* don't add any AoIP specific fields. CIC allocated by MSC */ + break; + default: + OSMO_ASSERT(lchan->abis_ip.ass_compl.valid); + addr_local = &conn->user_plane.aoip_rtp_addr_local; + + /* Extrapolate speech codec from speech mode */ + gsm0808_speech_codec_from_chan_type(&sc, perm_spch); + sc_ptr = ≻ + break; + } + /* FIXME: AMR codec configuration must be derived from lchan1! */ + } + + /* Generate message */ + resp = gsm0808_create_ass_compl(lchan->abis_ip.ass_compl.rr_cause, + lchan_to_chosen_channel(lchan), + lchan->encr.alg_id, perm_spch, + addr_local, sc_ptr, NULL); + + if (!resp) { + LOGPFSML(fi, LOGL_ERROR, "Failed to generate assignment completed message! (id=%i)\n", + conn->sccp.conn_id); + } + + /* Add LCLS BSS-Status IE in case there is any LCLS status for this connection */ + bssmap_add_lcls_status_if_needed(conn, resp); + + sigtran_send(conn, resp, fi); +} + +/* forward MT DTAP from BSSAP side to RSL side */ +static void submit_dtap(struct gsm_subscriber_connection *conn, struct msgb *msg, struct osmo_fsm_inst *fi) +{ + int rc; + struct msgb *resp = NULL; + + OSMO_ASSERT(fi); + OSMO_ASSERT(msg); + OSMO_ASSERT(conn); + + rc = gsm0808_submit_dtap(conn, msg, OBSC_LINKID_CB(msg), 1); + if (rc != 0) { + LOGPFSML(fi, LOGL_ERROR, "Tx BSSMAP CLEAR REQUEST to MSC\n"); + resp = gsm0808_create_clear_rqst(GSM0808_CAUSE_EQUIPMENT_FAILURE); + sigtran_send(conn, resp, fi); + osmo_fsm_inst_state_chg(fi, ST_ACTIVE, 0, 0); + return; + } +} + +/* forward MO DTAP from RSL side to BSSAP side */ +static void forward_dtap(struct gsm_subscriber_connection *conn, struct msgb *msg, struct osmo_fsm_inst *fi) +{ + struct msgb *resp = NULL; + + OSMO_ASSERT(msg); + OSMO_ASSERT(conn); + + resp = gsm0808_create_dtap(msg, OBSC_LINKID_CB(msg)); + sigtran_send(conn, resp, fi); +} + +/* In case there are open MGCP connections, toss + * those connections */ +static void toss_mgcp_conn(struct gsm_subscriber_connection *conn, struct osmo_fsm_inst *fi) +{ + LOGPFSML(fi, LOGL_ERROR, "tossing all MGCP connections...\n"); + + if (conn->user_plane.fi_bts) { + mgcp_conn_delete(conn->user_plane.fi_bts); + conn->user_plane.fi_bts = NULL; + } + + if (conn->user_plane.fi_msc) { + mgcp_conn_delete(conn->user_plane.fi_msc); + conn->user_plane.fi_msc = NULL; + } + + if (conn->user_plane.mgw_endpoint) { + talloc_free(conn->user_plane.mgw_endpoint); + conn->user_plane.mgw_endpoint = NULL; + } +} + +static void gscon_fsm_init(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_subscriber_connection *conn = fi->priv; + struct osmo_scu_prim *scu_prim = NULL; + struct msgb *msg = NULL; + int rc; + + switch (event) { + case GSCON_EV_A_CONN_REQ: + /* RLL ESTABLISH IND with initial L3 Message */ + msg = data; + /* FIXME: Extract Mobile ID and update FSM using osmo_fsm_inst_set_id() + * i.e. we will probably extract the mobile identity earlier, where the + * imsi filter code is. Then we could just use it here. + * related: OS#2969 */ + + rc = osmo_bsc_sigtran_open_conn(conn, msg); + if (rc < 0) { + osmo_fsm_inst_term(fi, OSMO_FSM_TERM_ERROR, NULL); + } else { + /* SCCP T(conn est) is 1-2 minutes, way too long. The MS will timeout + * using T3210 (20s), T3220 (5s) or T3230 (10s) */ + osmo_fsm_inst_state_chg(fi, ST_WAIT_CC, 20, 993210); + } + break; + case GSCON_EV_A_CONN_IND: + scu_prim = data; + if (!conn->sccp.msc) { + LOGPFSML(fi, LOGL_NOTICE, "N-CONNECT.ind from unknown MSC %s\n", + osmo_sccp_addr_dump(&scu_prim->u.connect.calling_addr)); + osmo_sccp_tx_disconn(conn->sccp.msc->a.sccp_user, scu_prim->u.connect.conn_id, + &scu_prim->u.connect.called_addr, 0); + osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL); + } + /* FIXME: Extract optional IMSI and update FSM using osmo_fsm_inst_set_id() + * related: OS2969 (same as above) */ + + LOGPFSML(fi, LOGL_NOTICE, "No support for MSC-originated SCCP Connections yet\n"); + osmo_sccp_tx_disconn(conn->sccp.msc->a.sccp_user, scu_prim->u.connect.conn_id, + &scu_prim->u.connect.called_addr, 0); + osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL); + break; + default: + OSMO_ASSERT(false); + break; + } +} + +/* We've sent the CONNECTION.req to the SCCP provider and are waiting for CC from MSC */ +static void gscon_fsm_wait_cc(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + switch (event) { + case GSCON_EV_A_CONN_CFM: + /* MSC has confirmed the connection, we now change into the + * active state and wait there for further operations */ + osmo_fsm_inst_state_chg(fi, ST_ACTIVE, 0, 0); + /* if there's user payload, forward it just like EV_MT_DTAP */ + /* FIXME: Question: if there's user payload attached to the CC, forward it like EV_MT_DTAP? */ + break; + default: + OSMO_ASSERT(false); + break; + } +} + +static const char *get_mgw_ep_name(struct gsm_subscriber_connection *conn) +{ + static char ep_name[256]; + struct bsc_msc_data *msc = conn->sccp.msc; + + switch (conn->sccp.msc->a.asp_proto) { + case OSMO_SS7_ASP_PROT_IPA: + /* derive endpoint name from CIC on A interface side */ + snprintf(ep_name, sizeof(ep_name), "%x@mgw", + mgcp_port_to_cic(conn->user_plane.rtp_port, msc->rtp_base)); + break; + default: + /* use dynamic RTPBRIDGE endpoint allocation in MGW */ + osmo_strlcpy(ep_name, ENDPOINT_ID, sizeof(ep_name)); + break; + } + return ep_name; +} + +/* We're on an active subscriber connection, passing DTAP back and forth */ +static void gscon_fsm_active(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_subscriber_connection *conn = fi->priv; + struct msgb *resp = NULL; + struct mgcp_conn_peer conn_peer; + int rc; + + switch (event) { + case GSCON_EV_A_ASSIGNMENT_CMD: + /* MSC requests us to perform assignment, this code section is + * triggered via signal GSCON_EV_A_ASSIGNMENT_CMD from + * bssmap_handle_assignm_req() in osmo_bsc_bssap.c, which does + * the parsing of incoming assignment requests. */ + + LOGPFSML(fi, LOGL_NOTICE, "Channel assignment: chan_mode=%s, full_rate=%i\n", + get_value_string(gsm48_chan_mode_names, conn->user_plane.chan_mode), + conn->user_plane.full_rate); + + /* FIXME: We need to check if current channel is sufficient. If + * yes, do MODIFY. If not, do assignment (see commented lines below) */ + + switch (conn->user_plane.chan_mode) { + case GSM48_CMODE_SPEECH_V1: + case GSM48_CMODE_SPEECH_EFR: + case GSM48_CMODE_SPEECH_AMR: + /* A voice channel is requested, so we run down the + * mgcp-ass-mgcp state-chain (see FIXME above) */ + memset(&conn_peer, 0, sizeof(conn_peer)); + conn_peer.call_id = conn->sccp.conn_id; + osmo_strlcpy(conn_peer.endpoint, get_mgw_ep_name(conn), sizeof(conn_peer.endpoint)); + + /* (Pre)Change state and create the connection */ + osmo_fsm_inst_state_chg(fi, ST_WAIT_CRCX_BTS, MGCP_MGW_TIMEOUT, MGCP_MGW_TIMEOUT_TIMER_NR); + conn->user_plane.fi_bts = + mgcp_conn_create(conn->network->mgw.client, fi, GSCON_EV_MGW_FAIL_BTS, + GSCON_EV_MGW_CRCX_RESP_BTS, &conn_peer); + if (!conn->user_plane.fi_bts) { + resp = gsm0808_create_assignment_failure(GSM0808_CAUSE_EQUIPMENT_FAILURE, NULL); + sigtran_send(conn, resp, fi); + return; + } + break; + case GSM48_CMODE_SIGN: + /* A signalling channel is requested, so we perform the + * channel assignment directly without performing any + * MGCP actions. ST_WAIT_ASS_CMPL will see by the + * conn->user_plane.chan_mode parameter that this + * assignment is for a signalling channel and will then + * change back to ST_ACTIVE (here) immediately. */ + rc = gsm0808_assign_req(conn, conn->user_plane.chan_mode, + conn->user_plane.full_rate); + + if (rc == 1) { + send_ass_compl(conn->lchan, fi, false); + return; + } else if (rc != 0) { + resp = gsm0808_create_assignment_failure(GSM0808_CAUSE_EQUIPMENT_FAILURE, NULL); + sigtran_send(conn, resp, fi); + return; + } + + osmo_fsm_inst_state_chg(fi, ST_WAIT_ASS_CMPL, GSM0808_T10_VALUE, GSM0808_T10_TIMER_NR); + break; + default: + /* An unsupported channel is requested, so we have to + * reject this request by sending an assignment failure + * message immediately */ + LOGPFSML(fi, LOGL_ERROR, "Requested channel mode is not supported! chan_mode=%s full_rate=%d\n", + get_value_string(gsm48_chan_mode_names, conn->user_plane.chan_mode), + conn->user_plane.full_rate); + + /* The requested channel mode is not supported */ + resp = gsm0808_create_assignment_failure(GSM0808_CAUSE_REQ_CODEC_TYPE_OR_CONFIG_NOT_SUPP, NULL); + sigtran_send(conn, resp, fi); + break; + } + break; + case GSCON_EV_HO_START: + rc = bsc_handover_start_gscon(conn); + if (rc) { + resp = gsm0808_create_clear_rqst(GSM0808_CAUSE_EQUIPMENT_FAILURE); + sigtran_send(conn, resp, fi); + osmo_fsm_inst_state_chg(fi, ST_CLEARING, 0, 0); + return; + } + + /* Note: No timeout is set here, T3103 in handover_logic.c + * will generate a GSCON_EV_HO_TIMEOUT event should the + * handover time out, so we do not need another timeout + * here (maybe its worth to think about giving GSCON + * more power over the actual handover process). */ + osmo_fsm_inst_state_chg(fi, ST_WAIT_HO_COMPL, 0, 0); + break; + case GSCON_EV_A_HO_REQ: + /* FIXME: reject any handover requests with HO FAIL until implemented */ + break; + case GSCON_EV_MO_DTAP: + forward_dtap(conn, (struct msgb *)data, fi); + break; + case GSCON_EV_MT_DTAP: + submit_dtap(conn, (struct msgb *)data, fi); + break; + case GSCON_EV_TX_SCCP: + sigtran_send(conn, (struct msgb *)data, fi); + break; + default: + OSMO_ASSERT(false); + break; + } +} + +/* Before we may start the channel assignment we need to get an IP/Port for the + * RTP connection from the MGW */ +static void gscon_fsm_wait_crcx_bts(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_subscriber_connection *conn = fi->priv; + struct mgcp_conn_peer *conn_peer = NULL; + struct msgb *resp = NULL; + int rc; + + switch (event) { + case GSCON_EV_MGW_CRCX_RESP_BTS: + conn_peer = data; + + /* Check if the MGW has assigned an enpoint to us, otherwise we + * can not proceed. */ + if (strlen(conn_peer->endpoint) <= 0) { + resp = gsm0808_create_assignment_failure(GSM0808_CAUSE_EQUIPMENT_FAILURE, NULL); + sigtran_send(conn, resp, fi); + osmo_fsm_inst_state_chg(fi, ST_ACTIVE, 0, 0); + return; + } + + /* Memorize the endpoint name we got assigned from the MGW. + * When the BTS sided connection is done, we need to create + * a second connection on that same endpoint, so we need + * to know its ID */ + if (!conn->user_plane.mgw_endpoint) + conn->user_plane.mgw_endpoint = talloc_zero_size(conn, MGCP_ENDPOINT_MAXLEN); + OSMO_ASSERT(conn->user_plane.mgw_endpoint); + osmo_strlcpy(conn->user_plane.mgw_endpoint, conn_peer->endpoint, MGCP_ENDPOINT_MAXLEN); + + /* Store the IP-Address and the port the MGW assigned to us, + * then start the channel assignment. */ + conn->user_plane.rtp_port = conn_peer->port; + conn->user_plane.rtp_ip = osmo_ntohl(inet_addr(conn_peer->addr)); + rc = gsm0808_assign_req(conn, conn->user_plane.chan_mode, conn->user_plane.full_rate); + if (rc != 0) { + resp = gsm0808_create_assignment_failure(GSM0808_CAUSE_RQSTED_SPEECH_VERSION_UNAVAILABLE, NULL); + sigtran_send(conn, resp, fi); + osmo_fsm_inst_state_chg(fi, ST_ACTIVE, 0, 0); + return; + } + + osmo_fsm_inst_state_chg(fi, ST_WAIT_ASS_CMPL, GSM0808_T10_VALUE, GSM0808_T10_TIMER_NR); + break; + case GSCON_EV_MO_DTAP: + forward_dtap(conn, (struct msgb *)data, fi); + break; + case GSCON_EV_MT_DTAP: + submit_dtap(conn, (struct msgb *)data, fi); + break; + case GSCON_EV_TX_SCCP: + sigtran_send(conn, (struct msgb *)data, fi); + break; + default: + OSMO_ASSERT(false); + break; + } +} + +/* We're waiting for an ASSIGNMENT COMPLETE from MS */ +static void gscon_fsm_wait_ass_cmpl(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_subscriber_connection *conn = fi->priv; + struct gsm_lchan *lchan = conn->lchan; + struct mgcp_conn_peer conn_peer; + struct in_addr addr; + struct msgb *resp = NULL; + int rc; + + switch (event) { + case GSCON_EV_RR_ASS_COMPL: + switch (conn->user_plane.chan_mode) { + case GSM48_CMODE_SPEECH_V1: + case GSM48_CMODE_SPEECH_EFR: + case GSM48_CMODE_SPEECH_AMR: + /* FIXME: What if we are using SCCP-Lite? */ + + /* We are dealing with a voice channel, so we can not + * confirm the assignment directly. We must first do + * some final steps on the MGCP side. */ + + /* Prepare parameters with the information we got during the assignment */ + memset(&conn_peer, 0, sizeof(conn_peer)); + addr.s_addr = osmo_ntohl(lchan->abis_ip.bound_ip); + osmo_strlcpy(conn_peer.addr, inet_ntoa(addr), sizeof(conn_peer.addr)); + conn_peer.port = lchan->abis_ip.bound_port; + + /* (Pre)Change state and modify the connection */ + osmo_fsm_inst_state_chg(fi, ST_WAIT_MDCX_BTS, MGCP_MGW_TIMEOUT, MGCP_MGW_TIMEOUT_TIMER_NR); + rc = mgcp_conn_modify(conn->user_plane.fi_bts, GSCON_EV_MGW_MDCX_RESP_BTS, &conn_peer); + if (rc != 0) { + resp = gsm0808_create_assignment_failure(GSM0808_CAUSE_EQUIPMENT_FAILURE, NULL); + sigtran_send(conn, resp, fi); + osmo_fsm_inst_state_chg(fi, ST_ACTIVE, 0, 0); + return; + } + break; + case GSM48_CMODE_SIGN: + /* Confirm the successful assignment on BSSMAP and + * change back into active state */ + send_ass_compl(lchan, fi, false); + osmo_fsm_inst_state_chg(fi, ST_ACTIVE, 0, 0); + break; + default: + /* Unsupported modes should have been already filtered + * by gscon_fsm_active(). If we reach the default + * section here anyway than some unsupported mode must + * have made it into the FSM, this would be a bug, so + * we fire an assertion here */ + OSMO_ASSERT(false); + break; + } + + break; + case GSCON_EV_RR_ASS_FAIL: + resp = gsm0808_create_assignment_failure(GSM0808_CAUSE_RQSTED_TERRESTRIAL_RESOURCE_UNAVAILABLE, NULL); + sigtran_send(conn, resp, fi); + osmo_fsm_inst_state_chg(fi, ST_ACTIVE, 0, 0); + break; + case GSCON_EV_MO_DTAP: + forward_dtap(conn, (struct msgb *)data, fi); + break; + case GSCON_EV_MT_DTAP: + submit_dtap(conn, (struct msgb *)data, fi); + break; + case GSCON_EV_TX_SCCP: + sigtran_send(conn, (struct msgb *)data, fi); + break; + default: + OSMO_ASSERT(false); + break; + } +} + +/* We are waiting for the MGW response to the MDCX */ +static void gscon_fsm_wait_mdcx_bts(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_subscriber_connection *conn = fi->priv; + struct mgcp_conn_peer conn_peer; + struct sockaddr_in *sin = NULL; + struct msgb *resp = NULL; + + switch (event) { + case GSCON_EV_MGW_MDCX_RESP_BTS: + + /* Prepare parameters with the connection information we got + * with the assignment command */ + memset(&conn_peer, 0, sizeof(conn_peer)); + conn_peer.call_id = conn->sccp.conn_id; + sin = (struct sockaddr_in *)&conn->user_plane.aoip_rtp_addr_remote; + conn_peer.port = osmo_ntohs(sin->sin_port); + osmo_strlcpy(conn_peer.addr, inet_ntoa(sin->sin_addr), sizeof(conn_peer.addr)); + + /* Make sure we use the same endpoint where we created the + * BTS connection. */ + osmo_strlcpy(conn_peer.endpoint, conn->user_plane.mgw_endpoint, sizeof(conn_peer.endpoint)); + + switch (conn->sccp.msc->a.asp_proto) { + case OSMO_SS7_ASP_PROT_IPA: + /* Send assignment complete message to the MSC */ + send_ass_compl(conn->lchan, fi, true); + osmo_fsm_inst_state_chg(fi, ST_ACTIVE, 0, 0); + break; + default: + /* (Pre)Change state and create the connection */ + osmo_fsm_inst_state_chg(fi, ST_WAIT_CRCX_MSC, MGCP_MGW_TIMEOUT, + MGCP_MGW_TIMEOUT_TIMER_NR); + conn->user_plane.fi_msc = mgcp_conn_create(conn->network->mgw.client, fi, + GSCON_EV_MGW_FAIL_MSC, + GSCON_EV_MGW_CRCX_RESP_MSC, &conn_peer); + if (!conn->user_plane.fi_msc) { + resp = gsm0808_create_assignment_failure(GSM0808_CAUSE_EQUIPMENT_FAILURE, NULL); + sigtran_send(conn, resp, fi); + osmo_fsm_inst_state_chg(fi, ST_ACTIVE, 0, 0); + return; + } + break; + } + + break; + case GSCON_EV_MO_DTAP: + forward_dtap(conn, (struct msgb *)data, fi); + break; + case GSCON_EV_MT_DTAP: + submit_dtap(conn, (struct msgb *)data, fi); + break; + case GSCON_EV_TX_SCCP: + sigtran_send(conn, (struct msgb *)data, fi); + break; + default: + OSMO_ASSERT(false); + break; + } +} + +static void gscon_fsm_wait_crcx_msc(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_subscriber_connection *conn = fi->priv; + struct mgcp_conn_peer *conn_peer = NULL; + struct gsm_lchan *lchan = conn->lchan; + struct sockaddr_in *sin = NULL; + + switch (event) { + case GSCON_EV_MGW_CRCX_RESP_MSC: + conn_peer = data; + + /* Store address information we got in response from the CRCX command. */ + sin = (struct sockaddr_in *)&conn->user_plane.aoip_rtp_addr_local; + sin->sin_family = AF_INET; + sin->sin_addr.s_addr = inet_addr(conn_peer->addr); + sin->sin_port = osmo_ntohs(conn_peer->port); + + /* Send assignment complete message to the MSC */ + send_ass_compl(lchan, fi, true); + + osmo_fsm_inst_state_chg(fi, ST_ACTIVE, 0, 0); + + break; + case GSCON_EV_MO_DTAP: + forward_dtap(conn, (struct msgb *)data, fi); + break; + case GSCON_EV_MT_DTAP: + submit_dtap(conn, (struct msgb *)data, fi); + break; + case GSCON_EV_TX_SCCP: + sigtran_send(conn, (struct msgb *)data, fi); + break; + default: + OSMO_ASSERT(false); + break; + } +} + +static void gscon_fsm_clearing(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_subscriber_connection *conn = fi->priv; + struct msgb *resp; + + switch (event) { + case GSCON_EV_RSL_CLEAR_COMPL: + resp = gsm0808_create_clear_complete(); + sigtran_send(conn, resp, fi); + osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, data); + break; + default: + OSMO_ASSERT(false); + break; + } +} + +/* Wait for the handover logic to tell us whether the handover completed, + * failed or has timed out */ +static void gscon_fsm_wait_ho_compl(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_subscriber_connection *conn = fi->priv; + struct mgcp_conn_peer conn_peer; + struct gsm_lchan *lchan = conn->lchan; + struct in_addr addr; + struct msgb *resp; + int rc; + + switch (event) { + case GSCON_EV_HO_COMPL: + /* The handover logic informs us that the handover has been + * completet. Now we have to tell the MGW the IP/Port on the + * new BTS so that the uplink RTP traffic can be redirected + * there. */ + + /* Prepare parameters with the information we got during the + * handover procedure (via IPACC) */ + memset(&conn_peer, 0, sizeof(conn_peer)); + addr.s_addr = osmo_ntohl(lchan->abis_ip.bound_ip); + osmo_strlcpy(conn_peer.addr, inet_ntoa(addr), sizeof(conn_peer.addr)); + conn_peer.port = lchan->abis_ip.bound_port; + + /* (Pre)Change state and modify the connection */ + osmo_fsm_inst_state_chg(fi, ST_WAIT_MDCX_BTS_HO, MGCP_MGW_TIMEOUT, MGCP_MGW_HO_TIMEOUT_TIMER_NR); + rc = mgcp_conn_modify(conn->user_plane.fi_bts, GSCON_EV_MGW_MDCX_RESP_BTS, &conn_peer); + if (rc != 0) { + resp = gsm0808_create_clear_rqst(GSM0808_CAUSE_EQUIPMENT_FAILURE); + sigtran_send(conn, resp, fi); + osmo_fsm_inst_state_chg(fi, ST_CLEARING, 0, 0); + return; + } + break; + case GSCON_EV_HO_TIMEOUT: + case GSCON_EV_HO_FAIL: + /* The handover logic informs us that the handover failed for + * some reason. This means the phone stays on the TS/BTS on + * which it currently is. We will change back to the active + * state again as there are no further operations needed */ + osmo_fsm_inst_state_chg(fi, ST_ACTIVE, 0, 0); + break; + default: + OSMO_ASSERT(false); + break; + } +} + +/* Wait for the MGW to confirm handover related modification of the connection + * parameters */ +static void gscon_fsm_wait_mdcx_bts_ho(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_subscriber_connection *conn = fi->priv; + + switch (event) { + case GSCON_EV_MGW_MDCX_RESP_BTS: + /* The MGW has confirmed the handover MDCX, and the handover + * is now also done on the RTP side. We may now change back + * to the active state. */ + osmo_fsm_inst_state_chg(fi, ST_ACTIVE, 0, 0); + break; + case GSCON_EV_MO_DTAP: + forward_dtap(conn, (struct msgb *)data, fi); + break; + case GSCON_EV_MT_DTAP: + submit_dtap(conn, (struct msgb *)data, fi); + break; + case GSCON_EV_TX_SCCP: + sigtran_send(conn, (struct msgb *)data, fi); + break; + default: + OSMO_ASSERT(false); + break; + } +} + +#define EV_TRANSPARENT_SCCP S(GSCON_EV_TX_SCCP) | S(GSCON_EV_MO_DTAP) | S(GSCON_EV_MT_DTAP) + +static const struct osmo_fsm_state gscon_fsm_states[] = { + [ST_INIT] = { + .name = OSMO_STRINGIFY(INIT), + .in_event_mask = S(GSCON_EV_A_CONN_REQ) | S(GSCON_EV_A_CONN_IND), + .out_state_mask = S(ST_WAIT_CC), + .action = gscon_fsm_init, + }, + [ST_WAIT_CC] = { + .name = OSMO_STRINGIFY(WAIT_CC), + .in_event_mask = S(GSCON_EV_A_CONN_CFM), + .out_state_mask = S(ST_ACTIVE), + .action = gscon_fsm_wait_cc, + }, + [ST_ACTIVE] = { + .name = OSMO_STRINGIFY(ACTIVE), + .in_event_mask = EV_TRANSPARENT_SCCP | S(GSCON_EV_A_ASSIGNMENT_CMD) | + S(GSCON_EV_A_HO_REQ) | S(GSCON_EV_HO_START), + .out_state_mask = S(ST_CLEARING) | S(ST_WAIT_CRCX_BTS) | S(ST_WAIT_ASS_CMPL) | + S(ST_WAIT_MO_HO_CMD) | S(ST_WAIT_HO_COMPL), + .action = gscon_fsm_active, + }, + [ST_WAIT_CRCX_BTS] = { + .name = OSMO_STRINGIFY(WAIT_CRCX_BTS), + .in_event_mask = EV_TRANSPARENT_SCCP | S(GSCON_EV_MGW_CRCX_RESP_BTS), + .out_state_mask = S(ST_ACTIVE) | S(ST_WAIT_ASS_CMPL), + .action = gscon_fsm_wait_crcx_bts, + }, + [ST_WAIT_ASS_CMPL] = { + .name = OSMO_STRINGIFY(WAIT_ASS_CMPL), + .in_event_mask = EV_TRANSPARENT_SCCP | S(GSCON_EV_RR_ASS_COMPL) | S(GSCON_EV_RR_ASS_FAIL), + .out_state_mask = S(ST_ACTIVE) | S(ST_WAIT_MDCX_BTS), + .action = gscon_fsm_wait_ass_cmpl, + }, + [ST_WAIT_MDCX_BTS] = { + .name = OSMO_STRINGIFY(WAIT_MDCX_BTS), + .in_event_mask = EV_TRANSPARENT_SCCP | S(GSCON_EV_MGW_MDCX_RESP_BTS), + .out_state_mask = S(ST_ACTIVE) | S(ST_WAIT_CRCX_MSC), + .action = gscon_fsm_wait_mdcx_bts, + }, + [ST_WAIT_CRCX_MSC] = { + .name = OSMO_STRINGIFY(WAIT_CRCX_MSC), + .in_event_mask = EV_TRANSPARENT_SCCP | S(GSCON_EV_MGW_CRCX_RESP_MSC), + .out_state_mask = S(ST_ACTIVE), + .action = gscon_fsm_wait_crcx_msc, + }, + [ST_CLEARING] = { + .name = OSMO_STRINGIFY(CLEARING), + .in_event_mask = S(GSCON_EV_RSL_CLEAR_COMPL), + .action = gscon_fsm_clearing, + }, + + /* TODO: external handover, probably it makes sense to break up the + * program flow in handover_logic.c a bit and handle some of the logic + * here? */ + [ST_WAIT_MT_HO_ACC] = { + .name = OSMO_STRINGIFY(WAIT_MT_HO_ACC), + }, + [ST_WAIT_MT_HO_COMPL] = { + .name = OSMO_STRINGIFY(WAIT_MT_HO_COMPL), + }, + [ST_WAIT_MO_HO_CMD] = { + .name = OSMO_STRINGIFY(WAIT_MO_HO_CMD), + }, + [ST_MO_HO_PROCEEDING] = { + .name = OSMO_STRINGIFY(MO_HO_PROCEEDING), + }, + + /* Internal handover */ + [ST_WAIT_HO_COMPL] = { + .name = OSMO_STRINGIFY(WAIT_HO_COMPL), + .in_event_mask = S(GSCON_EV_HO_COMPL) | S(GSCON_EV_HO_FAIL) | S(GSCON_EV_HO_TIMEOUT), + .out_state_mask = S(ST_ACTIVE) | S(ST_WAIT_MDCX_BTS_HO) | S(ST_CLEARING), + .action = gscon_fsm_wait_ho_compl, + }, + [ST_WAIT_MDCX_BTS_HO] = { + .name = OSMO_STRINGIFY(WAIT_MDCX_BTS_HO), + .in_event_mask = EV_TRANSPARENT_SCCP | S(GSCON_EV_MGW_MDCX_RESP_BTS), + .action = gscon_fsm_wait_mdcx_bts_ho, + .out_state_mask = S(ST_ACTIVE), + }, +}; + +static void gscon_fsm_allstate(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_subscriber_connection *conn = fi->priv; + struct msgb *resp = NULL; + + /* When a connection on the MGW fails, make sure that the reference + * in our book-keeping is erased. */ + switch (event) { + case GSCON_EV_MGW_FAIL_BTS: + conn->user_plane.fi_bts = NULL; + break; + case GSCON_EV_MGW_FAIL_MSC: + conn->user_plane.fi_msc = NULL; + break; + } + + /* Regular allstate event processing */ + switch (event) { + case GSCON_EV_MGW_FAIL_BTS: + case GSCON_EV_MGW_FAIL_MSC: + /* Note: An MGW connection die per definition at any time. + * However, if it dies during the assignment we must return + * with an assignment failure */ + OSMO_ASSERT(fi->state != ST_INIT && fi->state != ST_WAIT_CC); + if (fi->state == ST_WAIT_CRCX_BTS || fi->state == ST_WAIT_ASS_CMPL || fi->state == ST_WAIT_MDCX_BTS + || fi->state == ST_WAIT_CRCX_MSC) { + resp = gsm0808_create_assignment_failure(GSM0808_CAUSE_EQUIPMENT_FAILURE, NULL); + sigtran_send(conn, resp, fi); + osmo_fsm_inst_state_chg(fi, ST_ACTIVE, 0, 0); + } + break; + case GSCON_EV_A_CLEAR_CMD: + /* MSC tells us to cleanly shut down */ + osmo_fsm_inst_state_chg(fi, ST_CLEARING, 0, 0); + gsm0808_clear(conn); + /* FIXME: Release all terestrial resources in ST_CLEARING */ + /* According to 3GPP 48.008 3.1.9.1. "The BSS need not wait for the radio channel + * release to be completed or for the guard timer to expire before returning the + * CLEAR COMPLETE message" */ + + /* Close MGCP connections */ + toss_mgcp_conn(conn, fi); + + /* FIXME: Question: Is this a hack to force a clear complete from internel? + * nobody seems to send the event from outside? */ + osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_RSL_CLEAR_COMPL, NULL); + break; + case GSCON_EV_A_DISC_IND: + /* MSC or SIGTRAN network has hard-released SCCP connection, + * terminate the FSM now. */ + osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, data); + break; + case GSCON_EV_RLL_REL_IND: + /* BTS reports that one of the LAPDm data links was released */ + /* send proper clear request to MSC */ + LOGPFSML(fi, LOGL_DEBUG, "Tx BSSMAP CLEAR REQUEST to MSC\n"); + resp = gsm0808_create_clear_rqst(GSM0808_CAUSE_RADIO_INTERFACE_MESSAGE_FAILURE); + sigtran_send(conn, resp, fi); + break; + case GSCON_EV_RSL_CONN_FAIL: + LOGPFSML(fi, LOGL_DEBUG, "Tx BSSMAP CLEAR REQUEST to MSC\n"); + resp = gsm0808_create_clear_rqst(GSM0808_CAUSE_RADIO_INTERFACE_FAILURE); + sigtran_send(conn, resp, fi); + break; + case GSCON_EV_MGW_MDCX_RESP_MSC: + LOGPFSML(fi, LOGL_DEBUG, "Rx MDCX of MSC side (LCLS?)\n"); + break; + case GSCON_EV_LCLS_FAIL: + break; + default: + OSMO_ASSERT(false); + break; + } +} + +void ho_dtap_cache_flush(struct gsm_subscriber_connection *conn, int send); + +static void gscon_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause) +{ + struct gsm_subscriber_connection *conn = fi->priv; + + if (conn->ho) { + LOGPFSML(fi, LOGL_DEBUG, "Releasing handover state\n"); + bsc_clear_handover(conn, 1); + conn->ho = NULL; + } + + if (conn->secondary_lchan) { + LOGPFSML(fi, LOGL_DEBUG, "Releasing secondary_lchan\n"); + lchan_release(conn->secondary_lchan, 0, RSL_REL_LOCAL_END); + conn->secondary_lchan = NULL; + } + if (conn->lchan) { + LOGPFSML(fi, LOGL_DEBUG, "Releasing lchan\n"); + lchan_release(conn->lchan, 0, RSL_REL_LOCAL_END); + conn->lchan = NULL; + } + + if (conn->bsub) { + LOGPFSML(fi, LOGL_DEBUG, "Putting bsc_subscr\n"); + bsc_subscr_put(conn->bsub); + conn->bsub = NULL; + } + + if (conn->sccp.state != SUBSCR_SCCP_ST_NONE) { + LOGPFSML(fi, LOGL_DEBUG, "Disconnecting SCCP\n"); + struct bsc_msc_data *msc = conn->sccp.msc; + /* FIXME: include a proper cause value / error message? */ + osmo_sccp_tx_disconn(msc->a.sccp_user, conn->sccp.conn_id, &msc->a.bsc_addr, 0); + conn->sccp.state = SUBSCR_SCCP_ST_NONE; + } + + /* drop pending messages */ + ho_dtap_cache_flush(conn, 0); + + penalty_timers_free(&conn->hodec2.penalty_timers); + + llist_del(&conn->entry); + talloc_free(conn); + fi->priv = NULL; +} + +static void gscon_pre_term(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause) +{ + struct gsm_subscriber_connection *conn = fi->priv; + + /* Make sure all possibly still open MGCP connections get closed */ + toss_mgcp_conn(conn, fi); + + if (conn->lcls.fi) { + /* request termination of LCLS FSM */ + osmo_fsm_inst_term(conn->lcls.fi, cause, NULL); + conn->lcls.fi = NULL; + } +} + +static int gscon_timer_cb(struct osmo_fsm_inst *fi) +{ + struct gsm_subscriber_connection *conn = fi->priv; + struct msgb *resp = NULL; + + switch (fi->T) { + case 993210: + /* MSC has not responded/confirmed connection with CC, this + * could indicate a bad SCCP connection. We now inform the the + * FSM that controls the BSSMAP reset about the event. Maybe + * a BSSMAP reset is necessary. */ + a_reset_conn_fail(conn->sccp.msc->a.reset_fsm); + + /* Since we could not reach the MSC, we give up and terminate + * the FSM instance now (N-DISCONNET.req is sent in + * gscon_cleanup() above) */ + osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL); + break; + case GSM0808_T10_TIMER_NR: /* Assignment Failed */ + resp = gsm0808_create_assignment_failure(GSM0808_CAUSE_RADIO_INTERFACE_FAILURE, NULL); + sigtran_send(conn, resp, fi); + osmo_fsm_inst_state_chg(fi, ST_ACTIVE, 0, 0); + break; + case MGCP_MGW_TIMEOUT_TIMER_NR: /* Assignment failed (no response from MGW) */ + resp = gsm0808_create_assignment_failure(GSM0808_CAUSE_EQUIPMENT_FAILURE, NULL); + sigtran_send(conn, resp, fi); + osmo_fsm_inst_state_chg(fi, ST_ACTIVE, 0, 0); + break; + case MGCP_MGW_HO_TIMEOUT_TIMER_NR: /* Handover failed (no response from MGW) */ + osmo_fsm_inst_state_chg(fi, ST_ACTIVE, 0, 0); + break; + default: + OSMO_ASSERT(false); + } + return 0; +} + +static struct osmo_fsm gscon_fsm = { + .name = "SUBSCR_CONN", + .states = gscon_fsm_states, + .num_states = ARRAY_SIZE(gscon_fsm_states), + .allstate_event_mask = S(GSCON_EV_A_DISC_IND) | S(GSCON_EV_A_CLEAR_CMD) | S(GSCON_EV_RSL_CONN_FAIL) | + S(GSCON_EV_RLL_REL_IND) | S(GSCON_EV_MGW_FAIL_BTS) | S(GSCON_EV_MGW_FAIL_MSC) | + S(GSCON_EV_MGW_MDCX_RESP_MSC) | S(GSCON_EV_LCLS_FAIL), + .allstate_action = gscon_fsm_allstate, + .cleanup = gscon_cleanup, + .pre_term = gscon_pre_term, + .timer_cb = gscon_timer_cb, + .log_subsys = DMSC, + .event_names = gscon_fsm_event_names, +}; + +/* Allocate a subscriber connection and its associated FSM */ +struct gsm_subscriber_connection *bsc_subscr_con_allocate(struct gsm_network *net) +{ + struct gsm_subscriber_connection *conn; + static bool g_initialized = false; + + if (!g_initialized) { + osmo_fsm_register(&gscon_fsm); + osmo_fsm_register(&lcls_fsm); + g_initialized = true; + } + + conn = talloc_zero(net, struct gsm_subscriber_connection); + if (!conn) + return NULL; + + conn->network = net; + INIT_LLIST_HEAD(&conn->ho_dtap_cache); + /* BTW, penalty timers will be initialized on-demand. */ + conn->sccp.conn_id = -1; + + /* don't allocate from 'conn' context, as gscon_cleanup() will call talloc_free(conn) before + * libosmocore will call talloc_free(conn->fi), i.e. avoid use-after-free during cleanup */ + conn->fi = osmo_fsm_inst_alloc(&gscon_fsm, net, conn, LOGL_NOTICE, NULL); + if (!conn->fi) { + talloc_free(conn); + return NULL; + } + + /* initialize to some magic values that indicate "IE not [yet] received" */ + conn->lcls.config = 0xff; + conn->lcls.control = 0xff; + conn->lcls.fi = osmo_fsm_inst_alloc_child(&lcls_fsm, conn->fi, GSCON_EV_LCLS_FAIL); + if (!conn->lcls.fi) { + osmo_fsm_inst_term(conn->fi, OSMO_FSM_TERM_ERROR, NULL); + return NULL; + } + conn->lcls.fi->priv = conn; + + llist_add_tail(&conn->entry, &net->subscr_conns); + return conn; +} diff --git a/src/osmo-bsc/bsc_subscriber.c b/src/osmo-bsc/bsc_subscriber.c new file mode 100644 index 000000000..d9d90baa9 --- /dev/null +++ b/src/osmo-bsc/bsc_subscriber.c @@ -0,0 +1,168 @@ +/* GSM subscriber details for use in BSC land */ + +/* + * (C) 2016 by sysmocom s.f.m.c. GmbH <info@sysmocom.de> + * + * Author: Neels Hofmeyr <nhofmeyr@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 <talloc.h> +#include <string.h> +#include <limits.h> + +#include <osmocom/gsm/gsm48.h> +#include <osmocom/core/logging.h> + +#include <osmocom/bsc/bsc_subscriber.h> +#include <osmocom/bsc/debug.h> + +static struct bsc_subscr *bsc_subscr_alloc(struct llist_head *list) +{ + struct bsc_subscr *bsub; + + bsub = talloc_zero(list, struct bsc_subscr); + if (!bsub) + return NULL; + + llist_add_tail(&bsub->entry, list); + bsub->use_count = 1; + + return bsub; +} + +struct bsc_subscr *bsc_subscr_find_by_imsi(struct llist_head *list, + const char *imsi) +{ + struct bsc_subscr *bsub; + + if (!imsi || !*imsi) + return NULL; + + llist_for_each_entry(bsub, list, entry) { + if (!strcmp(bsub->imsi, imsi)) + return bsc_subscr_get(bsub); + } + return NULL; +} + +struct bsc_subscr *bsc_subscr_find_by_tmsi(struct llist_head *list, + uint32_t tmsi) +{ + struct bsc_subscr *bsub; + + if (tmsi == GSM_RESERVED_TMSI) + return NULL; + + llist_for_each_entry(bsub, list, entry) { + if (bsub->tmsi == tmsi) + return bsc_subscr_get(bsub); + } + return NULL; +} + +void bsc_subscr_set_imsi(struct bsc_subscr *bsub, const char *imsi) +{ + if (!bsub) + return; + osmo_strlcpy(bsub->imsi, imsi, sizeof(bsub->imsi)); +} + +struct bsc_subscr *bsc_subscr_find_or_create_by_imsi(struct llist_head *list, + const char *imsi) +{ + struct bsc_subscr *bsub; + bsub = bsc_subscr_find_by_imsi(list, imsi); + if (bsub) + return bsub; + bsub = bsc_subscr_alloc(list); + bsc_subscr_set_imsi(bsub, imsi); + return bsub; +} + +struct bsc_subscr *bsc_subscr_find_or_create_by_tmsi(struct llist_head *list, + uint32_t tmsi) +{ + struct bsc_subscr *bsub; + bsub = bsc_subscr_find_by_tmsi(list, tmsi); + if (bsub) + return bsub; + bsub = bsc_subscr_alloc(list); + bsub->tmsi = tmsi; + return bsub; +} + +const char *bsc_subscr_name(struct bsc_subscr *bsub) +{ + static char buf[32]; + if (!bsub) + return "unknown"; + if (bsub->imsi[0]) + snprintf(buf, sizeof(buf), "IMSI:%s", bsub->imsi); + else + snprintf(buf, sizeof(buf), "TMSI:0x%08x", bsub->tmsi); + return buf; +} + +static void bsc_subscr_free(struct bsc_subscr *bsub) +{ + llist_del(&bsub->entry); + talloc_free(bsub); +} + +struct bsc_subscr *_bsc_subscr_get(struct bsc_subscr *bsub, + const char *file, int line) +{ + OSMO_ASSERT(bsub->use_count < INT_MAX); + bsub->use_count++; + LOGPSRC(DREF, LOGL_DEBUG, file, line, + "BSC subscr %s usage increases to: %d\n", + bsc_subscr_name(bsub), bsub->use_count); + return bsub; +} + +struct bsc_subscr *_bsc_subscr_put(struct bsc_subscr *bsub, + const char *file, int line) +{ + bsub->use_count--; + LOGPSRC(DREF, bsub->use_count >= 0? LOGL_DEBUG : LOGL_ERROR, + file, line, + "BSC subscr %s usage decreases to: %d\n", + bsc_subscr_name(bsub), bsub->use_count); + if (bsub->use_count <= 0) + bsc_subscr_free(bsub); + return NULL; +} + +void log_set_filter_bsc_subscr(struct log_target *target, + struct bsc_subscr *bsc_subscr) +{ + struct bsc_subscr **fsub = (void*)&target->filter_data[LOG_FLT_BSC_SUBSCR]; + + /* free the old data */ + if (*fsub) { + bsc_subscr_put(*fsub); + *fsub = NULL; + } + + if (bsc_subscr) { + target->filter_map |= (1 << LOG_FLT_BSC_SUBSCR); + *fsub = bsc_subscr_get(bsc_subscr); + } else + target->filter_map &= ~(1 << LOG_FLT_BSC_SUBSCR); +} diff --git a/src/osmo-bsc/bsc_vty.c b/src/osmo-bsc/bsc_vty.c new file mode 100644 index 000000000..5d0feb663 --- /dev/null +++ b/src/osmo-bsc/bsc_vty.c @@ -0,0 +1,5003 @@ +/* OpenBSC interface to quagga VTY */ +/* (C) 2009-2017 by Harald Welte <laforge@gnumonks.org> + * 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 <stdlib.h> +#include <stdbool.h> +#include <unistd.h> +#include <time.h> + +#include <osmocom/vty/command.h> +#include <osmocom/vty/buffer.h> +#include <osmocom/vty/vty.h> +#include <osmocom/vty/logging.h> +#include <osmocom/vty/stats.h> +#include <osmocom/vty/telnet_interface.h> +#include <osmocom/vty/misc.h> +#include <osmocom/gsm/protocol/gsm_04_08.h> +#include <osmocom/gsm/gsm0502.h> +#include <osmocom/ctrl/control_if.h> +#include <osmocom/gsm/gsm48.h> + +#include <arpa/inet.h> + +#include <osmocom/core/linuxlist.h> +#include <osmocom/bsc/gsm_data.h> +#include <osmocom/abis/e1_input.h> +#include <osmocom/bsc/abis_nm.h> +#include <osmocom/bsc/abis_om2000.h> +#include <osmocom/core/utils.h> +#include <osmocom/gsm/gsm_utils.h> +#include <osmocom/gsm/abis_nm.h> +#include <osmocom/bsc/chan_alloc.h> +#include <osmocom/bsc/meas_rep.h> +#include <osmocom/bsc/vty.h> +#include <osmocom/gprs/gprs_ns.h> +#include <osmocom/bsc/system_information.h> +#include <osmocom/bsc/debug.h> +#include <osmocom/bsc/paging.h> +#include <osmocom/bsc/ipaccess.h> +#include <osmocom/bsc/abis_rsl.h> +#include <osmocom/bsc/bsc_msc_data.h> +#include <osmocom/bsc/osmo_bsc_rf.h> +#include <osmocom/bsc/pcu_if.h> +#include <osmocom/bsc/handover.h> +#include <osmocom/bsc/handover_cfg.h> +#include <osmocom/bsc/handover_vty.h> +#include <osmocom/bsc/gsm_04_08_utils.h> +#include <osmocom/bsc/acc_ramp.h> +#include <osmocom/bsc/meas_feed.h> + +#include <inttypes.h> + +#include "../../bscconfig.h" + +#define BTS_NR_STR "BTS Number\n" +#define TRX_NR_STR "TRX Number\n" +#define TS_NR_STR "Timeslot Number\n" +#define SS_NR_STR "Sub-slot Number\n" +#define LCHAN_NR_STR "Logical Channel Number\n" +#define BTS_TRX_STR BTS_NR_STR TRX_NR_STR +#define BTS_TRX_TS_STR BTS_TRX_STR TS_NR_STR +#define BTS_TRX_TS_LCHAN_STR BTS_TRX_TS_STR LCHAN_NR_STR +#define BTS_NR_TRX_TS_STR2 \ + "BTS for manual command\n" BTS_NR_STR \ + "TRX for manual command\n" TRX_NR_STR \ + "Timeslot for manual command\n" TS_NR_STR +#define BTS_NR_TRX_TS_SS_STR2 \ + BTS_NR_TRX_TS_STR2 \ + "Sub-slot for manual command\n" SS_NR_STR + +/* FIXME: this should go to some common file */ +static const struct value_string gprs_ns_timer_strs[] = { + { 0, "tns-block" }, + { 1, "tns-block-retries" }, + { 2, "tns-reset" }, + { 3, "tns-reset-retries" }, + { 4, "tns-test" }, + { 5, "tns-alive" }, + { 6, "tns-alive-retries" }, + { 0, NULL } +}; + +static const struct value_string gprs_bssgp_cfg_strs[] = { + { 0, "blocking-timer" }, + { 1, "blocking-retries" }, + { 2, "unblocking-retries" }, + { 3, "reset-timer" }, + { 4, "reset-retries" }, + { 5, "suspend-timer" }, + { 6, "suspend-retries" }, + { 7, "resume-timer" }, + { 8, "resume-retries" }, + { 9, "capability-update-timer" }, + { 10, "capability-update-retries" }, + { 0, NULL } +}; + +static const struct value_string bts_neigh_mode_strs[] = { + { NL_MODE_AUTOMATIC, "automatic" }, + { NL_MODE_MANUAL, "manual" }, + { NL_MODE_MANUAL_SI5SEP, "manual-si5" }, + { 0, NULL } +}; + +const struct value_string bts_loc_fix_names[] = { + { BTS_LOC_FIX_INVALID, "invalid" }, + { BTS_LOC_FIX_2D, "fix2d" }, + { BTS_LOC_FIX_3D, "fix3d" }, + { 0, NULL } +}; + +struct cmd_node net_node = { + GSMNET_NODE, + "%s(config-net)# ", + 1, +}; + +struct cmd_node bts_node = { + BTS_NODE, + "%s(config-net-bts)# ", + 1, +}; + +struct cmd_node trx_node = { + TRX_NODE, + "%s(config-net-bts-trx)# ", + 1, +}; + +struct cmd_node ts_node = { + TS_NODE, + "%s(config-net-bts-trx-ts)# ", + 1, +}; + +static struct gsm_network *vty_global_gsm_network = NULL; + +struct gsm_network *gsmnet_from_vty(struct vty *v) +{ + /* It can't hurt to force callers to continue to pass the vty instance + * to this function, in case we'd like to retrieve the global + * gsm_network instance from the vty at some point in the future. But + * until then, just return the global pointer, which should have been + * initialized by common_cs_vty_init(). + */ + OSMO_ASSERT(vty_global_gsm_network); + return vty_global_gsm_network; +} + +static int dummy_config_write(struct vty *v) +{ + return CMD_SUCCESS; +} + +static void net_dump_nmstate(struct vty *vty, struct gsm_nm_state *nms) +{ + vty_out(vty,"Oper '%s', Admin '%s', Avail '%s'%s", + abis_nm_opstate_name(nms->operational), + get_value_string(abis_nm_adm_state_names, nms->administrative), + abis_nm_avail_name(nms->availability), VTY_NEWLINE); +} + +static void dump_pchan_load_vty(struct vty *vty, char *prefix, + const struct pchan_load *pl) +{ + int i; + int dumped = 0; + + for (i = 0; i < ARRAY_SIZE(pl->pchan); i++) { + const struct load_counter *lc = &pl->pchan[i]; + unsigned int percent; + + if (lc->total == 0) + continue; + + percent = (lc->used * 100) / lc->total; + + vty_out(vty, "%s%20s: %3u%% (%u/%u)%s", prefix, + gsm_pchan_name(i), percent, lc->used, lc->total, + VTY_NEWLINE); + dumped ++; + } + if (!dumped) + vty_out(vty, "%s(none)%s", prefix, VTY_NEWLINE); +} + +static void net_dump_vty(struct vty *vty, struct gsm_network *net) +{ + struct pchan_load pl; + int i; + + vty_out(vty, "BSC is on MCC-MNC %s and has %u BTS%s", + osmo_plmn_name(&net->plmn), net->num_bts, VTY_NEWLINE); + vty_out(vty, "%s", VTY_NEWLINE); + vty_out(vty, " Encryption:"); + for (i = 0; i < 8; i++) { + if (net->a5_encryption_mask & (1 << i)) + vty_out(vty, " A5/%u", i); + } + vty_out(vty, "%s", VTY_NEWLINE); + vty_out(vty, " NECI (TCH/H): %u%s", net->neci, + VTY_NEWLINE); + vty_out(vty, " Use TCH for Paging any: %d%s", net->pag_any_tch, + VTY_NEWLINE); + + { + struct gsm_bts *bts; + unsigned int ho_active_count = 0; + unsigned int ho_inactive_count = 0; + + llist_for_each_entry(bts, &net->bts_list, list) { + if (ho_get_ho_active(bts->ho)) + ho_active_count ++; + else + ho_inactive_count ++; + } + + if (ho_active_count && ho_inactive_count) + vty_out(vty, " Handover: On at %u BTS, Off at %u BTS%s", + ho_active_count, ho_inactive_count, VTY_NEWLINE); + else + vty_out(vty, " Handover: %s%s", ho_active_count ? "On" : "Off", + VTY_NEWLINE); + } + + network_chan_load(&pl, net); + vty_out(vty, " Current Channel Load:%s", VTY_NEWLINE); + dump_pchan_load_vty(vty, " ", &pl); + + /* show rf */ + if (net->bsc_data) + vty_out(vty, " Last RF Command: %s%s", + net->bsc_data->rf_ctrl->last_state_command, + VTY_NEWLINE); + if (net->bsc_data) + vty_out(vty, " Last RF Lock Command: %s%s", + net->bsc_data->rf_ctrl->last_rf_lock_ctrl_command, + VTY_NEWLINE); +} + +DEFUN(bsc_show_net, bsc_show_net_cmd, "show network", + SHOW_STR "Display information about a GSM NETWORK\n") +{ + struct gsm_network *net = gsmnet_from_vty(vty); + net_dump_vty(vty, net); + + return CMD_SUCCESS; +} + +static void e1isl_dump_vty(struct vty *vty, struct e1inp_sign_link *e1l) +{ + struct e1inp_line *line; + + if (!e1l) { + vty_out(vty, " None%s", VTY_NEWLINE); + return; + } + + line = e1l->ts->line; + + vty_out(vty, " E1 Line %u, Type %s: Timeslot %u, Mode %s%s", + line->num, line->driver->name, e1l->ts->num, + e1inp_signtype_name(e1l->type), VTY_NEWLINE); + vty_out(vty, " E1 TEI %u, SAPI %u%s", + e1l->tei, e1l->sapi, VTY_NEWLINE); +} + +static void vty_out_neigh_list(struct vty *vty, struct bitvec *bv) +{ + int count = 0; + int i; + for (i = 0; i < 1024; i++) { + if (!bitvec_get_bit_pos(bv, i)) + continue; + vty_out(vty, " %u", i); + count ++; + } + if (!count) + vty_out(vty, " (none)"); + else + vty_out(vty, " (%d)", count); +} + +static void bts_dump_vty_features(struct vty *vty, struct gsm_bts *bts) +{ + unsigned int i; + bool no_features = true; + vty_out(vty, " Features:%s", VTY_NEWLINE); + + for (i = 0; i < _NUM_BTS_FEAT; i++) { + if (osmo_bts_has_feature(&bts->features, i)) { + vty_out(vty, " %03u ", i); + vty_out(vty, "%-40s%s", osmo_bts_feature_name(i), VTY_NEWLINE); + no_features = false; + } + } + + if (no_features) + vty_out(vty, " (not available)%s", VTY_NEWLINE); +} + +static void bts_dump_vty(struct vty *vty, struct gsm_bts *bts) +{ + struct pchan_load pl; + unsigned long long sec; + struct gsm_bts_trx *trx; + int ts_hopping_total; + int ts_non_hopping_total; + + vty_out(vty, "BTS %u is of %s type in band %s, has CI %u LAC %u, " + "BSIC %u (NCC=%u, BCC=%u) and %u TRX%s", + bts->nr, btstype2str(bts->type), gsm_band_name(bts->band), + bts->cell_identity, + bts->location_area_code, bts->bsic, + bts->bsic >> 3, bts->bsic & 7, + bts->num_trx, VTY_NEWLINE); + vty_out(vty, " Description: %s%s", + bts->description ? bts->description : "(null)", VTY_NEWLINE); + + vty_out(vty, " ARFCNs:"); + ts_hopping_total = 0; + ts_non_hopping_total = 0; + llist_for_each_entry(trx, &bts->trx_list, list) { + int ts_nr; + int ts_hopping = 0; + int ts_non_hopping = 0; + for (ts_nr = 0; ts_nr < TRX_NR_TS; ts_nr++) { + struct gsm_bts_trx_ts *ts = &trx->ts[ts_nr]; + if (ts->hopping.enabled) + ts_hopping++; + else + ts_non_hopping++; + } + + if (ts_non_hopping) + vty_out(vty, " %u", trx->arfcn); + ts_hopping_total += ts_hopping; + ts_non_hopping_total += ts_non_hopping; + } + if (ts_hopping_total) { + if (ts_non_hopping_total) + vty_out(vty, " / Hopping on %d of %d timeslots", + ts_hopping_total, ts_hopping_total + ts_non_hopping_total); + else + vty_out(vty, " Hopping on all %d timeslots", ts_hopping_total); + } + vty_out(vty, "%s", VTY_NEWLINE); + + if (strnlen(bts->pcu_version, MAX_VERSION_LENGTH)) + vty_out(vty, " PCU version %s connected%s", bts->pcu_version, + VTY_NEWLINE); + vty_out(vty, " MS Max power: %u dBm%s", bts->ms_max_power, VTY_NEWLINE); + vty_out(vty, " Minimum Rx Level for Access: %i dBm%s", + rxlev2dbm(bts->si_common.cell_sel_par.rxlev_acc_min), + VTY_NEWLINE); + vty_out(vty, " Cell Reselection Hysteresis: %u dBm%s", + bts->si_common.cell_sel_par.cell_resel_hyst*2, VTY_NEWLINE); + vty_out(vty, " Access Control Class ramping: %senabled%s", + acc_ramp_is_enabled(&bts->acc_ramp) ? "" : "not ", VTY_NEWLINE); + if (acc_ramp_is_enabled(&bts->acc_ramp)) { + if (!acc_ramp_step_interval_is_dynamic(&bts->acc_ramp)) + vty_out(vty, " Access Control Class ramping step interval: %u seconds%s", + acc_ramp_get_step_interval(&bts->acc_ramp), VTY_NEWLINE); + else + vty_out(vty, " Access Control Class ramping step interval: dynamic%s", VTY_NEWLINE); + vty_out(vty, " enabling %u Access Control Class%s per ramping step%s", + acc_ramp_get_step_size(&bts->acc_ramp), + acc_ramp_get_step_size(&bts->acc_ramp) > 1 ? "es" : "", VTY_NEWLINE); + } + vty_out(vty, " RACH TX-Integer: %u%s", bts->si_common.rach_control.tx_integer, + VTY_NEWLINE); + vty_out(vty, " RACH Max transmissions: %u%s", + rach_max_trans_raw2val(bts->si_common.rach_control.max_trans), + VTY_NEWLINE); + if (bts->si_common.rach_control.cell_bar) + vty_out(vty, " CELL IS BARRED%s", VTY_NEWLINE); + if (bts->dtxu != GSM48_DTX_SHALL_NOT_BE_USED) + vty_out(vty, " Uplink DTX: %s%s", + (bts->dtxu != GSM48_DTX_SHALL_BE_USED) ? + "enabled" : "forced", VTY_NEWLINE); + else + vty_out(vty, " Uplink DTX: not enabled%s", VTY_NEWLINE); + vty_out(vty, " Downlink DTX: %senabled%s", bts->dtxd ? "" : "not ", + VTY_NEWLINE); + vty_out(vty, " Channel Description Attachment: %s%s", + (bts->si_common.chan_desc.att) ? "yes" : "no", VTY_NEWLINE); + vty_out(vty, " Channel Description BS-PA-MFRMS: %u%s", + bts->si_common.chan_desc.bs_pa_mfrms + 2, VTY_NEWLINE); + vty_out(vty, " Channel Description BS-AG_BLKS-RES: %u%s", + bts->si_common.chan_desc.bs_ag_blks_res, VTY_NEWLINE); + vty_out(vty, " System Information present: 0x%08x, static: 0x%08x%s", + bts->si_valid, bts->si_mode_static, VTY_NEWLINE); + vty_out(vty, " Early Classmark Sending: 2G %s, 3G %s%s%s", + bts->early_classmark_allowed ? "allowed" : "forbidden", + bts->early_classmark_allowed_3g ? "allowed" : "forbidden", + bts->early_classmark_allowed_3g && !bts->early_classmark_allowed ? + " (forbidden by 2G bit)" : "", + VTY_NEWLINE); + if (bts->pcu_sock_path) + vty_out(vty, " PCU Socket Path: %s%s", bts->pcu_sock_path, VTY_NEWLINE); + if (is_ipaccess_bts(bts)) + vty_out(vty, " Unit ID: %u/%u/0, OML Stream ID 0x%02x%s", + bts->ip_access.site_id, bts->ip_access.bts_id, + bts->oml_tei, VTY_NEWLINE); + else if (bts->type == GSM_BTS_TYPE_NOKIA_SITE) + vty_out(vty, " Skip Reset: %d%s", + bts->nokia.skip_reset, VTY_NEWLINE); + vty_out(vty, " NM State: "); + net_dump_nmstate(vty, &bts->mo.nm_state); + vty_out(vty, " Site Mgr NM State: "); + net_dump_nmstate(vty, &bts->site_mgr.mo.nm_state); + vty_out(vty, " GPRS NSE: "); + net_dump_nmstate(vty, &bts->gprs.nse.mo.nm_state); + vty_out(vty, " GPRS CELL: "); + net_dump_nmstate(vty, &bts->gprs.cell.mo.nm_state); + vty_out(vty, " GPRS NSVC0: "); + net_dump_nmstate(vty, &bts->gprs.nsvc[0].mo.nm_state); + vty_out(vty, " GPRS NSVC1: "); + net_dump_nmstate(vty, &bts->gprs.nsvc[1].mo.nm_state); + vty_out(vty, " Paging: %u pending requests, %u free slots%s", + paging_pending_requests_nr(bts), + bts->paging.available_slots, VTY_NEWLINE); + if (is_ipaccess_bts(bts)) { + vty_out(vty, " OML Link state: %s", get_model_oml_status(bts)); + sec = bts_uptime(bts); + if (sec) + vty_out(vty, " %llu days %llu hours %llu min. %llu sec.", + OSMO_SEC2DAY(sec), OSMO_SEC2HRS(sec), OSMO_SEC2MIN(sec), sec % 60); + vty_out(vty, "%s", VTY_NEWLINE); + } else { + vty_out(vty, " E1 Signalling Link:%s", VTY_NEWLINE); + e1isl_dump_vty(vty, bts->oml_link); + } + + vty_out(vty, " Neighbor Cells: "); + switch (bts->neigh_list_manual_mode) { + default: + case NL_MODE_AUTOMATIC: + vty_out(vty, "Automatic"); + /* generate_bcch_chan_list() should populate si_common.neigh_list */ + break; + case NL_MODE_MANUAL: + vty_out(vty, "Manual"); + break; + case NL_MODE_MANUAL_SI5SEP: + vty_out(vty, "Manual/separate SI5"); + break; + } + vty_out(vty, ", ARFCNs:"); + vty_out_neigh_list(vty, &bts->si_common.neigh_list); + if (bts->neigh_list_manual_mode == NL_MODE_MANUAL_SI5SEP) { + vty_out(vty, " SI5:"); + vty_out_neigh_list(vty, &bts->si_common.si5_neigh_list); + } + vty_out(vty, "%s", VTY_NEWLINE); + + /* FIXME: chan_desc */ + memset(&pl, 0, sizeof(pl)); + bts_chan_load(&pl, bts); + vty_out(vty, " Current Channel Load:%s", VTY_NEWLINE); + dump_pchan_load_vty(vty, " ", &pl); + + vty_out(vty, " Channel Requests : %"PRIu64" total, %"PRIu64" no channel%s", + bts->bts_ctrs->ctr[BTS_CTR_CHREQ_TOTAL].current, + bts->bts_ctrs->ctr[BTS_CTR_CHREQ_NO_CHANNEL].current, + VTY_NEWLINE); + vty_out(vty, " Channel Failures : %"PRIu64" rf_failures, %"PRIu64" rll failures%s", + bts->bts_ctrs->ctr[BTS_CTR_CHAN_RF_FAIL].current, + bts->bts_ctrs->ctr[BTS_CTR_CHAN_RLL_ERR].current, + VTY_NEWLINE); + vty_out(vty, " BTS failures : %"PRIu64" OML, %"PRIu64" RSL%s", + bts->bts_ctrs->ctr[BTS_CTR_BTS_OML_FAIL].current, + bts->bts_ctrs->ctr[BTS_CTR_BTS_RSL_FAIL].current, + VTY_NEWLINE); + + bts_dump_vty_features(vty, bts); +} + +DEFUN(show_bts, show_bts_cmd, "show bts [<0-255>]", + SHOW_STR "Display information about a BTS\n" + "BTS number") +{ + struct gsm_network *net = gsmnet_from_vty(vty); + int bts_nr; + + if (argc != 0) { + /* use the BTS number that the user has specified */ + bts_nr = atoi(argv[0]); + if (bts_nr >= net->num_bts) { + vty_out(vty, "%% can't find BTS '%s'%s", argv[0], + VTY_NEWLINE); + return CMD_WARNING; + } + bts_dump_vty(vty, gsm_bts_num(net, bts_nr)); + return CMD_SUCCESS; + } + /* print all BTS's */ + for (bts_nr = 0; bts_nr < net->num_bts; bts_nr++) + bts_dump_vty(vty, gsm_bts_num(net, bts_nr)); + + return CMD_SUCCESS; +} + +/* utility functions */ +static void parse_e1_link(struct gsm_e1_subslot *e1_link, const char *line, + const char *ts, const char *ss) +{ + e1_link->e1_nr = atoi(line); + e1_link->e1_ts = atoi(ts); + if (!strcmp(ss, "full")) + e1_link->e1_ts_ss = 255; + else + e1_link->e1_ts_ss = atoi(ss); +} + +static void config_write_e1_link(struct vty *vty, struct gsm_e1_subslot *e1_link, + const char *prefix) +{ + if (!e1_link->e1_ts) + return; + + if (e1_link->e1_ts_ss == 255) + vty_out(vty, "%se1 line %u timeslot %u sub-slot full%s", + prefix, e1_link->e1_nr, e1_link->e1_ts, VTY_NEWLINE); + else + vty_out(vty, "%se1 line %u timeslot %u sub-slot %u%s", + prefix, e1_link->e1_nr, e1_link->e1_ts, + e1_link->e1_ts_ss, VTY_NEWLINE); +} + + +static void config_write_ts_single(struct vty *vty, struct gsm_bts_trx_ts *ts) +{ + vty_out(vty, " timeslot %u%s", ts->nr, VTY_NEWLINE); + if (ts->tsc != -1) + vty_out(vty, " training_sequence_code %u%s", ts->tsc, VTY_NEWLINE); + if (ts->pchan != GSM_PCHAN_NONE) + vty_out(vty, " phys_chan_config %s%s", + gsm_pchan_name(ts->pchan), VTY_NEWLINE); + vty_out(vty, " hopping enabled %u%s", + ts->hopping.enabled, VTY_NEWLINE); + if (ts->hopping.enabled) { + unsigned int i; + vty_out(vty, " hopping sequence-number %u%s", + ts->hopping.hsn, VTY_NEWLINE); + vty_out(vty, " hopping maio %u%s", + ts->hopping.maio, VTY_NEWLINE); + for (i = 0; i < ts->hopping.arfcns.data_len*8; i++) { + if (!bitvec_get_bit_pos(&ts->hopping.arfcns, i)) + continue; + vty_out(vty, " hopping arfcn add %u%s", + i, VTY_NEWLINE); + } + } + config_write_e1_link(vty, &ts->e1_link, " "); + + if (ts->trx->bts->model->config_write_ts) + ts->trx->bts->model->config_write_ts(vty, ts); +} + +static void config_write_trx_single(struct vty *vty, struct gsm_bts_trx *trx) +{ + int i; + + vty_out(vty, " trx %u%s", trx->nr, VTY_NEWLINE); + if (trx->description) + vty_out(vty, " description %s%s", trx->description, + VTY_NEWLINE); + vty_out(vty, " rf_locked %u%s", + trx->mo.nm_state.administrative == NM_STATE_LOCKED ? 1 : 0, + VTY_NEWLINE); + vty_out(vty, " arfcn %u%s", trx->arfcn, VTY_NEWLINE); + vty_out(vty, " nominal power %u%s", trx->nominal_power, VTY_NEWLINE); + vty_out(vty, " max_power_red %u%s", trx->max_power_red, VTY_NEWLINE); + config_write_e1_link(vty, &trx->rsl_e1_link, " rsl "); + vty_out(vty, " rsl e1 tei %u%s", trx->rsl_tei, VTY_NEWLINE); + + if (trx->bts->model->config_write_trx) + trx->bts->model->config_write_trx(vty, trx); + + for (i = 0; i < TRX_NR_TS; i++) + config_write_ts_single(vty, &trx->ts[i]); +} + +static void config_write_bts_gprs(struct vty *vty, struct gsm_bts *bts) +{ + unsigned int i; + vty_out(vty, " gprs mode %s%s", bts_gprs_mode_name(bts->gprs.mode), + VTY_NEWLINE); + if (bts->gprs.mode == BTS_GPRS_NONE) + return; + + vty_out(vty, " gprs 11bit_rach_support_for_egprs %u%s", + bts->gprs.supports_egprs_11bit_rach, VTY_NEWLINE); + + vty_out(vty, " gprs routing area %u%s", bts->gprs.rac, + VTY_NEWLINE); + vty_out(vty, " gprs network-control-order nc%u%s", + bts->gprs.net_ctrl_ord, VTY_NEWLINE); + if (!bts->gprs.ctrl_ack_type_use_block) + vty_out(vty, " gprs control-ack-type-rach%s", VTY_NEWLINE); + vty_out(vty, " gprs cell bvci %u%s", bts->gprs.cell.bvci, + VTY_NEWLINE); + for (i = 0; i < ARRAY_SIZE(bts->gprs.cell.timer); i++) + vty_out(vty, " gprs cell timer %s %u%s", + get_value_string(gprs_bssgp_cfg_strs, i), + bts->gprs.cell.timer[i], VTY_NEWLINE); + vty_out(vty, " gprs nsei %u%s", bts->gprs.nse.nsei, + VTY_NEWLINE); + for (i = 0; i < ARRAY_SIZE(bts->gprs.nse.timer); i++) + vty_out(vty, " gprs ns timer %s %u%s", + get_value_string(gprs_ns_timer_strs, i), + bts->gprs.nse.timer[i], VTY_NEWLINE); + for (i = 0; i < ARRAY_SIZE(bts->gprs.nsvc); i++) { + struct gsm_bts_gprs_nsvc *nsvc = + &bts->gprs.nsvc[i]; + struct in_addr ia; + + ia.s_addr = htonl(nsvc->remote_ip); + vty_out(vty, " gprs nsvc %u nsvci %u%s", i, + nsvc->nsvci, VTY_NEWLINE); + vty_out(vty, " gprs nsvc %u local udp port %u%s", i, + nsvc->local_port, VTY_NEWLINE); + vty_out(vty, " gprs nsvc %u remote udp port %u%s", i, + nsvc->remote_port, VTY_NEWLINE); + vty_out(vty, " gprs nsvc %u remote ip %s%s", i, + inet_ntoa(ia), VTY_NEWLINE); + } +} + +/* Write the model data if there is one */ +static void config_write_bts_model(struct vty *vty, struct gsm_bts *bts) +{ + struct gsm_bts_trx *trx; + + if (!bts->model) + return; + + if (bts->model->config_write_bts) + bts->model->config_write_bts(vty, bts); + + llist_for_each_entry(trx, &bts->trx_list, list) + config_write_trx_single(vty, trx); +} + +static void write_amr_modes(struct vty *vty, const char *prefix, + const char *name, struct amr_mode *modes, int num) +{ + int i; + + vty_out(vty, " %s threshold %s", prefix, name); + for (i = 0; i < num - 1; i++) + vty_out(vty, " %d", modes[i].threshold); + vty_out(vty, "%s", VTY_NEWLINE); + vty_out(vty, " %s hysteresis %s", prefix, name); + for (i = 0; i < num - 1; i++) + vty_out(vty, " %d", modes[i].hysteresis); + vty_out(vty, "%s", VTY_NEWLINE); +} + +static void config_write_bts_amr(struct vty *vty, struct gsm_bts *bts, + struct amr_multirate_conf *mr, int full) +{ + struct gsm48_multi_rate_conf *mr_conf; + const char *prefix = (full) ? "amr tch-f" : "amr tch-h"; + int i, num; + + if (!(mr->gsm48_ie[1])) + return; + + mr_conf = (struct gsm48_multi_rate_conf *) mr->gsm48_ie; + + num = 0; + vty_out(vty, " %s modes", prefix); + for (i = 0; i < ((full) ? 8 : 6); i++) { + if ((mr->gsm48_ie[1] & (1 << i))) { + vty_out(vty, " %d", i); + num++; + } + } + vty_out(vty, "%s", VTY_NEWLINE); + if (num > 4) + num = 4; + if (num > 1) { + write_amr_modes(vty, prefix, "ms", mr->ms_mode, num); + write_amr_modes(vty, prefix, "bts", mr->bts_mode, num); + } + vty_out(vty, " %s start-mode ", prefix); + if (mr_conf->icmi) { + num = 0; + for (i = 0; i < ((full) ? 8 : 6) && num < 4; i++) { + if ((mr->gsm48_ie[1] & (1 << i))) + num++; + if (mr_conf->smod == num - 1) { + vty_out(vty, "%d%s", num, VTY_NEWLINE); + break; + } + } + } else + vty_out(vty, "auto%s", VTY_NEWLINE); +} + +static void config_write_bts_single(struct vty *vty, struct gsm_bts *bts) +{ + int i; + uint8_t tmp; + + vty_out(vty, " bts %u%s", bts->nr, VTY_NEWLINE); + vty_out(vty, " type %s%s", btstype2str(bts->type), VTY_NEWLINE); + if (bts->description) + vty_out(vty, " description %s%s", bts->description, VTY_NEWLINE); + vty_out(vty, " band %s%s", gsm_band_name(bts->band), VTY_NEWLINE); + vty_out(vty, " cell_identity %u%s", bts->cell_identity, VTY_NEWLINE); + vty_out(vty, " location_area_code %u%s", bts->location_area_code, + VTY_NEWLINE); + if (bts->dtxu != GSM48_DTX_SHALL_NOT_BE_USED) + vty_out(vty, " dtx uplink%s%s", + (bts->dtxu != GSM48_DTX_SHALL_BE_USED) ? "" : " force", + VTY_NEWLINE); + if (bts->dtxd) + vty_out(vty, " dtx downlink%s", VTY_NEWLINE); + vty_out(vty, " base_station_id_code %u%s", bts->bsic, VTY_NEWLINE); + vty_out(vty, " ms max power %u%s", bts->ms_max_power, VTY_NEWLINE); + vty_out(vty, " cell reselection hysteresis %u%s", + bts->si_common.cell_sel_par.cell_resel_hyst*2, VTY_NEWLINE); + vty_out(vty, " rxlev access min %u%s", + bts->si_common.cell_sel_par.rxlev_acc_min, VTY_NEWLINE); + + if (bts->si_common.cell_ro_sel_par.present) { + struct gsm48_si_selection_params *sp; + sp = &bts->si_common.cell_ro_sel_par; + + if (sp->cbq) + vty_out(vty, " cell bar qualify %u%s", + sp->cbq, VTY_NEWLINE); + + if (sp->cell_resel_off) + vty_out(vty, " cell reselection offset %u%s", + sp->cell_resel_off*2, VTY_NEWLINE); + + if (sp->temp_offs == 7) + vty_out(vty, " temporary offset infinite%s", + VTY_NEWLINE); + else if (sp->temp_offs) + vty_out(vty, " temporary offset %u%s", + sp->temp_offs*10, VTY_NEWLINE); + + if (sp->penalty_time == 31) + vty_out(vty, " penalty time reserved%s", + VTY_NEWLINE); + else if (sp->penalty_time) + vty_out(vty, " penalty time %u%s", + (sp->penalty_time*20)+20, VTY_NEWLINE); + } + + if (gsm_bts_get_radio_link_timeout(bts) < 0) + vty_out(vty, " radio-link-timeout infinite%s", VTY_NEWLINE); + else + vty_out(vty, " radio-link-timeout %d%s", + gsm_bts_get_radio_link_timeout(bts), VTY_NEWLINE); + + vty_out(vty, " channel allocator %s%s", + bts->chan_alloc_reverse ? "descending" : "ascending", + VTY_NEWLINE); + vty_out(vty, " rach tx integer %u%s", + bts->si_common.rach_control.tx_integer, VTY_NEWLINE); + vty_out(vty, " rach max transmission %u%s", + rach_max_trans_raw2val(bts->si_common.rach_control.max_trans), + VTY_NEWLINE); + + vty_out(vty, " channel-descrption attach %u%s", + bts->si_common.chan_desc.att, VTY_NEWLINE); + vty_out(vty, " channel-descrption bs-pa-mfrms %u%s", + bts->si_common.chan_desc.bs_pa_mfrms + 2, VTY_NEWLINE); + vty_out(vty, " channel-descrption bs-ag-blks-res %u%s", + bts->si_common.chan_desc.bs_ag_blks_res, VTY_NEWLINE); + + if (bts->rach_b_thresh != -1) + vty_out(vty, " rach nm busy threshold %u%s", + bts->rach_b_thresh, VTY_NEWLINE); + if (bts->rach_ldavg_slots != -1) + vty_out(vty, " rach nm load average %u%s", + bts->rach_ldavg_slots, VTY_NEWLINE); + if (bts->si_common.rach_control.cell_bar) + vty_out(vty, " cell barred 1%s", VTY_NEWLINE); + if ((bts->si_common.rach_control.t2 & 0x4) == 0) + vty_out(vty, " rach emergency call allowed 1%s", VTY_NEWLINE); + if ((bts->si_common.rach_control.t3) != 0) + for (i = 0; i < 8; i++) + if (bts->si_common.rach_control.t3 & (0x1 << i)) + vty_out(vty, " rach access-control-class %d barred%s", i, VTY_NEWLINE); + if ((bts->si_common.rach_control.t2 & 0xfb) != 0) + for (i = 0; i < 8; i++) + if ((i != 2) && (bts->si_common.rach_control.t2 & (0x1 << i))) + vty_out(vty, " rach access-control-class %d barred%s", i+8, VTY_NEWLINE); + vty_out(vty, " %saccess-control-class-ramping%s", acc_ramp_is_enabled(&bts->acc_ramp) ? "" : "no ", VTY_NEWLINE); + if (!acc_ramp_step_interval_is_dynamic(&bts->acc_ramp)) { + vty_out(vty, " access-control-class-ramping-step-interval %u%s", + acc_ramp_get_step_interval(&bts->acc_ramp), VTY_NEWLINE); + } else { + vty_out(vty, " access-control-class-ramping-step-interval dynamic%s", VTY_NEWLINE); + } + vty_out(vty, " access-control-class-ramping-step-size %u%s", acc_ramp_get_step_size(&bts->acc_ramp), + VTY_NEWLINE); + for (i = SYSINFO_TYPE_1; i < _MAX_SYSINFO_TYPE; i++) { + if (bts->si_mode_static & (1 << i)) { + vty_out(vty, " system-information %s mode static%s", + get_value_string(osmo_sitype_strs, i), VTY_NEWLINE); + vty_out(vty, " system-information %s static %s%s", + get_value_string(osmo_sitype_strs, i), + osmo_hexdump_nospc(GSM_BTS_SI(bts, i), GSM_MACBLOCK_LEN), + VTY_NEWLINE); + } + } + vty_out(vty, " early-classmark-sending %s%s", + bts->early_classmark_allowed ? "allowed" : "forbidden", VTY_NEWLINE); + vty_out(vty, " early-classmark-sending-3g %s%s", + bts->early_classmark_allowed_3g ? "allowed" : "forbidden", VTY_NEWLINE); + switch (bts->type) { + case GSM_BTS_TYPE_NANOBTS: + case GSM_BTS_TYPE_OSMOBTS: + vty_out(vty, " ip.access unit_id %u %u%s", + bts->ip_access.site_id, bts->ip_access.bts_id, VTY_NEWLINE); + if (bts->ip_access.rsl_ip) { + struct in_addr ia; + ia.s_addr = htonl(bts->ip_access.rsl_ip); + vty_out(vty, " ip.access rsl-ip %s%s", inet_ntoa(ia), + VTY_NEWLINE); + } + vty_out(vty, " oml ip.access stream_id %u line %u%s", + bts->oml_tei, bts->oml_e1_link.e1_nr, VTY_NEWLINE); + break; + case GSM_BTS_TYPE_NOKIA_SITE: + vty_out(vty, " nokia_site skip-reset %d%s", bts->nokia.skip_reset, VTY_NEWLINE); + vty_out(vty, " nokia_site no-local-rel-conf %d%s", + bts->nokia.no_loc_rel_cnf, VTY_NEWLINE); + vty_out(vty, " nokia_site bts-reset-timer %d%s", bts->nokia.bts_reset_timer_cnf, VTY_NEWLINE); + /* fall through: Nokia requires "oml e1" parameters also */ + default: + config_write_e1_link(vty, &bts->oml_e1_link, " oml "); + vty_out(vty, " oml e1 tei %u%s", bts->oml_tei, VTY_NEWLINE); + break; + } + + /* if we have a limit, write it */ + if (bts->paging.free_chans_need >= 0) + vty_out(vty, " paging free %d%s", bts->paging.free_chans_need, VTY_NEWLINE); + + vty_out(vty, " neighbor-list mode %s%s", + get_value_string(bts_neigh_mode_strs, bts->neigh_list_manual_mode), VTY_NEWLINE); + if (bts->neigh_list_manual_mode != NL_MODE_AUTOMATIC) { + for (i = 0; i < 1024; i++) { + if (bitvec_get_bit_pos(&bts->si_common.neigh_list, i)) + vty_out(vty, " neighbor-list add arfcn %u%s", + i, VTY_NEWLINE); + } + } + if (bts->neigh_list_manual_mode == NL_MODE_MANUAL_SI5SEP) { + for (i = 0; i < 1024; i++) { + if (bitvec_get_bit_pos(&bts->si_common.si5_neigh_list, i)) + vty_out(vty, " si5 neighbor-list add arfcn %u%s", + i, VTY_NEWLINE); + } + } + + for (i = 0; i < MAX_EARFCN_LIST; i++) { + struct osmo_earfcn_si2q *e = &bts->si_common.si2quater_neigh_list; + if (e->arfcn[i] != OSMO_EARFCN_INVALID) { + vty_out(vty, " si2quater neighbor-list add earfcn %u " + "thresh-hi %u", e->arfcn[i], e->thresh_hi); + + vty_out(vty, " thresh-lo %u", + e->thresh_lo_valid ? e->thresh_lo : 32); + + vty_out(vty, " prio %u", + e->prio_valid ? e->prio : 8); + + vty_out(vty, " qrxlv %u", + e->qrxlm_valid ? e->qrxlm : 32); + + tmp = e->meas_bw[i]; + vty_out(vty, " meas %u", + (tmp != OSMO_EARFCN_MEAS_INVALID) ? tmp : 8); + + vty_out(vty, "%s", VTY_NEWLINE); + } + } + + for (i = 0; i < bts->si_common.uarfcn_length; i++) { + vty_out(vty, " si2quater neighbor-list add uarfcn %u %u %u%s", + bts->si_common.data.uarfcn_list[i], + bts->si_common.data.scramble_list[i] & ~(1 << 9), + (bts->si_common.data.scramble_list[i] >> 9) & 1, + VTY_NEWLINE); + } + + vty_out(vty, " codec-support fr"); + if (bts->codec.hr) + vty_out(vty, " hr"); + if (bts->codec.efr) + vty_out(vty, " efr"); + if (bts->codec.amr) + vty_out(vty, " amr"); + vty_out(vty, "%s", VTY_NEWLINE); + + config_write_bts_amr(vty, bts, &bts->mr_full, 1); + config_write_bts_amr(vty, bts, &bts->mr_half, 0); + + config_write_bts_gprs(vty, bts); + + if (bts->excl_from_rf_lock) + vty_out(vty, " rf-lock-exclude%s", VTY_NEWLINE); + + vty_out(vty, " %sforce-combined-si%s", + bts->force_combined_si ? "" : "no ", VTY_NEWLINE); + + for (i = 0; i < ARRAY_SIZE(bts->depends_on); ++i) { + int j; + + if (bts->depends_on[i] == 0) + continue; + + for (j = 0; j < sizeof(bts->depends_on[i]) * 8; ++j) { + int bts_nr; + + if ((bts->depends_on[i] & (1<<j)) == 0) + continue; + + bts_nr = (i * sizeof(bts->depends_on[i]) * 8) + j; + vty_out(vty, " depends-on-bts %d%s", bts_nr, VTY_NEWLINE); + } + } + if (bts->pcu_sock_path) + vty_out(vty, " pcu-socket %s%s", bts->pcu_sock_path, VTY_NEWLINE); + + ho_vty_write_bts(vty, bts); + + config_write_bts_model(vty, bts); +} + +static int config_write_bts(struct vty *v) +{ + struct gsm_network *gsmnet = gsmnet_from_vty(v); + struct gsm_bts *bts; + + llist_for_each_entry(bts, &gsmnet->bts_list, list) + config_write_bts_single(v, bts); + + return CMD_SUCCESS; +} + +/* small helper macro for conditional dumping of timer */ +#define VTY_OUT_TIMER(number) \ + if (gsmnet->T##number != GSM_T##number##_DEFAULT) \ + vty_out(vty, " timer t"#number" %u%s", gsmnet->T##number, VTY_NEWLINE) + +static int config_write_net(struct vty *vty) +{ + struct gsm_network *gsmnet = gsmnet_from_vty(vty); + int i; + + vty_out(vty, "network%s", VTY_NEWLINE); + vty_out(vty, " network country code %s%s", osmo_mcc_name(gsmnet->plmn.mcc), VTY_NEWLINE); + vty_out(vty, " mobile network code %s%s", + osmo_mnc_name(gsmnet->plmn.mnc, gsmnet->plmn.mnc_3_digits), VTY_NEWLINE); + vty_out(vty, " encryption a5"); + for (i = 0; i < 8; i++) { + if (gsmnet->a5_encryption_mask & (1 << i)) + vty_out(vty, " %u", i); + } + vty_out(vty, "%s", VTY_NEWLINE); + vty_out(vty, " neci %u%s", gsmnet->neci, VTY_NEWLINE); + vty_out(vty, " paging any use tch %d%s", gsmnet->pag_any_tch, VTY_NEWLINE); + + ho_vty_write_net(vty, gsmnet); + + VTY_OUT_TIMER(3101); + VTY_OUT_TIMER(3103); + VTY_OUT_TIMER(3105); + VTY_OUT_TIMER(3107); + VTY_OUT_TIMER(3109); + VTY_OUT_TIMER(3111); + VTY_OUT_TIMER(3113); + VTY_OUT_TIMER(3115); + VTY_OUT_TIMER(3117); + VTY_OUT_TIMER(3119); + VTY_OUT_TIMER(3122); + VTY_OUT_TIMER(3141); + if (!gsmnet->dyn_ts_allow_tch_f) + vty_out(vty, " dyn_ts_allow_tch_f 0%s", VTY_NEWLINE); + if (gsmnet->tz.override != 0) { + if (gsmnet->tz.dst) + vty_out(vty, " timezone %d %d %d%s", + gsmnet->tz.hr, gsmnet->tz.mn, gsmnet->tz.dst, + VTY_NEWLINE); + else + vty_out(vty, " timezone %d %d%s", + gsmnet->tz.hr, gsmnet->tz.mn, VTY_NEWLINE); + } + if (gsmnet->t3212 == 0) + vty_out(vty, " no periodic location update%s", VTY_NEWLINE); + else + vty_out(vty, " periodic location update %u%s", + gsmnet->t3212 * 6, VTY_NEWLINE); + + { + uint16_t meas_port; + char *meas_host; + const char *meas_scenario; + + meas_feed_cfg_get(&meas_host, &meas_port); + meas_scenario = meas_feed_scenario_get(); + + if (meas_port) + vty_out(vty, " meas-feed destination %s %u%s", + meas_host, meas_port, VTY_NEWLINE); + if (strlen(meas_scenario) > 0) + vty_out(vty, " meas-feed scenario %s%s", + meas_scenario, VTY_NEWLINE); + } + + return CMD_SUCCESS; +} + +static void trx_dump_vty(struct vty *vty, struct gsm_bts_trx *trx) +{ + vty_out(vty, "TRX %u of BTS %u is on ARFCN %u%s", + trx->nr, trx->bts->nr, trx->arfcn, VTY_NEWLINE); + vty_out(vty, "Description: %s%s", + trx->description ? trx->description : "(null)", VTY_NEWLINE); + vty_out(vty, " RF Nominal Power: %d dBm, reduced by %u dB, " + "resulting BS power: %d dBm%s", + trx->nominal_power, trx->max_power_red, + trx->nominal_power - trx->max_power_red, VTY_NEWLINE); + vty_out(vty, " NM State: "); + net_dump_nmstate(vty, &trx->mo.nm_state); + vty_out(vty, " RSL State: %s%s", trx->rsl_link? "connected" : "disconnected", VTY_NEWLINE); + vty_out(vty, " Baseband Transceiver NM State: "); + net_dump_nmstate(vty, &trx->bb_transc.mo.nm_state); + if (is_ipaccess_bts(trx->bts)) { + vty_out(vty, " ip.access stream ID: 0x%02x%s", + trx->rsl_tei, VTY_NEWLINE); + } else { + vty_out(vty, " E1 Signalling Link:%s", VTY_NEWLINE); + e1isl_dump_vty(vty, trx->rsl_link); + } +} + +static inline void print_all_trx(struct vty *vty, const struct gsm_bts *bts) +{ + uint8_t trx_nr; + for (trx_nr = 0; trx_nr < bts->num_trx; trx_nr++) + trx_dump_vty(vty, gsm_bts_trx_num(bts, trx_nr)); +} + +DEFUN(show_trx, + show_trx_cmd, + "show trx [<0-255>] [<0-255>]", + SHOW_STR "Display information about a TRX\n" + BTS_TRX_STR) +{ + struct gsm_network *net = gsmnet_from_vty(vty); + struct gsm_bts *bts = NULL; + int bts_nr, trx_nr; + + if (argc >= 1) { + /* use the BTS number that the user has specified */ + bts_nr = atoi(argv[0]); + if (bts_nr >= net->num_bts) { + vty_out(vty, "%% can't find BTS '%s'%s", argv[0], + VTY_NEWLINE); + return CMD_WARNING; + } + bts = gsm_bts_num(net, bts_nr); + } + if (argc >= 2) { + trx_nr = atoi(argv[1]); + if (trx_nr >= bts->num_trx) { + vty_out(vty, "%% can't find TRX '%s'%s", argv[1], + VTY_NEWLINE); + return CMD_WARNING; + } + trx_dump_vty(vty, gsm_bts_trx_num(bts, trx_nr)); + return CMD_SUCCESS; + } + if (bts) { + /* print all TRX in this BTS */ + print_all_trx(vty, bts); + return CMD_SUCCESS; + } + + for (bts_nr = 0; bts_nr < net->num_bts; bts_nr++) + print_all_trx(vty, gsm_bts_num(net, bts_nr)); + + return CMD_SUCCESS; +} + + +static void ts_dump_vty(struct vty *vty, struct gsm_bts_trx_ts *ts) +{ + vty_out(vty, "BTS %u, TRX %u, Timeslot %u, phys cfg %s, TSC %u", + ts->trx->bts->nr, ts->trx->nr, ts->nr, + gsm_pchan_name(ts->pchan), gsm_ts_tsc(ts)); + if (ts->pchan == GSM_PCHAN_TCH_F_PDCH) { + vty_out(vty, " (%s mode)", + ts->flags & TS_F_PDCH_ACTIVE ? "PDCH" : "TCH/F"); + } else if (ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH) { + vty_out(vty, " (%s mode)", gsm_pchan_name(ts->dyn.pchan_is)); + } + vty_out(vty, "%s", VTY_NEWLINE); + vty_out(vty, " NM State: "); + net_dump_nmstate(vty, &ts->mo.nm_state); + if (!is_ipaccess_bts(ts->trx->bts)) + vty_out(vty, " E1 Line %u, Timeslot %u, Subslot %u%s", + ts->e1_link.e1_nr, ts->e1_link.e1_ts, + ts->e1_link.e1_ts_ss, VTY_NEWLINE); +} + +DEFUN(show_ts, + show_ts_cmd, + "show timeslot [<0-255>] [<0-255>] [<0-7>]", + SHOW_STR "Display information about a TS\n" + BTS_TRX_TS_STR) +{ + struct gsm_network *net = gsmnet_from_vty(vty); + struct gsm_bts *bts = NULL; + struct gsm_bts_trx *trx = NULL; + struct gsm_bts_trx_ts *ts = NULL; + int bts_nr, trx_nr, ts_nr; + + if (argc >= 1) { + /* use the BTS number that the user has specified */ + bts_nr = atoi(argv[0]); + if (bts_nr >= net->num_bts) { + vty_out(vty, "%% can't find BTS '%s'%s", argv[0], + VTY_NEWLINE); + return CMD_WARNING; + } + bts = gsm_bts_num(net, bts_nr); + } + if (argc >= 2) { + trx_nr = atoi(argv[1]); + if (trx_nr >= bts->num_trx) { + vty_out(vty, "%% can't find TRX '%s'%s", argv[1], + VTY_NEWLINE); + return CMD_WARNING; + } + trx = gsm_bts_trx_num(bts, trx_nr); + } + if (argc >= 3) { + ts_nr = atoi(argv[2]); + if (ts_nr >= TRX_NR_TS) { + vty_out(vty, "%% can't find TS '%s'%s", argv[2], + VTY_NEWLINE); + return CMD_WARNING; + } + /* Fully Specified: print and exit */ + ts = &trx->ts[ts_nr]; + ts_dump_vty(vty, ts); + return CMD_SUCCESS; + } + + if (bts && trx) { + /* Iterate over all TS in this TRX */ + for (ts_nr = 0; ts_nr < TRX_NR_TS; ts_nr++) { + ts = &trx->ts[ts_nr]; + ts_dump_vty(vty, ts); + } + } else if (bts) { + /* Iterate over all TRX in this BTS, TS in each TRX */ + for (trx_nr = 0; trx_nr < bts->num_trx; trx_nr++) { + trx = gsm_bts_trx_num(bts, trx_nr); + for (ts_nr = 0; ts_nr < TRX_NR_TS; ts_nr++) { + ts = &trx->ts[ts_nr]; + ts_dump_vty(vty, ts); + } + } + } else { + /* Iterate over all BTS, TRX in each BTS, TS in each TRX */ + for (bts_nr = 0; bts_nr < net->num_bts; bts_nr++) { + bts = gsm_bts_num(net, bts_nr); + for (trx_nr = 0; trx_nr < bts->num_trx; trx_nr++) { + trx = gsm_bts_trx_num(bts, trx_nr); + for (ts_nr = 0; ts_nr < TRX_NR_TS; ts_nr++) { + ts = &trx->ts[ts_nr]; + ts_dump_vty(vty, ts); + } + } + } + } + + return CMD_SUCCESS; +} + +static void bsc_subscr_dump_vty(struct vty *vty, struct bsc_subscr *bsub) +{ + if (strlen(bsub->imsi)) + vty_out(vty, " IMSI: %s%s", bsub->imsi, VTY_NEWLINE); + if (bsub->tmsi != GSM_RESERVED_TMSI) + vty_out(vty, " TMSI: 0x%08x%s", bsub->tmsi, + VTY_NEWLINE); + vty_out(vty, " Use count: %d%s", bsub->use_count, VTY_NEWLINE); +} + +static void meas_rep_dump_uni_vty(struct vty *vty, + struct gsm_meas_rep_unidir *mru, + const char *prefix, + const char *dir) +{ + vty_out(vty, "%s RXL-FULL-%s: %4d dBm, RXL-SUB-%s: %4d dBm ", + prefix, dir, rxlev2dbm(mru->full.rx_lev), + dir, rxlev2dbm(mru->sub.rx_lev)); + vty_out(vty, "RXQ-FULL-%s: %d, RXQ-SUB-%s: %d%s", + dir, mru->full.rx_qual, dir, mru->sub.rx_qual, + VTY_NEWLINE); +} + +static void meas_rep_dump_vty(struct vty *vty, struct gsm_meas_rep *mr, + const char *prefix) +{ + vty_out(vty, "%sMeasurement Report:%s", prefix, VTY_NEWLINE); + vty_out(vty, "%s Flags: %s%s%s%s%s", prefix, + mr->flags & MEAS_REP_F_UL_DTX ? "DTXu " : "", + mr->flags & MEAS_REP_F_DL_DTX ? "DTXd " : "", + mr->flags & MEAS_REP_F_FPC ? "FPC " : "", + mr->flags & MEAS_REP_F_DL_VALID ? " " : "DLinval ", + VTY_NEWLINE); + if (mr->flags & MEAS_REP_F_MS_TO) + vty_out(vty, "%s MS Timing Offset: %d%s", prefix, mr->ms_timing_offset, VTY_NEWLINE); + if (mr->flags & MEAS_REP_F_MS_L1) + vty_out(vty, "%s L1 MS Power: %u dBm, Timing Advance: %u%s", + prefix, mr->ms_l1.pwr, mr->ms_l1.ta, VTY_NEWLINE); + if (mr->flags & MEAS_REP_F_DL_VALID) + meas_rep_dump_uni_vty(vty, &mr->dl, prefix, "dl"); + meas_rep_dump_uni_vty(vty, &mr->ul, prefix, "ul"); +} + +/* FIXME: move this to libosmogsm */ +static const struct value_string gsm48_cmode_names[] = { + { GSM48_CMODE_SIGN, "signalling" }, + { GSM48_CMODE_SPEECH_V1, "FR or HR" }, + { GSM48_CMODE_SPEECH_EFR, "EFR" }, + { GSM48_CMODE_SPEECH_AMR, "AMR" }, + { GSM48_CMODE_DATA_14k5, "CSD(14k5)" }, + { GSM48_CMODE_DATA_12k0, "CSD(12k0)" }, + { GSM48_CMODE_DATA_6k0, "CSD(6k0)" }, + { GSM48_CMODE_DATA_3k6, "CSD(3k6)" }, + { 0, NULL } +}; + +/* call vty_out() to print a string like " as TCH/H" for dynamic timeslots. + * Don't do anything if the ts is not dynamic. */ +static void vty_out_dyn_ts_status(struct vty *vty, struct gsm_bts_trx_ts *ts) +{ + switch (ts->pchan) { + case GSM_PCHAN_TCH_F_TCH_H_PDCH: + if (ts->dyn.pchan_is == ts->dyn.pchan_want) + vty_out(vty, " as %s", + gsm_pchan_name(ts->dyn.pchan_is)); + else + vty_out(vty, " switching %s -> %s", + gsm_pchan_name(ts->dyn.pchan_is), + gsm_pchan_name(ts->dyn.pchan_want)); + break; + case GSM_PCHAN_TCH_F_PDCH: + if ((ts->flags & TS_F_PDCH_PENDING_MASK) == 0) + vty_out(vty, " as %s", + (ts->flags & TS_F_PDCH_ACTIVE)? "PDCH" + : "TCH/F"); + else + vty_out(vty, " switching %s -> %s", + (ts->flags & TS_F_PDCH_ACTIVE)? "PDCH" + : "TCH/F", + (ts->flags & TS_F_PDCH_ACT_PENDING)? "PDCH" + : "TCH/F"); + break; + default: + /* no dyn ts */ + break; + } +} + +static void lchan_dump_full_vty(struct vty *vty, struct gsm_lchan *lchan) +{ + int idx; + + vty_out(vty, "BTS %u, TRX %u, Timeslot %u, Lchan %u: Type %s%s", + lchan->ts->trx->bts->nr, lchan->ts->trx->nr, lchan->ts->nr, + lchan->nr, gsm_lchant_name(lchan->type), VTY_NEWLINE); + /* show dyn TS details, if applicable */ + switch (lchan->ts->pchan) { + case GSM_PCHAN_TCH_F_TCH_H_PDCH: + vty_out(vty, " Osmocom Dyn TS:"); + vty_out_dyn_ts_status(vty, lchan->ts); + vty_out(vty, VTY_NEWLINE); + break; + case GSM_PCHAN_TCH_F_PDCH: + vty_out(vty, " IPACC Dyn PDCH TS:"); + vty_out_dyn_ts_status(vty, lchan->ts); + vty_out(vty, VTY_NEWLINE); + break; + default: + /* no dyn ts */ + break; + } + vty_out(vty, " Connection: %u, State: %s%s%s%s", + lchan->conn ? 1: 0, + gsm_lchans_name(lchan->state), + lchan->state == LCHAN_S_BROKEN ? " Error reason: " : "", + lchan->state == LCHAN_S_BROKEN ? lchan->broken_reason : "", + VTY_NEWLINE); + vty_out(vty, " BS Power: %u dBm, MS Power: %u dBm%s", + lchan->ts->trx->nominal_power - lchan->ts->trx->max_power_red + - lchan->bs_power*2, + ms_pwr_dbm(lchan->ts->trx->bts->band, lchan->ms_power), + VTY_NEWLINE); + vty_out(vty, " Channel Mode / Codec: %s%s", + get_value_string(gsm48_cmode_names, lchan->tch_mode), + VTY_NEWLINE); + if (lchan->conn && lchan->conn->bsub) { + vty_out(vty, " Subscriber:%s", VTY_NEWLINE); + bsc_subscr_dump_vty(vty, lchan->conn->bsub); + } else + vty_out(vty, " No Subscriber%s", VTY_NEWLINE); + if (is_ipaccess_bts(lchan->ts->trx->bts)) { + struct in_addr ia; + if (lchan->abis_ip.bound_ip) { + ia.s_addr = htonl(lchan->abis_ip.bound_ip); + vty_out(vty, " Bound IP: %s Port %u RTP_TYPE2=%u CONN_ID=%u%s", + inet_ntoa(ia), lchan->abis_ip.bound_port, + lchan->abis_ip.rtp_payload2, lchan->abis_ip.conn_id, + VTY_NEWLINE); + } + if (lchan->abis_ip.connect_ip) { + ia.s_addr = htonl(lchan->abis_ip.connect_ip); + vty_out(vty, " Conn. IP: %s Port %u RTP_TYPE=%u SPEECH_MODE=0x%02x%s", + inet_ntoa(ia), lchan->abis_ip.connect_port, + lchan->abis_ip.rtp_payload, lchan->abis_ip.speech_mode, + VTY_NEWLINE); + } + + } + + /* we want to report the last measurement report */ + idx = calc_initial_idx(ARRAY_SIZE(lchan->meas_rep), + lchan->meas_rep_idx, 1); + meas_rep_dump_vty(vty, &lchan->meas_rep[idx], " "); +} + +static void lchan_dump_short_vty(struct vty *vty, struct gsm_lchan *lchan) +{ + struct gsm_meas_rep *mr; + int idx; + + /* we want to report the last measurement report */ + idx = calc_initial_idx(ARRAY_SIZE(lchan->meas_rep), + lchan->meas_rep_idx, 1); + mr = &lchan->meas_rep[idx]; + + vty_out(vty, "BTS %u, TRX %u, Timeslot %u %s", + lchan->ts->trx->bts->nr, lchan->ts->trx->nr, lchan->ts->nr, + gsm_pchan_name(lchan->ts->pchan)); + vty_out_dyn_ts_status(vty, lchan->ts); + vty_out(vty, ", Lchan %u, Type %s, State %s - " + "L1 MS Power: %u dBm RXL-FULL-dl: %4d dBm RXL-FULL-ul: %4d dBm%s", + lchan->nr, + gsm_lchant_name(lchan->type), gsm_lchans_name(lchan->state), + mr->ms_l1.pwr, + rxlev2dbm(mr->dl.full.rx_lev), + rxlev2dbm(mr->ul.full.rx_lev), + VTY_NEWLINE); +} + + +static int dump_lchan_trx_ts(struct gsm_bts_trx_ts *ts, struct vty *vty, + void (*dump_cb)(struct vty *, struct gsm_lchan *)) +{ + int lchan_nr; + for (lchan_nr = 0; lchan_nr < TS_MAX_LCHAN; lchan_nr++) { + struct gsm_lchan *lchan = &ts->lchan[lchan_nr]; + if ((lchan->type == GSM_LCHAN_NONE) && (lchan->state == LCHAN_S_NONE)) + continue; + dump_cb(vty, lchan); + } + + return CMD_SUCCESS; +} + +static int dump_lchan_trx(struct gsm_bts_trx *trx, struct vty *vty, + void (*dump_cb)(struct vty *, struct gsm_lchan *)) +{ + int ts_nr; + + for (ts_nr = 0; ts_nr < TRX_NR_TS; ts_nr++) { + struct gsm_bts_trx_ts *ts = &trx->ts[ts_nr]; + dump_lchan_trx_ts(ts, vty, dump_cb); + } + + return CMD_SUCCESS; +} + +static int dump_lchan_bts(struct gsm_bts *bts, struct vty *vty, + void (*dump_cb)(struct vty *, struct gsm_lchan *)) +{ + int trx_nr; + + for (trx_nr = 0; trx_nr < bts->num_trx; trx_nr++) { + struct gsm_bts_trx *trx = gsm_bts_trx_num(bts, trx_nr); + dump_lchan_trx(trx, vty, dump_cb); + } + + return CMD_SUCCESS; +} + +static int lchan_summary(struct vty *vty, int argc, const char **argv, + void (*dump_cb)(struct vty *, struct gsm_lchan *)) +{ + struct gsm_network *net = gsmnet_from_vty(vty); + struct gsm_bts *bts = NULL; + struct gsm_bts_trx *trx = NULL; + struct gsm_bts_trx_ts *ts = NULL; + struct gsm_lchan *lchan; + int bts_nr, trx_nr, ts_nr, lchan_nr; + + if (argc >= 1) { + /* use the BTS number that the user has specified */ + bts_nr = atoi(argv[0]); + if (bts_nr >= net->num_bts) { + vty_out(vty, "%% can't find BTS %s%s", argv[0], + VTY_NEWLINE); + return CMD_WARNING; + } + bts = gsm_bts_num(net, bts_nr); + + if (argc == 1) + return dump_lchan_bts(bts, vty, dump_cb); + } + if (argc >= 2) { + trx_nr = atoi(argv[1]); + if (trx_nr >= bts->num_trx) { + vty_out(vty, "%% can't find TRX %s%s", argv[1], + VTY_NEWLINE); + return CMD_WARNING; + } + trx = gsm_bts_trx_num(bts, trx_nr); + + if (argc == 2) + return dump_lchan_trx(trx, vty, dump_cb); + } + if (argc >= 3) { + ts_nr = atoi(argv[2]); + if (ts_nr >= TRX_NR_TS) { + vty_out(vty, "%% can't find TS %s%s", argv[2], + VTY_NEWLINE); + return CMD_WARNING; + } + ts = &trx->ts[ts_nr]; + + if (argc == 3) + return dump_lchan_trx_ts(ts, vty, dump_cb); + } + if (argc >= 4) { + lchan_nr = atoi(argv[3]); + if (lchan_nr >= TS_MAX_LCHAN) { + vty_out(vty, "%% can't find LCHAN %s%s", argv[3], + VTY_NEWLINE); + return CMD_WARNING; + } + lchan = &ts->lchan[lchan_nr]; + dump_cb(vty, lchan); + return CMD_SUCCESS; + } + + + for (bts_nr = 0; bts_nr < net->num_bts; bts_nr++) { + bts = gsm_bts_num(net, bts_nr); + dump_lchan_bts(bts, vty, dump_cb); + } + + return CMD_SUCCESS; +} + + +DEFUN(show_lchan, + show_lchan_cmd, + "show lchan [<0-255>] [<0-255>] [<0-7>] [<0-7>]", + SHOW_STR "Display information about a logical channel\n" + BTS_TRX_TS_LCHAN_STR) +{ + return lchan_summary(vty, argc, argv, lchan_dump_full_vty); +} + +DEFUN(show_lchan_summary, + show_lchan_summary_cmd, + "show lchan summary [<0-255>] [<0-255>] [<0-7>] [<0-7>]", + SHOW_STR "Display information about a logical channel\n" + "Short summary\n" + BTS_TRX_TS_LCHAN_STR) +{ + return lchan_summary(vty, argc, argv, lchan_dump_short_vty); +} + +static void dump_one_subscr_conn(struct vty *vty, const struct gsm_subscriber_connection *conn) +{ + vty_out(vty, "conn ID=%u, MSC=%u, hodec2_fail=%d, mode=%s, mgw_ep=%s%s", + conn->sccp.conn_id, conn->sccp.msc->nr, conn->hodec2.failures, + get_value_string(gsm48_chan_mode_names, conn->user_plane.chan_mode), + conn->user_plane.mgw_endpoint, VTY_NEWLINE); + if (conn->lcls.global_call_ref_len) { + vty_out(vty, " LCLS GCR: %s%s", + osmo_hexdump_nospc(conn->lcls.global_call_ref, conn->lcls.global_call_ref_len), + VTY_NEWLINE); + vty_out(vty, " LCLS Config: 0x%02x, LCLS Control: 0x%02x, LCLS BSS Status: %s%s", + conn->lcls.config, conn->lcls.control, osmo_fsm_inst_state_name(conn->lcls.fi), + VTY_NEWLINE); + } + if (conn->lchan) + lchan_dump_full_vty(vty, conn->lchan); + if (conn->secondary_lchan) + lchan_dump_full_vty(vty, conn->secondary_lchan); +} + +DEFUN(show_subscr_conn, + show_subscr_conn_cmd, + "show conns", + SHOW_STR "Display currently active subscriber connections\n") +{ + struct gsm_subscriber_connection *conn; + struct gsm_network *net = gsmnet_from_vty(vty); + bool no_conns = true; + unsigned int count = 0; + + vty_out(vty, "Active subscriber connections: %s", VTY_NEWLINE); + + llist_for_each_entry(conn, &net->subscr_conns, entry) { + dump_one_subscr_conn(vty, conn); + no_conns = false; + count++; + } + + if (no_conns) + vty_out(vty, "None%s", VTY_NEWLINE); + + return CMD_SUCCESS; +} + +static int trigger_ho_or_as(struct vty *vty, struct gsm_lchan *from_lchan, struct gsm_bts *to_bts) +{ + int rc; + + if (!to_bts || from_lchan->ts->trx->bts == to_bts) { + LOGP(DHO, LOGL_NOTICE, "%s Manually triggering Assignment from VTY\n", + gsm_lchan_name(from_lchan)); + to_bts = from_lchan->ts->trx->bts; + } else + LOGP(DHO, LOGL_NOTICE, "%s (ARFCN %u) --> BTS %u Manually triggering Handover from VTY\n", + gsm_lchan_name(from_lchan), from_lchan->ts->trx->arfcn, to_bts->nr); + rc = bsc_handover_start(HODEC_NONE, from_lchan, to_bts, from_lchan->type); + if (rc) { + vty_out(vty, "bsc_handover_start() returned %d=%s%s", rc, + strerror(-rc), VTY_NEWLINE); + return CMD_WARNING; + } + return CMD_SUCCESS; +} + +static int ho_or_as(struct vty *vty, const char *argv[], int argc) +{ + struct gsm_network *net = gsmnet_from_vty(vty); + struct gsm_subscriber_connection *conn; + struct gsm_bts *bts; + struct gsm_bts *new_bts = NULL; + unsigned int bts_nr = atoi(argv[0]); + unsigned int trx_nr = atoi(argv[1]); + unsigned int ts_nr = atoi(argv[2]); + unsigned int ss_nr = atoi(argv[3]); + unsigned int bts_nr_new; + const char *action; + + if (argc > 4) { + bts_nr_new = atoi(argv[4]); + + /* Lookup the BTS where we want to handover to */ + llist_for_each_entry(bts, &net->bts_list, list) { + if (bts->nr == bts_nr_new) { + new_bts = bts; + break; + } + } + + if (!new_bts) { + vty_out(vty, "Unable to trigger handover, specified bts #%u does not exist %s", + bts_nr_new, VTY_NEWLINE); + return CMD_WARNING; + } + } + + action = new_bts ? "handover" : "assignment"; + + /* Find the connection/lchan that we want to handover */ + llist_for_each_entry(conn, &net->subscr_conns, entry) { + if (conn_get_bts(conn)->nr == bts_nr && + conn->lchan->ts->trx->nr == trx_nr && + conn->lchan->ts->nr == ts_nr && conn->lchan->nr == ss_nr) { + vty_out(vty, "starting %s for lchan %s...%s", action, conn->lchan->name, VTY_NEWLINE); + lchan_dump_full_vty(vty, conn->lchan); + return trigger_ho_or_as(vty, conn->lchan, new_bts); + } + } + + vty_out(vty, "Unable to trigger %s, specified connection (bts=%u,trx=%u,ts=%u,ss=%u) does not exist%s", + action, bts_nr, trx_nr, ts_nr, ss_nr, VTY_NEWLINE); + + return CMD_WARNING; +} + +#define MANUAL_HANDOVER_STR "Manually trigger handover (for debugging)\n" +#define MANUAL_ASSIGNMENT_STR "Manually trigger assignment (for debugging)\n" + +DEFUN(handover_subscr_conn, + handover_subscr_conn_cmd, + "bts <0-255> trx <0-255> timeslot <0-7> sub-slot <0-7> handover <0-255>", + BTS_NR_TRX_TS_SS_STR2 + MANUAL_HANDOVER_STR + "New " BTS_NR_STR) +{ + return ho_or_as(vty, argv, argc); +} + +DEFUN(assignment_subscr_conn, + assignment_subscr_conn_cmd, + "bts <0-255> trx <0-255> timeslot <0-7> sub-slot <0-7> assignment", + BTS_NR_TRX_TS_SS_STR2 + MANUAL_ASSIGNMENT_STR) +{ + return ho_or_as(vty, argv, argc); +} + +static struct gsm_lchan *find_used_voice_lchan(struct vty *vty) +{ + struct gsm_bts *bts; + struct gsm_network *network = gsmnet_from_vty(vty); + + llist_for_each_entry(bts, &network->bts_list, list) { + struct gsm_bts_trx *trx; + + llist_for_each_entry(trx, &bts->trx_list, list) { + int i; + for (i = 0; i < ARRAY_SIZE(trx->ts); i++) { + struct gsm_bts_trx_ts *ts = &trx->ts[i]; + int j; + int subslots; + + /* skip administratively deactivated timeslots */ + if (!nm_is_running(&ts->mo.nm_state)) + continue; + + subslots = ts_subslots(ts); + for (j = 0; j < subslots; j++) { + struct gsm_lchan *lchan = &ts->lchan[j]; + + if (lchan->state == LCHAN_S_ACTIVE + && (lchan->type == GSM_LCHAN_TCH_F + || lchan->type == GSM_LCHAN_TCH_H)) { + + vty_out(vty, "Found voice call: %s%s", + gsm_lchan_name(lchan), VTY_NEWLINE); + lchan_dump_full_vty(vty, lchan); + return lchan; + } + } + } + } + } + + vty_out(vty, "Cannot find any ongoing voice calls%s", VTY_NEWLINE); + return NULL; +} + +static struct gsm_bts *find_other_bts_with_free_slots(struct vty *vty, struct gsm_bts *not_this_bts, + enum gsm_phys_chan_config free_type) +{ + struct gsm_bts *bts; + struct gsm_network *network = gsmnet_from_vty(vty); + + llist_for_each_entry(bts, &network->bts_list, list) { + struct gsm_bts_trx *trx; + + if (bts == not_this_bts) + continue; + + llist_for_each_entry(trx, &bts->trx_list, list) { + int i; + for (i = 0; i < ARRAY_SIZE(trx->ts); i++) { + struct gsm_bts_trx_ts *ts = &trx->ts[i]; + int j; + int subslots; + + /* skip administratively deactivated timeslots */ + if (!nm_is_running(&ts->mo.nm_state)) + continue; + + if (ts->pchan != free_type) + continue; + + subslots = ts_subslots(ts); + for (j = 0; j < subslots; j++) { + struct gsm_lchan *lchan = &ts->lchan[j]; + + if (lchan->state == LCHAN_S_NONE) { + vty_out(vty, "Found unused %s slot: %s%s", + gsm_pchan_name(free_type), + gsm_lchan_name(lchan), + VTY_NEWLINE); + lchan_dump_full_vty(vty, lchan); + return bts; + } + } + } + } + } + vty_out(vty, "Cannot find any BTS (other than BTS %u) with free %s lchan%s", + not_this_bts? not_this_bts->nr : 255, gsm_lchant_name(free_type), VTY_NEWLINE); + return NULL; +} + +DEFUN(handover_any, handover_any_cmd, + "handover any", + MANUAL_HANDOVER_STR + "Pick any actively used TCH/F or TCH/H lchan and handover to any other BTS." + " This is likely to fail if not all BTS are guaranteed to be reachable by the MS.\n") +{ + struct gsm_lchan *from_lchan; + struct gsm_bts *to_bts; + + from_lchan = find_used_voice_lchan(vty); + if (!from_lchan) + return CMD_WARNING; + + to_bts = find_other_bts_with_free_slots(vty, from_lchan->ts->trx->bts, + ts_pchan(from_lchan->ts)); + if (!to_bts) + return CMD_WARNING; + + return trigger_ho_or_as(vty, from_lchan, to_bts); +} + +DEFUN(assignment_any, assignment_any_cmd, + "assignment any", + MANUAL_ASSIGNMENT_STR + "Pick any actively used TCH/F or TCH/H lchan and re-assign within the same BTS." + " This will fail if no lchans of the same type are available besides the used one.\n") +{ + struct gsm_lchan *from_lchan; + + from_lchan = find_used_voice_lchan(vty); + if (!from_lchan) + return CMD_WARNING; + + return trigger_ho_or_as(vty, from_lchan, NULL); +} + +static void paging_dump_vty(struct vty *vty, struct gsm_paging_request *pag) +{ + vty_out(vty, "Paging on BTS %u%s", pag->bts->nr, VTY_NEWLINE); + bsc_subscr_dump_vty(vty, pag->bsub); +} + +static void bts_paging_dump_vty(struct vty *vty, struct gsm_bts *bts) +{ + struct gsm_paging_request *pag; + + if (!bts->paging.bts) + return; + + llist_for_each_entry(pag, &bts->paging.pending_requests, entry) + paging_dump_vty(vty, pag); +} + +DEFUN(show_paging, + show_paging_cmd, + "show paging [<0-255>]", + SHOW_STR "Display information about paging reuqests of a BTS\n" + BTS_NR_STR) +{ + struct gsm_network *net = gsmnet_from_vty(vty); + struct gsm_bts *bts; + int bts_nr; + + if (argc >= 1) { + /* use the BTS number that the user has specified */ + bts_nr = atoi(argv[0]); + if (bts_nr >= net->num_bts) { + vty_out(vty, "%% can't find BTS %s%s", argv[0], + VTY_NEWLINE); + return CMD_WARNING; + } + bts = gsm_bts_num(net, bts_nr); + bts_paging_dump_vty(vty, bts); + + return CMD_SUCCESS; + } + for (bts_nr = 0; bts_nr < net->num_bts; bts_nr++) { + bts = gsm_bts_num(net, bts_nr); + bts_paging_dump_vty(vty, bts); + } + + return CMD_SUCCESS; +} + +DEFUN(show_paging_group, + show_paging_group_cmd, + "show paging-group <0-255> IMSI", + SHOW_STR "Display the paging group\n" + BTS_NR_STR "IMSI\n") +{ + struct gsm_network *net = gsmnet_from_vty(vty); + struct gsm_bts *bts; + unsigned int page_group; + int bts_nr = atoi(argv[0]); + + if (bts_nr >= net->num_bts) { + vty_out(vty, "%% can't find BTS %s%s", argv[0], VTY_NEWLINE); + return CMD_WARNING; + } + + bts = gsm_bts_num(net, bts_nr); + if (!bts) { + vty_out(vty, "%% can't find BTS %s%s", argv[0], VTY_NEWLINE); + return CMD_WARNING; + } + + page_group = gsm0502_calc_paging_group(&bts->si_common.chan_desc, + str_to_imsi(argv[1])); + vty_out(vty, "%%Paging group for IMSI %" PRIu64 " on BTS #%d is %u%s", + str_to_imsi(argv[1]), bts->nr, + page_group, VTY_NEWLINE); + return CMD_SUCCESS; +} + +DEFUN(cfg_net_neci, + cfg_net_neci_cmd, + "neci (0|1)", + "New Establish Cause Indication\n" + "Don't set the NECI bit\n" "Set the NECI bit\n") +{ + struct gsm_network *gsmnet = gsmnet_from_vty(vty); + + gsmnet->neci = atoi(argv[0]); + gsm_net_update_ctype(gsmnet); + return CMD_SUCCESS; +} + +DEFUN(cfg_net_pag_any_tch, + cfg_net_pag_any_tch_cmd, + "paging any use tch (0|1)", + "Assign a TCH when receiving a Paging Any request\n" + "Any Channel\n" "Use\n" "TCH\n" + "Do not use TCH for Paging Request Any\n" + "Do use TCH for Paging Request Any\n") +{ + struct gsm_network *gsmnet = gsmnet_from_vty(vty); + gsmnet->pag_any_tch = atoi(argv[0]); + gsm_net_update_ctype(gsmnet); + return CMD_SUCCESS; +} + +#define DEFAULT_TIMER(number) GSM_T##number##_DEFAULT +/* Add another expansion so that DEFAULT_TIMER() becomes its value */ +#define EXPAND_AND_STRINGIFY(x) OSMO_STRINGIFY(x) + +#define DECLARE_TIMER(number, doc) \ + DEFUN(cfg_net_T##number, \ + cfg_net_T##number##_cmd, \ + "timer t" #number " (default|<1-65535>)", \ + "Configure GSM Timers\n" \ + doc " (default: " EXPAND_AND_STRINGIFY(DEFAULT_TIMER(number)) " seconds)\n" \ + "Set to default timer value" \ + " (" EXPAND_AND_STRINGIFY(DEFAULT_TIMER(number)) " seconds)\n" \ + "Timer Value in seconds\n") \ +{ \ + struct gsm_network *gsmnet = gsmnet_from_vty(vty); \ + int value; \ + if (strcmp(argv[0], "default") == 0) \ + value = DEFAULT_TIMER(number); \ + else \ + value = atoi(argv[0]); \ + \ + gsmnet->T##number = value; \ + return CMD_SUCCESS; \ +} + +DECLARE_TIMER(3101, "Set the timeout value for IMMEDIATE ASSIGNMENT") +DECLARE_TIMER(3103, "Set the timeout value for HANDOVER") +DECLARE_TIMER(3105, "Set the timer for repetition of PHYSICAL INFORMATION") +DECLARE_TIMER(3107, "Currently not used") +DECLARE_TIMER(3109, "Set the RSL SACCH deactivation timeout") +DECLARE_TIMER(3111, "Set the RSL timeout to wait before releasing the RF Channel") +DECLARE_TIMER(3113, "Set the time to try paging a subscriber") +DECLARE_TIMER(3115, "Currently not used") +DECLARE_TIMER(3117, "Currently not used") +DECLARE_TIMER(3119, "Currently not used") +DECLARE_TIMER(3122, "Default waiting time (seconds) after IMM ASS REJECT") +DECLARE_TIMER(3141, "Currently not used") + +DEFUN_DEPRECATED(cfg_net_dtx, + cfg_net_dtx_cmd, + "dtx-used (0|1)", + ".HIDDEN\n""Obsolete\n""Obsolete\n") +{ + vty_out(vty, "%% 'dtx-used' is now deprecated: use dtx * " + "configuration options of BTS instead%s", VTY_NEWLINE); + return CMD_SUCCESS; +} + +/* per-BTS configuration */ +DEFUN(cfg_bts, + cfg_bts_cmd, + "bts <0-255>", + "Select a BTS to configure\n" + BTS_NR_STR) +{ + struct gsm_network *gsmnet = gsmnet_from_vty(vty); + int bts_nr = atoi(argv[0]); + struct gsm_bts *bts; + + if (bts_nr > gsmnet->num_bts) { + vty_out(vty, "%% The next unused BTS number is %u%s", + gsmnet->num_bts, VTY_NEWLINE); + return CMD_WARNING; + } else if (bts_nr == gsmnet->num_bts) { + /* allocate a new one */ + bts = bsc_bts_alloc_register(gsmnet, GSM_BTS_TYPE_UNKNOWN, + HARDCODED_BSIC); + /* + * Initalize bts->acc_ramp here. Else we could segfault while + * processing a configuration file with ACC ramping settings. + */ + acc_ramp_init(&bts->acc_ramp, bts); + } else + bts = gsm_bts_num(gsmnet, bts_nr); + + if (!bts) { + vty_out(vty, "%% Unable to allocate BTS %u%s", + gsmnet->num_bts, VTY_NEWLINE); + return CMD_WARNING; + } + + vty->index = bts; + vty->index_sub = &bts->description; + vty->node = BTS_NODE; + + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_type, + cfg_bts_type_cmd, + "type TYPE", /* dynamically created */ + "Set the BTS type\n" "Type\n") +{ + struct gsm_bts *bts = vty->index; + int rc; + + rc = gsm_set_bts_type(bts, str2btstype(argv[0])); + if (rc < 0) + return CMD_WARNING; + + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_band, + cfg_bts_band_cmd, + "band BAND", + "Set the frequency band of this BTS\n" "Frequency band\n") +{ + struct gsm_bts *bts = vty->index; + int band = gsm_band_parse(argv[0]); + + if (band < 0) { + vty_out(vty, "%% BAND %d is not a valid GSM band%s", + band, VTY_NEWLINE); + return CMD_WARNING; + } + + bts->band = band; + + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_dtxu, cfg_bts_dtxu_cmd, "dtx uplink [force]", + "Configure discontinuous transmission\n" + "Enable Uplink DTX for this BTS\n" + "MS 'shall' use DTXu instead of 'may' use (might not be supported by " + "older phones).\n") +{ + struct gsm_bts *bts = vty->index; + + bts->dtxu = (argc > 0) ? GSM48_DTX_SHALL_BE_USED : GSM48_DTX_MAY_BE_USED; + if (!is_ipaccess_bts(bts)) + vty_out(vty, "%% DTX enabled on non-IP BTS: this configuration " + "neither supported nor tested!%s", VTY_NEWLINE); + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_no_dtxu, cfg_bts_no_dtxu_cmd, "no dtx uplink", + NO_STR + "Configure discontinuous transmission\n" + "Disable Uplink DTX for this BTS\n") +{ + struct gsm_bts *bts = vty->index; + + bts->dtxu = GSM48_DTX_SHALL_NOT_BE_USED; + + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_dtxd, cfg_bts_dtxd_cmd, "dtx downlink", + "Configure discontinuous transmission\n" + "Enable Downlink DTX for this BTS\n") +{ + struct gsm_bts *bts = vty->index; + + bts->dtxd = true; + if (!is_ipaccess_bts(bts)) + vty_out(vty, "%% DTX enabled on non-IP BTS: this configuration " + "neither supported nor tested!%s", VTY_NEWLINE); + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_no_dtxd, cfg_bts_no_dtxd_cmd, "no dtx downlink", + NO_STR + "Configure discontinuous transmission\n" + "Disable Downlink DTX for this BTS\n") +{ + struct gsm_bts *bts = vty->index; + + bts->dtxd = false; + + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_ci, + cfg_bts_ci_cmd, + "cell_identity <0-65535>", + "Set the Cell identity of this BTS\n" "Cell Identity\n") +{ + struct gsm_bts *bts = vty->index; + int ci = atoi(argv[0]); + + if (ci < 0 || ci > 0xffff) { + vty_out(vty, "%% CI %d is not in the valid range (0-65535)%s", + ci, VTY_NEWLINE); + return CMD_WARNING; + } + bts->cell_identity = ci; + + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_lac, + cfg_bts_lac_cmd, + "location_area_code <0-65535>", + "Set the Location Area Code (LAC) of this BTS\n" "LAC\n") +{ + struct gsm_bts *bts = vty->index; + int lac = atoi(argv[0]); + + if (lac < 0 || lac > 0xffff) { + vty_out(vty, "%% LAC %d is not in the valid range (0-65535)%s", + lac, VTY_NEWLINE); + return CMD_WARNING; + } + + if (lac == GSM_LAC_RESERVED_DETACHED || lac == GSM_LAC_RESERVED_ALL_BTS) { + vty_out(vty, "%% LAC %d is reserved by GSM 04.08%s", + lac, VTY_NEWLINE); + return CMD_WARNING; + } + + bts->location_area_code = lac; + + return CMD_SUCCESS; +} + + +/* compatibility wrapper for old config files */ +DEFUN_HIDDEN(cfg_bts_tsc, + cfg_bts_tsc_cmd, + "training_sequence_code <0-7>", + "Set the Training Sequence Code (TSC) of this BTS\n" "TSC\n") +{ + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_bsic, + cfg_bts_bsic_cmd, + "base_station_id_code <0-63>", + "Set the Base Station Identity Code (BSIC) of this BTS\n" + "BSIC of this BTS\n") +{ + struct gsm_bts *bts = vty->index; + int bsic = atoi(argv[0]); + + if (bsic < 0 || bsic > 0x3f) { + vty_out(vty, "%% BSIC %d is not in the valid range (0-255)%s", + bsic, VTY_NEWLINE); + return CMD_WARNING; + } + bts->bsic = bsic; + + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_unit_id, + cfg_bts_unit_id_cmd, + "ip.access unit_id <0-65534> <0-255>", + "Abis/IP specific options\n" + "Set the IPA BTS Unit ID\n" + "Unit ID (Site)\n" + "Unit ID (BTS)\n") +{ + struct gsm_bts *bts = vty->index; + int site_id = atoi(argv[0]); + int bts_id = atoi(argv[1]); + + if (!is_ipaccess_bts(bts)) { + vty_out(vty, "%% BTS is not of ip.access type%s", VTY_NEWLINE); + return CMD_WARNING; + } + + bts->ip_access.site_id = site_id; + bts->ip_access.bts_id = bts_id; + + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_rsl_ip, + cfg_bts_rsl_ip_cmd, + "ip.access rsl-ip A.B.C.D", + "Abis/IP specific options\n" + "Set the IPA RSL IP Address of the BSC\n" + "Destination IP address for RSL connection\n") +{ + struct gsm_bts *bts = vty->index; + struct in_addr ia; + + if (!is_ipaccess_bts(bts)) { + vty_out(vty, "%% BTS is not of ip.access type%s", VTY_NEWLINE); + return CMD_WARNING; + } + + inet_aton(argv[0], &ia); + bts->ip_access.rsl_ip = ntohl(ia.s_addr); + + return CMD_SUCCESS; +} + +#define NOKIA_STR "Nokia *Site related commands\n" + +DEFUN(cfg_bts_nokia_site_skip_reset, + cfg_bts_nokia_site_skip_reset_cmd, + "nokia_site skip-reset (0|1)", + NOKIA_STR + "Skip the reset step during bootstrap process of this BTS\n" + "Do NOT skip the reset\n" "Skip the reset\n") +{ + struct gsm_bts *bts = vty->index; + + if (bts->type != GSM_BTS_TYPE_NOKIA_SITE) { + vty_out(vty, "%% BTS is not of Nokia *Site type%s", VTY_NEWLINE); + return CMD_WARNING; + } + + bts->nokia.skip_reset = atoi(argv[0]); + + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_nokia_site_no_loc_rel_cnf, + cfg_bts_nokia_site_no_loc_rel_cnf_cmd, + "nokia_site no-local-rel-conf (0|1)", + NOKIA_STR + "Do not wait for RELease CONFirm message when releasing channel locally\n" + "Wait for RELease CONFirm\n" "Do not wait for RELease CONFirm\n") +{ + struct gsm_bts *bts = vty->index; + + if (!is_nokia_bts(bts)) { + vty_out(vty, "%% BTS is not of Nokia *Site type%s", + VTY_NEWLINE); + return CMD_WARNING; + } + + bts->nokia.no_loc_rel_cnf = atoi(argv[0]); + + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_nokia_site_bts_reset_timer_cnf, + cfg_bts_nokia_site_bts_reset_timer_cnf_cmd, + "nokia_site bts-reset-timer <15-100>", + NOKIA_STR + "The amount of time (in sec.) between BTS_RESET is sent,\n" + "and the BTS is being bootstrapped.\n") +{ + struct gsm_bts *bts = vty->index; + + if (!is_nokia_bts(bts)) { + vty_out(vty, "%% BTS is not of Nokia *Site type%s", + VTY_NEWLINE); + return CMD_WARNING; + } + + bts->nokia.bts_reset_timer_cnf = atoi(argv[0]); + + return CMD_SUCCESS; +} +#define OML_STR "Organization & Maintenance Link\n" +#define IPA_STR "A-bis/IP Specific Options\n" + +DEFUN(cfg_bts_stream_id, + cfg_bts_stream_id_cmd, + "oml ip.access stream_id <0-255> line E1_LINE", + OML_STR IPA_STR + "Set the ip.access Stream ID of the OML link of this BTS\n" + "Stream Identifier\n" "Virtual E1 Line Number\n" "Virtual E1 Line Number\n") +{ + struct gsm_bts *bts = vty->index; + int stream_id = atoi(argv[0]), linenr = atoi(argv[1]); + + if (!is_ipaccess_bts(bts)) { + vty_out(vty, "%% BTS is not of ip.access type%s", VTY_NEWLINE); + return CMD_WARNING; + } + + bts->oml_tei = stream_id; + /* This is used by e1inp_bind_ops callback for each BTS model. */ + bts->oml_e1_link.e1_nr = linenr; + + return CMD_SUCCESS; +} + +#define OML_E1_STR OML_STR "OML E1/T1 Configuration\n" + +DEFUN(cfg_bts_oml_e1, + cfg_bts_oml_e1_cmd, + "oml e1 line E1_LINE timeslot <1-31> sub-slot (0|1|2|3|full)", + OML_E1_STR + "E1/T1 line number to be used for OML\n" + "E1/T1 line number to be used for OML\n" + "E1/T1 timeslot to be used for OML\n" + "E1/T1 timeslot to be used for OML\n" + "E1/T1 sub-slot to be used for OML\n" + "Use E1/T1 sub-slot 0\n" + "Use E1/T1 sub-slot 1\n" + "Use E1/T1 sub-slot 2\n" + "Use E1/T1 sub-slot 3\n" + "Use full E1 slot 3\n" + ) +{ + struct gsm_bts *bts = vty->index; + + parse_e1_link(&bts->oml_e1_link, argv[0], argv[1], argv[2]); + + return CMD_SUCCESS; +} + + +DEFUN(cfg_bts_oml_e1_tei, + cfg_bts_oml_e1_tei_cmd, + "oml e1 tei <0-63>", + OML_E1_STR + "Set the TEI to be used for OML\n" + "TEI Number\n") +{ + struct gsm_bts *bts = vty->index; + + bts->oml_tei = atoi(argv[0]); + + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_challoc, cfg_bts_challoc_cmd, + "channel allocator (ascending|descending)", + "Channnel Allocator\n" "Channel Allocator\n" + "Allocate Timeslots and Transceivers in ascending order\n" + "Allocate Timeslots and Transceivers in descending order\n") +{ + struct gsm_bts *bts = vty->index; + + if (!strcmp(argv[0], "ascending")) + bts->chan_alloc_reverse = 0; + else + bts->chan_alloc_reverse = 1; + + return CMD_SUCCESS; +} + +#define RACH_STR "Random Access Control Channel\n" + +DEFUN(cfg_bts_rach_tx_integer, + cfg_bts_rach_tx_integer_cmd, + "rach tx integer <0-15>", + RACH_STR + "Set the raw tx integer value in RACH Control parameters IE\n" + "Set the raw tx integer value in RACH Control parameters IE\n" + "Raw tx integer value in RACH Control parameters IE\n") +{ + struct gsm_bts *bts = vty->index; + bts->si_common.rach_control.tx_integer = atoi(argv[0]) & 0xf; + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_rach_max_trans, + cfg_bts_rach_max_trans_cmd, + "rach max transmission (1|2|4|7)", + RACH_STR + "Set the maximum number of RACH burst transmissions\n" + "Set the maximum number of RACH burst transmissions\n" + "Maximum number of 1 RACH burst transmissions\n" + "Maximum number of 2 RACH burst transmissions\n" + "Maximum number of 4 RACH burst transmissions\n" + "Maximum number of 7 RACH burst transmissions\n") +{ + struct gsm_bts *bts = vty->index; + bts->si_common.rach_control.max_trans = rach_max_trans_val2raw(atoi(argv[0])); + return CMD_SUCCESS; +} + +#define CD_STR "Channel Description\n" + +DEFUN(cfg_bts_chan_desc_att, + cfg_bts_chan_desc_att_cmd, + "channel-descrption attach (0|1)", + CD_STR + "Set if attachment is required\n" + "Attachment is NOT required\n" + "Attachment is required (standard)\n") +{ + struct gsm_bts *bts = vty->index; + bts->si_common.chan_desc.att = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_chan_desc_bs_pa_mfrms, + cfg_bts_chan_desc_bs_pa_mfrms_cmd, + "channel-descrption bs-pa-mfrms <2-9>", + CD_STR + "Set number of multiframe periods for paging groups\n" + "Number of multiframe periods for paging groups\n") +{ + struct gsm_bts *bts = vty->index; + int bs_pa_mfrms = atoi(argv[0]); + + bts->si_common.chan_desc.bs_pa_mfrms = bs_pa_mfrms - 2; + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_chan_desc_bs_ag_blks_res, + cfg_bts_chan_desc_bs_ag_blks_res_cmd, + "channel-descrption bs-ag-blks-res <0-7>", + CD_STR + "Set number of blocks reserved for access grant\n" + "Number of blocks reserved for access grant\n") +{ + struct gsm_bts *bts = vty->index; + int bs_ag_blks_res = atoi(argv[0]); + + bts->si_common.chan_desc.bs_ag_blks_res = bs_ag_blks_res; + return CMD_SUCCESS; +} + +#define NM_STR "Network Management\n" + +DEFUN(cfg_bts_rach_nm_b_thresh, + cfg_bts_rach_nm_b_thresh_cmd, + "rach nm busy threshold <0-255>", + RACH_STR NM_STR + "Set the NM Busy Threshold\n" + "Set the NM Busy Threshold\n" + "NM Busy Threshold in dB") +{ + struct gsm_bts *bts = vty->index; + bts->rach_b_thresh = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_rach_nm_ldavg, + cfg_bts_rach_nm_ldavg_cmd, + "rach nm load average <0-65535>", + RACH_STR NM_STR + "Set the NM Loadaverage Slots value\n" + "Set the NM Loadaverage Slots value\n" + "NM Loadaverage Slots value\n") +{ + struct gsm_bts *bts = vty->index; + bts->rach_ldavg_slots = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_cell_barred, cfg_bts_cell_barred_cmd, + "cell barred (0|1)", + "Should this cell be barred from access?\n" + "Should this cell be barred from access?\n" + "Cell should NOT be barred\n" + "Cell should be barred\n") + +{ + struct gsm_bts *bts = vty->index; + + bts->si_common.rach_control.cell_bar = atoi(argv[0]); + + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_rach_ec_allowed, cfg_bts_rach_ec_allowed_cmd, + "rach emergency call allowed (0|1)", + RACH_STR + "Should this cell allow emergency calls?\n" + "Should this cell allow emergency calls?\n" + "Should this cell allow emergency calls?\n" + "Do NOT allow emergency calls\n" + "Allow emergency calls\n") +{ + struct gsm_bts *bts = vty->index; + + if (atoi(argv[0]) == 0) + bts->si_common.rach_control.t2 |= 0x4; + else + bts->si_common.rach_control.t2 &= ~0x4; + + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_rach_ac_class, cfg_bts_rach_ac_class_cmd, + "rach access-control-class (0|1|2|3|4|5|6|7|8|9|11|12|13|14|15) (barred|allowed)", + RACH_STR + "Set access control class\n" + "Access control class 0\n" + "Access control class 1\n" + "Access control class 2\n" + "Access control class 3\n" + "Access control class 4\n" + "Access control class 5\n" + "Access control class 6\n" + "Access control class 7\n" + "Access control class 8\n" + "Access control class 9\n" + "Access control class 11 for PLMN use\n" + "Access control class 12 for security services\n" + "Access control class 13 for public utilities (e.g. water/gas suppliers)\n" + "Access control class 14 for emergency services\n" + "Access control class 15 for PLMN staff\n" + "barred to use access control class\n" + "allowed to use access control class\n") +{ + struct gsm_bts *bts = vty->index; + + uint8_t control_class; + uint8_t allowed = 0; + + if (strcmp(argv[1], "allowed") == 0) + allowed = 1; + + control_class = atoi(argv[0]); + if (control_class < 8) + if (allowed) + bts->si_common.rach_control.t3 &= ~(0x1 << control_class); + else + bts->si_common.rach_control.t3 |= (0x1 << control_class); + else + if (allowed) + bts->si_common.rach_control.t2 &= ~(0x1 << (control_class - 8)); + else + bts->si_common.rach_control.t2 |= (0x1 << (control_class - 8)); + + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_ms_max_power, cfg_bts_ms_max_power_cmd, + "ms max power <0-40>", + "MS Options\n" + "Maximum transmit power of the MS\n" + "Maximum transmit power of the MS\n" + "Maximum transmit power of the MS in dBm") +{ + struct gsm_bts *bts = vty->index; + + bts->ms_max_power = atoi(argv[0]); + + return CMD_SUCCESS; +} + +#define CELL_STR "Cell Parameters\n" + +DEFUN(cfg_bts_cell_resel_hyst, cfg_bts_cell_resel_hyst_cmd, + "cell reselection hysteresis <0-14>", + CELL_STR "Cell re-selection parameters\n" + "Cell Re-Selection Hysteresis in dB\n" + "Cell Re-Selection Hysteresis in dB") +{ + struct gsm_bts *bts = vty->index; + + bts->si_common.cell_sel_par.cell_resel_hyst = atoi(argv[0])/2; + + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_rxlev_acc_min, cfg_bts_rxlev_acc_min_cmd, + "rxlev access min <0-63>", + "Minimum RxLev needed for cell access\n" + "Minimum RxLev needed for cell access\n" + "Minimum RxLev needed for cell access\n" + "Minimum RxLev needed for cell access (better than -110dBm)") +{ + struct gsm_bts *bts = vty->index; + + bts->si_common.cell_sel_par.rxlev_acc_min = atoi(argv[0]); + + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_cell_bar_qualify, cfg_bts_cell_bar_qualify_cmd, + "cell bar qualify (0|1)", + CELL_STR "Cell Bar Qualify\n" "Cell Bar Qualify\n" + "Set CBQ to 0\n" "Set CBQ to 1\n") +{ + struct gsm_bts *bts = vty->index; + + bts->si_common.cell_ro_sel_par.present = 1; + bts->si_common.cell_ro_sel_par.cbq = atoi(argv[0]); + + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_cell_resel_ofs, cfg_bts_cell_resel_ofs_cmd, + "cell reselection offset <0-126>", + CELL_STR "Cell Re-Selection Parameters\n" + "Cell Re-Selection Offset (CRO) in dB\n" + "Cell Re-Selection Offset (CRO) in dB\n" + ) +{ + struct gsm_bts *bts = vty->index; + + bts->si_common.cell_ro_sel_par.present = 1; + bts->si_common.cell_ro_sel_par.cell_resel_off = atoi(argv[0])/2; + + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_temp_ofs, cfg_bts_temp_ofs_cmd, + "temporary offset <0-60>", + "Cell selection temporary negative offset\n" + "Cell selection temporary negative offset\n" + "Cell selection temporary negative offset in dB") +{ + struct gsm_bts *bts = vty->index; + + bts->si_common.cell_ro_sel_par.present = 1; + bts->si_common.cell_ro_sel_par.temp_offs = atoi(argv[0])/10; + + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_temp_ofs_inf, cfg_bts_temp_ofs_inf_cmd, + "temporary offset infinite", + "Cell selection temporary negative offset\n" + "Cell selection temporary negative offset\n" + "Sets cell selection temporary negative offset to infinity") +{ + struct gsm_bts *bts = vty->index; + + bts->si_common.cell_ro_sel_par.present = 1; + bts->si_common.cell_ro_sel_par.temp_offs = 7; + + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_penalty_time, cfg_bts_penalty_time_cmd, + "penalty time <20-620>", + "Cell selection penalty time\n" + "Cell selection penalty time\n" + "Cell selection penalty time in seconds (by 20s increments)\n") +{ + struct gsm_bts *bts = vty->index; + + bts->si_common.cell_ro_sel_par.present = 1; + bts->si_common.cell_ro_sel_par.penalty_time = (atoi(argv[0])-20)/20; + + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_penalty_time_rsvd, cfg_bts_penalty_time_rsvd_cmd, + "penalty time reserved", + "Cell selection penalty time\n" + "Cell selection penalty time\n" + "Set cell selection penalty time to reserved value 31, " + "(indicate that CELL_RESELECT_OFFSET is subtracted from C2 " + "and TEMPORARY_OFFSET is ignored)") +{ + struct gsm_bts *bts = vty->index; + + bts->si_common.cell_ro_sel_par.present = 1; + bts->si_common.cell_ro_sel_par.penalty_time = 31; + + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_radio_link_timeout, cfg_bts_radio_link_timeout_cmd, + "radio-link-timeout <4-64>", + "Radio link timeout criterion (BTS side)\n" + "Radio link timeout value (lost SACCH block)\n") +{ + struct gsm_bts *bts = vty->index; + + gsm_bts_set_radio_link_timeout(bts, atoi(argv[0])); + + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_radio_link_timeout_inf, cfg_bts_radio_link_timeout_inf_cmd, + "radio-link-timeout infinite", + "Radio link timeout criterion (BTS side)\n" + "Infinite Radio link timeout value (use only for BTS RF testing)\n") +{ + struct gsm_bts *bts = vty->index; + + if (bts->type != GSM_BTS_TYPE_OSMOBTS) { + vty_out(vty, "%% infinite radio link timeout not supported by this BTS%s", VTY_NEWLINE); + return CMD_WARNING; + } + + vty_out(vty, "%% INFINITE RADIO LINK TIMEOUT, USE ONLY FOR BTS RF TESTING%s", VTY_NEWLINE); + gsm_bts_set_radio_link_timeout(bts, -1); + + return CMD_SUCCESS; +} + +#define GPRS_TEXT "GPRS Packet Network\n" + +DEFUN(cfg_bts_prs_bvci, cfg_bts_gprs_bvci_cmd, + "gprs cell bvci <2-65535>", + GPRS_TEXT + "GPRS Cell Settings\n" + "GPRS BSSGP VC Identifier\n" + "GPRS BSSGP VC Identifier") +{ + /* ETSI TS 101 343: values 0 and 1 are reserved for signalling and PTM */ + struct gsm_bts *bts = vty->index; + + if (bts->gprs.mode == BTS_GPRS_NONE) { + vty_out(vty, "%% GPRS not enabled on this BTS%s", VTY_NEWLINE); + return CMD_WARNING; + } + + bts->gprs.cell.bvci = atoi(argv[0]); + + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_gprs_nsei, cfg_bts_gprs_nsei_cmd, + "gprs nsei <0-65535>", + GPRS_TEXT + "GPRS NS Entity Identifier\n" + "GPRS NS Entity Identifier") +{ + struct gsm_bts *bts = vty->index; + + if (bts->gprs.mode == BTS_GPRS_NONE) { + vty_out(vty, "%% GPRS not enabled on this BTS%s", VTY_NEWLINE); + return CMD_WARNING; + } + + bts->gprs.nse.nsei = atoi(argv[0]); + + return CMD_SUCCESS; +} + +#define NSVC_TEXT "Network Service Virtual Connection (NS-VC)\n" \ + "NSVC Logical Number\n" + +DEFUN(cfg_bts_gprs_nsvci, cfg_bts_gprs_nsvci_cmd, + "gprs nsvc <0-1> nsvci <0-65535>", + GPRS_TEXT NSVC_TEXT + "NS Virtual Connection Identifier\n" + "GPRS NS VC Identifier") +{ + struct gsm_bts *bts = vty->index; + int idx = atoi(argv[0]); + + if (bts->gprs.mode == BTS_GPRS_NONE) { + vty_out(vty, "%% GPRS not enabled on this BTS%s", VTY_NEWLINE); + return CMD_WARNING; + } + + bts->gprs.nsvc[idx].nsvci = atoi(argv[1]); + + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_gprs_nsvc_lport, cfg_bts_gprs_nsvc_lport_cmd, + "gprs nsvc <0-1> local udp port <0-65535>", + GPRS_TEXT NSVC_TEXT + "GPRS NS Local UDP Port\n" + "GPRS NS Local UDP Port\n" + "GPRS NS Local UDP Port\n" + "GPRS NS Local UDP Port Number\n") +{ + struct gsm_bts *bts = vty->index; + int idx = atoi(argv[0]); + + if (bts->gprs.mode == BTS_GPRS_NONE) { + vty_out(vty, "%% GPRS not enabled on this BTS%s", VTY_NEWLINE); + return CMD_WARNING; + } + + bts->gprs.nsvc[idx].local_port = atoi(argv[1]); + + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_gprs_nsvc_rport, cfg_bts_gprs_nsvc_rport_cmd, + "gprs nsvc <0-1> remote udp port <0-65535>", + GPRS_TEXT NSVC_TEXT + "GPRS NS Remote UDP Port\n" + "GPRS NS Remote UDP Port\n" + "GPRS NS Remote UDP Port\n" + "GPRS NS Remote UDP Port Number\n") +{ + struct gsm_bts *bts = vty->index; + int idx = atoi(argv[0]); + + if (bts->gprs.mode == BTS_GPRS_NONE) { + vty_out(vty, "%% GPRS not enabled on this BTS%s", VTY_NEWLINE); + return CMD_WARNING; + } + + bts->gprs.nsvc[idx].remote_port = atoi(argv[1]); + + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_gprs_nsvc_rip, cfg_bts_gprs_nsvc_rip_cmd, + "gprs nsvc <0-1> remote ip A.B.C.D", + GPRS_TEXT NSVC_TEXT + "GPRS NS Remote IP Address\n" + "GPRS NS Remote IP Address\n" + "GPRS NS Remote IP Address\n") +{ + struct gsm_bts *bts = vty->index; + int idx = atoi(argv[0]); + struct in_addr ia; + + if (bts->gprs.mode == BTS_GPRS_NONE) { + vty_out(vty, "%% GPRS not enabled on this BTS%s", VTY_NEWLINE); + return CMD_WARNING; + } + + inet_aton(argv[1], &ia); + bts->gprs.nsvc[idx].remote_ip = ntohl(ia.s_addr); + + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_pag_free, cfg_bts_pag_free_cmd, + "paging free <-1-1024>", + "Paging options\n" + "Only page when having a certain amount of free slots\n" + "amount of required free paging slots. -1 to disable\n") +{ + struct gsm_bts *bts = vty->index; + + bts->paging.free_chans_need = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_gprs_ns_timer, cfg_bts_gprs_ns_timer_cmd, + "gprs ns timer " NS_TIMERS " <0-255>", + GPRS_TEXT "Network Service\n" + "Network Service Timer\n" + NS_TIMERS_HELP "Timer Value\n") +{ + struct gsm_bts *bts = vty->index; + int idx = get_string_value(gprs_ns_timer_strs, argv[0]); + int val = atoi(argv[1]); + + if (bts->gprs.mode == BTS_GPRS_NONE) { + vty_out(vty, "%% GPRS not enabled on this BTS%s", VTY_NEWLINE); + return CMD_WARNING; + } + + if (idx < 0 || idx >= ARRAY_SIZE(bts->gprs.nse.timer)) + return CMD_WARNING; + + bts->gprs.nse.timer[idx] = val; + + return CMD_SUCCESS; +} + +#define BSSGP_TIMERS "(blocking-timer|blocking-retries|unblocking-retries|reset-timer|reset-retries|suspend-timer|suspend-retries|resume-timer|resume-retries|capability-update-timer|capability-update-retries)" +#define BSSGP_TIMERS_HELP \ + "Tbvc-block timeout\n" \ + "Tbvc-block retries\n" \ + "Tbvc-unblock retries\n" \ + "Tbvcc-reset timeout\n" \ + "Tbvc-reset retries\n" \ + "Tbvc-suspend timeout\n" \ + "Tbvc-suspend retries\n" \ + "Tbvc-resume timeout\n" \ + "Tbvc-resume retries\n" \ + "Tbvc-capa-update timeout\n" \ + "Tbvc-capa-update retries\n" + +DEFUN(cfg_bts_gprs_cell_timer, cfg_bts_gprs_cell_timer_cmd, + "gprs cell timer " BSSGP_TIMERS " <0-255>", + GPRS_TEXT "Cell / BSSGP\n" + "Cell/BSSGP Timer\n" + BSSGP_TIMERS_HELP "Timer Value\n") +{ + struct gsm_bts *bts = vty->index; + int idx = get_string_value(gprs_bssgp_cfg_strs, argv[0]); + int val = atoi(argv[1]); + + if (bts->gprs.mode == BTS_GPRS_NONE) { + vty_out(vty, "%% GPRS not enabled on this BTS%s", VTY_NEWLINE); + return CMD_WARNING; + } + + if (idx < 0 || idx >= ARRAY_SIZE(bts->gprs.cell.timer)) + return CMD_WARNING; + + bts->gprs.cell.timer[idx] = val; + + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_gprs_rac, cfg_bts_gprs_rac_cmd, + "gprs routing area <0-255>", + GPRS_TEXT + "GPRS Routing Area Code\n" + "GPRS Routing Area Code\n" + "GPRS Routing Area Code\n") +{ + struct gsm_bts *bts = vty->index; + + if (bts->gprs.mode == BTS_GPRS_NONE) { + vty_out(vty, "%% GPRS not enabled on this BTS%s", VTY_NEWLINE); + return CMD_WARNING; + } + + bts->gprs.rac = atoi(argv[0]); + + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_gprs_ctrl_ack, cfg_bts_gprs_ctrl_ack_cmd, + "gprs control-ack-type-rach", GPRS_TEXT + "Set GPRS Control Ack Type for PACKET CONTROL ACKNOWLEDGMENT message to " + "four access bursts format instead of default RLC/MAC control block\n") +{ + struct gsm_bts *bts = vty->index; + + if (bts->gprs.mode == BTS_GPRS_NONE) { + vty_out(vty, "%% GPRS not enabled on this BTS%s", VTY_NEWLINE); + return CMD_WARNING; + } + + bts->gprs.ctrl_ack_type_use_block = false; + + return CMD_SUCCESS; +} + +DEFUN(cfg_no_bts_gprs_ctrl_ack, cfg_no_bts_gprs_ctrl_ack_cmd, + "no gprs control-ack-type-rach", NO_STR GPRS_TEXT + "Set GPRS Control Ack Type for PACKET CONTROL ACKNOWLEDGMENT message to " + "four access bursts format instead of default RLC/MAC control block\n") +{ + struct gsm_bts *bts = vty->index; + + if (bts->gprs.mode == BTS_GPRS_NONE) { + vty_out(vty, "%% GPRS not enabled on this BTS%s", VTY_NEWLINE); + return CMD_WARNING; + } + + bts->gprs.ctrl_ack_type_use_block = true; + + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_gprs_net_ctrl_ord, cfg_bts_gprs_net_ctrl_ord_cmd, + "gprs network-control-order (nc0|nc1|nc2)", + GPRS_TEXT + "GPRS Network Control Order\n" + "MS controlled cell re-selection, no measurement reporting\n" + "MS controlled cell re-selection, MS sends measurement reports\n" + "Network controlled cell re-selection, MS sends measurement reports\n") +{ + struct gsm_bts *bts = vty->index; + + if (bts->gprs.mode == BTS_GPRS_NONE) { + vty_out(vty, "%% GPRS not enabled on this BTS%s", VTY_NEWLINE); + return CMD_WARNING; + } + + bts->gprs.net_ctrl_ord = atoi(argv[0] + 2); + + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_gprs_mode, cfg_bts_gprs_mode_cmd, + "gprs mode (none|gprs|egprs)", + GPRS_TEXT + "GPRS Mode for this BTS\n" + "GPRS Disabled on this BTS\n" + "GPRS Enabled on this BTS\n" + "EGPRS (EDGE) Enabled on this BTS\n") +{ + struct gsm_bts *bts = vty->index; + enum bts_gprs_mode mode = bts_gprs_mode_parse(argv[0], NULL); + + if (!bts_gprs_mode_is_compat(bts, mode)) { + vty_out(vty, "This BTS type does not support %s%s", argv[0], + VTY_NEWLINE); + return CMD_WARNING; + } + + bts->gprs.mode = mode; + + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_gprs_11bit_rach_support_for_egprs, + cfg_bts_gprs_11bit_rach_support_for_egprs_cmd, + "gprs 11bit_rach_support_for_egprs (0|1)", + GPRS_TEXT "11 bit RACH options\n" + "Disable 11 bit RACH for EGPRS\n" + "Enable 11 bit RACH for EGPRS") +{ + struct gsm_bts *bts = vty->index; + + bts->gprs.supports_egprs_11bit_rach = atoi(argv[0]); + + if (bts->gprs.supports_egprs_11bit_rach > 1) { + vty_out(vty, "Error in RACH type%s", VTY_NEWLINE); + return CMD_WARNING; + } + + if ((bts->gprs.mode == BTS_GPRS_NONE) && + (bts->gprs.supports_egprs_11bit_rach == 1)) { + vty_out(vty, "Error:gprs mode is none and 11bit rach is" + " enabled%s", VTY_NEWLINE); + return CMD_WARNING; + } + + return CMD_SUCCESS; +} + +#define SI_TEXT "System Information Messages\n" +#define SI_TYPE_TEXT "(1|2|3|4|5|6|7|8|9|10|13|16|17|18|19|20|2bis|2ter|2quater|5bis|5ter)" +#define SI_TYPE_HELP "System Information Type 1\n" \ + "System Information Type 2\n" \ + "System Information Type 3\n" \ + "System Information Type 4\n" \ + "System Information Type 5\n" \ + "System Information Type 6\n" \ + "System Information Type 7\n" \ + "System Information Type 8\n" \ + "System Information Type 9\n" \ + "System Information Type 10\n" \ + "System Information Type 13\n" \ + "System Information Type 16\n" \ + "System Information Type 17\n" \ + "System Information Type 18\n" \ + "System Information Type 19\n" \ + "System Information Type 20\n" \ + "System Information Type 2bis\n" \ + "System Information Type 2ter\n" \ + "System Information Type 2quater\n" \ + "System Information Type 5bis\n" \ + "System Information Type 5ter\n" + +DEFUN(cfg_bts_si_mode, cfg_bts_si_mode_cmd, + "system-information " SI_TYPE_TEXT " mode (static|computed)", + SI_TEXT SI_TYPE_HELP + "System Information Mode\n" + "Static user-specified\n" + "Dynamic, BSC-computed\n") +{ + struct gsm_bts *bts = vty->index; + int type; + + type = get_string_value(osmo_sitype_strs, argv[0]); + if (type < 0) { + vty_out(vty, "Error SI Type%s", VTY_NEWLINE); + return CMD_WARNING; + } + + if (!strcmp(argv[1], "static")) + bts->si_mode_static |= (1 << type); + else + bts->si_mode_static &= ~(1 << type); + + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_si_static, cfg_bts_si_static_cmd, + "system-information " SI_TYPE_TEXT " static HEXSTRING", + SI_TEXT SI_TYPE_HELP + "Static System Information filling\n" + "Static user-specified SI content in HEX notation\n") +{ + struct gsm_bts *bts = vty->index; + int rc, type; + + type = get_string_value(osmo_sitype_strs, argv[0]); + if (type < 0) { + vty_out(vty, "Error SI Type%s", VTY_NEWLINE); + return CMD_WARNING; + } + + if (!(bts->si_mode_static & (1 << type))) { + vty_out(vty, "SI Type %s is not configured in static mode%s", + get_value_string(osmo_sitype_strs, type), VTY_NEWLINE); + return CMD_WARNING; + } + + /* Fill buffer with padding pattern */ + memset(GSM_BTS_SI(bts, type), 0x2b, GSM_MACBLOCK_LEN); + + /* Parse the user-specified SI in hex format, [partially] overwriting padding */ + rc = osmo_hexparse(argv[1], GSM_BTS_SI(bts, type), GSM_MACBLOCK_LEN); + if (rc < 0 || rc > GSM_MACBLOCK_LEN) { + vty_out(vty, "Error parsing HEXSTRING%s", VTY_NEWLINE); + return CMD_WARNING; + } + + /* Mark this SI as present */ + bts->si_valid |= (1 << type); + + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_early_cm, cfg_bts_early_cm_cmd, + "early-classmark-sending (allowed|forbidden)", + "Early Classmark Sending\n" + "Early Classmark Sending is allowed\n" + "Early Classmark Sending is forbidden\n") +{ + struct gsm_bts *bts = vty->index; + + if (!strcmp(argv[0], "allowed")) + bts->early_classmark_allowed = true; + else + bts->early_classmark_allowed = false; + + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_early_cm_3g, cfg_bts_early_cm_3g_cmd, + "early-classmark-sending-3g (allowed|forbidden)", + "3G Early Classmark Sending\n" + "3G Early Classmark Sending is allowed\n" + "3G Early Classmark Sending is forbidden\n") +{ + struct gsm_bts *bts = vty->index; + + if (!strcmp(argv[0], "allowed")) + bts->early_classmark_allowed_3g = true; + else + bts->early_classmark_allowed_3g = false; + + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_neigh_mode, cfg_bts_neigh_mode_cmd, + "neighbor-list mode (automatic|manual|manual-si5)", + "Neighbor List\n" "Mode of Neighbor List generation\n" + "Automatically from all BTS in this OpenBSC\n" "Manual\n" + "Manual with different lists for SI2 and SI5\n") +{ + struct gsm_bts *bts = vty->index; + int mode = get_string_value(bts_neigh_mode_strs, argv[0]); + + switch (mode) { + case NL_MODE_MANUAL_SI5SEP: + case NL_MODE_MANUAL: + /* make sure we clear the current list when switching to + * manual mode */ + if (bts->neigh_list_manual_mode == 0) + memset(&bts->si_common.data.neigh_list, 0, + sizeof(bts->si_common.data.neigh_list)); + break; + default: + break; + } + + bts->neigh_list_manual_mode = mode; + + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_neigh, cfg_bts_neigh_cmd, + "neighbor-list (add|del) arfcn <0-1023>", + "Neighbor List\n" "Add to manual neighbor list\n" + "Delete from manual neighbor list\n" "ARFCN of neighbor\n" + "ARFCN of neighbor\n") +{ + struct gsm_bts *bts = vty->index; + struct bitvec *bv = &bts->si_common.neigh_list; + uint16_t arfcn = atoi(argv[1]); + + if (!bts->neigh_list_manual_mode) { + vty_out(vty, "%% Cannot configure neighbor list in " + "automatic mode%s", VTY_NEWLINE); + return CMD_WARNING; + } + + if (!strcmp(argv[0], "add")) + bitvec_set_bit_pos(bv, arfcn, 1); + else + bitvec_set_bit_pos(bv, arfcn, 0); + + return CMD_SUCCESS; +} + +/* help text should be kept in sync with EARFCN_*_INVALID defines */ +DEFUN(cfg_bts_si2quater_neigh_add, cfg_bts_si2quater_neigh_add_cmd, + "si2quater neighbor-list add earfcn <0-65535> thresh-hi <0-31> " + "thresh-lo <0-32> prio <0-8> qrxlv <0-32> meas <0-8>", + "SI2quater Neighbor List\n" "SI2quater Neighbor List\n" + "Add to manual SI2quater neighbor list\n" + "EARFCN of neighbor\n" "EARFCN of neighbor\n" + "threshold high bits\n" "threshold high bits\n" + "threshold low bits\n" "threshold low bits (32 means NA)\n" + "priority\n" "priority (8 means NA)\n" + "QRXLEVMIN\n" "QRXLEVMIN (32 means NA)\n" + "measurement bandwidth\n" "measurement bandwidth (8 means NA)\n") +{ + struct gsm_bts *bts = vty->index; + struct osmo_earfcn_si2q *e = &bts->si_common.si2quater_neigh_list; + uint16_t arfcn = atoi(argv[0]); + uint8_t thresh_hi = atoi(argv[1]), thresh_lo = atoi(argv[2]), + prio = atoi(argv[3]), qrx = atoi(argv[4]), meas = atoi(argv[5]); + int r = bts_earfcn_add(bts, arfcn, thresh_hi, thresh_lo, prio, qrx, meas); + + switch (r) { + case 1: + vty_out(vty, "Warning: multiple threshold-high are not supported, overriding with %u%s", + thresh_hi, VTY_NEWLINE); + break; + case EARFCN_THRESH_LOW_INVALID: + vty_out(vty, "Warning: multiple threshold-low are not supported, overriding with %u%s", + thresh_lo, VTY_NEWLINE); + break; + case EARFCN_QRXLV_INVALID + 1: + vty_out(vty, "Warning: multiple QRXLEVMIN are not supported, overriding with %u%s", + qrx, VTY_NEWLINE); + break; + case EARFCN_PRIO_INVALID: + vty_out(vty, "Warning: multiple priorities are not supported, overriding with %u%s", + prio, VTY_NEWLINE); + break; + default: + if (r < 0) { + vty_out(vty, "Unable to add ARFCN %u: %s%s", arfcn, strerror(-r), VTY_NEWLINE); + return CMD_WARNING; + } + } + + if (si2q_num(bts) <= SI2Q_MAX_NUM) + return CMD_SUCCESS; + + vty_out(vty, "Warning: not enough space in SI2quater (%u/%u used) for a given EARFCN %u%s", + bts->si2q_count, SI2Q_MAX_NUM, arfcn, VTY_NEWLINE); + osmo_earfcn_del(e, arfcn); + + return CMD_WARNING; +} + +DEFUN(cfg_bts_si2quater_neigh_del, cfg_bts_si2quater_neigh_del_cmd, + "si2quater neighbor-list del earfcn <0-65535>", + "SI2quater Neighbor List\n" + "SI2quater Neighbor List\n" + "Delete from SI2quater manual neighbor list\n" + "EARFCN of neighbor\n" + "EARFCN\n") +{ + struct gsm_bts *bts = vty->index; + struct osmo_earfcn_si2q *e = &bts->si_common.si2quater_neigh_list; + uint16_t arfcn = atoi(argv[0]); + int r = osmo_earfcn_del(e, arfcn); + if (r < 0) { + vty_out(vty, "Unable to delete arfcn %u: %s%s", arfcn, + strerror(-r), VTY_NEWLINE); + return CMD_WARNING; + } + + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_si2quater_uarfcn_add, cfg_bts_si2quater_uarfcn_add_cmd, + "si2quater neighbor-list add uarfcn <0-16383> <0-511> <0-1>", + "SI2quater Neighbor List\n" + "SI2quater Neighbor List\n" "Add to manual SI2quater neighbor list\n" + "UARFCN of neighbor\n" "UARFCN of neighbor\n" "scrambling code\n" + "diversity bit\n") +{ + struct gsm_bts *bts = vty->index; + uint16_t arfcn = atoi(argv[0]), scramble = atoi(argv[1]); + + switch(bts_uarfcn_add(bts, arfcn, scramble, atoi(argv[2]))) { + case -ENOMEM: + vty_out(vty, "Unable to add UARFCN: max number of UARFCNs (%u) reached%s", MAX_EARFCN_LIST, VTY_NEWLINE); + return CMD_WARNING; + case -ENOSPC: + vty_out(vty, "Warning: not enough space in SI2quater for a given UARFCN (%u, %u)%s", + arfcn, scramble, VTY_NEWLINE); + return CMD_WARNING; + case -EADDRINUSE: + vty_out(vty, "Unable to add UARFCN: (%u, %u) is already added%s", arfcn, scramble, VTY_NEWLINE); + return CMD_WARNING; + } + + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_si2quater_uarfcn_del, cfg_bts_si2quater_uarfcn_del_cmd, + "si2quater neighbor-list del uarfcn <0-16383> <0-511>", + "SI2quater Neighbor List\n" + "SI2quater Neighbor List\n" + "Delete from SI2quater manual neighbor list\n" + "UARFCN of neighbor\n" + "UARFCN\n" + "scrambling code\n") +{ + struct gsm_bts *bts = vty->index; + + if (bts_uarfcn_del(bts, atoi(argv[0]), atoi(argv[1])) < 0) { + vty_out(vty, "Unable to delete uarfcn: pair not found%s", + VTY_NEWLINE); + return CMD_WARNING; + } + + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_si5_neigh, cfg_bts_si5_neigh_cmd, + "si5 neighbor-list (add|del) arfcn <0-1023>", + "SI5 Neighbor List\n" + "SI5 Neighbor List\n" "Add to manual SI5 neighbor list\n" + "Delete from SI5 manual neighbor list\n" "ARFCN of neighbor\n" + "ARFCN of neighbor\n") +{ + struct gsm_bts *bts = vty->index; + struct bitvec *bv = &bts->si_common.si5_neigh_list; + uint16_t arfcn = atoi(argv[1]); + + if (!bts->neigh_list_manual_mode) { + vty_out(vty, "%% Cannot configure neighbor list in " + "automatic mode%s", VTY_NEWLINE); + return CMD_WARNING; + } + + if (!strcmp(argv[0], "add")) + bitvec_set_bit_pos(bv, arfcn, 1); + else + bitvec_set_bit_pos(bv, arfcn, 0); + + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_pcu_sock, cfg_bts_pcu_sock_cmd, + "pcu-socket PATH", + "PCU Socket Path for using OsmoPCU co-located with BSC (legacy BTS)\n" + "Path in the file system for the unix-domain PCU socket\n") +{ + struct gsm_bts *bts = vty->index; + int rc; + + osmo_talloc_replace_string(bts, &bts->pcu_sock_path, argv[0]); + pcu_sock_exit(bts); + rc = pcu_sock_init(bts->pcu_sock_path, bts); + if (rc < 0) { + vty_out(vty, "%% Error creating PCU socket `%s' for BTS %u%s", + bts->pcu_sock_path, bts->nr, VTY_NEWLINE); + return CMD_WARNING; + } + + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_acc_ramping, + cfg_bts_acc_ramping_cmd, + "access-control-class-ramping", + "Enable Access Control Class ramping\n") +{ + struct gsm_bts *bts = vty->index; + + if (!acc_ramp_is_enabled(&bts->acc_ramp)) + acc_ramp_set_enabled(&bts->acc_ramp, true); + + /* + * ACC ramping takes effect either when the BTS reconnects RSL, + * or when RF administrative state changes to 'unlocked'. + */ + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_no_acc_ramping, cfg_bts_no_acc_ramping_cmd, + "no access-control-class-ramping", + NO_STR + "Disable Access Control Class ramping\n") +{ + struct gsm_bts *bts = vty->index; + + if (acc_ramp_is_enabled(&bts->acc_ramp)) { + acc_ramp_abort(&bts->acc_ramp); + acc_ramp_set_enabled(&bts->acc_ramp, false); + gsm_bts_set_system_infos(bts); + } + + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_acc_ramping_step_interval, + cfg_bts_acc_ramping_step_interval_cmd, + "access-control-class-ramping-step-interval (<" + OSMO_STRINGIFY_VAL(ACC_RAMP_STEP_INTERVAL_MIN) "-" + OSMO_STRINGIFY_VAL(ACC_RAMP_STEP_INTERVAL_MAX) ">|dynamic)", + "Configure Access Control Class ramping step interval\n" + "Set a fixed step interval (in seconds)\n" + "Use dynamic step interval based on BTS channel load\n") +{ + struct gsm_bts *bts = vty->index; + bool dynamic = (strcmp(argv[0], "dynamic") == 0); + int error; + + if (dynamic) { + acc_ramp_set_step_interval_dynamic(&bts->acc_ramp); + return CMD_SUCCESS; + } + + error = acc_ramp_set_step_interval(&bts->acc_ramp, atoi(argv[0])); + if (error != 0) { + if (error == -ERANGE) + vty_out(vty, "Unable to set ACC ramp step interval: value out of range%s", VTY_NEWLINE); + else + vty_out(vty, "Unable to set ACC ramp step interval: unknown error%s", VTY_NEWLINE); + return CMD_WARNING; + } + + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_acc_ramping_step_size, + cfg_bts_acc_ramping_step_size_cmd, + "access-control-class-ramping-step-size (<" + OSMO_STRINGIFY_VAL(ACC_RAMP_STEP_SIZE_MIN) "-" + OSMO_STRINGIFY_VAL(ACC_RAMP_STEP_SIZE_MAX) ">)", + "Configure Access Control Class ramping step size\n" + "Set the number of Access Control Classes to enable per ramping step\n") +{ + struct gsm_bts *bts = vty->index; + int error; + + error = acc_ramp_set_step_size(&bts->acc_ramp, atoi(argv[0])); + if (error != 0) { + if (error == -ERANGE) + vty_out(vty, "Unable to set ACC ramp step size: value out of range%s", VTY_NEWLINE); + else + vty_out(vty, "Unable to set ACC ramp step size: unknown error%s", VTY_NEWLINE); + return CMD_WARNING; + } + + return CMD_SUCCESS; +} + +#define EXCL_RFLOCK_STR "Exclude this BTS from the global RF Lock\n" + +DEFUN(cfg_bts_excl_rf_lock, + cfg_bts_excl_rf_lock_cmd, + "rf-lock-exclude", + EXCL_RFLOCK_STR) +{ + struct gsm_bts *bts = vty->index; + bts->excl_from_rf_lock = 1; + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_no_excl_rf_lock, + cfg_bts_no_excl_rf_lock_cmd, + "no rf-lock-exclude", + NO_STR EXCL_RFLOCK_STR) +{ + struct gsm_bts *bts = vty->index; + bts->excl_from_rf_lock = 0; + return CMD_SUCCESS; +} + +#define FORCE_COMB_SI_STR "Force the generation of a single SI (no ter/bis)\n" + +DEFUN(cfg_bts_force_comb_si, + cfg_bts_force_comb_si_cmd, + "force-combined-si", + FORCE_COMB_SI_STR) +{ + struct gsm_bts *bts = vty->index; + bts->force_combined_si = 1; + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_no_force_comb_si, + cfg_bts_no_force_comb_si_cmd, + "no force-combined-si", + NO_STR FORCE_COMB_SI_STR) +{ + struct gsm_bts *bts = vty->index; + bts->force_combined_si = 0; + return CMD_SUCCESS; +} + +static void _get_codec_from_arg(struct vty *vty, int argc, const char *argv[]) +{ + struct gsm_bts *bts = vty->index; + struct bts_codec_conf *codec = &bts->codec; + int i; + + codec->hr = 0; + codec->efr = 0; + codec->amr = 0; + for (i = 0; i < argc; i++) { + if (!strcmp(argv[i], "hr")) + codec->hr = 1; + if (!strcmp(argv[i], "efr")) + codec->efr = 1; + if (!strcmp(argv[i], "amr")) + codec->amr = 1; + } +} + +#define CODEC_PAR_STR " (hr|efr|amr)" +#define CODEC_HELP_STR "Half Rate\n" \ + "Enhanced Full Rate\nAdaptive Multirate\n" + +DEFUN(cfg_bts_codec0, cfg_bts_codec0_cmd, + "codec-support fr", + "Codec Support settings\nFullrate\n") +{ + _get_codec_from_arg(vty, 0, argv); + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_codec1, cfg_bts_codec1_cmd, + "codec-support fr" CODEC_PAR_STR, + "Codec Support settings\nFullrate\n" + CODEC_HELP_STR) +{ + _get_codec_from_arg(vty, 1, argv); + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_codec2, cfg_bts_codec2_cmd, + "codec-support fr" CODEC_PAR_STR CODEC_PAR_STR, + "Codec Support settings\nFullrate\n" + CODEC_HELP_STR CODEC_HELP_STR) +{ + _get_codec_from_arg(vty, 2, argv); + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_codec3, cfg_bts_codec3_cmd, + "codec-support fr" CODEC_PAR_STR CODEC_PAR_STR CODEC_PAR_STR, + "Codec Support settings\nFullrate\n" + CODEC_HELP_STR CODEC_HELP_STR CODEC_HELP_STR) +{ + _get_codec_from_arg(vty, 3, argv); + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_codec4, cfg_bts_codec4_cmd, + "codec-support fr" CODEC_PAR_STR CODEC_PAR_STR CODEC_PAR_STR CODEC_PAR_STR, + "Codec Support settings\nFullrate\n" + CODEC_HELP_STR CODEC_HELP_STR CODEC_HELP_STR CODEC_HELP_STR) +{ + _get_codec_from_arg(vty, 4, argv); + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_depends_on, cfg_bts_depends_on_cmd, + "depends-on-bts <0-255>", + "This BTS can only be started if another one is up\n" + BTS_NR_STR) +{ + struct gsm_bts *bts = vty->index; + struct gsm_bts *other_bts; + int dep = atoi(argv[0]); + + + if (!is_ipaccess_bts(bts)) { + vty_out(vty, "This feature is only available for IP systems.%s", + VTY_NEWLINE); + return CMD_WARNING; + } + + other_bts = gsm_bts_num(bts->network, dep); + if (!other_bts || !is_ipaccess_bts(other_bts)) { + vty_out(vty, "This feature is only available for IP systems.%s", + VTY_NEWLINE); + return CMD_WARNING; + } + + if (dep >= bts->nr) { + vty_out(vty, "%%Need to depend on an already declared unit.%s", + VTY_NEWLINE); + return CMD_WARNING; + } + + bts_depend_mark(bts, dep); + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_no_depends_on, cfg_bts_no_depends_on_cmd, + "depeneds-on-bts <0-255>", + NO_STR "This BTS can only be started if another one is up\n" + BTS_NR_STR) +{ + struct gsm_bts *bts = vty->index; + int dep = atoi(argv[0]); + + bts_depend_clear(bts, dep); + return CMD_SUCCESS; +} + +#define AMR_TEXT "Adaptive Multi Rate settings\n" +#define AMR_MODE_TEXT "Codec modes to use with AMR codec\n" +#define AMR_START_TEXT "Initial codec to use with AMR\n" \ + "Automatically\nFirst codec\nSecond codec\nThird codec\nFourth codec\n" +#define AMR_TH_TEXT "AMR threshold between codecs\nMS side\nBTS side\n" +#define AMR_HY_TEXT "AMR hysteresis between codecs\nMS side\nBTS side\n" + +static void get_amr_from_arg(struct vty *vty, int argc, const char *argv[], int full) +{ + struct gsm_bts *bts = vty->index; + struct amr_multirate_conf *mr = (full) ? &bts->mr_full: &bts->mr_half; + struct gsm48_multi_rate_conf *mr_conf = + (struct gsm48_multi_rate_conf *) mr->gsm48_ie; + int i; + + mr->gsm48_ie[1] = 0; + for (i = 0; i < argc; i++) + mr->gsm48_ie[1] |= 1 << atoi(argv[i]); + mr_conf->icmi = 0; +} + +static void get_amr_th_from_arg(struct vty *vty, int argc, const char *argv[], int full) +{ + struct gsm_bts *bts = vty->index; + struct amr_multirate_conf *mr = (full) ? &bts->mr_full: &bts->mr_half; + struct amr_mode *modes; + int i; + + modes = argv[0][0]=='m' ? mr->ms_mode : mr->bts_mode; + for (i = 0; i < argc - 1; i++) + modes[i].threshold = atoi(argv[i + 1]); +} + +static void get_amr_hy_from_arg(struct vty *vty, int argc, const char *argv[], int full) +{ + struct gsm_bts *bts = vty->index; + struct amr_multirate_conf *mr = (full) ? &bts->mr_full: &bts->mr_half; + struct amr_mode *modes; + int i; + + modes = argv[0][0]=='m' ? mr->ms_mode : mr->bts_mode; + for (i = 0; i < argc - 1; i++) + modes[i].hysteresis = atoi(argv[i + 1]); +} + +static void get_amr_start_from_arg(struct vty *vty, const char *argv[], int full) +{ + struct gsm_bts *bts = vty->index; + struct amr_multirate_conf *mr = (full) ? &bts->mr_full: &bts->mr_half; + struct gsm48_multi_rate_conf *mr_conf = + (struct gsm48_multi_rate_conf *) mr->gsm48_ie; + int num = 0, i; + + for (i = 0; i < ((full) ? 8 : 6); i++) { + if ((mr->gsm48_ie[1] & (1 << i))) { + num++; + } + } + + if (argv[0][0] == 'a' || num == 0) + mr_conf->icmi = 0; + else { + mr_conf->icmi = 1; + if (num < atoi(argv[0])) + mr_conf->smod = num - 1; + else + mr_conf->smod = atoi(argv[0]) - 1; + } +} + +#define AMR_TCHF_PAR_STR " (0|1|2|3|4|5|6|7)" +#define AMR_TCHF_HELP_STR "4,75k\n5,15k\n5,90k\n6,70k\n7,40k\n7,95k\n" \ + "10,2k\n12,2k\n" + +#define AMR_TCHH_PAR_STR " (0|1|2|3|4|5)" +#define AMR_TCHH_HELP_STR "4,75k\n5,15k\n5,90k\n6,70k\n7,40k\n7,95k\n" + +#define AMR_TH_HELP_STR "Threshold between codec 1 and 2\n" +#define AMR_HY_HELP_STR "Hysteresis between codec 1 and 2\n" + +DEFUN(cfg_bts_amr_fr_modes1, cfg_bts_amr_fr_modes1_cmd, + "amr tch-f modes" AMR_TCHF_PAR_STR, + AMR_TEXT "Full Rate\n" AMR_MODE_TEXT + AMR_TCHF_HELP_STR) +{ + get_amr_from_arg(vty, 1, argv, 1); + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_amr_fr_modes2, cfg_bts_amr_fr_modes2_cmd, + "amr tch-f modes" AMR_TCHF_PAR_STR AMR_TCHF_PAR_STR, + AMR_TEXT "Full Rate\n" AMR_MODE_TEXT + AMR_TCHF_HELP_STR AMR_TCHF_HELP_STR) +{ + get_amr_from_arg(vty, 2, argv, 1); + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_amr_fr_modes3, cfg_bts_amr_fr_modes3_cmd, + "amr tch-f modes" AMR_TCHF_PAR_STR AMR_TCHF_PAR_STR AMR_TCHF_PAR_STR, + AMR_TEXT "Full Rate\n" AMR_MODE_TEXT + AMR_TCHF_HELP_STR AMR_TCHF_HELP_STR AMR_TCHF_HELP_STR) +{ + get_amr_from_arg(vty, 3, argv, 1); + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_amr_fr_modes4, cfg_bts_amr_fr_modes4_cmd, + "amr tch-f modes" AMR_TCHF_PAR_STR AMR_TCHF_PAR_STR AMR_TCHF_PAR_STR AMR_TCHF_PAR_STR, + AMR_TEXT "Full Rate\n" AMR_MODE_TEXT + AMR_TCHF_HELP_STR AMR_TCHF_HELP_STR AMR_TCHF_HELP_STR AMR_TCHF_HELP_STR) +{ + get_amr_from_arg(vty, 4, argv, 1); + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_amr_fr_start_mode, cfg_bts_amr_fr_start_mode_cmd, + "amr tch-f start-mode (auto|1|2|3|4)", + AMR_TEXT "Full Rate\n" AMR_START_TEXT) +{ + get_amr_start_from_arg(vty, argv, 1); + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_amr_fr_thres1, cfg_bts_amr_fr_thres1_cmd, + "amr tch-f threshold (ms|bts) <0-63>", + AMR_TEXT "Full Rate\n" AMR_TH_TEXT + AMR_TH_HELP_STR) +{ + get_amr_th_from_arg(vty, 2, argv, 1); + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_amr_fr_thres2, cfg_bts_amr_fr_thres2_cmd, + "amr tch-f threshold (ms|bts) <0-63> <0-63>", + AMR_TEXT "Full Rate\n" AMR_TH_TEXT + AMR_TH_HELP_STR AMR_TH_HELP_STR) +{ + get_amr_th_from_arg(vty, 3, argv, 1); + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_amr_fr_thres3, cfg_bts_amr_fr_thres3_cmd, + "amr tch-f threshold (ms|bts) <0-63> <0-63> <0-63>", + AMR_TEXT "Full Rate\n" AMR_TH_TEXT + AMR_TH_HELP_STR AMR_TH_HELP_STR AMR_TH_HELP_STR) +{ + get_amr_th_from_arg(vty, 4, argv, 1); + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_amr_fr_hyst1, cfg_bts_amr_fr_hyst1_cmd, + "amr tch-f hysteresis (ms|bts) <0-15>", + AMR_TEXT "Full Rate\n" AMR_HY_TEXT + AMR_HY_HELP_STR) +{ + get_amr_hy_from_arg(vty, 2, argv, 1); + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_amr_fr_hyst2, cfg_bts_amr_fr_hyst2_cmd, + "amr tch-f hysteresis (ms|bts) <0-15> <0-15>", + AMR_TEXT "Full Rate\n" AMR_HY_TEXT + AMR_HY_HELP_STR AMR_HY_HELP_STR) +{ + get_amr_hy_from_arg(vty, 3, argv, 1); + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_amr_fr_hyst3, cfg_bts_amr_fr_hyst3_cmd, + "amr tch-f hysteresis (ms|bts) <0-15> <0-15> <0-15>", + AMR_TEXT "Full Rate\n" AMR_HY_TEXT + AMR_HY_HELP_STR AMR_HY_HELP_STR AMR_HY_HELP_STR) +{ + get_amr_hy_from_arg(vty, 4, argv, 1); + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_amr_hr_modes1, cfg_bts_amr_hr_modes1_cmd, + "amr tch-h modes" AMR_TCHH_PAR_STR, + AMR_TEXT "Half Rate\n" AMR_MODE_TEXT + AMR_TCHH_HELP_STR) +{ + get_amr_from_arg(vty, 1, argv, 0); + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_amr_hr_modes2, cfg_bts_amr_hr_modes2_cmd, + "amr tch-h modes" AMR_TCHH_PAR_STR AMR_TCHH_PAR_STR, + AMR_TEXT "Half Rate\n" AMR_MODE_TEXT + AMR_TCHH_HELP_STR AMR_TCHH_HELP_STR) +{ + get_amr_from_arg(vty, 2, argv, 0); + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_amr_hr_modes3, cfg_bts_amr_hr_modes3_cmd, + "amr tch-h modes" AMR_TCHH_PAR_STR AMR_TCHH_PAR_STR AMR_TCHH_PAR_STR, + AMR_TEXT "Half Rate\n" AMR_MODE_TEXT + AMR_TCHH_HELP_STR AMR_TCHH_HELP_STR AMR_TCHH_HELP_STR) +{ + get_amr_from_arg(vty, 3, argv, 0); + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_amr_hr_modes4, cfg_bts_amr_hr_modes4_cmd, + "amr tch-h modes" AMR_TCHH_PAR_STR AMR_TCHH_PAR_STR AMR_TCHH_PAR_STR AMR_TCHH_PAR_STR, + AMR_TEXT "Half Rate\n" AMR_MODE_TEXT + AMR_TCHH_HELP_STR AMR_TCHH_HELP_STR AMR_TCHH_HELP_STR AMR_TCHH_HELP_STR) +{ + get_amr_from_arg(vty, 4, argv, 0); + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_amr_hr_start_mode, cfg_bts_amr_hr_start_mode_cmd, + "amr tch-h start-mode (auto|1|2|3|4)", + AMR_TEXT "Half Rate\n" AMR_START_TEXT) +{ + get_amr_start_from_arg(vty, argv, 0); + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_amr_hr_thres1, cfg_bts_amr_hr_thres1_cmd, + "amr tch-h threshold (ms|bts) <0-63>", + AMR_TEXT "Half Rate\n" AMR_TH_TEXT + AMR_TH_HELP_STR) +{ + get_amr_th_from_arg(vty, 2, argv, 0); + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_amr_hr_thres2, cfg_bts_amr_hr_thres2_cmd, + "amr tch-h threshold (ms|bts) <0-63> <0-63>", + AMR_TEXT "Half Rate\n" AMR_TH_TEXT + AMR_TH_HELP_STR AMR_TH_HELP_STR) +{ + get_amr_th_from_arg(vty, 3, argv, 0); + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_amr_hr_thres3, cfg_bts_amr_hr_thres3_cmd, + "amr tch-h threshold (ms|bts) <0-63> <0-63> <0-63>", + AMR_TEXT "Half Rate\n" AMR_TH_TEXT + AMR_TH_HELP_STR AMR_TH_HELP_STR AMR_TH_HELP_STR) +{ + get_amr_th_from_arg(vty, 4, argv, 0); + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_amr_hr_hyst1, cfg_bts_amr_hr_hyst1_cmd, + "amr tch-h hysteresis (ms|bts) <0-15>", + AMR_TEXT "Half Rate\n" AMR_HY_TEXT + AMR_HY_HELP_STR) +{ + get_amr_hy_from_arg(vty, 2, argv, 0); + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_amr_hr_hyst2, cfg_bts_amr_hr_hyst2_cmd, + "amr tch-h hysteresis (ms|bts) <0-15> <0-15>", + AMR_TEXT "Half Rate\n" AMR_HY_TEXT + AMR_HY_HELP_STR AMR_HY_HELP_STR) +{ + get_amr_hy_from_arg(vty, 3, argv, 0); + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_amr_hr_hyst3, cfg_bts_amr_hr_hyst3_cmd, + "amr tch-h hysteresis (ms|bts) <0-15> <0-15> <0-15>", + AMR_TEXT "Half Rate\n" AMR_HY_TEXT + AMR_HY_HELP_STR AMR_HY_HELP_STR AMR_HY_HELP_STR) +{ + get_amr_hy_from_arg(vty, 4, argv, 0); + return CMD_SUCCESS; +} + +#define TRX_TEXT "Radio Transceiver\n" + +/* per TRX configuration */ +DEFUN(cfg_trx, + cfg_trx_cmd, + "trx <0-255>", + TRX_TEXT + "Select a TRX to configure") +{ + int trx_nr = atoi(argv[0]); + struct gsm_bts *bts = vty->index; + struct gsm_bts_trx *trx; + + if (trx_nr > bts->num_trx) { + vty_out(vty, "%% The next unused TRX number in this BTS is %u%s", + bts->num_trx, VTY_NEWLINE); + return CMD_WARNING; + } else if (trx_nr == bts->num_trx) { + /* we need to allocate a new one */ + trx = gsm_bts_trx_alloc(bts); + } else + trx = gsm_bts_trx_num(bts, trx_nr); + + if (!trx) + return CMD_WARNING; + + vty->index = trx; + vty->index_sub = &trx->description; + vty->node = TRX_NODE; + + return CMD_SUCCESS; +} + +DEFUN(cfg_trx_arfcn, + cfg_trx_arfcn_cmd, + "arfcn <0-1023>", + "Set the ARFCN for this TRX\n" + "Absolute Radio Frequency Channel Number\n") +{ + int arfcn = atoi(argv[0]); + struct gsm_bts_trx *trx = vty->index; + + /* FIXME: check if this ARFCN is supported by this TRX */ + + trx->arfcn = arfcn; + + /* FIXME: patch ARFCN into SYSTEM INFORMATION */ + /* FIXME: use OML layer to update the ARFCN */ + /* FIXME: use RSL layer to update SYSTEM INFORMATION */ + + return CMD_SUCCESS; +} + +DEFUN(cfg_trx_nominal_power, + cfg_trx_nominal_power_cmd, + "nominal power <0-100>", + "Nominal TRX RF Power in dBm\n" + "Nominal TRX RF Power in dBm\n" + "Nominal TRX RF Power in dBm\n") +{ + struct gsm_bts_trx *trx = vty->index; + + trx->nominal_power = atoi(argv[0]); + + return CMD_SUCCESS; +} + +DEFUN(cfg_trx_max_power_red, + cfg_trx_max_power_red_cmd, + "max_power_red <0-100>", + "Reduction of maximum BS RF Power (relative to nominal power)\n" + "Reduction of maximum BS RF Power in dB\n") +{ + int maxpwr_r = atoi(argv[0]); + struct gsm_bts_trx *trx = vty->index; + int upper_limit = 24; /* default 12.21 max power red. */ + + /* FIXME: check if our BTS type supports more than 12 */ + if (maxpwr_r < 0 || maxpwr_r > upper_limit) { + vty_out(vty, "%% Power %d dB is not in the valid range%s", + maxpwr_r, VTY_NEWLINE); + return CMD_WARNING; + } + if (maxpwr_r & 1) { + vty_out(vty, "%% Power %d dB is not an even value%s", + maxpwr_r, VTY_NEWLINE); + return CMD_WARNING; + } + + trx->max_power_red = maxpwr_r; + + /* FIXME: make sure we update this using OML */ + + return CMD_SUCCESS; +} + +DEFUN(cfg_trx_rsl_e1, + cfg_trx_rsl_e1_cmd, + "rsl e1 line E1_LINE timeslot <1-31> sub-slot (0|1|2|3|full)", + "RSL Parameters\n" + "E1/T1 interface to be used for RSL\n" + "E1/T1 interface to be used for RSL\n" + "E1/T1 Line Number to be used for RSL\n" + "E1/T1 Timeslot to be used for RSL\n" + "E1/T1 Timeslot to be used for RSL\n" + "E1/T1 Sub-slot to be used for RSL\n" + "E1/T1 Sub-slot 0 is to be used for RSL\n" + "E1/T1 Sub-slot 1 is to be used for RSL\n" + "E1/T1 Sub-slot 2 is to be used for RSL\n" + "E1/T1 Sub-slot 3 is to be used for RSL\n" + "E1/T1 full timeslot is to be used for RSL\n") +{ + struct gsm_bts_trx *trx = vty->index; + + parse_e1_link(&trx->rsl_e1_link, argv[0], argv[1], argv[2]); + + return CMD_SUCCESS; +} + +DEFUN(cfg_trx_rsl_e1_tei, + cfg_trx_rsl_e1_tei_cmd, + "rsl e1 tei <0-63>", + "RSL Parameters\n" + "Set the TEI to be used for RSL\n" + "Set the TEI to be used for RSL\n" + "TEI to be used for RSL\n") +{ + struct gsm_bts_trx *trx = vty->index; + + trx->rsl_tei = atoi(argv[0]); + + return CMD_SUCCESS; +} + +DEFUN(cfg_trx_rf_locked, + cfg_trx_rf_locked_cmd, + "rf_locked (0|1)", + "Set or unset the RF Locking (Turn off RF of the TRX)\n" + "TRX is NOT RF locked (active)\n" + "TRX is RF locked (turned off)\n") +{ + int locked = atoi(argv[0]); + struct gsm_bts_trx *trx = vty->index; + + gsm_trx_lock_rf(trx, locked, "vty"); + return CMD_SUCCESS; +} + +/* per TS configuration */ +DEFUN(cfg_ts, + cfg_ts_cmd, + "timeslot <0-7>", + "Select a Timeslot to configure\n" + "Timeslot number\n") +{ + int ts_nr = atoi(argv[0]); + struct gsm_bts_trx *trx = vty->index; + struct gsm_bts_trx_ts *ts; + + if (ts_nr >= TRX_NR_TS) { + vty_out(vty, "%% A GSM TRX only has %u Timeslots per TRX%s", + TRX_NR_TS, VTY_NEWLINE); + return CMD_WARNING; + } + + ts = &trx->ts[ts_nr]; + + vty->index = ts; + vty->node = TS_NODE; + + return CMD_SUCCESS; +} + +DEFUN(cfg_ts_pchan, + cfg_ts_pchan_cmd, + "phys_chan_config PCHAN", /* dynamically generated! */ + "Physical Channel configuration (TCH/SDCCH/...)\n" "Physical Channel\n") +{ + struct gsm_bts_trx_ts *ts = vty->index; + int pchanc; + + pchanc = gsm_pchan_parse(argv[0]); + if (pchanc < 0) + return CMD_WARNING; + + ts->pchan = pchanc; + + return CMD_SUCCESS; +} + +/* used for backwards compatibility with old config files that still + * have uppercase pchan type names */ +DEFUN_HIDDEN(cfg_ts_pchan_compat, + cfg_ts_pchan_compat_cmd, + "phys_chan_config PCHAN", + "Physical Channel configuration (TCH/SDCCH/...)\n" "Physical Channel\n") +{ + struct gsm_bts_trx_ts *ts = vty->index; + int pchanc; + + pchanc = gsm_pchan_parse(argv[0]); + if (pchanc < 0) + return CMD_WARNING; + + ts->pchan = pchanc; + + return CMD_SUCCESS; +} + + + +DEFUN(cfg_ts_tsc, + cfg_ts_tsc_cmd, + "training_sequence_code <0-7>", + "Training Sequence Code of the Timeslot\n" "TSC\n") +{ + struct gsm_bts_trx_ts *ts = vty->index; + + if (!osmo_bts_has_feature(&ts->trx->bts->model->features, BTS_FEAT_MULTI_TSC)) { + vty_out(vty, "%% This BTS does not support a TSC != BCC, " + "falling back to BCC%s", VTY_NEWLINE); + ts->tsc = -1; + return CMD_WARNING; + } + + ts->tsc = atoi(argv[0]); + + return CMD_SUCCESS; +} + +#define HOPPING_STR "Configure frequency hopping\n" + +DEFUN(cfg_ts_hopping, + cfg_ts_hopping_cmd, + "hopping enabled (0|1)", + HOPPING_STR "Enable or disable frequency hopping\n" + "Disable frequency hopping\n" "Enable frequency hopping\n") +{ + struct gsm_bts_trx_ts *ts = vty->index; + int enabled = atoi(argv[0]); + + if (enabled && !osmo_bts_has_feature(&ts->trx->bts->model->features, BTS_FEAT_HOPPING)) { + vty_out(vty, "BTS model does not support hopping%s", + VTY_NEWLINE); + return CMD_WARNING; + } + + ts->hopping.enabled = enabled; + + return CMD_SUCCESS; +} + +DEFUN(cfg_ts_hsn, + cfg_ts_hsn_cmd, + "hopping sequence-number <0-63>", + HOPPING_STR + "Which hopping sequence to use for this channel\n" + "Hopping Sequence Number (HSN)\n") +{ + struct gsm_bts_trx_ts *ts = vty->index; + + ts->hopping.hsn = atoi(argv[0]); + + return CMD_SUCCESS; +} + +DEFUN(cfg_ts_maio, + cfg_ts_maio_cmd, + "hopping maio <0-63>", + HOPPING_STR + "Which hopping MAIO to use for this channel\n" + "Mobile Allocation Index Offset (MAIO)\n") +{ + struct gsm_bts_trx_ts *ts = vty->index; + + ts->hopping.maio = atoi(argv[0]); + + return CMD_SUCCESS; +} + +DEFUN(cfg_ts_arfcn_add, + cfg_ts_arfcn_add_cmd, + "hopping arfcn add <0-1023>", + HOPPING_STR "Configure hopping ARFCN list\n" + "Add an entry to the hopping ARFCN list\n" "ARFCN\n") +{ + struct gsm_bts_trx_ts *ts = vty->index; + int arfcn = atoi(argv[0]); + + bitvec_set_bit_pos(&ts->hopping.arfcns, arfcn, 1); + + return CMD_SUCCESS; +} + +DEFUN(cfg_ts_arfcn_del, + cfg_ts_arfcn_del_cmd, + "hopping arfcn del <0-1023>", + HOPPING_STR "Configure hopping ARFCN list\n" + "Delete an entry to the hopping ARFCN list\n" "ARFCN\n") +{ + struct gsm_bts_trx_ts *ts = vty->index; + int arfcn = atoi(argv[0]); + + bitvec_set_bit_pos(&ts->hopping.arfcns, arfcn, 0); + + return CMD_SUCCESS; +} + +DEFUN(cfg_ts_e1_subslot, + cfg_ts_e1_subslot_cmd, + "e1 line E1_LINE timeslot <1-31> sub-slot (0|1|2|3|full)", + "E1/T1 channel connected to this on-air timeslot\n" + "E1/T1 channel connected to this on-air timeslot\n" + "E1/T1 line connected to this on-air timeslot\n" + "E1/T1 timeslot connected to this on-air timeslot\n" + "E1/T1 timeslot connected to this on-air timeslot\n" + "E1/T1 sub-slot connected to this on-air timeslot\n" + "E1/T1 sub-slot 0 connected to this on-air timeslot\n" + "E1/T1 sub-slot 1 connected to this on-air timeslot\n" + "E1/T1 sub-slot 2 connected to this on-air timeslot\n" + "E1/T1 sub-slot 3 connected to this on-air timeslot\n" + "Full E1/T1 timeslot connected to this on-air timeslot\n") +{ + struct gsm_bts_trx_ts *ts = vty->index; + + parse_e1_link(&ts->e1_link, argv[0], argv[1], argv[2]); + + return CMD_SUCCESS; +} + +int print_counter(struct rate_ctr_group *bsc_ctrs, struct rate_ctr *ctr, const struct rate_ctr_desc *desc, void *data) +{ + struct vty *vty = data; + vty_out(vty, "%25s: %10"PRIu64" %s%s", desc->name, ctr->current, desc->description, VTY_NEWLINE); + return 0; +} + +void openbsc_vty_print_statistics(struct vty *vty, struct gsm_network *net) +{ + rate_ctr_for_each_counter(net->bsc_ctrs, print_counter, vty); +} + +DEFUN(drop_bts, + drop_bts_cmd, + "drop bts connection <0-65535> (oml|rsl)", + "Debug/Simulation command to drop Abis/IP BTS\n" + "Debug/Simulation command to drop Abis/IP BTS\n" + "Debug/Simulation command to drop Abis/IP BTS\n" + "BTS NR\n" "Drop OML Connection\n" "Drop RSL Connection\n") +{ + struct gsm_network *gsmnet; + struct gsm_bts_trx *trx; + struct gsm_bts *bts; + unsigned int bts_nr; + + gsmnet = gsmnet_from_vty(vty); + + bts_nr = atoi(argv[0]); + if (bts_nr >= gsmnet->num_bts) { + vty_out(vty, "BTS number must be between 0 and %d. It was %d.%s", + gsmnet->num_bts, bts_nr, VTY_NEWLINE); + return CMD_WARNING; + } + + bts = gsm_bts_num(gsmnet, bts_nr); + if (!bts) { + vty_out(vty, "BTS Nr. %d could not be found.%s", bts_nr, VTY_NEWLINE); + return CMD_WARNING; + } + + if (!is_ipaccess_bts(bts)) { + vty_out(vty, "This command only works for ipaccess.%s", VTY_NEWLINE); + return CMD_WARNING; + } + + + /* close all connections */ + if (strcmp(argv[1], "oml") == 0) { + ipaccess_drop_oml(bts); + } else if (strcmp(argv[1], "rsl") == 0) { + /* close all rsl connections */ + llist_for_each_entry(trx, &bts->trx_list, list) { + ipaccess_drop_rsl(trx); + } + } else { + vty_out(vty, "Argument must be 'oml# or 'rsl'.%s", VTY_NEWLINE); + return CMD_WARNING; + } + + return CMD_SUCCESS; +} + +DEFUN(restart_bts, restart_bts_cmd, + "restart-bts <0-65535>", + "Restart ip.access nanoBTS through OML\n" + BTS_NR_STR) +{ + struct gsm_network *gsmnet; + struct gsm_bts_trx *trx; + struct gsm_bts *bts; + unsigned int bts_nr; + + gsmnet = gsmnet_from_vty(vty); + + bts_nr = atoi(argv[0]); + if (bts_nr >= gsmnet->num_bts) { + vty_out(vty, "BTS number must be between 0 and %d. It was %d.%s", + gsmnet->num_bts, bts_nr, VTY_NEWLINE); + return CMD_WARNING; + } + + bts = gsm_bts_num(gsmnet, bts_nr); + if (!bts) { + vty_out(vty, "BTS Nr. %d could not be found.%s", bts_nr, VTY_NEWLINE); + return CMD_WARNING; + } + + if (!is_ipaccess_bts(bts) || is_sysmobts_v2(bts)) { + vty_out(vty, "This command only works for ipaccess nanoBTS.%s", + VTY_NEWLINE); + return CMD_WARNING; + } + + /* go from last TRX to c0 */ + llist_for_each_entry_reverse(trx, &bts->trx_list, list) + abis_nm_ipaccess_restart(trx); + + return CMD_SUCCESS; +} + +DEFUN(bts_resend, bts_resend_cmd, + "bts <0-255> resend-system-information", + "BTS Specific Commands\n" BTS_NR_STR + "Re-generate + re-send BCCH SYSTEM INFORMATION\n") +{ + struct gsm_network *gsmnet; + struct gsm_bts_trx *trx; + struct gsm_bts *bts; + unsigned int bts_nr; + + gsmnet = gsmnet_from_vty(vty); + + bts_nr = atoi(argv[0]); + if (bts_nr >= gsmnet->num_bts) { + vty_out(vty, "BTS number must be between 0 and %d. It was %d.%s", + gsmnet->num_bts, bts_nr, VTY_NEWLINE); + return CMD_WARNING; + } + + bts = gsm_bts_num(gsmnet, bts_nr); + if (!bts) { + vty_out(vty, "BTS Nr. %d could not be found.%s", bts_nr, VTY_NEWLINE); + return CMD_WARNING; + } + + llist_for_each_entry_reverse(trx, &bts->trx_list, list) + gsm_bts_trx_set_system_infos(trx); + + return CMD_SUCCESS; +} + + +DEFUN(smscb_cmd, smscb_cmd_cmd, + "bts <0-255> smscb-command <1-4> HEXSTRING", + "BTS related commands\n" BTS_NR_STR + "SMS Cell Broadcast\n" "Last Valid Block\n" + "Hex Encoded SMSCB message (up to 88 octets)\n") +{ + struct gsm_bts *bts; + int bts_nr = atoi(argv[0]); + int last_block = atoi(argv[1]); + struct rsl_ie_cb_cmd_type cb_cmd; + uint8_t buf[88]; + int rc; + + bts = gsm_bts_num(gsmnet_from_vty(vty), bts_nr); + if (!bts) { + vty_out(vty, "%% No such BTS (%d)%s", bts_nr, VTY_NEWLINE); + return CMD_WARNING; + } + rc = osmo_hexparse(argv[2], buf, sizeof(buf)); + if (rc < 0 || rc > sizeof(buf)) { + vty_out(vty, "Error parsing HEXSTRING%s", VTY_NEWLINE); + return CMD_WARNING; + } + + cb_cmd.spare = 0; + cb_cmd.def_bcast = 0; + cb_cmd.command = RSL_CB_CMD_TYPE_NORMAL; + + switch (last_block) { + case 1: + cb_cmd.last_block = RSL_CB_CMD_LASTBLOCK_1; + break; + case 2: + cb_cmd.last_block = RSL_CB_CMD_LASTBLOCK_2; + break; + case 3: + cb_cmd.last_block = RSL_CB_CMD_LASTBLOCK_3; + break; + case 4: + cb_cmd.last_block = RSL_CB_CMD_LASTBLOCK_4; + break; + default: + vty_out(vty, "Error parsing LASTBLOCK%s", VTY_NEWLINE); + return CMD_WARNING; + } + + rsl_sms_cb_command(bts, RSL_CHAN_SDCCH4_ACCH, cb_cmd, buf, rc); + + return CMD_SUCCESS; +} + +/* resolve a gsm_bts_trx_ts basd on the given numeric identifiers */ +static struct gsm_bts_trx_ts *vty_get_ts(struct vty *vty, const char *bts_str, const char *trx_str, + const char *ts_str) +{ + int bts_nr = atoi(bts_str); + int trx_nr = atoi(trx_str); + int ts_nr = atoi(ts_str); + struct gsm_bts *bts; + struct gsm_bts_trx *trx; + struct gsm_bts_trx_ts *ts; + + bts = gsm_bts_num(gsmnet_from_vty(vty), bts_nr); + if (!bts) { + vty_out(vty, "%% No such BTS (%d)%s", bts_nr, VTY_NEWLINE); + return NULL; + } + + trx = gsm_bts_trx_num(bts, trx_nr); + if (!trx) { + vty_out(vty, "%% No such TRX (%d)%s", trx_nr, VTY_NEWLINE); + return NULL; + } + + ts = &trx->ts[ts_nr]; + + return ts; +} + +DEFUN(pdch_act, pdch_act_cmd, + "bts <0-255> trx <0-255> timeslot <0-7> pdch (activate|deactivate)", + BTS_NR_TRX_TS_STR2 + "Packet Data Channel\n" + "Activate Dynamic PDCH/TCH (-> PDCH mode)\n" + "Deactivate Dynamic PDCH/TCH (-> TCH mode)\n") +{ + struct gsm_bts_trx_ts *ts; + int activate; + + ts = vty_get_ts(vty, argv[0], argv[1], argv[2]); + if (!ts) + return CMD_WARNING; + + if (!is_ipaccess_bts(ts->trx->bts)) { + vty_out(vty, "%% This command only works for ipaccess BTS%s", + VTY_NEWLINE); + return CMD_WARNING; + } + + if (ts->pchan != GSM_PCHAN_TCH_F_PDCH) { + vty_out(vty, "%% Timeslot %u is not in dynamic TCH_F/PDCH " + "mode%s", ts->nr, VTY_NEWLINE); + return CMD_WARNING; + } + + if (!strcmp(argv[3], "activate")) + activate = 1; + else + activate = 0; + + rsl_ipacc_pdch_activate(ts, activate); + + return CMD_SUCCESS; + +} + +/* determine the logical channel type based on the physical channel type */ +static int lchan_type_by_pchan(enum gsm_phys_chan_config pchan) +{ + switch (pchan) { + case GSM_PCHAN_TCH_F: + return GSM_LCHAN_TCH_F; + case GSM_PCHAN_TCH_H: + return GSM_LCHAN_TCH_H; + case GSM_PCHAN_SDCCH8_SACCH8C: + case GSM_PCHAN_SDCCH8_SACCH8C_CBCH: + case GSM_PCHAN_CCCH_SDCCH4: + case GSM_PCHAN_CCCH_SDCCH4_CBCH: + return GSM_LCHAN_SDCCH; + default: + return -1; + } +} + +/* configure the lchan for a single AMR mode (as specified) */ +static int lchan_set_single_amr_mode(struct gsm_lchan *lchan, uint8_t amr_mode) +{ + struct amr_multirate_conf mr; + struct gsm48_multi_rate_conf *mr_conf; + mr_conf = (struct gsm48_multi_rate_conf *) &mr.gsm48_ie; + + if (amr_mode > 7) + return -1; + + memset(&mr, 0, sizeof(mr)); + mr_conf->ver = 1; + /* bit-mask of supported modes, only one bit is set. Reflects + * Figure 10.5.2.47a where there are no thershold and only a + * single mode */ + mr.gsm48_ie[1] = 1 << amr_mode; + + mr.ms_mode[0].mode = amr_mode; + mr.bts_mode[0].mode = amr_mode; + + /* encode this configuration into the lchan for both uplink and + * downlink direction */ + gsm48_multirate_config(lchan->mr_ms_lv, &mr, mr.ms_mode); + gsm48_multirate_config(lchan->mr_bts_lv, &mr, mr.bts_mode); + + return 0; +} + +/* Debug/Measurement command to activate a given logical channel + * manually in a given mode/codec. This is useful for receiver + * performance testing (FER/RBER/...) */ +DEFUN(lchan_act, lchan_act_cmd, + "bts <0-255> trx <0-255> timeslot <0-7> sub-slot <0-7> (activate|deactivate) (hr|fr|efr|amr) [<0-7>]", + BTS_NR_TRX_TS_SS_STR2 + "Manual Channel Activation (e.g. for BER test)\n" + "Manual Channel Deactivation (e.g. for BER test)\n" + "Half-Rate v1\n" "Full-Rate\n" "Enhanced Full Rate\n" "Adaptive Multi-Rate\n" "AMR Mode\n") +{ + struct gsm_bts_trx_ts *ts; + struct gsm_lchan *lchan; + int ss_nr = atoi(argv[3]); + const char *act_str = argv[4]; + const char *codec_str = argv[5]; + int activate; + + ts = vty_get_ts(vty, argv[0], argv[1], argv[2]); + if (!ts) + return CMD_WARNING; + + lchan = &ts->lchan[ss_nr]; + + if (!strcmp(act_str, "activate")) + activate = 1; + else + activate = 0; + + if (ss_nr >= ts_subslots(ts)) { + vty_out(vty, "%% subslot %d >= permitted %d for physical channel %s%s", + ss_nr, ts_subslots(ts), gsm_pchan_name(ts->pchan), VTY_NEWLINE); + return CMD_WARNING; + } + + if (activate) { + int lchan_t; + if (lchan->state != LCHAN_S_NONE) { + vty_out(vty, "%% Cannot activate: Channel busy!%s", VTY_NEWLINE); + return CMD_WARNING; + } + lchan_t = lchan_type_by_pchan(ts->pchan); + if (lchan_t < 0) + return CMD_WARNING; + /* configure the lchan */ + lchan->type = lchan_t; + lchan->rsl_cmode = RSL_CMOD_SPD_SPEECH; + if (!strcmp(codec_str, "hr") || !strcmp(codec_str, "fr")) + lchan->tch_mode = GSM48_CMODE_SPEECH_V1; + else if (!strcmp(codec_str, "efr")) + lchan->tch_mode = GSM48_CMODE_SPEECH_EFR; + else if (!strcmp(codec_str, "amr")) { + int amr_mode; + if (argc < 7) { + vty_out(vty, "%% AMR requires specification of AMR mode%s", VTY_NEWLINE); + return CMD_WARNING; + } + amr_mode = atoi(argv[6]); + lchan->tch_mode = GSM48_CMODE_SPEECH_AMR; + lchan_set_single_amr_mode(lchan, amr_mode); + } + vty_out(vty, "%% activating lchan %s%s", gsm_lchan_name(lchan), VTY_NEWLINE); + rsl_chan_activate_lchan(lchan, RSL_ACT_TYPE_INITIAL, 0); + rsl_ipacc_crcx(lchan); + } else { + rsl_direct_rf_release(lchan); + } + + return CMD_SUCCESS; +} + +DEFUN(lchan_mdcx, lchan_mdcx_cmd, + "bts <0-255> trx <0-255> timeslot <0-7> sub-slot <0-7> mdcx A.B.C.D <0-65535>", + BTS_NR_TRX_TS_SS_STR2 + "Modify RTP Connection\n" "MGW IP Address\n" "MGW UDP Port\n") +{ + struct gsm_bts_trx_ts *ts; + struct gsm_lchan *lchan; + int ss_nr = atoi(argv[3]); + int port = atoi(argv[5]); + struct in_addr ia; + inet_aton(argv[4], &ia); + + ts = vty_get_ts(vty, argv[0], argv[1], argv[2]); + if (!ts) + return CMD_WARNING; + + lchan = &ts->lchan[ss_nr]; + + if (ss_nr >= ts_subslots(ts)) { + vty_out(vty, "%% subslot %d >= permitted %d for physical channel %s%s", + ss_nr, ts_subslots(ts), gsm_pchan_name(ts->pchan), VTY_NEWLINE); + return CMD_WARNING; + } + + vty_out(vty, "%% connecting RTP of %s to %s:%u%s", gsm_lchan_name(lchan), + inet_ntoa(ia), port, VTY_NEWLINE); + rsl_ipacc_mdcx(lchan, ntohl(ia.s_addr), port, 0); + return CMD_SUCCESS; +} + +DEFUN(ctrl_trap, ctrl_trap_cmd, + "ctrl-interface generate-trap TRAP VALUE", + "Commands related to the CTRL Interface\n" + "Generate a TRAP for test purpose\n" + "Identity/Name of the TRAP variable\n" + "Value of the TRAP variable\n") +{ + struct gsm_network *net = gsmnet_from_vty(vty); + + ctrl_cmd_send_trap(net->ctrl, argv[0], (char *) argv[1]); + return CMD_SUCCESS; +} + +#define NETWORK_STR "Configure the GSM network\n" +#define CODE_CMD_STR "Code commands\n" +#define NAME_CMD_STR "Name Commands\n" +#define NAME_STR "Name to use\n" + +DEFUN(cfg_net, + cfg_net_cmd, + "network", NETWORK_STR) +{ + vty->index = gsmnet_from_vty(vty); + vty->node = GSMNET_NODE; + + return CMD_SUCCESS; +} + +DEFUN(cfg_net_ncc, + cfg_net_ncc_cmd, + "network country code <1-999>", + "Set the GSM network country code\n" + "Country commands\n" + CODE_CMD_STR + "Network Country Code to use\n") +{ + struct gsm_network *gsmnet = gsmnet_from_vty(vty); + uint16_t mcc; + + if (osmo_mcc_from_str(argv[0], &mcc)) { + vty_out(vty, "%% Error decoding MCC: %s%s", argv[0], VTY_NEWLINE); + return CMD_WARNING; + } + + gsmnet->plmn.mcc = mcc; + + return CMD_SUCCESS; +} + +DEFUN(cfg_net_mnc, + cfg_net_mnc_cmd, + "mobile network code <0-999>", + "Set the GSM mobile network code\n" + "Network Commands\n" + CODE_CMD_STR + "Mobile Network Code to use\n") +{ + struct gsm_network *gsmnet = gsmnet_from_vty(vty); + uint16_t mnc; + bool mnc_3_digits; + + if (osmo_mnc_from_str(argv[0], &mnc, &mnc_3_digits)) { + vty_out(vty, "%% Error decoding MNC: %s%s", argv[0], VTY_NEWLINE); + return CMD_WARNING; + } + + gsmnet->plmn.mnc = mnc; + gsmnet->plmn.mnc_3_digits = mnc_3_digits; + + return CMD_SUCCESS; +} + +DEFUN(cfg_net_encryption, + cfg_net_encryption_cmd, + "encryption a5 <0-3> [<0-3>] [<0-3>] [<0-3>]", + "Encryption options\n" + "GSM A5 Air Interface Encryption\n" + "A5/n Algorithm Number\n" + "A5/n Algorithm Number\n" + "A5/n Algorithm Number\n" + "A5/n Algorithm Number\n") +{ + struct gsm_network *gsmnet = gsmnet_from_vty(vty); + unsigned int i; + + gsmnet->a5_encryption_mask = 0; + for (i = 0; i < argc; i++) + gsmnet->a5_encryption_mask |= (1 << atoi(argv[i])); + + return CMD_SUCCESS; +} + +DEFUN_DEPRECATED(cfg_net_dyn_ts_allow_tch_f, + cfg_net_dyn_ts_allow_tch_f_cmd, + "dyn_ts_allow_tch_f (0|1)", + "Allow or disallow allocating TCH/F on TCH_F_TCH_H_PDCH timeslots\n" + "Disallow TCH/F on TCH_F_TCH_H_PDCH (default)\n" + "Allow TCH/F on TCH_F_TCH_H_PDCH\n") +{ + struct gsm_network *gsmnet = gsmnet_from_vty(vty); + gsmnet->dyn_ts_allow_tch_f = atoi(argv[0]) ? true : false; + vty_out(vty, "%% dyn_ts_allow_tch_f is deprecated, rather use msc/codec-list to pick codecs%s", + VTY_NEWLINE); + return CMD_SUCCESS; +} + +DEFUN(cfg_net_timezone, + cfg_net_timezone_cmd, + "timezone <-19-19> (0|15|30|45)", + "Set the Timezone Offset of the network\n" + "Timezone offset (hours)\n" + "Timezone offset (00 minutes)\n" + "Timezone offset (15 minutes)\n" + "Timezone offset (30 minutes)\n" + "Timezone offset (45 minutes)\n" + ) +{ + struct gsm_network *net = vty->index; + int tzhr = atoi(argv[0]); + int tzmn = atoi(argv[1]); + + net->tz.hr = tzhr; + net->tz.mn = tzmn; + net->tz.dst = 0; + net->tz.override = 1; + + return CMD_SUCCESS; +} + +DEFUN(cfg_net_timezone_dst, + cfg_net_timezone_dst_cmd, + "timezone <-19-19> (0|15|30|45) <0-2>", + "Set the Timezone Offset of the network\n" + "Timezone offset (hours)\n" + "Timezone offset (00 minutes)\n" + "Timezone offset (15 minutes)\n" + "Timezone offset (30 minutes)\n" + "Timezone offset (45 minutes)\n" + "DST offset (hours)\n" + ) +{ + struct gsm_network *net = vty->index; + int tzhr = atoi(argv[0]); + int tzmn = atoi(argv[1]); + int tzdst = atoi(argv[2]); + + net->tz.hr = tzhr; + net->tz.mn = tzmn; + net->tz.dst = tzdst; + net->tz.override = 1; + + return CMD_SUCCESS; +} + +DEFUN(cfg_net_no_timezone, + cfg_net_no_timezone_cmd, + "no timezone", + NO_STR + "Disable network timezone override, use system tz\n") +{ + struct gsm_network *net = vty->index; + + net->tz.override = 0; + + return CMD_SUCCESS; +} + +DEFUN(cfg_net_per_loc_upd, cfg_net_per_loc_upd_cmd, + "periodic location update <6-1530>", + "Periodic Location Updating Interval\n" + "Periodic Location Updating Interval\n" + "Periodic Location Updating Interval\n" + "Periodic Location Updating Interval in Minutes\n") +{ + struct gsm_network *net = vty->index; + + net->t3212 = atoi(argv[0]) / 6; + + return CMD_SUCCESS; +} + +DEFUN(cfg_net_no_per_loc_upd, cfg_net_no_per_loc_upd_cmd, + "no periodic location update", + NO_STR + "Periodic Location Updating Interval\n" + "Periodic Location Updating Interval\n" + "Periodic Location Updating Interval\n") +{ + struct gsm_network *net = vty->index; + + net->t3212 = 0; + + return CMD_SUCCESS; +} + +#define MEAS_FEED_STR "Measurement Report export\n" + +DEFUN(cfg_net_meas_feed_dest, cfg_net_meas_feed_dest_cmd, + "meas-feed destination ADDR <0-65535>", + MEAS_FEED_STR "Where to forward Measurement Report feeds\n" "address or hostname\n" "port number\n") +{ + int rc; + const char *host = argv[0]; + uint16_t port = atoi(argv[1]); + + rc = meas_feed_cfg_set(host, port); + if (rc < 0) + return CMD_WARNING; + + return CMD_SUCCESS; +} + +DEFUN(cfg_net_meas_feed_scenario, cfg_net_meas_feed_scenario_cmd, + "meas-feed scenario NAME", + MEAS_FEED_STR "Set a name to include in the Measurement Report feeds\n" "Name string, up to 31 characters\n") +{ + meas_feed_scenario_set(argv[0]); + + return CMD_SUCCESS; +} + +extern int bsc_vty_init_extra(void); + +int bsc_vty_init(struct gsm_network *network) +{ + cfg_ts_pchan_cmd.string = + vty_cmd_string_from_valstr(tall_bsc_ctx, + gsm_pchant_names, + "phys_chan_config (", "|", ")", + VTY_DO_LOWER); + cfg_ts_pchan_cmd.doc = + vty_cmd_string_from_valstr(tall_bsc_ctx, + gsm_pchant_descs, + "Physical Channel Combination\n", + "\n", "", 0); + + cfg_bts_type_cmd.string = + vty_cmd_string_from_valstr(tall_bsc_ctx, + bts_type_names, + "type (", "|", ")", + VTY_DO_LOWER); + cfg_bts_type_cmd.doc = + vty_cmd_string_from_valstr(tall_bsc_ctx, + bts_type_descs, + "BTS Vendor/Type\n", + "\n", "", 0); + + OSMO_ASSERT(vty_global_gsm_network == NULL); + vty_global_gsm_network = network; + + osmo_stats_vty_add_cmds(); + + install_element(CONFIG_NODE, &cfg_net_cmd); + install_node(&net_node, config_write_net); + install_element(GSMNET_NODE, &cfg_net_ncc_cmd); + install_element(GSMNET_NODE, &cfg_net_mnc_cmd); + install_element(GSMNET_NODE, &cfg_net_encryption_cmd); + install_element(GSMNET_NODE, &cfg_net_timezone_cmd); + install_element(GSMNET_NODE, &cfg_net_timezone_dst_cmd); + install_element(GSMNET_NODE, &cfg_net_no_timezone_cmd); + install_element(GSMNET_NODE, &cfg_net_per_loc_upd_cmd); + install_element(GSMNET_NODE, &cfg_net_no_per_loc_upd_cmd); + install_element(GSMNET_NODE, &cfg_net_dyn_ts_allow_tch_f_cmd); + install_element(GSMNET_NODE, &cfg_net_meas_feed_dest_cmd); + install_element(GSMNET_NODE, &cfg_net_meas_feed_scenario_cmd); + + install_element_ve(&bsc_show_net_cmd); + install_element_ve(&show_bts_cmd); + install_element_ve(&show_trx_cmd); + install_element_ve(&show_ts_cmd); + install_element_ve(&show_lchan_cmd); + install_element_ve(&show_lchan_summary_cmd); + + install_element_ve(&show_subscr_conn_cmd); + install_element_ve(&handover_any_cmd); + install_element_ve(&assignment_any_cmd); + + install_element_ve(&show_paging_cmd); + install_element_ve(&show_paging_group_cmd); + + logging_vty_add_cmds(NULL); + osmo_talloc_vty_add_cmds(); + + install_element(GSMNET_NODE, &cfg_net_neci_cmd); + install_element(GSMNET_NODE, &cfg_net_T3101_cmd); + install_element(GSMNET_NODE, &cfg_net_T3103_cmd); + install_element(GSMNET_NODE, &cfg_net_T3105_cmd); + install_element(GSMNET_NODE, &cfg_net_T3107_cmd); + install_element(GSMNET_NODE, &cfg_net_T3109_cmd); + install_element(GSMNET_NODE, &cfg_net_T3111_cmd); + install_element(GSMNET_NODE, &cfg_net_T3113_cmd); + install_element(GSMNET_NODE, &cfg_net_T3115_cmd); + install_element(GSMNET_NODE, &cfg_net_T3117_cmd); + install_element(GSMNET_NODE, &cfg_net_T3119_cmd); + install_element(GSMNET_NODE, &cfg_net_T3122_cmd); + install_element(GSMNET_NODE, &cfg_net_T3141_cmd); + install_element(GSMNET_NODE, &cfg_net_dtx_cmd); + install_element(GSMNET_NODE, &cfg_net_pag_any_tch_cmd); + /* See also handover commands added on net level from handover_vty.c */ + + install_element(GSMNET_NODE, &cfg_bts_cmd); + install_node(&bts_node, config_write_bts); + install_element(BTS_NODE, &cfg_bts_type_cmd); + install_element(BTS_NODE, &cfg_description_cmd); + install_element(BTS_NODE, &cfg_no_description_cmd); + install_element(BTS_NODE, &cfg_bts_band_cmd); + install_element(BTS_NODE, &cfg_bts_ci_cmd); + install_element(BTS_NODE, &cfg_bts_dtxu_cmd); + install_element(BTS_NODE, &cfg_bts_dtxd_cmd); + install_element(BTS_NODE, &cfg_bts_no_dtxu_cmd); + install_element(BTS_NODE, &cfg_bts_no_dtxd_cmd); + install_element(BTS_NODE, &cfg_bts_lac_cmd); + install_element(BTS_NODE, &cfg_bts_tsc_cmd); + install_element(BTS_NODE, &cfg_bts_bsic_cmd); + install_element(BTS_NODE, &cfg_bts_unit_id_cmd); + install_element(BTS_NODE, &cfg_bts_rsl_ip_cmd); + install_element(BTS_NODE, &cfg_bts_nokia_site_skip_reset_cmd); + install_element(BTS_NODE, &cfg_bts_nokia_site_no_loc_rel_cnf_cmd); + install_element(BTS_NODE, &cfg_bts_nokia_site_bts_reset_timer_cnf_cmd); + install_element(BTS_NODE, &cfg_bts_stream_id_cmd); + install_element(BTS_NODE, &cfg_bts_oml_e1_cmd); + install_element(BTS_NODE, &cfg_bts_oml_e1_tei_cmd); + install_element(BTS_NODE, &cfg_bts_challoc_cmd); + install_element(BTS_NODE, &cfg_bts_rach_tx_integer_cmd); + install_element(BTS_NODE, &cfg_bts_rach_max_trans_cmd); + install_element(BTS_NODE, &cfg_bts_chan_desc_att_cmd); + install_element(BTS_NODE, &cfg_bts_chan_desc_bs_pa_mfrms_cmd); + install_element(BTS_NODE, &cfg_bts_chan_desc_bs_ag_blks_res_cmd); + install_element(BTS_NODE, &cfg_bts_rach_nm_b_thresh_cmd); + install_element(BTS_NODE, &cfg_bts_rach_nm_ldavg_cmd); + install_element(BTS_NODE, &cfg_bts_cell_barred_cmd); + install_element(BTS_NODE, &cfg_bts_rach_ec_allowed_cmd); + install_element(BTS_NODE, &cfg_bts_rach_ac_class_cmd); + install_element(BTS_NODE, &cfg_bts_ms_max_power_cmd); + install_element(BTS_NODE, &cfg_bts_cell_resel_hyst_cmd); + install_element(BTS_NODE, &cfg_bts_rxlev_acc_min_cmd); + install_element(BTS_NODE, &cfg_bts_cell_bar_qualify_cmd); + install_element(BTS_NODE, &cfg_bts_cell_resel_ofs_cmd); + install_element(BTS_NODE, &cfg_bts_temp_ofs_cmd); + install_element(BTS_NODE, &cfg_bts_temp_ofs_inf_cmd); + install_element(BTS_NODE, &cfg_bts_penalty_time_cmd); + install_element(BTS_NODE, &cfg_bts_penalty_time_rsvd_cmd); + install_element(BTS_NODE, &cfg_bts_radio_link_timeout_cmd); + install_element(BTS_NODE, &cfg_bts_radio_link_timeout_inf_cmd); + install_element(BTS_NODE, &cfg_bts_gprs_mode_cmd); + install_element(BTS_NODE, &cfg_bts_gprs_11bit_rach_support_for_egprs_cmd); + install_element(BTS_NODE, &cfg_bts_gprs_ns_timer_cmd); + install_element(BTS_NODE, &cfg_bts_gprs_rac_cmd); + install_element(BTS_NODE, &cfg_bts_gprs_net_ctrl_ord_cmd); + install_element(BTS_NODE, &cfg_bts_gprs_ctrl_ack_cmd); + install_element(BTS_NODE, &cfg_no_bts_gprs_ctrl_ack_cmd); + install_element(BTS_NODE, &cfg_bts_gprs_bvci_cmd); + install_element(BTS_NODE, &cfg_bts_gprs_cell_timer_cmd); + install_element(BTS_NODE, &cfg_bts_gprs_nsei_cmd); + install_element(BTS_NODE, &cfg_bts_gprs_nsvci_cmd); + install_element(BTS_NODE, &cfg_bts_gprs_nsvc_lport_cmd); + install_element(BTS_NODE, &cfg_bts_gprs_nsvc_rport_cmd); + install_element(BTS_NODE, &cfg_bts_gprs_nsvc_rip_cmd); + install_element(BTS_NODE, &cfg_bts_pag_free_cmd); + install_element(BTS_NODE, &cfg_bts_si_mode_cmd); + install_element(BTS_NODE, &cfg_bts_si_static_cmd); + install_element(BTS_NODE, &cfg_bts_early_cm_cmd); + install_element(BTS_NODE, &cfg_bts_early_cm_3g_cmd); + install_element(BTS_NODE, &cfg_bts_neigh_mode_cmd); + install_element(BTS_NODE, &cfg_bts_neigh_cmd); + install_element(BTS_NODE, &cfg_bts_si5_neigh_cmd); + install_element(BTS_NODE, &cfg_bts_si2quater_neigh_add_cmd); + install_element(BTS_NODE, &cfg_bts_si2quater_neigh_del_cmd); + install_element(BTS_NODE, &cfg_bts_si2quater_uarfcn_add_cmd); + install_element(BTS_NODE, &cfg_bts_si2quater_uarfcn_del_cmd); + install_element(BTS_NODE, &cfg_bts_excl_rf_lock_cmd); + install_element(BTS_NODE, &cfg_bts_no_excl_rf_lock_cmd); + install_element(BTS_NODE, &cfg_bts_force_comb_si_cmd); + install_element(BTS_NODE, &cfg_bts_no_force_comb_si_cmd); + install_element(BTS_NODE, &cfg_bts_codec0_cmd); + install_element(BTS_NODE, &cfg_bts_codec1_cmd); + install_element(BTS_NODE, &cfg_bts_codec2_cmd); + install_element(BTS_NODE, &cfg_bts_codec3_cmd); + install_element(BTS_NODE, &cfg_bts_codec4_cmd); + install_element(BTS_NODE, &cfg_bts_depends_on_cmd); + install_element(BTS_NODE, &cfg_bts_no_depends_on_cmd); + install_element(BTS_NODE, &cfg_bts_amr_fr_modes1_cmd); + install_element(BTS_NODE, &cfg_bts_amr_fr_modes2_cmd); + install_element(BTS_NODE, &cfg_bts_amr_fr_modes3_cmd); + install_element(BTS_NODE, &cfg_bts_amr_fr_modes4_cmd); + install_element(BTS_NODE, &cfg_bts_amr_fr_thres1_cmd); + install_element(BTS_NODE, &cfg_bts_amr_fr_thres2_cmd); + install_element(BTS_NODE, &cfg_bts_amr_fr_thres3_cmd); + install_element(BTS_NODE, &cfg_bts_amr_fr_hyst1_cmd); + install_element(BTS_NODE, &cfg_bts_amr_fr_hyst2_cmd); + install_element(BTS_NODE, &cfg_bts_amr_fr_hyst3_cmd); + install_element(BTS_NODE, &cfg_bts_amr_fr_start_mode_cmd); + install_element(BTS_NODE, &cfg_bts_amr_hr_modes1_cmd); + install_element(BTS_NODE, &cfg_bts_amr_hr_modes2_cmd); + install_element(BTS_NODE, &cfg_bts_amr_hr_modes3_cmd); + install_element(BTS_NODE, &cfg_bts_amr_hr_modes4_cmd); + install_element(BTS_NODE, &cfg_bts_amr_hr_thres1_cmd); + install_element(BTS_NODE, &cfg_bts_amr_hr_thres2_cmd); + install_element(BTS_NODE, &cfg_bts_amr_hr_thres3_cmd); + install_element(BTS_NODE, &cfg_bts_amr_hr_hyst1_cmd); + install_element(BTS_NODE, &cfg_bts_amr_hr_hyst2_cmd); + install_element(BTS_NODE, &cfg_bts_amr_hr_hyst3_cmd); + install_element(BTS_NODE, &cfg_bts_amr_hr_start_mode_cmd); + install_element(BTS_NODE, &cfg_bts_pcu_sock_cmd); + install_element(BTS_NODE, &cfg_bts_acc_ramping_cmd); + install_element(BTS_NODE, &cfg_bts_no_acc_ramping_cmd); + install_element(BTS_NODE, &cfg_bts_acc_ramping_step_interval_cmd); + install_element(BTS_NODE, &cfg_bts_acc_ramping_step_size_cmd); + /* See also handover commands added on bts level from handover_vty.c */ + + install_element(BTS_NODE, &cfg_trx_cmd); + install_node(&trx_node, dummy_config_write); + install_element(TRX_NODE, &cfg_trx_arfcn_cmd); + install_element(TRX_NODE, &cfg_description_cmd); + install_element(TRX_NODE, &cfg_no_description_cmd); + install_element(TRX_NODE, &cfg_trx_nominal_power_cmd); + install_element(TRX_NODE, &cfg_trx_max_power_red_cmd); + install_element(TRX_NODE, &cfg_trx_rsl_e1_cmd); + install_element(TRX_NODE, &cfg_trx_rsl_e1_tei_cmd); + install_element(TRX_NODE, &cfg_trx_rf_locked_cmd); + + install_element(TRX_NODE, &cfg_ts_cmd); + install_node(&ts_node, dummy_config_write); + install_element(TS_NODE, &cfg_ts_pchan_cmd); + install_element(TS_NODE, &cfg_ts_pchan_compat_cmd); + install_element(TS_NODE, &cfg_ts_tsc_cmd); + install_element(TS_NODE, &cfg_ts_hopping_cmd); + install_element(TS_NODE, &cfg_ts_hsn_cmd); + install_element(TS_NODE, &cfg_ts_maio_cmd); + install_element(TS_NODE, &cfg_ts_arfcn_add_cmd); + install_element(TS_NODE, &cfg_ts_arfcn_del_cmd); + install_element(TS_NODE, &cfg_ts_e1_subslot_cmd); + + install_element(ENABLE_NODE, &drop_bts_cmd); + install_element(ENABLE_NODE, &restart_bts_cmd); + install_element(ENABLE_NODE, &bts_resend_cmd); + install_element(ENABLE_NODE, &pdch_act_cmd); + install_element(ENABLE_NODE, &lchan_act_cmd); + install_element(ENABLE_NODE, &lchan_mdcx_cmd); + install_element(ENABLE_NODE, &handover_subscr_conn_cmd); + install_element(ENABLE_NODE, &assignment_subscr_conn_cmd); + install_element(ENABLE_NODE, &smscb_cmd_cmd); + install_element(ENABLE_NODE, &ctrl_trap_cmd); + + abis_nm_vty_init(); + abis_om2k_vty_init(); + e1inp_vty_init(); + osmo_fsm_vty_add_cmds(); + + ho_vty_init(); + + bsc_vty_init_extra(); + + return 0; +} diff --git a/src/osmo-bsc/bts_ericsson_rbs2000.c b/src/osmo-bsc/bts_ericsson_rbs2000.c new file mode 100644 index 000000000..9c8b90ee2 --- /dev/null +++ b/src/osmo-bsc/bts_ericsson_rbs2000.c @@ -0,0 +1,212 @@ +/* Ericsson RBS-2xxx specific code */ + +/* (C) 2011 by Harald Welte <laforge@gnumonks.org> + * + * 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 <osmocom/gsm/tlv.h> + +#include <osmocom/bsc/debug.h> +#include <osmocom/bsc/gsm_data.h> +#include <osmocom/bsc/abis_om2000.h> +#include <osmocom/bsc/abis_nm.h> +#include <osmocom/abis/e1_input.h> +#include <osmocom/bsc/signal.h> + +#include <osmocom/abis/lapd.h> + +static void bootstrap_om_bts(struct gsm_bts *bts) +{ + LOGP(DNM, LOGL_NOTICE, "bootstrapping OML for BTS %u\n", bts->nr); + + /* FIXME: this is global init, not bootstrapping */ + abis_om2k_bts_init(bts); + abis_om2k_trx_init(bts->c0); + + /* TODO: Should we wait for a Failure report? */ + om2k_bts_fsm_start(bts); +} + +static void bootstrap_om_trx(struct gsm_bts_trx *trx) +{ + LOGP(DNM, LOGL_NOTICE, "bootstrapping OML for TRX %u/%u\n", + trx->bts->nr, trx->nr); + /* FIXME */ +} + +static int shutdown_om(struct gsm_bts *bts) +{ + gsm_bts_mark_all_ts_uninitialized(bts); + + /* FIXME */ + return 0; +} + + +/* Tell LAPD to start start the SAP (send SABM requests) for all signalling + * timeslots in this line */ +static void start_sabm_in_line(struct e1inp_line *line, int start) +{ + struct e1inp_sign_link *link; + int i; + + for (i = 0; i < ARRAY_SIZE(line->ts); i++) { + struct e1inp_ts *ts = &line->ts[i]; + + if (ts->type != E1INP_TS_TYPE_SIGN) + continue; + + llist_for_each_entry(link, &ts->sign.sign_links, list) { + if (!ts->lapd) + continue; + lapd_instance_set_profile(ts->lapd, + &lapd_profile_abis_ericsson); + + if (start) + lapd_sap_start(ts->lapd, link->tei, link->sapi); + else + lapd_sap_stop(ts->lapd, link->tei, link->sapi); + } + } +} + +/* Callback function to be called every time we receive a signal from INPUT */ +static int gbl_sig_cb(unsigned int subsys, unsigned int signal, + void *handler_data, void *signal_data) +{ + struct gsm_bts *bts; + + if (subsys != SS_L_GLOBAL) + return 0; + + switch (signal) { + case S_GLOBAL_BTS_CLOSE_OM: + bts = signal_data; + if (bts->type == GSM_BTS_TYPE_RBS2000) + shutdown_om(signal_data); + break; + } + + return 0; +} + +/* Callback function to be called every time we receive a signal from INPUT */ +static int inp_sig_cb(unsigned int subsys, unsigned int signal, + void *handler_data, void *signal_data) +{ + struct input_signal_data *isd = signal_data; + struct e1inp_ts *e1i_ts; + + if (subsys != SS_L_INPUT) + return 0; + + LOGP(DNM, LOGL_DEBUG, "%s(): Input signal '%s' received\n", __func__, + get_value_string(e1inp_signal_names, signal)); + switch (signal) { + case S_L_INP_TEI_UP: + switch (isd->link_type) { + case E1INP_SIGN_OML: + if (isd->trx->bts->type != GSM_BTS_TYPE_RBS2000) + break; + if (isd->tei == isd->trx->bts->oml_tei) + bootstrap_om_bts(isd->trx->bts); + else + bootstrap_om_trx(isd->trx); + break; + } + break; + case S_L_INP_TEI_DN: + if (isd->trx->bts->type != GSM_BTS_TYPE_RBS2000) + break; + LOGP(DNM, LOGL_NOTICE, "Line-%u TS-%u TEI-%u SAPI-%u: Link " + "Lost for Ericsson RBS2000. Re-starting DL Establishment\n", + isd->line->num, isd->ts_nr, isd->tei, isd->sapi); + /* Some datalink for a given TEI/SAPI went down, try to re-start it */ + e1i_ts = &isd->line->ts[isd->ts_nr-1]; + OSMO_ASSERT(e1i_ts->type == E1INP_TS_TYPE_SIGN); + lapd_sap_start(e1i_ts->lapd, isd->tei, isd->sapi); + break; + case S_L_INP_LINE_INIT: + case S_L_INP_LINE_NOALARM: + if (strcasecmp(isd->line->driver->name, "DAHDI") + && strcasecmp(isd->line->driver->name, "MISDN_LAPD") + && strcasecmp(isd->line->driver->name, "UNIXSOCKET")) + break; + start_sabm_in_line(isd->line, 1); + break; + case S_L_INP_LINE_ALARM: + if (strcasecmp(isd->line->driver->name, "DAHDI") + && strcasecmp(isd->line->driver->name, "MISDN_LAPD") + && strcasecmp(isd->line->driver->name, "UNIXSOCKET")) + break; + start_sabm_in_line(isd->line, 0); + break; + } + + return 0; +} + +static void config_write_bts(struct vty *vty, struct gsm_bts *bts) +{ + abis_om2k_config_write_bts(vty, bts); +} + +static int bts_model_rbs2k_start(struct gsm_network *net); + +static void bts_model_rbs2k_e1line_bind_ops(struct e1inp_line *line) +{ + e1inp_line_bind_ops(line, &bts_isdn_e1inp_line_ops); +} + +static bool bts_model_rbs2k_is_ts_ready(const struct gsm_bts_trx_ts *ts) +{ + return ts && ts->mo.nm_state.operational == NM_OPSTATE_ENABLED; +} + +static struct gsm_bts_model model_rbs2k = { + .type = GSM_BTS_TYPE_RBS2000, + .name = "rbs2000", + .start = bts_model_rbs2k_start, + .oml_rcvmsg = &abis_om2k_rcvmsg, + .oml_is_ts_ready = bts_model_rbs2k_is_ts_ready, + .config_write_bts = &config_write_bts, + .e1line_bind_ops = &bts_model_rbs2k_e1line_bind_ops, +}; + +static int bts_model_rbs2k_start(struct gsm_network *net) +{ + model_rbs2k.features.data = &model_rbs2k._features_data[0]; + model_rbs2k.features.data_len = sizeof(model_rbs2k._features_data); + + osmo_bts_set_feature(&model_rbs2k.features, BTS_FEAT_GPRS); + osmo_bts_set_feature(&model_rbs2k.features, BTS_FEAT_EGPRS); + osmo_bts_set_feature(&model_rbs2k.features, BTS_FEAT_HOPPING); + osmo_bts_set_feature(&model_rbs2k.features, BTS_FEAT_HSCSD); + osmo_bts_set_feature(&model_rbs2k.features, BTS_FEAT_MULTI_TSC); + + osmo_signal_register_handler(SS_L_INPUT, inp_sig_cb, NULL); + osmo_signal_register_handler(SS_L_GLOBAL, gbl_sig_cb, NULL); + + return 0; +} + +int bts_model_rbs2k_init(void) +{ + return gsm_bts_model_register(&model_rbs2k); +} diff --git a/src/osmo-bsc/bts_init.c b/src/osmo-bsc/bts_init.c new file mode 100644 index 000000000..18f1ed4c8 --- /dev/null +++ b/src/osmo-bsc/bts_init.c @@ -0,0 +1,30 @@ +/* (C) 2011 by Harald Welte <laforge@gnumonks.org> + * + * 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 <osmocom/bsc/bss.h> + +int bts_init(void) +{ + bts_model_bs11_init(); + bts_model_rbs2k_init(); + bts_model_nanobts_init(); + bts_model_nokia_site_init(); + bts_model_sysmobts_init(); + /* Your new BTS here. */ + return 0; +} diff --git a/src/osmo-bsc/bts_ipaccess_nanobts.c b/src/osmo-bsc/bts_ipaccess_nanobts.c new file mode 100644 index 000000000..843f264ae --- /dev/null +++ b/src/osmo-bsc/bts_ipaccess_nanobts.c @@ -0,0 +1,591 @@ +/* ip.access nanoBTS specific code */ + +/* (C) 2009-2018 by Harald Welte <laforge@gnumonks.org> + * + * 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/talloc.h> +#include <osmocom/bsc/gsm_data.h> +#include <osmocom/bsc/abis_nm.h> +#include <osmocom/bsc/abis_rsl.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> + +static int bts_model_nanobts_start(struct gsm_network *net); +static void bts_model_nanobts_e1line_bind_ops(struct e1inp_line *line); + +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"; +} + +static bool oml_is_ts_ready(const struct gsm_bts_trx_ts *ts) +{ + return ts && ts->mo.nm_state.operational == NM_OPSTATE_ENABLED; +} + +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, + .oml_is_ts_ready = oml_is_ts_ready, + .e1line_bind_ops = bts_model_nanobts_e1line_bind_ops, + .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 }, + }, + }, +}; + + +/* 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_nm_state *new_state = nsd->new_state; + + struct gsm_bts *bts; + struct gsm_bts_trx *trx; + struct gsm_bts_trx_ts *ts; + struct gsm_bts_gprs_nsvc *nsvc; + + struct msgb *msgb; + + if (!is_ipaccess_bts(nsd->bts)) + return 0; + + /* This event-driven BTS setup is currently only required on nanoBTS */ + + /* S_NM_STATECHG_ADM is called after we call chg_adm_state() and would create + * endless loop */ + if (evt != S_NM_STATECHG_OPER) + return 0; + + switch (obj_class) { + case NM_OC_SITE_MANAGER: + bts = container_of(obj, struct gsm_bts, site_mgr); + if ((new_state->operational == NM_OPSTATE_ENABLED && + new_state->availability == NM_AVSTATE_OK) || + (new_state->operational == NM_OPSTATE_DISABLED && + new_state->availability == NM_AVSTATE_OFF_LINE)) + abis_nm_opstart(bts, obj_class, 0xff, 0xff, 0xff); + break; + case NM_OC_BTS: + bts = obj; + if (new_state->availability == NM_AVSTATE_DEPENDENCY) { + msgb = nanobts_attr_bts_get(bts); + abis_nm_set_bts_attr(bts, msgb->data, msgb->len); + msgb_free(msgb); + abis_nm_chg_adm_state(bts, obj_class, + bts->bts_nr, 0xff, 0xff, + NM_STATE_UNLOCKED); + abis_nm_opstart(bts, obj_class, + bts->bts_nr, 0xff, 0xff); + } + break; + case NM_OC_CHANNEL: + ts = obj; + trx = ts->trx; + if (new_state->operational == NM_OPSTATE_DISABLED && + new_state->availability == NM_AVSTATE_DEPENDENCY) { + enum abis_nm_chan_comb ccomb = + abis_nm_chcomb4pchan(ts->pchan); + if (abis_nm_set_channel_attr(ts, ccomb) == -EINVAL) { + ipaccess_drop_oml(trx->bts); + return -1; + } + abis_nm_chg_adm_state(trx->bts, obj_class, + trx->bts->bts_nr, trx->nr, ts->nr, + NM_STATE_UNLOCKED); + abis_nm_opstart(trx->bts, obj_class, + trx->bts->bts_nr, trx->nr, ts->nr); + } + break; + case NM_OC_RADIO_CARRIER: + trx = obj; + if (new_state->operational == NM_OPSTATE_DISABLED && + new_state->availability == NM_AVSTATE_OK) + abis_nm_opstart(trx->bts, obj_class, trx->bts->bts_nr, + trx->nr, 0xff); + break; + case NM_OC_GPRS_NSE: + bts = container_of(obj, struct gsm_bts, gprs.nse); + if (bts->gprs.mode == BTS_GPRS_NONE) + break; + if (new_state->availability == NM_AVSTATE_DEPENDENCY) { + msgb = nanobts_attr_nse_get(bts); + abis_nm_ipaccess_set_attr(bts, obj_class, bts->bts_nr, + 0xff, 0xff, msgb->data, + msgb->len); + msgb_free(msgb); + abis_nm_opstart(bts, obj_class, bts->bts_nr, + 0xff, 0xff); + } + break; + case NM_OC_GPRS_CELL: + bts = container_of(obj, struct gsm_bts, gprs.cell); + if (bts->gprs.mode == BTS_GPRS_NONE) + break; + if (new_state->availability == NM_AVSTATE_DEPENDENCY) { + msgb = nanobts_attr_cell_get(bts); + abis_nm_ipaccess_set_attr(bts, obj_class, bts->bts_nr, + 0, 0xff, msgb->data, + msgb->len); + msgb_free(msgb); + abis_nm_opstart(bts, obj_class, bts->bts_nr, + 0, 0xff); + abis_nm_chg_adm_state(bts, obj_class, bts->bts_nr, + 0, 0xff, NM_STATE_UNLOCKED); + abis_nm_chg_adm_state(bts, NM_OC_GPRS_NSE, bts->bts_nr, + 0xff, 0xff, NM_STATE_UNLOCKED); + } + break; + case NM_OC_GPRS_NSVC: + nsvc = obj; + bts = nsvc->bts; + if (bts->gprs.mode == BTS_GPRS_NONE) + break; + /* We skip NSVC1 since we only use NSVC0 */ + if (nsvc->id == 1) + break; + if ((new_state->availability == NM_AVSTATE_OFF_LINE) || + (new_state->availability == NM_AVSTATE_DEPENDENCY)) { + msgb = nanobts_attr_nscv_get(bts); + abis_nm_ipaccess_set_attr(bts, obj_class, bts->bts_nr, + nsvc->id, 0xff, + msgb->data, msgb->len); + msgb_free(msgb); + abis_nm_opstart(bts, obj_class, bts->bts_nr, + nsvc->id, 0xff); + abis_nm_chg_adm_state(bts, obj_class, bts->bts_nr, + nsvc->id, 0xff, + NM_STATE_UNLOCKED); + } + 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) +{ + 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_bts_trx *trx = gsm_bts_trx_num(bts, foh->obj_inst.trx_nr); + + if (!trx) + return -EINVAL; + + if (!is_ipaccess_bts(trx->bts)) + return 0; + + switch (foh->obj_class) { + case NM_OC_BASEB_TRANSC: + abis_nm_chg_adm_state(trx->bts, foh->obj_class, + trx->bts->bts_nr, trx->nr, 0xff, + NM_STATE_UNLOCKED); + abis_nm_opstart(trx->bts, foh->obj_class, + trx->bts->bts_nr, trx->nr, 0xff); + /* TRX software is active, tell it to initiate RSL Link */ + abis_nm_ipaccess_rsl_connect(trx, trx->bts->ip_access.rsl_ip, + 3003, trx->rsl_tei); + break; + case NM_OC_RADIO_CARRIER: { + /* + * Locking the radio carrier will make it go + * offline again and we would come here. The + * framework should determine that there was + * no change and avoid recursion. + * + * This code is here to make sure that on start + * a TRX remains locked. + */ + int rc_state = trx->mo.nm_state.administrative; + /* Patch ARFCN into radio attribute */ + struct msgb *msgb = nanobts_attr_radio_get(trx->bts, trx); + abis_nm_set_radio_attr(trx, msgb->data, msgb->len); + msgb_free(msgb); + abis_nm_chg_adm_state(trx->bts, foh->obj_class, + trx->bts->bts_nr, trx->nr, 0xff, + rc_state); + abis_nm_opstart(trx->bts, foh->obj_class, trx->bts->bts_nr, + trx->nr, 0xff); + break; + } + } + return 0; +} + +static struct gsm_bts_trx_ts *gsm_bts_trx_ts(struct gsm_network *net, + int bts_nr, int trx_nr, int ts_nr) +{ + struct gsm_bts *bts; + struct gsm_bts_trx *trx; + bts = gsm_bts_num(net, bts_nr); + if (!bts) + return NULL; + trx = gsm_bts_trx_by_nr(bts, trx_nr); + if (!trx) + return NULL; + if (ts_nr < 0 || ts_nr > ARRAY_SIZE(trx->ts)) + return NULL; + return &trx->ts[ts_nr]; +} + +static void nm_rx_opstart_ack_chan(struct abis_om_fom_hdr *foh) +{ + struct gsm_bts_trx_ts *ts; + ts = gsm_bts_trx_ts(bsc_gsmnet, foh->obj_inst.bts_nr, foh->obj_inst.trx_nr, foh->obj_inst.ts_nr); + if (!ts) { + LOGP(DNM, LOGL_ERROR, "%s Channel OPSTART ACK for non-existent TS\n", + abis_nm_dump_foh(foh)); + return; + } + + gsm_ts_check_init(ts); +} + +static void nm_rx_opstart_ack(struct abis_om_fom_hdr *foh) +{ + switch (foh->obj_class) { + case NM_OC_CHANNEL: + nm_rx_opstart_ack_chan(foh); + 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_OPER: + case S_NM_STATECHG_ADM: + return nm_statechg_event(signal, signal_data); + case S_NM_OPSTART_ACK: + nm_rx_opstart_ack(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_ipaccess_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) +{ + if (!trx->rsl_link) + return; + + LOGP(DLINP, LOGL_NOTICE, "(bts=%d,trx=%d) Dropping RSL link.\n", trx->bts->nr, trx->nr); + e1inp_sign_link_destroy(trx->rsl_link); + trx->rsl_link = NULL; + + if (trx->bts->c0 == trx) + paging_flush_bts(trx->bts, NULL); +} + +void ipaccess_drop_oml(struct gsm_bts *bts) +{ + struct gsm_bts *rdep_bts; + struct gsm_bts_trx *trx; + + if (!bts->oml_link) + return; + + LOGP(DLINP, LOGL_NOTICE, "(bts=%d) Dropping OML link.\n", bts->nr); + e1inp_sign_link_destroy(bts->oml_link); + bts->oml_link = NULL; + bts->uptime = 0; + + /* we have issues reconnecting RSL, drop everything. */ + llist_for_each_entry(trx, &bts->trx_list, list) + ipaccess_drop_rsl(trx); + + gsm_bts_mark_all_ts_uninitialized(bts); + + bts->ip_access.flags = 0; + + /* + * 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); + } +} + +/* 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; + + bts = find_bts_by_unitid(bsc_gsmnet, dev->site_id, dev->bts_id); + if (!bts) { + LOGP(DLINP, LOGL_ERROR, "Unable to find BTS configuration for " + " %u/%u/%u, disconnecting\n", dev->site_id, + dev->bts_id, dev->trx_id); + rate_ctr_inc(&bsc_gsmnet->bsc_ctrs->ctr[BSC_CTR_UNKNOWN_UNIT_ID]); + return NULL; + } + DEBUGP(DLINP, "Identified BTS %u/%u/%u\n", + dev->site_id, dev->bts_id, dev->trx_id); + + switch(type) { + case E1INP_SIGN_OML: + /* remove old OML signal link for this BTS. */ + ipaccess_drop_oml(bts); + + 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(&line->ts[E1INP_SIGN_OML - 1], + E1INP_SIGN_OML, bts->c0, + bts->oml_tei, 0); + rc = clock_gettime(CLOCK_MONOTONIC, &tp); + bts->uptime = (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; + } + 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); + + /* set new RSL link for this TRX. */ + line = bts->oml_link->ts->line; + ts = &line->ts[E1INP_SIGN_RSL + dev->trx_id - 1]; + e1inp_ts_config_sign(ts, line); + sign_link = trx->rsl_link = + e1inp_sign_link_create(ts, E1INP_SIGN_RSL, + trx, trx->rsl_tei, 0); + trx->rsl_link->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); + } + 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 = &line->ts[E1INP_SIGN_OML-1]; + struct gsm_bts *bts = NULL; + struct e1inp_sign_link *link; + + 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); +} + +/* 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; + 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); +} diff --git a/src/osmo-bsc/bts_ipaccess_nanobts_omlattr.c b/src/osmo-bsc/bts_ipaccess_nanobts_omlattr.c new file mode 100644 index 000000000..1a8d9b07b --- /dev/null +++ b/src/osmo-bsc/bts_ipaccess_nanobts_omlattr.c @@ -0,0 +1,240 @@ +/* ip.access nanoBTS specific code, OML attribute table generator */ + +/* (C) 2016 by sysmocom s.f.m.c. GmbH <info@sysmocom.de> + * All Rights Reserved + * + * Author: Philipp Maier + * + * 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 <osmocom/core/msgb.h> +#include <osmocom/bsc/gsm_data.h> +#include <osmocom/bsc/abis_nm.h> + +static void patch_16(uint8_t *data, const uint16_t val) +{ + memcpy(data, &val, sizeof(val)); +} + +static void patch_32(uint8_t *data, const uint32_t val) +{ + memcpy(data, &val, sizeof(val)); +} + +struct msgb *nanobts_attr_bts_get(struct gsm_bts *bts) +{ + struct msgb *msgb; + uint8_t buf[256]; + int rlt; + msgb = msgb_alloc(1024, "nanobts_attr_bts"); + + memcpy(buf, "\x55\x5b\x61\x67\x6d\x73", 6); + msgb_tv_fixed_put(msgb, NM_ATT_INTERF_BOUND, 6, buf); + + /* interference avg. period in numbers of SACCH multifr */ + msgb_tv_put(msgb, NM_ATT_INTAVE_PARAM, 0x06); + + rlt = gsm_bts_get_radio_link_timeout(bts); + if (rlt == -1) { + /* Osmocom extension: Use infinite radio link timeout */ + buf[0] = 0xFF; + buf[1] = 0x00; + } else { + /* conn fail based on SACCH error rate */ + buf[0] = 0x01; + buf[1] = rlt; + } + msgb_tl16v_put(msgb, NM_ATT_CONN_FAIL_CRIT, 2, buf); + + memcpy(buf, "\x1e\x24\x24\xa8\x34\x21\xa8", 7); + msgb_tv_fixed_put(msgb, NM_ATT_T200, 7, buf); + + msgb_tv_put(msgb, NM_ATT_MAX_TA, 0x3f); + + /* seconds */ + memcpy(buf, "\x00\x01\x0a", 3); + msgb_tv_fixed_put(msgb, NM_ATT_OVERL_PERIOD, 3, buf); + + /* percent */ + msgb_tv_put(msgb, NM_ATT_CCCH_L_T, 10); + + /* seconds */ + msgb_tv_put(msgb, NM_ATT_CCCH_L_I_P, 1); + + /* busy threshold in - dBm */ + buf[0] = 90; /* -90 dBm as default "busy" threshold */ + if (bts->rach_b_thresh != -1) + buf[0] = bts->rach_b_thresh & 0xff; + msgb_tv_put(msgb, NM_ATT_RACH_B_THRESH, buf[0]); + + /* rach load averaging 1000 slots */ + buf[0] = 0x03; + buf[1] = 0xe8; + if (bts->rach_ldavg_slots != -1) { + buf[0] = (bts->rach_ldavg_slots >> 8) & 0x0f; + buf[1] = bts->rach_ldavg_slots & 0xff; + } + msgb_tv_fixed_put(msgb, NM_ATT_LDAVG_SLOTS, 2, buf); + + /* 10 milliseconds */ + msgb_tv_put(msgb, NM_ATT_BTS_AIR_TIMER, bts->network->T3105 > 0? bts->network->T3105 : 13); + + /* 10 retransmissions of physical config */ + msgb_tv_put(msgb, NM_ATT_NY1, 10); + + buf[0] = (bts->c0->arfcn >> 8) & 0x0f; + buf[1] = bts->c0->arfcn & 0xff; + msgb_tv_fixed_put(msgb, NM_ATT_BCCH_ARFCN, 2, buf); + + msgb_tv_put(msgb, NM_ATT_BSIC, bts->bsic); + + abis_nm_ipaccess_cgi(buf, bts); + msgb_tl16v_put(msgb, NM_ATT_IPACC_CGI, 7, buf); + + return msgb; +} + +struct msgb *nanobts_attr_nse_get(struct gsm_bts *bts) +{ + struct msgb *msgb; + uint8_t buf[256]; + msgb = msgb_alloc(1024, "nanobts_attr_bts"); + + /* NSEI 925 */ + buf[0] = bts->gprs.nse.nsei >> 8; + buf[1] = bts->gprs.nse.nsei & 0xff; + msgb_tl16v_put(msgb, NM_ATT_IPACC_NSEI, 2, buf); + + /* all timers in seconds */ + OSMO_ASSERT(ARRAY_SIZE(bts->gprs.nse.timer) < sizeof(buf)); + memcpy(buf, bts->gprs.nse.timer, ARRAY_SIZE(bts->gprs.nse.timer)); + msgb_tl16v_put(msgb, NM_ATT_IPACC_NS_CFG, 7, buf); + + /* all timers in seconds */ + buf[0] = 3; /* blockimg timer (T1) */ + buf[1] = 3; /* blocking retries */ + buf[2] = 3; /* unblocking retries */ + buf[3] = 3; /* reset timer (T2) */ + buf[4] = 3; /* reset retries */ + buf[5] = 10; /* suspend timer (T3) in 100ms */ + buf[6] = 3; /* suspend retries */ + buf[7] = 10; /* resume timer (T4) in 100ms */ + buf[8] = 3; /* resume retries */ + buf[9] = 10; /* capability update timer (T5) */ + buf[10] = 3; /* capability update retries */ + + OSMO_ASSERT(ARRAY_SIZE(bts->gprs.cell.timer) < sizeof(buf)); + memcpy(buf, bts->gprs.cell.timer, ARRAY_SIZE(bts->gprs.cell.timer)); + msgb_tl16v_put(msgb, NM_ATT_IPACC_BSSGP_CFG, 11, buf); + + return msgb; +} + +struct msgb *nanobts_attr_cell_get(struct gsm_bts *bts) +{ + struct msgb *msgb; + uint8_t buf[256]; + msgb = msgb_alloc(1024, "nanobts_attr_bts"); + + /* routing area code */ + buf[0] = bts->gprs.rac; + msgb_tl16v_put(msgb, NM_ATT_IPACC_RAC, 1, buf); + + buf[0] = 5; /* repeat time (50ms) */ + buf[1] = 3; /* repeat count */ + msgb_tl16v_put(msgb, NM_ATT_IPACC_GPRS_PAGING_CFG, 2, buf); + + /* BVCI 925 */ + buf[0] = bts->gprs.cell.bvci >> 8; + buf[1] = bts->gprs.cell.bvci & 0xff; + msgb_tl16v_put(msgb, NM_ATT_IPACC_BVCI, 2, buf); + + /* all timers in seconds, unless otherwise stated */ + buf[0] = 20; /* T3142 */ + buf[1] = 5; /* T3169 */ + buf[2] = 5; /* T3191 */ + buf[3] = 160; /* T3193 (units of 10ms) */ + buf[4] = 5; /* T3195 */ + buf[5] = 10; /* N3101 */ + buf[6] = 4; /* N3103 */ + buf[7] = 8; /* N3105 */ + buf[8] = 15; /* RLC CV countdown */ + msgb_tl16v_put(msgb, NM_ATT_IPACC_RLC_CFG, 9, buf); + + if (bts->gprs.mode == BTS_GPRS_EGPRS) { + buf[0] = 0x8f; + buf[1] = 0xff; + } else { + buf[0] = 0x0f; + buf[1] = 0x00; + } + msgb_tl16v_put(msgb, NM_ATT_IPACC_CODING_SCHEMES, 2, buf); + + buf[0] = 0; /* T downlink TBF extension (0..500, high byte) */ + buf[1] = 250; /* T downlink TBF extension (0..500, low byte) */ + buf[2] = 0; /* T uplink TBF extension (0..500, high byte) */ + buf[3] = 250; /* T uplink TBF extension (0..500, low byte) */ + buf[4] = 2; /* CS2 */ + msgb_tl16v_put(msgb, NM_ATT_IPACC_RLC_CFG_2, 5, buf); + +#if 0 + /* EDGE model only, breaks older models. + * Should inquire the BTS capabilities */ + buf[0] = 2; /* MCS2 */ + msgb_tl16v_put(msgb, NM_ATT_IPACC_RLC_CFG_3, 1, buf); +#endif + + return msgb; +} + +struct msgb *nanobts_attr_nscv_get(struct gsm_bts *bts) +{ + struct msgb *msgb; + uint8_t buf[256]; + msgb = msgb_alloc(1024, "nanobts_attr_bts"); + + /* 925 */ + buf[0] = bts->gprs.nsvc[0].nsvci >> 8; + buf[1] = bts->gprs.nsvc[0].nsvci & 0xff; + msgb_tl16v_put(msgb, NM_ATT_IPACC_NSVCI, 2, buf); + + /* remote udp port */ + patch_16(&buf[0], htons(bts->gprs.nsvc[0].remote_port)); + /* remote ip address */ + patch_32(&buf[2], htonl(bts->gprs.nsvc[0].remote_ip)); + /* local udp port */ + patch_16(&buf[6], htons(bts->gprs.nsvc[0].local_port)); + msgb_tl16v_put(msgb, NM_ATT_IPACC_NS_LINK_CFG, 8, buf); + + return msgb; +} + +struct msgb *nanobts_attr_radio_get(struct gsm_bts *bts, + struct gsm_bts_trx *trx) +{ + struct msgb *msgb; + uint8_t buf[256]; + msgb = msgb_alloc(1024, "nanobts_attr_bts"); + + /* number of -2dB reduction steps / Pn */ + msgb_tv_put(msgb, NM_ATT_RF_MAXPOWR_R, trx->max_power_red / 2); + + buf[0] = trx->arfcn >> 8; + buf[1] = trx->arfcn & 0xff; + msgb_tl16v_put(msgb, NM_ATT_ARFCN_LIST, 2, buf); + + return msgb; +} diff --git a/src/osmo-bsc/bts_nokia_site.c b/src/osmo-bsc/bts_nokia_site.c new file mode 100644 index 000000000..4a24c3931 --- /dev/null +++ b/src/osmo-bsc/bts_nokia_site.c @@ -0,0 +1,1744 @@ +/* Nokia XXXsite family specific code */ + +/* (C) 2011 by Dieter Spaar <spaar@mirider.augusta.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/>. + * + */ + +/* + TODO: Attention: There are some static variables used for states during + configuration. Those variables have to be moved to a BTS specific context, + otherwise there will most certainly be problems if more than one Nokia BTS + is used. +*/ + +#include <time.h> + +#include <osmocom/gsm/tlv.h> + +#include <osmocom/bsc/debug.h> +#include <osmocom/bsc/gsm_data.h> +#include <osmocom/bsc/abis_nm.h> +#include <osmocom/abis/e1_input.h> +#include <osmocom/bsc/signal.h> + +#include <osmocom/core/timer.h> + +#include <osmocom/abis/lapd.h> + +/* TODO: put in a separate file ? */ + +extern int abis_nm_sendmsg(struct gsm_bts *bts, struct msgb *msg); +/* was static in system_information.c */ +extern int generate_cell_chan_list(uint8_t * chan_list, struct gsm_bts *bts); + +static void nokia_abis_nm_queue_send_next(struct gsm_bts *bts); +static void reset_timer_cb(void *_bts); +static int abis_nm_reset(struct gsm_bts *bts, uint16_t ref); +static int dump_elements(uint8_t * data, int len) __attribute__((unused)); + +static void bootstrap_om_bts(struct gsm_bts *bts) +{ + LOGP(DNM, LOGL_NOTICE, "bootstrapping OML for BTS %u\n", bts->nr); + + gsm_bts_mark_all_ts_uninitialized(bts); + + if (!bts->nokia.skip_reset) { + if (!bts->nokia.did_reset) + abis_nm_reset(bts, 1); + } else + bts->nokia.did_reset = 1; +} + +static void bootstrap_om_trx(struct gsm_bts_trx *trx) +{ + LOGP(DNM, LOGL_NOTICE, "bootstrapping OML for TRX %u/%u\n", + trx->bts->nr, trx->nr); + + gsm_trx_mark_all_ts_uninitialized(trx); +} + +static int shutdown_om(struct gsm_bts *bts) +{ + /* TODO !? */ + return 0; +} + +#define SAPI_OML 62 +#define SAPI_RSL 0 + +/* + + Tell LAPD to start start the SAP (send SABM requests) for all signalling + timeslots in this line + + Attention: this has to be adapted for mISDN +*/ + +static void start_sabm_in_line(struct e1inp_line *line, int start, int sapi) +{ + struct e1inp_sign_link *link; + int i; + + for (i = 0; i < ARRAY_SIZE(line->ts); i++) { + struct e1inp_ts *ts = &line->ts[i]; + + if (ts->type != E1INP_TS_TYPE_SIGN) + continue; + + llist_for_each_entry(link, &ts->sign.sign_links, list) { + if (sapi != -1 && link->sapi != sapi) + continue; + +#if 0 /* debugging */ + printf("sap start/stop (%d): %d tei=%d sapi=%d\n", + start, i + 1, link->tei, link->sapi); +#endif + + if (start) { + ts->lapd->profile.t200_sec = 1; + ts->lapd->profile.t200_usec = 0; + lapd_sap_start(ts->lapd, link->tei, + link->sapi); + } else + lapd_sap_stop(ts->lapd, link->tei, + link->sapi); + } + } +} + +/* Callback function to be called every time we receive a signal from INPUT */ +static int gbl_sig_cb(unsigned int subsys, unsigned int signal, + void *handler_data, void *signal_data) +{ + struct gsm_bts *bts; + + if (subsys != SS_L_GLOBAL) + return 0; + + switch (signal) { + case S_GLOBAL_BTS_CLOSE_OM: + bts = signal_data; + if (bts->type == GSM_BTS_TYPE_NOKIA_SITE) + shutdown_om(signal_data); + break; + } + + return 0; +} + +/* Callback function to be called every time we receive a signal from INPUT */ +static int inp_sig_cb(unsigned int subsys, unsigned int signal, + void *handler_data, void *signal_data) +{ + struct input_signal_data *isd = signal_data; + + if (subsys != SS_L_INPUT) + return 0; + + switch (signal) { + case S_L_INP_LINE_INIT: + start_sabm_in_line(isd->line, 1, SAPI_OML); /* start only OML */ + break; + case S_L_INP_TEI_DN: + break; + case S_L_INP_TEI_UP: + switch (isd->link_type) { + case E1INP_SIGN_OML: + if (isd->trx->bts->type != GSM_BTS_TYPE_NOKIA_SITE) + break; + + if (isd->tei == isd->trx->bts->oml_tei) + bootstrap_om_bts(isd->trx->bts); + else + bootstrap_om_trx(isd->trx); + break; + } + break; + case S_L_INP_TEI_UNKNOWN: + /* We are receiving LAPD frames with one TEI that we do not + * seem to know, likely that we (the BSC) stopped working + * and lost our local states. However, the BTS is already + * configured, we try to take over the RSL links. */ + start_sabm_in_line(isd->line, 1, SAPI_RSL); + break; + } + + return 0; +} + +static void nm_statechg_evt(unsigned int signal, + struct nm_statechg_signal_data *nsd) +{ + if (nsd->bts->type != GSM_BTS_TYPE_NOKIA_SITE) + return; +} + +static int 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_STATECHG_OPER: + case S_NM_STATECHG_ADM: + nm_statechg_evt(signal, signal_data); + break; + default: + break; + } + + return 0; +} + +/* TODO: put in a separate file ? */ + +static const struct value_string nokia_msgt_name[] = { + { 0x80, "NOKIA_BTS_CONF_DATA" }, + { 0x81, "NOKIA_BTS_ACK" }, + { 0x82, "NOKIA_BTS_OMU_STARTED" }, + { 0x83, "NOKIA_BTS_START_DOWNLOAD_REQ" }, + { 0x84, "NOKIA_BTS_MF_REQ" }, + { 0x85, "NOKIA_BTS_AF_REQ" }, + { 0x86, "NOKIA_BTS_RESET_REQ" }, + { 0x87, "NOKIA_reserved" }, + { 0x88, "NOKIA_BTS_CONF_REQ" }, + { 0x89, "NOKIA_BTS_TEST_REQ" }, + { 0x8A, "NOKIA_BTS_TEST_REPORT" }, + { 0x8B, "NOKIA_reserved" }, + { 0x8C, "NOKIA_reserved" }, + { 0x8D, "NOKIA_reserved" }, + { 0x8E, "NOKIA_BTS_CONF_COMPL" }, + { 0x8F, "NOKIA_reserved" }, + { 0x90, "NOKIA_BTS_STM_TEST_REQ" }, + { 0x91, "NOKIA_BTS_STM_TEST_REPORT" }, + { 0x92, "NOKIA_BTS_TRANSMISSION_COMMAND" }, + { 0x93, "NOKIA_BTS_TRANSMISSION_ANSWER" }, + { 0x94, "NOKIA_BTS_HW_DB_UPLOAD_REQ" }, + { 0x95, "NOKIA_BTS_START_HW_DB_DOWNLOAD_REQ" }, + { 0x96, "NOKIA_BTS_HW_DB_SAVE_REQ" }, + { 0x97, "NOKIA_BTS_FLASH_ERASURE_REQ" }, + { 0x98, "NOKIA_BTS_HW_DB_DOWNLOAD_REQ" }, + { 0x99, "NOKIA_BTS_PWR_SUPPLY_CONTROL" }, + { 0x9A, "NOKIA_BTS_ATTRIBUTE_REQ" }, + { 0x9B, "NOKIA_BTS_ATTRIBUTE_REPORT" }, + { 0x9C, "NOKIA_BTS_HW_REQ" }, + { 0x9D, "NOKIA_BTS_HW_REPORT" }, + { 0x9E, "NOKIA_BTS_RTE_TEST_REQ" }, + { 0x9F, "NOKIA_BTS_RTE_TEST_REPORT" }, + { 0xA0, "NOKIA_BTS_HW_DB_VERIFICATION_REQ" }, + { 0xA1, "NOKIA_BTS_CLOCK_REQ" }, + { 0xA2, "NOKIA_AC_CIRCUIT_REQ_NACK" }, + { 0xA3, "NOKIA_AC_INTERRUPTED" }, + { 0xA4, "NOKIA_BTS_NEW_TRE_INFO" }, + { 0xA5, "NOKIA_AC_BSC_CIRCUITS_ALLOCATED" }, + { 0xA6, "NOKIA_BTS_TRE_POLL_LIST" }, + { 0xA7, "NOKIA_AC_CIRCUIT_REQ" }, + { 0xA8, "NOKIA_BTS_BLOCK_CTRL_REQ" }, + { 0xA9, "NOKIA_BTS_GSM_TIME_REQ" }, + { 0xAA, "NOKIA_BTS_GSM_TIME" }, + { 0xAB, "NOKIA_BTS_OUTPUT_CONTROL" }, + { 0xAC, "NOKIA_BTS_STATE_CHANGED" }, + { 0xAD, "NOKIA_BTS_SW_SAVE_REQ" }, + { 0xAE, "NOKIA_BTS_ALARM" }, + { 0xAF, "NOKIA_BTS_CHA_ADM_STATE" }, + { 0xB0, "NOKIA_AC_POOL_SIZE_REPORT" }, + { 0xB1, "NOKIA_AC_POOL_SIZE_INQUIRY" }, + { 0xB2, "NOKIA_BTS_COMMISS_TEST_COMPLETED" }, + { 0xB3, "NOKIA_BTS_COMMISS_TEST_REQ" }, + { 0xB4, "NOKIA_BTS_TRANSP_BTS_TO_BSC" }, + { 0xB5, "NOKIA_BTS_TRANSP_BSC_TO_BTS" }, + { 0xB6, "NOKIA_BTS_LCS_COMMAND" }, + { 0xB7, "NOKIA_BTS_LCS_ANSWER" }, + { 0xB8, "NOKIA_BTS_LMU_FN_OFFSET_COMMAND" }, + { 0xB9, "NOKIA_BTS_LMU_FN_OFFSET_ANSWER" }, + { 0, NULL } +}; + +static const char *get_msg_type_name_string(uint8_t msg_type) +{ + return get_value_string(nokia_msgt_name, msg_type); +} + +static const struct value_string nokia_element_name[] = { + { 0x01, "Ny1" }, + { 0x02, "T3105_F" }, + { 0x03, "Interference band limits" }, + { 0x04, "Interference report timer in secs" }, + { 0x05, "Channel configuration per TS" }, + { 0x06, "BSIC" }, + { 0x07, "RACH report timer in secs" }, + { 0x08, "Hardware database status" }, + { 0x09, "BTS RX level" }, + { 0x0A, "ARFN" }, + { 0x0B, "STM antenna attenuation" }, + { 0x0C, "Cell allocation bitmap" }, + { 0x0D, "Radio definition per TS" }, + { 0x0E, "Frame number" }, + { 0x0F, "Antenna diversity" }, + { 0x10, "T3105_D" }, + { 0x11, "File format" }, + { 0x12, "Last File" }, + { 0x13, "BTS type" }, + { 0x14, "Erasure mode" }, + { 0x15, "Hopping mode" }, + { 0x16, "Floating TRX" }, + { 0x17, "Power supplies" }, + { 0x18, "Reset type" }, + { 0x19, "Averaging period" }, + { 0x1A, "RBER2" }, + { 0x1B, "LAC" }, + { 0x1C, "CI" }, + { 0x1D, "Failure parameters" }, + { 0x1E, "(RF max power reduction)" }, + { 0x1F, "Measured RX_SENS" }, + { 0x20, "Extended cell radius" }, + { 0x21, "reserved" }, + { 0x22, "Success-Failure" }, + { 0x23, "Ack-Nack" }, + { 0x24, "OMU test results" }, + { 0x25, "File identity" }, + { 0x26, "Generation and version code" }, + { 0x27, "SW description" }, + { 0x28, "BCCH LEV" }, + { 0x29, "Test type" }, + { 0x2A, "Subscriber number" }, + { 0x2B, "reserved" }, + { 0x2C, "HSN" }, + { 0x2D, "reserved" }, + { 0x2E, "MS RXLEV" }, + { 0x2F, "MS TXLEV" }, + { 0x30, "RXQUAL" }, + { 0x31, "RX SENS" }, + { 0x32, "Alarm block" }, + { 0x33, "Neighbouring BCCH levels" }, + { 0x34, "STM report type" }, + { 0x35, "MA" }, + { 0x36, "MAIO" }, + { 0x37, "H_FLAG" }, + { 0x38, "TCH_ARFN" }, + { 0x39, "Clock output" }, + { 0x3A, "Transmitted power" }, + { 0x3B, "Clock sync" }, + { 0x3C, "TMS protocol discriminator" }, + { 0x3D, "TMS protocol data" }, + { 0x3E, "FER" }, + { 0x3F, "SWR result" }, + { 0x40, "Object identity" }, + { 0x41, "STM RX Antenna Test" }, + { 0x42, "reserved" }, + { 0x43, "reserved" }, + { 0x44, "Object current state" }, + { 0x45, "reserved" }, + { 0x46, "FU channel configuration" }, + { 0x47, "reserved" }, + { 0x48, "ARFN of a CU" }, + { 0x49, "FU radio definition" }, + { 0x4A, "reserved" }, + { 0x4B, "Severity" }, + { 0x4C, "Diversity selection" }, + { 0x4D, "RX antenna test" }, + { 0x4E, "RX antenna supervision period" }, + { 0x4F, "RX antenna state" }, + { 0x50, "Sector configuration" }, + { 0x51, "Additional info" }, + { 0x52, "SWR parameters" }, + { 0x53, "HW inquiry mode" }, + { 0x54, "reserved" }, + { 0x55, "Availability status" }, + { 0x56, "reserved" }, + { 0x57, "EAC inputs" }, + { 0x58, "EAC outputs" }, + { 0x59, "reserved" }, + { 0x5A, "Position" }, + { 0x5B, "HW unit identity" }, + { 0x5C, "RF test signal attenuation" }, + { 0x5D, "Operational state" }, + { 0x5E, "Logical object identity" }, + { 0x5F, "reserved" }, + { 0x60, "BS_TXPWR_OM" }, + { 0x61, "Loop_Duration" }, + { 0x62, "LNA_Path_Selection" }, + { 0x63, "Serial number" }, + { 0x64, "HW version" }, + { 0x65, "Obj. identity and obj. state" }, + { 0x66, "reserved" }, + { 0x67, "EAC input definition" }, + { 0x68, "EAC id and text" }, + { 0x69, "HW unit status" }, + { 0x6A, "SW release version" }, + { 0x6B, "FW version" }, + { 0x6C, "Bit_Error_Ratio" }, + { 0x6D, "RXLEV_with_Attenuation" }, + { 0x6E, "RXLEV_without_Attenuation" }, + { 0x6F, "reserved" }, + { 0x70, "CU_Results" }, + { 0x71, "reserved" }, + { 0x72, "LNA_Path_Results" }, + { 0x73, "RTE Results" }, + { 0x74, "Real Time" }, + { 0x75, "RX diversity selection" }, + { 0x76, "EAC input config" }, + { 0x77, "Feature support" }, + { 0x78, "File version" }, + { 0x79, "Outputs" }, + { 0x7A, "FU parameters" }, + { 0x7B, "Diagnostic info" }, + { 0x7C, "FU BSIC" }, + { 0x7D, "TRX Configuration" }, + { 0x7E, "Download status" }, + { 0x7F, "RX difference limit" }, + { 0x80, "TRX HW capability" }, + { 0x81, "Common HW config" }, + { 0x82, "Autoconfiguration pool size" }, + { 0x83, "TRE diagnostic info" }, + { 0x84, "TRE object identity" }, + { 0x85, "New TRE Info" }, + { 0x86, "Acknowledgement period" }, + { 0x87, "Synchronization mode" }, + { 0x88, "reserved" }, + { 0x89, "Block Control Data" }, + { 0x8A, "SW load mode" }, + { 0x8B, "Recommended recovery action" }, + { 0x8C, "BSC BCF id" }, + { 0x8D, "Q1 baud rate" }, + { 0x8E, "Allocation status" }, + { 0x8F, "Functional entity number" }, + { 0x90, "Transmission delay" }, + { 0x91, "Loop Duration ms" }, + { 0x92, "Logical channel" }, + { 0x93, "Q1 address" }, + { 0x94, "Alarm detail" }, + { 0x95, "Cabinet type" }, + { 0x96, "HW unit existence" }, + { 0x97, "RF power parameters" }, + { 0x98, "Message scenario" }, + { 0x99, "HW unit max amount" }, + { 0x9A, "Master TRX" }, + { 0x9B, "Transparent data" }, + { 0x9C, "BSC topology info" }, + { 0x9D, "Air i/f modulation" }, + { 0x9E, "LCS Q1 command data" }, + { 0x9F, "Frame number offset" }, + { 0xA0, "Abis TSL" }, + { 0xA1, "Dynamic pool info" }, + { 0xA2, "LCS LLP data" }, + { 0xA3, "LCS Q1 answer data" }, + { 0xA4, "DFCA FU Radio Definition" }, + { 0xA5, "Antenna hopping" }, + { 0xA6, "Field record sequence number" }, + { 0xA7, "Timeslot offslot" }, + { 0xA8, "EPCR capability" }, + { 0xA9, "Connectsite optional element" }, + { 0xAA, "TSC" }, + { 0xAB, "Special TX Power Setting" }, + { 0xAC, "Optional sync settings" }, + { 0xFA, "Abis If parameters" }, + { 0, NULL } +}; + +static const char *get_element_name_string(uint16_t element) +{ + return get_value_string(nokia_element_name, element); +} + +static const struct value_string nokia_bts_types[] = { + { 0x0a, "MetroSite GSM 900" }, + { 0x0b, "MetroSite GSM 1800" }, + { 0x0c, "MetroSite GSM 1900 (PCS)" }, + { 0x0d, "MetroSite GSM 900 & 1800" }, + { 0x0e, "InSite GSM 900" }, + { 0x0f, "InSite GSM 1800" }, + { 0x10, "InSite GSM 1900" }, + { 0x11, "UltraSite GSM 900" }, + { 0x12, "UltraSite GSM 1800" }, + { 0x13, "UltraSite GSM/US-TDMA 1900" }, + { 0x14, "UltraSite GSM 900 & 1800" }, + { 0x16, "UltraSite GSM/US-TDMA 850" }, + { 0x18, "MetroSite GSM/US-TDMA 850" }, + { 0x19, "UltraSite GSM 800/1900" }, + { 0, NULL } +}; + +static const char *get_bts_type_string(uint8_t type) +{ + return get_value_string(nokia_bts_types, type); +} + +static const struct value_string nokia_severity[] = { + { 0, "indeterminate" }, + { 1, "critical" }, + { 2, "major" }, + { 3, "minor" }, + { 4, "warning" }, + { 0, NULL } +}; + +static const char *get_severity_string(uint8_t severity) +{ + return get_value_string(nokia_severity, severity); +} + +/* TODO: put in a separate file ? */ + +/* some message IDs */ + +#define NOKIA_MSG_CONF_DATA 128 +#define NOKIA_MSG_ACK 129 +#define NOKIA_MSG_OMU_STARTED 130 +#define NOKIA_MSG_START_DOWNLOAD_REQ 131 +#define NOKIA_MSG_MF_REQ 132 +#define NOKIA_MSG_RESET_REQ 134 +#define NOKIA_MSG_CONF_REQ 136 +#define NOKIA_MSG_CONF_COMPLETE 142 +#define NOKIA_MSG_BLOCK_CTRL_REQ 168 +#define NOKIA_MSG_STATE_CHANGED 172 +#define NOKIA_MSG_ALARM 174 + +/* some element IDs */ + +#define NOKIA_EI_BTS_TYPE 0x13 +#define NOKIA_EI_ACK 0x23 +#define NOKIA_EI_ADD_INFO 0x51 +#define NOKIA_EI_SEVERITY 0x4B +#define NOKIA_EI_ALARM_DETAIL 0x94 + +#define OM_ALLOC_SIZE 1024 +#define OM_HEADROOM_SIZE 128 + +static uint8_t fu_config_template[] = { + 0x7F, 0x7A, 0x39, + /* ID = 0x7A (FU parameters) ## constructed ## */ + /* length = 57 */ + /* [3] */ + + 0x5F, 0x40, 0x04, + /* ID = 0x40 (Object identity) */ + /* length = 4 */ + /* [6] */ + 0x00, 0x07, 0x01, 0xFF, + + 0x41, 0x02, + /* ID = 0x01 (Ny1) */ + /* length = 2 */ + /* [12] */ + 0x00, 0x05, + + 0x42, 0x02, + /* ID = 0x02 (T3105_F) */ + /* length = 2 */ + /* [16] */ + 0x00, 0x28, /* FIXME: use net->T3105 */ + + 0x50, 0x02, + /* ID = 0x10 (T3105_D) */ + /* length = 2 */ + /* [20] */ + 0x00, 0x28, /* FIXME: use net->T3105 */ + + 0x43, 0x05, + /* ID = 0x03 (Interference band limits) */ + /* length = 5 */ + /* [24] */ + 0x0F, 0x1B, 0x27, 0x33, 0x3F, + + 0x44, 0x02, + /* ID = 0x04 (Interference report timer in secs) */ + /* length = 2 */ + /* [31] */ + 0x00, 0x10, + + 0x47, 0x01, + /* ID = 0x07 (RACH report timer in secs) */ + /* length = 1 */ + /* [35] */ + 0x1E, + + 0x4C, 0x10, + /* ID = 0x0C (Cell allocation bitmap) ####### */ + /* length = 16 */ + /* [38] */ + 0x8F, 0xB1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + 0x59, 0x01, + /* ID = 0x19 (Averaging period) */ + /* length = 1 */ + /* [56] */ + 0x01, + + 0x5E, 0x01, + /* ID = 0x1E ((RF max power reduction)) */ + /* length = 1 */ + /* [59] */ + 0x00, + + 0x7F, 0x46, 0x11, + /* ID = 0x46 (FU channel configuration) ## constructed ## */ + /* length = 17 */ + /* [63] */ + + 0x5F, 0x40, 0x04, + /* ID = 0x40 (Object identity) */ + /* length = 4 */ + /* [66] */ + 0x00, 0x07, 0x01, 0xFF, + + 0x45, 0x08, + /* ID = 0x05 (Channel configuration per TS) */ + /* length = 8 */ + /* [72] */ + 0x01, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + + 0x7F, 0x65, 0x0B, + /* ID = 0x65 (Obj. identity and obj. state) ## constructed ## */ + /* length = 11 */ + /* [83] */ + + 0x5F, 0x40, 0x04, + /* ID = 0x40 (Object identity) */ + /* length = 4 */ + /* [86] */ + 0x00, 0x04, 0x01, 0xFF, + + 0x5F, 0x44, 0x01, + /* ID = 0x44 (Object current state) */ + /* length = 1 */ + /* [93] */ + 0x03, + + 0x7F, 0x7C, 0x0A, + /* ID = 0x7C (FU BSIC) ## constructed ## */ + /* length = 10 */ + /* [97] */ + + 0x5F, 0x40, 0x04, + /* ID = 0x40 (Object identity) */ + /* length = 4 */ + /* [100] */ + 0x00, 0x07, 0x01, 0xFF, + + 0x46, 0x01, + /* ID = 0x06 (BSIC) */ + /* length = 1 */ + /* [106] */ + 0x00, + + 0x7F, 0x48, 0x0B, + /* ID = 0x48 (ARFN of a CU) ## constructed ## */ + /* length = 11 */ + /* [110] */ + + 0x5F, 0x40, 0x04, + /* ID = 0x40 (Object identity) */ + /* length = 4 */ + /* [113] */ + 0x00, 0x08, 0x01, 0xFF, + + 0x4A, 0x02, + /* ID = 0x0A (ARFN) ####### */ + /* length = 2 */ + /* [119] */ + 0x03, 0x62, + + 0x7F, 0x49, 0x59, + /* ID = 0x49 (FU radio definition) ## constructed ## */ + /* length = 89 */ + /* [124] */ + + 0x5F, 0x40, 0x04, + /* ID = 0x40 (Object identity) */ + /* length = 4 */ + /* [127] */ + 0x00, 0x07, 0x01, 0xFF, + + 0x4D, 0x50, + /* ID = 0x0D (Radio definition per TS) ####### */ + /* length = 80 */ + /* [133] */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* MA */ + 0x03, 0x62, /* HSN, MAIO or ARFCN if no hopping */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x03, 0x62, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x03, 0x62, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x03, 0x62, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x03, 0x62, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x03, 0x62, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x03, 0x62, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x03, 0x62, +}; + +/* TODO: put in a separate file ? */ + +/* + build the configuration for each TRX +*/ + +static int make_fu_config(struct gsm_bts_trx *trx, uint8_t id, + uint8_t * fu_config, int *hopping) +{ + int i; + + *hopping = 0; + + memcpy(fu_config, fu_config_template, sizeof(fu_config_template)); + + /* set ID */ + + fu_config[6 + 2] = id; + fu_config[66 + 2] = id; + fu_config[86 + 2] = id; + fu_config[100 + 2] = id; + fu_config[113 + 2] = id; + fu_config[127 + 2] = id; + + /* set ARFCN */ + + uint16_t arfcn = trx->arfcn; + + fu_config[119] = arfcn >> 8; + fu_config[119 + 1] = arfcn & 0xFF; + + for (i = 0; i < ARRAY_SIZE(trx->ts); i++) { + struct gsm_bts_trx_ts *ts = &trx->ts[i]; + + if (ts->hopping.enabled) { + /* reverse order */ + int j; + for (j = 0; j < ts->hopping.ma_len; j++) + fu_config[133 + (i * 10) + (7 - j)] = + ts->hopping.ma_data[j]; + fu_config[133 + 8 + (i * 10)] = ts->hopping.hsn; + fu_config[133 + 8 + 1 + (i * 10)] = ts->hopping.maio; + *hopping = 1; + } else { + fu_config[133 + 8 + (i * 10)] = arfcn >> 8; + fu_config[133 + 8 + 1 + (i * 10)] = arfcn & 0xFF; + } + } + + /* set BSIC */ + + /* + Attention: all TRX except the first one seem to get the TSC + from the CHANNEL ACTIVATION command (in CHANNEL IDENTIFICATION, + GSM 04.08 CHANNEL DESCRIPTION). + There was a bug in rsl_chan_activate_lchan() setting this parameter. + */ + + uint8_t bsic = trx->bts->bsic; + + fu_config[106] = bsic; + + /* set CA */ + + if (generate_cell_chan_list(&fu_config[38], trx->bts) != 0) { + fprintf(stderr, "generate_cell_chan_list failed\n"); + return 0; + } + + /* set channel configuration */ + + for (i = 0; i < ARRAY_SIZE(trx->ts); i++) { + struct gsm_bts_trx_ts *ts = &trx->ts[i]; + uint8_t chan_config; + + /* + 0 = FCCH + SCH + BCCH + CCCH + 1 = FCCH + SCH + BCCH + CCCH + SDCCH/4 + SACCH/4 + 2 = BCCH + CCCH (This combination is not used in any BTS) + 3 = FCCH + SCH + BCCH + CCCH + SDCCH/4 with SDCCH2 used as CBCH + 4 = SDCCH/8 + SACCH/8 + 5 = SDCCH/8 with SDCCH2 used as CBCH + 6 = TCH/F + FACCH/F + SACCH/F + 7 = E-RACH (Talk family) + 9 = Dual rate (capability for TCH/F and TCH/H) + 10 = reserved for BTS internal use + 11 = PBCCH + PCCCH + PDTCH + PACCH + PTCCH (can be used in GPRS release 2). + 0xFF = spare TS + */ + + if (ts->pchan == GSM_PCHAN_NONE) + chan_config = 0xFF; + else if (ts->pchan == GSM_PCHAN_CCCH) + chan_config = 0; + else if (ts->pchan == GSM_PCHAN_CCCH_SDCCH4) + chan_config = 1; + else if (ts->pchan == GSM_PCHAN_TCH_F) + chan_config = 6; /* 9 should work too */ + else if (ts->pchan == GSM_PCHAN_TCH_H) + chan_config = 9; + else if (ts->pchan == GSM_PCHAN_SDCCH8_SACCH8C) + chan_config = 4; + else if (ts->pchan == GSM_PCHAN_PDCH) + chan_config = 11; + else { + fprintf(stderr, + "unsupported channel config %d for timeslot %d\n", + ts->pchan, i); + return 0; + } + + fu_config[72 + i] = chan_config; + } + return sizeof(fu_config_template); +} + +/* TODO: put in a separate file ? */ + +static uint8_t bts_config_1[] = { + 0x4E, 0x02, + /* ID = 0x0E (Frame number) */ + /* length = 2 */ + /* [2] */ + 0xFF, 0xFF, + + 0x5F, 0x4E, 0x02, + /* ID = 0x4E (RX antenna supervision period) */ + /* length = 2 */ + /* [7] */ + 0xFF, 0xFF, + + 0x5F, 0x50, 0x02, + /* ID = 0x50 (Sector configuration) */ + /* length = 2 */ + /* [12] */ + 0x01, 0x01, +}; + +static uint8_t bts_config_2[] = { + 0x55, 0x02, + /* ID = 0x15 (Hopping mode) */ + /* length = 2 */ + /* [2] */ + 0x01, 0x00, + + 0x5F, 0x75, 0x02, + /* ID = 0x75 (RX diversity selection) */ + /* length = 2 */ + /* [7] */ + 0x01, 0x01, +}; + +static uint8_t bts_config_3[] = { + 0x5F, 0x20, 0x02, + /* ID = 0x20 (Extended cell radius) */ + /* length = 2 */ + /* [3] */ + 0x01, 0x00, +}; + +static uint8_t bts_config_4[] = { + 0x5F, 0x74, 0x09, + /* ID = 0x74 (Real Time) */ + /* length = 9 */ + /* [3] year-high, year-low, month, day, hour, minute, second, msec-high, msec-low */ + 0x07, 0xDB, 0x06, 0x02, 0x0B, 0x20, 0x0C, 0x00, + 0x00, + + 0x5F, 0x76, 0x03, + /* ID = 0x76 (EAC input config) */ + /* length = 3 */ + /* [15] */ + 0x01, 0x01, 0x00, + + 0x5F, 0x76, 0x03, + /* ID = 0x76 (EAC input config) */ + /* length = 3 */ + /* [21] */ + 0x02, 0x01, 0x00, + + 0x5F, 0x76, 0x03, + /* ID = 0x76 (EAC input config) */ + /* length = 3 */ + /* [27] */ + 0x03, 0x01, 0x00, + + 0x5F, 0x76, 0x03, + /* ID = 0x76 (EAC input config) */ + /* length = 3 */ + /* [33] */ + 0x04, 0x01, 0x00, + + 0x5F, 0x76, 0x03, + /* ID = 0x76 (EAC input config) */ + /* length = 3 */ + /* [39] */ + 0x05, 0x01, 0x00, + + 0x5F, 0x76, 0x03, + /* ID = 0x76 (EAC input config) */ + /* length = 3 */ + /* [45] */ + 0x06, 0x01, 0x00, + + 0x5F, 0x76, 0x03, + /* ID = 0x76 (EAC input config) */ + /* length = 3 */ + /* [51] */ + 0x07, 0x01, 0x00, + + 0x5F, 0x76, 0x03, + /* ID = 0x76 (EAC input config) */ + /* length = 3 */ + /* [57] */ + 0x08, 0x01, 0x00, + + 0x5F, 0x76, 0x03, + /* ID = 0x76 (EAC input config) */ + /* length = 3 */ + /* [63] */ + 0x09, 0x01, 0x00, + + 0x5F, 0x76, 0x03, + /* ID = 0x76 (EAC input config) */ + /* length = 3 */ + /* [69] */ + 0x0A, 0x01, 0x00, +}; + +static uint8_t bts_config_insite[] = { + 0x4E, 0x02, + /* ID = 0x0E (Frame number) */ + /* length = 2 */ + /* [2] */ + 0xFF, 0xFF, + + 0x5F, 0x4E, 0x02, + /* ID = 0x4E (RX antenna supervision period) */ + /* length = 2 */ + /* [7] */ + 0xFF, 0xFF, + + 0x5F, 0x50, 0x02, + /* ID = 0x50 (Sector configuration) */ + /* length = 2 */ + /* [12] */ + 0x01, 0x01, + + 0x55, 0x02, + /* ID = 0x15 (Hopping mode) */ + /* length = 2 */ + /* [16] */ + 0x01, 0x00, + + 0x5F, 0x20, 0x02, + /* ID = 0x20 (Extended cell radius) */ + /* length = 2 */ + /* [21] */ + 0x01, 0x00, + + 0x5F, 0x74, 0x09, + /* ID = 0x74 (Real Time) */ + /* length = 9 */ + /* [26] */ + 0x07, 0xDB, 0x07, 0x0A, 0x0F, 0x09, 0x0B, 0x00, + 0x00, +}; + +void set_real_time(uint8_t * real_time) +{ + time_t t; + struct tm *tm; + + t = time(NULL); + tm = localtime(&t); + + /* year-high, year-low, month, day, hour, minute, second, msec-high, msec-low */ + + real_time[0] = (1900 + tm->tm_year) >> 8; + real_time[1] = (1900 + tm->tm_year) & 0xFF; + real_time[2] = tm->tm_mon + 1; + real_time[3] = tm->tm_mday; + real_time[4] = tm->tm_hour; + real_time[5] = tm->tm_min; + real_time[6] = tm->tm_sec; + real_time[7] = 0; + real_time[8] = 0; +} + +/* TODO: put in a separate file ? */ + +/* + build the configuration data +*/ + +static int make_bts_config(uint8_t bts_type, int n_trx, uint8_t * fu_config, + int need_hopping) +{ + /* is it an InSite BTS ? */ + if (bts_type == 0x0E || bts_type == 0x0F || bts_type == 0x10) { /* TODO */ + if (n_trx != 1) { + fprintf(stderr, "InSite has only one TRX\n"); + return 0; + } + if (need_hopping != 0) { + fprintf(stderr, "InSite does not support hopping\n"); + return 0; + } + memcpy(fu_config, bts_config_insite, sizeof(bts_config_insite)); + set_real_time(&fu_config[26]); + return sizeof(bts_config_insite); + } + + int len = 0; + int i; + + memcpy(fu_config + len, bts_config_1, sizeof(bts_config_1)); + + /* set sector configuration */ + fu_config[len + 12 - 1] = 1 + n_trx; /* len */ + for (i = 0; i < n_trx; i++) + fu_config[len + 12 + 1 + i] = ((i + 1) & 0xFF); + + len += (sizeof(bts_config_1) + (n_trx - 1)); + + memcpy(fu_config + len, bts_config_2, sizeof(bts_config_2)); + /* set hopping mode (Baseband and RF hopping work for the MetroSite) */ + if (need_hopping) + fu_config[len + 2 + 1] = 1; /* 0: no hopping, 1: Baseband hopping, 2: RF hopping */ + len += sizeof(bts_config_2); + + /* set extended cell radius for each TRX */ + for (i = 0; i < n_trx; i++) { + memcpy(fu_config + len, bts_config_3, sizeof(bts_config_3)); + fu_config[len + 3] = ((i + 1) & 0xFF); + len += sizeof(bts_config_3); + } + + memcpy(fu_config + len, bts_config_4, sizeof(bts_config_4)); + set_real_time(&fu_config[len + 3]); + len += sizeof(bts_config_4); + + return len; +} + +/* TODO: put in a separate file ? */ + +static struct msgb *nm_msgb_alloc(void) +{ + return msgb_alloc_headroom(OM_ALLOC_SIZE, OM_HEADROOM_SIZE, "OML"); +} + +/* TODO: put in a separate file ? */ + +struct abis_om_nokia_hdr { + uint8_t msg_type; + uint8_t spare; + uint16_t reference; + uint8_t data[0]; +} __attribute__ ((packed)); + +#define ABIS_OM_NOKIA_HDR_SIZE (sizeof(struct abis_om_hdr) + sizeof(struct abis_om_nokia_hdr)) + +static int abis_nm_send(struct gsm_bts *bts, uint8_t msg_type, uint16_t ref, + uint8_t * data, int len_data) +{ + struct abis_om_hdr *oh; + struct abis_om_nokia_hdr *noh; + struct msgb *msg = nm_msgb_alloc(); + + oh = (struct abis_om_hdr *)msgb_put(msg, + ABIS_OM_NOKIA_HDR_SIZE + len_data); + + oh->mdisc = ABIS_OM_MDISC_FOM; + oh->placement = ABIS_OM_PLACEMENT_ONLY; + oh->sequence = 0; + oh->length = sizeof(struct abis_om_nokia_hdr) + len_data; + + noh = (struct abis_om_nokia_hdr *)oh->data; + + noh->msg_type = msg_type; + noh->spare = 0; + noh->reference = htons(ref); + memcpy(noh->data, data, len_data); + + DEBUGPC(DNM, "Sending %s\n", get_msg_type_name_string(msg_type)); + + return abis_nm_sendmsg(bts, msg); +} + +/* TODO: put in a separate file ? */ + +static uint8_t download_req[] = { + 0x5F, 0x25, 0x0B, + /* ID = 0x25 (File identity) */ + /* length = 11 */ + /* [3] */ + 0x2A, 0x2A, 0x2A, 0x2A, 0x2A, 0x2A, 0x2A, 0x2A, + 0x2A, 0x2A, 0x2A, + + 0x5F, 0x78, 0x03, + /* ID = 0x78 (File version) */ + /* length = 3 */ + /* [17] */ + 0x2A, 0x2A, 0x2A, + + 0x5F, 0x81, 0x0A, 0x01, + /* ID = 0x8A (SW load mode) */ + /* length = 1 */ + /* [24] */ + 0x01, + + 0x5F, 0x81, 0x06, 0x01, + /* ID = 0x86 (Acknowledgement period) */ + /* length = 1 */ + /* [29] */ + 0x01, +}; + +static int abis_nm_download_req(struct gsm_bts *bts, uint16_t ref) +{ + uint8_t *data = download_req; + int len_data = sizeof(download_req); + + return abis_nm_send(bts, NOKIA_MSG_START_DOWNLOAD_REQ, ref, data, + len_data); +} + +/* TODO: put in a separate file ? */ + +static uint8_t ack[] = { + 0x5F, 0x23, 0x01, + /* ID = 0x23 (Ack-Nack) */ + /* length = 1 */ + /* [3] */ + 0x01, +}; + +static int abis_nm_ack(struct gsm_bts *bts, uint16_t ref) +{ + uint8_t *data = ack; + int len_data = sizeof(ack); + + return abis_nm_send(bts, NOKIA_MSG_ACK, ref, data, len_data); +} + +/* TODO: put in a separate file ? */ + +static uint8_t reset[] = { + 0x5F, 0x40, 0x04, + /* ID = 0x40 (Object identity) */ + /* length = 4 */ + /* [3] */ + 0x00, 0x01, 0xFF, 0xFF, +}; + +static int abis_nm_reset(struct gsm_bts *bts, uint16_t ref) +{ + uint8_t *data = reset; + int len_data = sizeof(reset); + LOGP(DLINP, LOGL_INFO, "Nokia BTS reset timer: %d\n", bts->nokia.bts_reset_timer_cnf); + return abis_nm_send(bts, NOKIA_MSG_RESET_REQ, ref, data, len_data); +} + +/* TODO: put in a separate file ? */ + +static int abis_nm_send_multi_segments(struct gsm_bts *bts, uint8_t msg_type, + uint16_t ref, uint8_t * data, int len) +{ + int len_remain, len_to_send, max_send; + int seq = 0; + int ret; + + len_remain = len; + + while (len_remain) { + struct abis_om_hdr *oh; + struct abis_om_nokia_hdr *noh; + struct msgb *msg = nm_msgb_alloc(); + + if (seq == 0) + max_send = 256 - sizeof(struct abis_om_nokia_hdr); + else + max_send = 256; + + if (len_remain > max_send) { + len_to_send = max_send; + + if (seq == 0) { + /* first segment */ + oh = (struct abis_om_hdr *)msgb_put(msg, + ABIS_OM_NOKIA_HDR_SIZE + + + len_to_send); + + oh->mdisc = ABIS_OM_MDISC_FOM; + oh->placement = ABIS_OM_PLACEMENT_FIRST; /* first segment of multi-segment message */ + oh->sequence = seq; + oh->length = 0; /* 256 bytes */ + + noh = (struct abis_om_nokia_hdr *)oh->data; + + noh->msg_type = msg_type; + noh->spare = 0; + noh->reference = htons(ref); + memcpy(noh->data, data, len_to_send); + } else { + /* segment in between */ + oh = (struct abis_om_hdr *)msgb_put(msg, + sizeof + (struct + abis_om_hdr) + + + len_to_send); + + oh->mdisc = ABIS_OM_MDISC_FOM; + oh->placement = ABIS_OM_PLACEMENT_MIDDLE; /* segment of multi-segment message */ + oh->sequence = seq; + oh->length = 0; /* 256 bytes */ + + memcpy(oh->data, data, len_to_send); + } + } else { + + len_to_send = len_remain; + + /* check if message fits in a single segment */ + + if (seq == 0) + return abis_nm_send(bts, msg_type, ref, data, + len_to_send); + + /* last segment */ + + oh = (struct abis_om_hdr *)msgb_put(msg, + sizeof(struct + abis_om_hdr) + + len_to_send); + + oh->mdisc = ABIS_OM_MDISC_FOM; + oh->placement = ABIS_OM_PLACEMENT_LAST; /* last segment of multi-segment message */ + oh->sequence = seq; + oh->length = len_to_send; + + memcpy(oh->data, data, len_to_send); + } + + DEBUGPC(DNM, "Sending multi-segment %d\n", seq); + + ret = abis_nm_sendmsg(bts, msg); + if (ret < 0) + return ret; + + nokia_abis_nm_queue_send_next(bts); + + /* next segment */ + len_remain -= len_to_send; + data += len_to_send; + seq++; + } + + return 0; +} + +/* TODO: put in a separate file ? */ + +static int abis_nm_send_config(struct gsm_bts *bts, uint8_t bts_type) +{ + struct gsm_bts_trx *trx; + uint8_t config[2048]; /* TODO: might be too small if lots of TRX are used */ + int len = 0; + int idx = 0; + int ret; + int hopping = 0; + int need_hopping = 0; + + memset(config, 0, sizeof(config)); + + llist_for_each_entry(trx, &bts->trx_list, list) { +#if 0 /* debugging */ + printf("TRX\n"); + printf(" arfcn: %d\n", trx->arfcn); + printf(" bsic: %d\n", trx->bts->bsic); + uint8_t ca[20]; + memset(ca, 0xFF, sizeof(ca)); + ret = generate_cell_chan_list(ca, trx->bts); + printf(" ca (%d): %s\n", ret, osmo_hexdump(ca, sizeof(ca))); + int i; + for (i = 0; i < ARRAY_SIZE(trx->ts); i++) { + struct gsm_bts_trx_ts *ts = &trx->ts[i]; + + printf(" pchan %d: %d\n", i, ts->pchan); + } +#endif + ret = make_fu_config(trx, idx + 1, config + len, &hopping); + need_hopping |= hopping; + len += ret; + + idx++; + } + + ret = make_bts_config(bts_type, idx, config + len, need_hopping); + len += ret; + +#if 0 /* debugging */ + dump_elements(config, len); +#endif + + return abis_nm_send_multi_segments(bts, NOKIA_MSG_CONF_DATA, 1, config, + len); +} + +#define GET_NEXT_BYTE if(idx >= len) return 0; \ + ub = data[idx++]; + +static int find_element(uint8_t * data, int len, uint16_t id, uint8_t * value, + int max_value) +{ + uint8_t ub; + int idx = 0; + int found = 0; + int constructed __attribute__((unused)); + uint16_t id_value; + + for (;;) { + + GET_NEXT_BYTE; + + /* encoding bit, construced means that other elements are contained */ + constructed = ((ub & 0x20) ? 1 : 0); + + if ((ub & 0x1F) == 0x1F) { + /* fixed pattern, ID follows */ + GET_NEXT_BYTE; /* ID */ + id_value = ub & 0x7F; + if (ub & 0x80) { + /* extension bit */ + GET_NEXT_BYTE; /* ID low part */ + id_value = (id_value << 7) | (ub & 0x7F); + } + if (id_value == id) + found = 1; + } else { + id_value = (ub & 0x3F); + if (id_value == id) + found = 1; + } + + GET_NEXT_BYTE; /* length */ + + if (found) { + /* get data */ + uint8_t n = ub; + uint8_t i; + for (i = 0; i < n; i++) { + GET_NEXT_BYTE; + if (max_value <= 0) + return -1; /* buffer too small */ + *value = ub; + value++; + max_value--; + } + return n; /* length */ + } else { + /* skip data */ + uint8_t n = ub; + uint8_t i; + for (i = 0; i < n; i++) { + GET_NEXT_BYTE; + } + } + } + return 0; /* not found */ +} + +static int dump_elements(uint8_t * data, int len) +{ + uint8_t ub; + int idx = 0; + int constructed; + uint16_t id_value; + static char indent[100] = ""; /* TODO: move static to BTS context */ + + for (;;) { + + GET_NEXT_BYTE; + + /* encoding bit, construced means that other elements are contained */ + constructed = ((ub & 0x20) ? 1 : 0); + + if ((ub & 0x1F) == 0x1F) { + /* fixed pattern, ID follows */ + GET_NEXT_BYTE; /* ID */ + id_value = ub & 0x7F; + if (ub & 0x80) { + /* extension bit */ + GET_NEXT_BYTE; /* ID low part */ + id_value = (id_value << 7) | (ub & 0x7F); + } + + } else { + id_value = (ub & 0x3F); + } + + GET_NEXT_BYTE; /* length */ + + printf("%s--ID = 0x%02X (%s) %s\n", indent, id_value, + get_element_name_string(id_value), + constructed ? "** constructed **" : ""); + printf("%s length = %d\n", indent, ub); + printf("%s %s\n", indent, osmo_hexdump(data + idx, ub)); + + if (constructed) { + int indent_len = strlen(indent); + strcat(indent, " "); + + dump_elements(data + idx, ub); + + indent[indent_len] = 0; + } + /* skip data */ + uint8_t n = ub; + uint8_t i; + for (i = 0; i < n; i++) { + GET_NEXT_BYTE; + } + } + return 0; +} + +/* TODO: put in a separate file ? */ + +/* taken from abis_nm.c */ + +static void nokia_abis_nm_queue_send_next(struct gsm_bts *bts) +{ + int wait = 0; + struct msgb *msg; + /* the queue is empty */ + while (!llist_empty(&bts->abis_queue)) { + msg = msgb_dequeue(&bts->abis_queue); + wait = OBSC_NM_W_ACK_CB(msg); + abis_sendmsg(msg); + + if (wait) + break; + } + + bts->abis_nm_pend = wait; +} + +/* TODO: put in a separate file ? */ + +/* timer for restarting OML after BTS reset */ + +static void reset_timer_cb(void *_bts) +{ + struct gsm_bts *bts = _bts; + struct gsm_e1_subslot *e1_link = &bts->oml_e1_link; + struct e1inp_line *line; + + bts->nokia.wait_reset = 0; + + /* OML link */ + line = e1inp_line_find(e1_link->e1_nr); + if (!line) { + LOGP(DLINP, LOGL_ERROR, "BTS %u OML link referring to " + "non-existing E1 line %u\n", bts->nr, e1_link->e1_nr); + return; + } + + start_sabm_in_line(line, 0, -1); /* stop all first */ + start_sabm_in_line(line, 1, SAPI_OML); /* start only OML */ +} + +/* TODO: put in a separate file ? */ + +/* + This is how the configuration is done: + - start OML link + - reset BTS + - receive ACK, wait some time and restart OML link + - receive OMU STARTED message, send START DOWNLOAD REQ + - receive CNF REQ message, send CONF DATA + - receive ACK, start RSL link(s) + ACK some other messages received from the BTS. + + Probably its also possible to configure the BTS without a reset, this + has not been tested yet. +*/ + +static int abis_nm_rcvmsg_fom(struct msgb *mb) +{ + struct e1inp_sign_link *sign_link = (struct e1inp_sign_link *)mb->dst; + struct gsm_bts *bts = sign_link->trx->bts; + struct abis_om_hdr *oh = msgb_l2(mb); + struct abis_om_nokia_hdr *noh = msgb_l3(mb); + uint8_t mt = noh->msg_type; + int ret = 0; + uint16_t ref = ntohs(noh->reference); + uint8_t info[256]; + uint8_t ack = 0xFF; + uint8_t severity = 0xFF; + int str_len; + int len_data; + + if (bts->nokia.wait_reset) { + LOGP(DNM, LOGL_INFO, + "Ignore message while waiting for reset\n"); + return ret; + } + + if (oh->length < sizeof(struct abis_om_nokia_hdr)) { + LOGP(DNM, LOGL_ERROR, "Message too short\n"); + return -EINVAL; + } + + len_data = oh->length - sizeof(struct abis_om_nokia_hdr); + LOGP(DNM, LOGL_INFO, "(0x%02X) %s\n", mt, get_msg_type_name_string(mt)); +#if 0 /* debugging */ + dump_elements(noh->data, len_data); +#endif + + switch (mt) { + case NOKIA_MSG_OMU_STARTED: + if (find_element(noh->data, len_data, + NOKIA_EI_BTS_TYPE, &bts->nokia.bts_type, + sizeof(uint8_t)) == sizeof(uint8_t)) + LOGP(DNM, LOGL_INFO, "BTS type = %d (%s)\n", + bts->nokia.bts_type, + get_bts_type_string(bts->nokia.bts_type)); + else + LOGP(DNM, LOGL_ERROR, "BTS type not found\n"); + /* send START_DOWNLOAD_REQ */ + abis_nm_download_req(bts, ref); + break; + case NOKIA_MSG_MF_REQ: + break; + case NOKIA_MSG_CONF_REQ: + /* send ACK */ + abis_nm_ack(bts, ref); + nokia_abis_nm_queue_send_next(bts); + /* send CONF_DATA */ + abis_nm_send_config(bts, bts->nokia.bts_type); + bts->nokia.configured = 1; + break; + case NOKIA_MSG_ACK: + if (find_element + (noh->data, len_data, NOKIA_EI_ACK, &ack, + sizeof(uint8_t)) == sizeof(uint8_t)) { + LOGP(DNM, LOGL_INFO, "ACK = %d\n", ack); + if (ack != 1) { + LOGP(DNM, LOGL_ERROR, "No ACK received (%d)\n", + ack); + /* TODO: properly handle failures (NACK) */ + } + } else + LOGP(DNM, LOGL_ERROR, "ACK not found\n"); + + /* TODO: the assumption for the following is that no NACK was received */ + + /* ACK for reset message ? */ + if (!bts->nokia.did_reset) { + bts->nokia.did_reset = 1; + + /* + TODO: For the InSite processing the received data is + blocked in the driver during reset. + Otherwise the LAPD module might assert because the InSite + sends garbage on the E1 line during reset. + This is done by looking at "wait_reset" in the driver + (function handle_ts1_read()) and ignoring the received data. + It seems to be necessary for the MetroSite too. + */ + bts->nokia.wait_reset = 1; + + osmo_timer_setup(&bts->nokia.reset_timer, + reset_timer_cb, bts); + osmo_timer_schedule(&bts->nokia.reset_timer, bts->nokia.bts_reset_timer_cnf, 0); + + struct gsm_e1_subslot *e1_link = &bts->oml_e1_link; + struct e1inp_line *line; + /* OML link */ + line = e1inp_line_find(e1_link->e1_nr); + if (!line) { + LOGP(DLINP, LOGL_ERROR, + "BTS %u OML link referring to " + "non-existing E1 line %u\n", bts->nr, + e1_link->e1_nr); + return -ENOMEM; + } + + start_sabm_in_line(line, 0, -1); /* stop all first */ + } + + /* ACK for CONF DATA message ? */ + if (bts->nokia.configured != 0) { + /* start TRX (RSL link) */ + + struct gsm_e1_subslot *e1_link = + &sign_link->trx->rsl_e1_link; + struct e1inp_line *line; + + bts->nokia.configured = 0; + + /* RSL Link */ + line = e1inp_line_find(e1_link->e1_nr); + if (!line) { + LOGP(DLINP, LOGL_ERROR, + "TRX (%u/%u) RSL link referring " + "to non-existing E1 line %u\n", + sign_link->trx->bts->nr, sign_link->trx->nr, + e1_link->e1_nr); + return -ENOMEM; + } + /* start TRX */ + start_sabm_in_line(line, 1, SAPI_RSL); /* start only RSL */ + } + break; + case NOKIA_MSG_STATE_CHANGED: + /* send ACK */ + abis_nm_ack(bts, ref); + break; + case NOKIA_MSG_CONF_COMPLETE: + /* send ACK */ + abis_nm_ack(bts, ref); + break; + case NOKIA_MSG_BLOCK_CTRL_REQ: /* seems to be send when something goes wrong !? */ + /* send ACK (do we have to send an ACK ?) */ + abis_nm_ack(bts, ref); + break; + case NOKIA_MSG_ALARM: + find_element(noh->data, len_data, NOKIA_EI_SEVERITY, &severity, + sizeof(severity)); + /* TODO: there might be alarms with both elements set */ + str_len = + find_element(noh->data, len_data, NOKIA_EI_ADD_INFO, info, + sizeof(info)); + if (str_len > 0) { + info[str_len] = 0; + LOGP(DNM, LOGL_INFO, "ALARM Severity %s (%d) : %s\n", + get_severity_string(severity), severity, info); + } else { /* nothing found, try details */ + str_len = + find_element(noh->data, len_data, + NOKIA_EI_ALARM_DETAIL, info, + sizeof(info)); + if (str_len > 0) { + uint16_t code; + info[str_len] = 0; + code = (info[0] << 8) + info[1]; + LOGP(DNM, LOGL_INFO, + "ALARM Severity %s (%d), code 0x%X : %s\n", + get_severity_string(severity), severity, + code, info + 2); + } + } + /* send ACK */ + abis_nm_ack(bts, ref); + break; + } + + nokia_abis_nm_queue_send_next(bts); + + return ret; +} + +/* TODO: put in a separate file ? */ + +int abis_nokia_rcvmsg(struct msgb *msg) +{ + struct abis_om_hdr *oh = msgb_l2(msg); + int rc = 0; + + /* Various consistency checks */ + if (oh->placement != ABIS_OM_PLACEMENT_ONLY) { + LOGP(DNM, LOGL_ERROR, "ABIS OML placement 0x%x not supported\n", + oh->placement); + if (oh->placement != ABIS_OM_PLACEMENT_FIRST) + return -EINVAL; + } + if (oh->sequence != 0) { + LOGP(DNM, LOGL_ERROR, "ABIS OML sequence 0x%x != 0x00\n", + oh->sequence); + return -EINVAL; + } + msg->l3h = (unsigned char *)oh + sizeof(*oh); + + switch (oh->mdisc) { + case ABIS_OM_MDISC_FOM: + LOGP(DNM, LOGL_INFO, "ABIS_OM_MDISC_FOM\n"); + rc = abis_nm_rcvmsg_fom(msg); + break; + case ABIS_OM_MDISC_MANUF: + LOGP(DNM, LOGL_INFO, "ABIS_OM_MDISC_MANUF\n"); + break; + case ABIS_OM_MDISC_MMI: + case ABIS_OM_MDISC_TRAU: + LOGP(DNM, LOGL_ERROR, + "unimplemented ABIS OML message discriminator 0x%x\n", + oh->mdisc); + break; + default: + LOGP(DNM, LOGL_ERROR, + "unknown ABIS OML message discriminator 0x%x\n", + oh->mdisc); + return -EINVAL; + } + + msgb_free(msg); + return rc; +} + +static int bts_model_nokia_site_start(struct gsm_network *net); + +static void bts_model_nokia_site_e1line_bind_ops(struct e1inp_line *line) +{ + e1inp_line_bind_ops(line, &bts_isdn_e1inp_line_ops); +} + +static struct gsm_bts_model model_nokia_site = { + .type = GSM_BTS_TYPE_NOKIA_SITE, + .name = "nokia_site", + .start = bts_model_nokia_site_start, + .oml_rcvmsg = &abis_nokia_rcvmsg, + .e1line_bind_ops = &bts_model_nokia_site_e1line_bind_ops, +}; + +static struct gsm_network *my_net; + +static int bts_model_nokia_site_start(struct gsm_network *net) +{ + model_nokia_site.features.data = &model_nokia_site._features_data[0]; + model_nokia_site.features.data_len = + sizeof(model_nokia_site._features_data); + + osmo_bts_set_feature(&model_nokia_site.features, BTS_FEAT_HOPPING); + osmo_bts_set_feature(&model_nokia_site.features, BTS_FEAT_HSCSD); + osmo_bts_set_feature(&model_nokia_site.features, BTS_FEAT_MULTI_TSC); + + osmo_signal_register_handler(SS_L_INPUT, inp_sig_cb, NULL); + osmo_signal_register_handler(SS_L_GLOBAL, gbl_sig_cb, NULL); + osmo_signal_register_handler(SS_NM, nm_sig_cb, NULL); + + my_net = net; + + return 0; +} + +int bts_model_nokia_site_init(void) +{ + return gsm_bts_model_register(&model_nokia_site); +} diff --git a/src/osmo-bsc/bts_siemens_bs11.c b/src/osmo-bsc/bts_siemens_bs11.c new file mode 100644 index 000000000..2d2351702 --- /dev/null +++ b/src/osmo-bsc/bts_siemens_bs11.c @@ -0,0 +1,604 @@ +/* Siemens BS-11 specific code */ + +/* (C) 2009-2010 by Harald Welte <laforge@gnumonks.org> + * + * 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 <osmocom/gsm/tlv.h> + +#include <osmocom/bsc/debug.h> +#include <osmocom/bsc/gsm_data.h> +#include <osmocom/bsc/abis_nm.h> +#include <osmocom/abis/e1_input.h> +#include <osmocom/bsc/signal.h> + +static int bts_model_bs11_start(struct gsm_network *net); + +static void bts_model_bs11_e1line_bind_ops(struct e1inp_line *line) +{ + e1inp_line_bind_ops(line, &bts_isdn_e1inp_line_ops); +} + +static struct gsm_bts_model model_bs11 = { + .type = GSM_BTS_TYPE_BS11, + .name = "bs11", + .start = bts_model_bs11_start, + .oml_rcvmsg = &abis_nm_rcvmsg, + .e1line_bind_ops = bts_model_bs11_e1line_bind_ops, + .nm_att_tlvdef = { + .def = { + [NM_ATT_AVAIL_STATUS] = { TLV_TYPE_TLV }, + /* BS11 specifics */ + [NM_ATT_BS11_ESN_FW_CODE_NO] = { TLV_TYPE_TLV }, + [NM_ATT_BS11_ESN_HW_CODE_NO] = { TLV_TYPE_TLV }, + [NM_ATT_BS11_ESN_PCB_SERIAL] = { TLV_TYPE_TLV }, + [NM_ATT_BS11_BOOT_SW_VERS] = { TLV_TYPE_TLV }, + [0xd5] = { TLV_TYPE_TLV }, + [0xa8] = { TLV_TYPE_TLV }, + [NM_ATT_BS11_PASSWORD] = { TLV_TYPE_TLV }, + [NM_ATT_BS11_TXPWR] = { TLV_TYPE_TLV }, + [NM_ATT_BS11_RSSI_OFFS] = { TLV_TYPE_TLV }, + [NM_ATT_BS11_LINE_CFG] = { TLV_TYPE_TV }, + [NM_ATT_BS11_L1_PROT_TYPE] = { TLV_TYPE_TV }, + [NM_ATT_BS11_BIT_ERR_THESH] = { TLV_TYPE_FIXED, 2 }, + [NM_ATT_BS11_DIVERSITY] = { TLV_TYPE_TLV }, + [NM_ATT_BS11_LMT_LOGON_SESSION]={ TLV_TYPE_TLV }, + [NM_ATT_BS11_LMT_LOGIN_TIME] = { TLV_TYPE_TLV }, + [NM_ATT_BS11_LMT_USER_ACC_LEV] ={ TLV_TYPE_TLV }, + [NM_ATT_BS11_LMT_USER_NAME] = { TLV_TYPE_TLV }, + [NM_ATT_BS11_BTS_STATE] = { TLV_TYPE_TLV }, + [NM_ATT_BS11_E1_STATE] = { TLV_TYPE_TLV }, + [NM_ATT_BS11_PLL_MODE] = { TLV_TYPE_TLV }, + [NM_ATT_BS11_PLL] = { TLV_TYPE_TLV }, + [NM_ATT_BS11_CCLK_ACCURACY] = { TLV_TYPE_TV }, + [NM_ATT_BS11_CCLK_TYPE] = { TLV_TYPE_TV }, + [0x95] = { TLV_TYPE_FIXED, 2 }, + }, + }, +}; + +/* The following definitions are for OM and NM packets that we cannot yet + * generate by code but we just pass on */ + +// BTS Site Manager, SET ATTRIBUTES + +/* + Object Class: BTS Site Manager + Instance 1: FF + Instance 2: FF + Instance 3: FF +SET ATTRIBUTES + sAbisExternalTime: 2007/09/08 14:36:11 + omLAPDRelTimer: 30sec + shortLAPDIntTimer: 5sec + emergencyTimer1: 10 minutes + emergencyTimer2: 0 minutes +*/ + +unsigned char msg_1[] = +{ + NM_MT_BS11_SET_ATTR, NM_OC_SITE_MANAGER, 0xFF, 0xFF, 0xFF, + NM_ATT_BS11_ABIS_EXT_TIME, 0x07, + 0xD7, 0x09, 0x08, 0x0E, 0x24, 0x0B, 0xCE, + 0x02, + 0x00, 0x1E, + NM_ATT_BS11_SH_LAPD_INT_TIMER, + 0x01, 0x05, + 0x42, 0x02, 0x00, 0x0A, + 0x44, 0x02, 0x00, 0x00 +}; + +// BTS, SET BTS ATTRIBUTES + +/* + Object Class: BTS + BTS relat. Number: 0 + Instance 2: FF + Instance 3: FF +SET BTS ATTRIBUTES + bsIdentityCode / BSIC: + PLMN_colour_code: 7h + BS_colour_code: 7h + BTS Air Timer T3105: 4 ,unit 10 ms + btsIsHopping: FALSE + periodCCCHLoadIndication: 1sec + thresholdCCCHLoadIndication: 0% + cellAllocationNumber: 00h = GSM 900 + enableInterferenceClass: 00h = Disabled + fACCHQual: 6 (FACCH stealing flags minus 1) + intaveParameter: 31 SACCH multiframes + interferenceLevelBoundaries: + Interference Boundary 1: 0Ah + Interference Boundary 2: 0Fh + Interference Boundary 3: 14h + Interference Boundary 4: 19h + Interference Boundary 5: 1Eh + mSTxPwrMax: 11 + GSM range: 2=39dBm, 15=13dBm, stepsize 2 dBm + DCS1800 range: 0=30dBm, 15=0dBm, stepsize 2 dBm + PCS1900 range: 0=30dBm, 15=0dBm, stepsize 2 dBm + 30=33dBm, 31=32dBm + ny1: + Maximum number of repetitions for PHYSICAL INFORMATION message (GSM 04.08): 20 + powerOutputThresholds: + Out Power Fault Threshold: -10 dB + Red Out Power Threshold: - 6 dB + Excessive Out Power Threshold: 5 dB + rACHBusyThreshold: -127 dBm + rACHLoadAveragingSlots: 250 ,number of RACH burst periods + rfResourceIndicationPeriod: 125 SACCH multiframes + T200: + SDCCH: 044 in 5 ms + FACCH/Full rate: 031 in 5 ms + FACCH/Half rate: 041 in 5 ms + SACCH with TCH SAPI0: 090 in 10 ms + SACCH with SDCCH: 090 in 10 ms + SDCCH with SAPI3: 090 in 5 ms + SACCH with TCH SAPI3: 135 in 10 ms + tSync: 9000 units of 10 msec + tTrau: 9000 units of 10 msec + enableUmLoopTest: 00h = disabled + enableExcessiveDistance: 00h = Disabled + excessiveDistance: 64km + hoppingMode: 00h = baseband hopping + cellType: 00h = Standard Cell + BCCH ARFCN / bCCHFrequency: 1 +*/ + +static unsigned char bs11_attr_bts[] = +{ + NM_ATT_BSIC, HARDCODED_BSIC, + NM_ATT_BTS_AIR_TIMER, 0x04, + NM_ATT_BS11_BTSLS_HOPPING, 0x00, + NM_ATT_CCCH_L_I_P, 0x01, + NM_ATT_CCCH_L_T, 0x00, + NM_ATT_BS11_CELL_ALLOC_NR, NM_BS11_CANR_GSM, + NM_ATT_BS11_ENA_INTERF_CLASS, 0x01, + NM_ATT_BS11_FACCH_QUAL, 0x06, + /* interference avg. period in numbers of SACCH multifr */ + NM_ATT_INTAVE_PARAM, 0x1F, + NM_ATT_INTERF_BOUND, 0x0A, 0x0F, 0x14, 0x19, 0x1E, 0x7B, + NM_ATT_CCCH_L_T, 0x23, + NM_ATT_GSM_TIME, 0x28, 0x00, + NM_ATT_ADM_STATE, 0x03, + NM_ATT_RACH_B_THRESH, 0x7F, + NM_ATT_LDAVG_SLOTS, 0x00, 0xFA, + NM_ATT_BS11_RF_RES_IND_PER, 0x7D, + NM_ATT_T200, 0x2C, 0x1F, 0x29, 0x5A, 0x5A, 0x5A, 0x87, + NM_ATT_BS11_TSYNC, 0x23, 0x28, + NM_ATT_BS11_TTRAU, 0x23, 0x28, + NM_ATT_TEST_DUR, 0x01, 0x00, + NM_ATT_OUTST_ALARM, 0x01, 0x00, + NM_ATT_BS11_EXCESSIVE_DISTANCE, 0x01, 0x40, + NM_ATT_BS11_HOPPING_MODE, 0x01, 0x00, + NM_ATT_BS11_PLL, 0x01, 0x00, + NM_ATT_BCCH_ARFCN, 0x00, HARDCODED_ARFCN/*0x01*/, +}; + +// Handover Recognition, SET ATTRIBUTES + +/* +Illegal Contents GSM Formatted O&M Msg + Object Class: Handover Recognition + BTS relat. Number: 0 + Instance 2: FF + Instance 3: FF +SET ATTRIBUTES + enableDelayPowerBudgetHO: 00h = Disabled + enableDistanceHO: 00h = Disabled + enableInternalInterCellHandover: 00h = Disabled + enableInternalIntraCellHandover: 00h = Disabled + enablePowerBudgetHO: 00h = Disabled + enableRXLEVHO: 00h = Disabled + enableRXQUALHO: 00h = Disabled + hoAveragingDistance: 8 SACCH multiframes + hoAveragingLev: + A_LEV_HO: 8 SACCH multiframes + W_LEV_HO: 1 SACCH multiframes + hoAveragingPowerBudget: 16 SACCH multiframes + hoAveragingQual: + A_QUAL_HO: 8 SACCH multiframes + W_QUAL_HO: 2 SACCH multiframes + hoLowerThresholdLevDL: (10 - 110) dBm + hoLowerThresholdLevUL: (5 - 110) dBm + hoLowerThresholdQualDL: 06h = 6.4% < BER < 12.8% + hoLowerThresholdQualUL: 06h = 6.4% < BER < 12.8% + hoThresholdLevDLintra : (20 - 110) dBm + hoThresholdLevULintra: (20 - 110) dBm + hoThresholdMsRangeMax: 20 km + nCell: 06h + timerHORequest: 3 ,unit 2 SACCH multiframes +*/ + +unsigned char msg_3[] = +{ + NM_MT_BS11_SET_ATTR, NM_OC_BS11_HANDOVER, 0x00, 0xFF, 0xFF, + 0xD0, 0x00, /* enableDelayPowerBudgetHO */ + 0x64, 0x00, /* enableDistanceHO */ + 0x67, 0x00, /* enableInternalInterCellHandover */ + 0x68, 0x00, /* enableInternalInterCellHandover */ + 0x6A, 0x00, /* enablePowerBudgetHO */ + 0x6C, 0x00, /* enableRXLEVHO */ + 0x6D, 0x00, /* enableRXQUALHO */ + 0x6F, 0x08, /* hoAveragingDistance */ + 0x70, 0x08, 0x01, /* hoAveragingLev */ + 0x71, 0x10, 0x10, 0x10, + 0x72, 0x08, 0x02, /* hoAveragingQual */ + 0x73, 0x0A, /* hoLowerThresholdLevDL */ + 0x74, 0x05, /* hoLowerThresholdLevUL */ + 0x75, 0x06, /* hoLowerThresholdQualDL */ + 0x76, 0x06, /* hoLowerThresholdQualUL */ + 0x78, 0x14, /* hoThresholdLevDLintra */ + 0x79, 0x14, /* hoThresholdLevULintra */ + 0x7A, 0x14, /* hoThresholdMsRangeMax */ + 0x7D, 0x06, /* nCell */ + NM_ATT_BS11_TIMER_HO_REQUEST, 0x03, + 0x20, 0x01, 0x00, + 0x45, 0x01, 0x00, + 0x48, 0x01, 0x00, + 0x5A, 0x01, 0x00, + 0x5B, 0x01, 0x05, + 0x5E, 0x01, 0x1A, + 0x5F, 0x01, 0x20, + 0x9D, 0x01, 0x00, + 0x47, 0x01, 0x00, + 0x5C, 0x01, 0x64, + 0x5D, 0x01, 0x1E, + 0x97, 0x01, 0x20, + 0xF7, 0x01, 0x3C, +}; + +// Power Control, SET ATTRIBUTES + +/* + Object Class: Power Control + BTS relat. Number: 0 + Instance 2: FF + Instance 3: FF +SET ATTRIBUTES + enableMsPowerControl: 00h = Disabled + enablePowerControlRLFW: 00h = Disabled + pcAveragingLev: + A_LEV_PC: 4 SACCH multiframes + W_LEV_PC: 1 SACCH multiframes + pcAveragingQual: + A_QUAL_PC: 4 SACCH multiframes + W_QUAL_PC: 2 SACCH multiframes + pcLowerThresholdLevDL: 0Fh + pcLowerThresholdLevUL: 0Ah + pcLowerThresholdQualDL: 05h = 3.2% < BER < 6.4% + pcLowerThresholdQualUL: 05h = 3.2% < BER < 6.4% + pcRLFThreshold: 0Ch + pcUpperThresholdLevDL: 14h + pcUpperThresholdLevUL: 0Fh + pcUpperThresholdQualDL: 04h = 1.6% < BER < 3.2% + pcUpperThresholdQualUL: 04h = 1.6% < BER < 3.2% + powerConfirm: 2 ,unit 2 SACCH multiframes + powerControlInterval: 2 ,unit 2 SACCH multiframes + powerIncrStepSize: 02h = 4 dB + powerRedStepSize: 01h = 2 dB + radioLinkTimeoutBs: 64 SACCH multiframes + enableBSPowerControl: 00h = disabled +*/ + +unsigned char msg_4[] = +{ + NM_MT_BS11_SET_ATTR, NM_OC_BS11_PWR_CTRL, 0x00, 0xFF, 0xFF, + NM_ATT_BS11_ENA_MS_PWR_CTRL, 0x00, + NM_ATT_BS11_ENA_PWR_CTRL_RLFW, 0x00, + 0x7E, 0x04, 0x01, /* pcAveragingLev */ + 0x7F, 0x04, 0x02, /* pcAveragingQual */ + 0x80, 0x0F, /* pcLowerThresholdLevDL */ + 0x81, 0x0A, /* pcLowerThresholdLevUL */ + 0x82, 0x05, /* pcLowerThresholdQualDL */ + 0x83, 0x05, /* pcLowerThresholdQualUL */ + 0x84, 0x0C, /* pcRLFThreshold */ + 0x85, 0x14, /* pcUpperThresholdLevDL */ + 0x86, 0x0F, /* pcUpperThresholdLevUL */ + 0x87, 0x04, /* pcUpperThresholdQualDL */ + 0x88, 0x04, /* pcUpperThresholdQualUL */ + 0x89, 0x02, /* powerConfirm */ + 0x8A, 0x02, /* powerConfirmInterval */ + 0x8B, 0x02, /* powerIncrStepSize */ + 0x8C, 0x01, /* powerRedStepSize */ + 0x8D, 0x40, /* radioLinkTimeoutBs */ + 0x65, 0x01, 0x00 // set to 0x01 to enable BSPowerControl +}; + + +// Transceiver, SET TRX ATTRIBUTES (TRX 0) + +/* + Object Class: Transceiver + BTS relat. Number: 0 + Tranceiver number: 0 + Instance 3: FF +SET TRX ATTRIBUTES + aRFCNList (HEX): 0001 + txPwrMaxReduction: 00h = 30dB + radioMeasGran: 254 SACCH multiframes + radioMeasRep: 01h = enabled + memberOfEmergencyConfig: 01h = TRUE + trxArea: 00h = TRX doesn't belong to a concentric cell +*/ + +static unsigned char bs11_attr_radio[] = +{ + NM_ATT_ARFCN_LIST, 0x01, 0x00, HARDCODED_ARFCN /*0x01*/, + NM_ATT_RF_MAXPOWR_R, 0x00, + NM_ATT_BS11_RADIO_MEAS_GRAN, 0x01, 0x05, + NM_ATT_BS11_RADIO_MEAS_REP, 0x01, 0x01, + NM_ATT_BS11_EMRG_CFG_MEMBER, 0x01, 0x01, + NM_ATT_BS11_TRX_AREA, 0x01, 0x00, +}; + +/* + * Patch the various SYSTEM INFORMATION tables to update + * the LAI + */ +static void patch_nm_tables(struct gsm_bts *bts) +{ + uint8_t arfcn_low = bts->c0->arfcn & 0xff; + uint8_t arfcn_high = (bts->c0->arfcn >> 8) & 0x0f; + + /* T3105 attribute in units of 10ms */ + bs11_attr_bts[2] = bts->network->T3105 / 10; + + /* patch ARFCN into BTS Attributes */ + bs11_attr_bts[69] &= 0xf0; + bs11_attr_bts[69] |= arfcn_high; + bs11_attr_bts[70] = arfcn_low; + + /* patch ARFCN into TRX Attributes */ + bs11_attr_radio[2] &= 0xf0; + bs11_attr_radio[2] |= arfcn_high; + bs11_attr_radio[3] = arfcn_low; + + /* patch the RACH attributes */ + if (bts->rach_b_thresh != -1) + bs11_attr_bts[33] = bts->rach_b_thresh & 0xff; + + if (bts->rach_ldavg_slots != -1) { + uint8_t avg_high = bts->rach_ldavg_slots & 0xff; + uint8_t avg_low = (bts->rach_ldavg_slots >> 8) & 0x0f; + + bs11_attr_bts[35] = avg_high; + bs11_attr_bts[36] = avg_low; + } + + /* patch BSIC */ + bs11_attr_bts[1] = bts->bsic; + + /* patch the power reduction */ + bs11_attr_radio[5] = bts->c0->max_power_red / 2; +} + + +static void nm_reconfig_ts(struct gsm_bts_trx_ts *ts) +{ + enum abis_nm_chan_comb ccomb = abis_nm_chcomb4pchan(ts->pchan); + struct gsm_e1_subslot *e1l = &ts->e1_link; + + abis_nm_set_channel_attr(ts, ccomb); + + if (is_ipaccess_bts(ts->trx->bts)) + return; + + if (ts_is_tch(ts)) + abis_nm_conn_terr_traf(ts, e1l->e1_nr, e1l->e1_ts, + e1l->e1_ts_ss); +} + +static void nm_reconfig_trx(struct gsm_bts_trx *trx) +{ + struct gsm_e1_subslot *e1l = &trx->rsl_e1_link; + int i; + + patch_nm_tables(trx->bts); + + switch (trx->bts->type) { + case GSM_BTS_TYPE_BS11: + /* FIXME: discover this by fetching an attribute */ +#if 0 + trx->nominal_power = 15; /* 15dBm == 30mW PA configuration */ +#else + trx->nominal_power = 24; /* 24dBm == 250mW PA configuration */ +#endif + abis_nm_conn_terr_sign(trx, e1l->e1_nr, e1l->e1_ts, + e1l->e1_ts_ss); + abis_nm_establish_tei(trx->bts, trx->nr, e1l->e1_nr, + e1l->e1_ts, e1l->e1_ts_ss, trx->rsl_tei); + + /* Set Radio Attributes */ + if (trx == trx->bts->c0) + abis_nm_set_radio_attr(trx, bs11_attr_radio, + sizeof(bs11_attr_radio)); + else { + uint8_t trx1_attr_radio[sizeof(bs11_attr_radio)]; + uint8_t arfcn_low = trx->arfcn & 0xff; + uint8_t arfcn_high = (trx->arfcn >> 8) & 0x0f; + memcpy(trx1_attr_radio, bs11_attr_radio, + sizeof(trx1_attr_radio)); + + /* patch ARFCN into TRX Attributes */ + trx1_attr_radio[2] &= 0xf0; + trx1_attr_radio[2] |= arfcn_high; + trx1_attr_radio[3] = arfcn_low; + + abis_nm_set_radio_attr(trx, trx1_attr_radio, + sizeof(trx1_attr_radio)); + } + break; + case GSM_BTS_TYPE_NANOBTS: + switch (trx->bts->band) { + case GSM_BAND_850: + case GSM_BAND_900: + trx->nominal_power = 20; + break; + case GSM_BAND_1800: + case GSM_BAND_1900: + trx->nominal_power = 23; + break; + default: + LOGP(DNM, LOGL_ERROR, "Unsupported nanoBTS GSM band %s\n", + gsm_band_name(trx->bts->band)); + break; + } + break; + default: + break; + } + + for (i = 0; i < TRX_NR_TS; i++) + nm_reconfig_ts(&trx->ts[i]); +} + +static void nm_reconfig_bts(struct gsm_bts *bts) +{ + struct gsm_bts_trx *trx; + + switch (bts->type) { + case GSM_BTS_TYPE_BS11: + patch_nm_tables(bts); + abis_nm_raw_msg(bts, sizeof(msg_1), msg_1); /* set BTS SiteMgr attr*/ + abis_nm_set_bts_attr(bts, bs11_attr_bts, sizeof(bs11_attr_bts)); + abis_nm_raw_msg(bts, sizeof(msg_3), msg_3); /* set BTS handover attr */ + abis_nm_raw_msg(bts, sizeof(msg_4), msg_4); /* set BTS power control attr */ + break; + default: + break; + } + + llist_for_each_entry(trx, &bts->trx_list, list) + nm_reconfig_trx(trx); +} + + +static void bootstrap_om_bs11(struct gsm_bts *bts) +{ + LOGP(DNM, LOGL_NOTICE, "bootstrapping OML for BTS %u\n", bts->nr); + + /* stop sending event reports */ + abis_nm_event_reports(bts, 0); + + /* begin DB transmission */ + abis_nm_bs11_db_transmission(bts, 1); + + /* end DB transmission */ + abis_nm_bs11_db_transmission(bts, 0); + + /* Reset BTS Site manager resource */ + abis_nm_bs11_reset_resource(bts); + + /* begin DB transmission */ + abis_nm_bs11_db_transmission(bts, 1); + + /* reconfigure BTS with all TRX and all TS */ + nm_reconfig_bts(bts); + + /* end DB transmission */ + abis_nm_bs11_db_transmission(bts, 0); + + /* Reset BTS Site manager resource */ + abis_nm_bs11_reset_resource(bts); + + /* restart sending event reports */ + abis_nm_event_reports(bts, 1); +} + +static int shutdown_om(struct gsm_bts *bts) +{ + /* stop sending event reports */ + abis_nm_event_reports(bts, 0); + + /* begin DB transmission */ + abis_nm_bs11_db_transmission(bts, 1); + + /* end DB transmission */ + abis_nm_bs11_db_transmission(bts, 0); + + /* Reset BTS Site manager resource */ + abis_nm_bs11_reset_resource(bts); + + gsm_bts_mark_all_ts_uninitialized(bts); + + return 0; +} + +/* Callback function to be called every time we receive a signal from INPUT */ +static int gbl_sig_cb(unsigned int subsys, unsigned int signal, + void *handler_data, void *signal_data) +{ + struct gsm_bts *bts; + + if (subsys != SS_L_GLOBAL) + return 0; + + switch (signal) { + case S_GLOBAL_BTS_CLOSE_OM: + bts = signal_data; + if (bts->type == GSM_BTS_TYPE_BS11) + shutdown_om(signal_data); + break; + } + + return 0; +} + +/* Callback function to be called every time we receive a signal from INPUT */ +static int inp_sig_cb(unsigned int subsys, unsigned int signal, + void *handler_data, void *signal_data) +{ + struct input_signal_data *isd = signal_data; + + if (subsys != SS_L_INPUT) + return 0; + + switch (signal) { + case S_L_INP_TEI_UP: + switch (isd->link_type) { + case E1INP_SIGN_OML: + if (isd->trx->bts->type == GSM_BTS_TYPE_BS11) + bootstrap_om_bs11(isd->trx->bts); + break; + } + } + + return 0; +} + +static int bts_model_bs11_start(struct gsm_network *net) +{ + model_bs11.features.data = &model_bs11._features_data[0]; + model_bs11.features.data_len = sizeof(model_bs11._features_data); + + osmo_bts_set_feature(&model_bs11.features, BTS_FEAT_HOPPING); + osmo_bts_set_feature(&model_bs11.features, BTS_FEAT_HSCSD); + osmo_bts_set_feature(&model_bs11.features, BTS_FEAT_MULTI_TSC); + + osmo_signal_register_handler(SS_L_INPUT, inp_sig_cb, NULL); + osmo_signal_register_handler(SS_L_GLOBAL, gbl_sig_cb, NULL); + + return 0; +} + +int bts_model_bs11_init(void) +{ + return gsm_bts_model_register(&model_bs11); +} diff --git a/src/osmo-bsc/bts_sysmobts.c b/src/osmo-bsc/bts_sysmobts.c new file mode 100644 index 000000000..91d1118c5 --- /dev/null +++ b/src/osmo-bsc/bts_sysmobts.c @@ -0,0 +1,60 @@ +/* sysmocom sysmoBTS specific code */ + +/* (C) 2010-2012 by Harald Welte <laforge@gnumonks.org> + * + * 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 <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/talloc.h> +#include <osmocom/bsc/gsm_data.h> +#include <osmocom/bsc/abis_nm.h> +#include <osmocom/bsc/abis_rsl.h> +#include <osmocom/bsc/debug.h> +#include <osmocom/abis/subchan_demux.h> +#include <osmocom/abis/ipaccess.h> +#include <osmocom/core/logging.h> + +extern struct gsm_bts_model bts_model_nanobts; + +static struct gsm_bts_model model_sysmobts; + +int bts_model_sysmobts_init(void) +{ + model_sysmobts = bts_model_nanobts; + model_sysmobts.name = "sysmobts"; + model_sysmobts.type = GSM_BTS_TYPE_OSMOBTS; + + model_sysmobts.features.data = &model_sysmobts._features_data[0]; + model_sysmobts.features.data_len = + sizeof(model_sysmobts._features_data); + memset(model_sysmobts.features.data, 0, sizeof(model_sysmobts.features.data_len)); + + osmo_bts_set_feature(&model_sysmobts.features, BTS_FEAT_GPRS); + osmo_bts_set_feature(&model_sysmobts.features, BTS_FEAT_EGPRS); + + return gsm_bts_model_register(&model_sysmobts); +} diff --git a/src/osmo-bsc/bts_unknown.c b/src/osmo-bsc/bts_unknown.c new file mode 100644 index 000000000..5ecf875c1 --- /dev/null +++ b/src/osmo-bsc/bts_unknown.c @@ -0,0 +1,40 @@ +/* Generic BTS - VTY code tries to allocate this BTS before type is known */ + +/* (C) 2010 by Daniel Willmann <daniel@totalueberwachung.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 <osmocom/bsc/gsm_data.h> +#include <osmocom/gsm/tlv.h> +#include <osmocom/bsc/abis_nm.h> + +static struct gsm_bts_model model_unknown = { + .type = GSM_BTS_TYPE_UNKNOWN, + .name = "unknown", + .oml_rcvmsg = &abis_nm_rcvmsg, + .nm_att_tlvdef = { + .def = { + }, + }, +}; + +int bts_model_unknown_init(void) +{ + return gsm_bts_model_register(&model_unknown); +} diff --git a/src/osmo-bsc/chan_alloc.c b/src/osmo-bsc/chan_alloc.c new file mode 100644 index 000000000..a24fbea94 --- /dev/null +++ b/src/osmo-bsc/chan_alloc.c @@ -0,0 +1,734 @@ +/* GSM Channel allocation routines + * + * (C) 2008 by Harald Welte <laforge@gnumonks.org> + * (C) 2008, 2009 by Holger Hans Peter Freyther <zecke@selfish.org> + * + * 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 <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <inttypes.h> + +#include <osmocom/bsc/chan_alloc.h> +#include <osmocom/bsc/abis_nm.h> +#include <osmocom/bsc/abis_rsl.h> +#include <osmocom/bsc/debug.h> +#include <osmocom/bsc/signal.h> +#include <osmocom/bsc/gsm_04_08_utils.h> + +#include <osmocom/core/talloc.h> + +bool ts_is_usable(const struct gsm_bts_trx_ts *ts) +{ + if (!trx_is_usable(ts->trx)) { + LOGP(DRLL, LOGL_DEBUG, "%s not usable\n", gsm_trx_name(ts->trx)); + return false; + } + + /* If a TCH/F_PDCH TS is busy changing, it is already taken or not + * yet available. */ + if (ts->pchan == GSM_PCHAN_TCH_F_PDCH) { + if (ts->flags & TS_F_PDCH_PENDING_MASK) { + LOGP(DRLL, LOGL_DEBUG, "%s in switchover, not available\n", + gsm_ts_and_pchan_name(ts)); + return false; + } + } + + /* If a dynamic channel is busy changing, it is already taken or not + * yet available. */ + if (ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH) { + if (ts->dyn.pchan_is != ts->dyn.pchan_want) { + LOGP(DRLL, LOGL_DEBUG, "%s in switchover, not available\n", + gsm_ts_and_pchan_name(ts)); + return false; + } + } + + return true; +} + +static int trx_count_free_ts(struct gsm_bts_trx *trx, enum gsm_phys_chan_config pchan) +{ + struct gsm_bts_trx_ts *ts; + int j, ss; + int count = 0; + + if (!trx_is_usable(trx)) + return 0; + + for (j = 0; j < ARRAY_SIZE(trx->ts); j++) { + enum gsm_phys_chan_config ts_pchan_is; + ts = &trx->ts[j]; + if (!ts_is_usable(ts)) + continue; + + ts_pchan_is = ts_pchan(ts); + + if (ts_pchan_is == GSM_PCHAN_PDCH) { + /* Dynamic timeslots in PDCH mode will become TCH if needed. */ + switch (ts->pchan) { + case GSM_PCHAN_TCH_F_PDCH: + if (pchan == GSM_PCHAN_TCH_F) + count++; + continue; + + case GSM_PCHAN_TCH_F_TCH_H_PDCH: + if (pchan == GSM_PCHAN_TCH_F) + count++; + else if (pchan == GSM_PCHAN_TCH_H) + count += 2; + continue; + + default: + /* Not dynamic, not applicable. */ + continue; + } + } + + if (ts_pchan_is != pchan) + continue; + /* check if all sub-slots are allocated yet */ + for (ss = 0; ss < ts_subslots(ts); ss++) { + struct gsm_lchan *lc = &ts->lchan[ss]; + if (lc->type == GSM_LCHAN_NONE && + lc->state == LCHAN_S_NONE) + count++; + } + } + + return count; +} + +/* Count number of free TS of given pchan type */ +int bts_count_free_ts(struct gsm_bts *bts, enum gsm_phys_chan_config pchan) +{ + struct gsm_bts_trx *trx; + int count = 0; + + llist_for_each_entry(trx, &bts->trx_list, list) + count += trx_count_free_ts(trx, pchan); + + return count; +} + +static bool ts_usable_as_pchan(struct gsm_bts_trx_ts *ts, + enum gsm_phys_chan_config as_pchan) +{ + switch (ts->pchan) { + case GSM_PCHAN_TCH_F_PDCH: + if (ts->flags & TS_F_PDCH_PENDING_MASK) { + /* currently being switched over. Not usable. */ + return false; + } + switch (as_pchan) { + case GSM_PCHAN_TCH_F: + case GSM_PCHAN_PDCH: + /* continue to check below. */ + break; + default: + return false; + } + break; + + case GSM_PCHAN_TCH_F_TCH_H_PDCH: + if (ts->dyn.pchan_is != ts->dyn.pchan_want) { + /* currently being switched over. Not usable. */ + return false; + } + switch (as_pchan) { + case GSM_PCHAN_TCH_F: + case GSM_PCHAN_TCH_H: + case GSM_PCHAN_PDCH: + /* continue to check below. */ + break; + default: + return false; + } + break; + + default: + /* static timeslots never switch. */ + return ts->pchan == as_pchan; + } + + /* Dynamic timeslots -- Checks depending on the current actual pchan mode: */ + switch (ts_pchan(ts)) { + case GSM_PCHAN_NONE: + /* Not initialized, possibly because GPRS was disabled. We may switch. */ + return true; + + case GSM_PCHAN_PDCH: + /* This slot is in PDCH mode and available to switch pchan mode. But check for + * error states: */ + if (ts->lchan->state != LCHAN_S_NONE && ts->lchan->state != LCHAN_S_ACTIVE) + return false; + return true; + + case GSM_PCHAN_TCH_F: + case GSM_PCHAN_TCH_H: + /* No need to switch at all? */ + if (ts_pchan(ts) == as_pchan) + return true; + + /* If any lchan is in use, we can't change the pchan kind */ + { + int ss; + int subslots = ts_subslots(ts); + for (ss = 0; ss < subslots; ss++) { + struct gsm_lchan *lc = &ts->lchan[ss]; + if (lc->type != GSM_LCHAN_NONE || lc->state != LCHAN_S_NONE) + return false; + } + } + return true; + + default: + /* Not implemented. */ + return false; + } +} + +static struct gsm_lchan * +_lc_find_trx(struct gsm_bts_trx *trx, enum gsm_phys_chan_config pchan, + enum gsm_phys_chan_config as_pchan) +{ + struct gsm_bts_trx_ts *ts; + int j, start, stop, dir, ss; + int check_subslots; + +#define LOGPLCHANALLOC(fmt, args...) \ + LOGP(DRLL, LOGL_DEBUG, "looking for lchan %s as %s: " fmt, \ + gsm_pchan_name(pchan), gsm_pchan_name(as_pchan), ## args) + + if (!trx_is_usable(trx)) { + LOGPLCHANALLOC("%s trx not usable\n", gsm_trx_name(trx)); + return NULL; + } + + if (trx->bts->chan_alloc_reverse) { + /* check TS 7..0 */ + start = 7; + stop = -1; + dir = -1; + } else { + /* check TS 0..7 */ + start = 0; + stop = 8; + dir = 1; + } + + for (j = start; j != stop; j += dir) { + ts = &trx->ts[j]; + if (!ts_is_usable(ts)) + continue; + /* The caller first selects what kind of TS to search in, e.g. looking for exact + * GSM_PCHAN_TCH_F, or maybe among dynamic GSM_PCHAN_TCH_F_TCH_H_PDCH... */ + if (ts->pchan != pchan) { + LOGPLCHANALLOC("%s is != %s\n", gsm_ts_and_pchan_name(ts), + gsm_pchan_name(pchan)); + continue; + } + /* Next, is this timeslot in or can it be switched to the pchan we want to use it for? */ + if (!ts_usable_as_pchan(ts, as_pchan)) { + LOGPLCHANALLOC("%s is not usable as %s\n", gsm_ts_and_pchan_name(ts), + gsm_pchan_name(as_pchan)); + continue; + } + /* If we need to switch it, after above check we are also allowed to switch it, and we + * will always use the first lchan after the switch. Return that lchan and rely on the + * caller to perform the pchan switchover. */ + if (ts_pchan(ts) != as_pchan) { + LOGPLCHANALLOC("%s is a match, will switch to %s\n", gsm_ts_and_pchan_name(ts), + gsm_pchan_name(as_pchan)); + return ts->lchan; + } + + /* TS is in desired pchan mode. Go ahead and check for an available lchan. */ + check_subslots = ts_subslots(ts); + for (ss = 0; ss < check_subslots; ss++) { + struct gsm_lchan *lc = &ts->lchan[ss]; + if (lc->type == GSM_LCHAN_NONE && + lc->state == LCHAN_S_NONE) { + LOGPLCHANALLOC("%s ss=%d is available\n", gsm_ts_and_pchan_name(ts), + lc->nr); + return lc; + } + LOGPLCHANALLOC("%s ss=%d in type=%s,state=%s not suitable\n", + gsm_ts_and_pchan_name(ts), lc->nr, gsm_lchant_name(lc->type), + gsm_lchans_name(lc->state)); + } + } + + return NULL; +#undef LOGPLCHANALLOC +} + +static struct gsm_lchan * +_lc_dyn_find_bts(struct gsm_bts *bts, enum gsm_phys_chan_config pchan, + enum gsm_phys_chan_config dyn_as_pchan) +{ + struct gsm_bts_trx *trx; + struct gsm_lchan *lc; + + if (bts->chan_alloc_reverse) { + llist_for_each_entry_reverse(trx, &bts->trx_list, list) { + lc = _lc_find_trx(trx, pchan, dyn_as_pchan); + if (lc) + return lc; + } + } else { + llist_for_each_entry(trx, &bts->trx_list, list) { + lc = _lc_find_trx(trx, pchan, dyn_as_pchan); + if (lc) + return lc; + } + } + + return NULL; +} + +static struct gsm_lchan * +_lc_find_bts(struct gsm_bts *bts, enum gsm_phys_chan_config pchan) +{ + return _lc_dyn_find_bts(bts, pchan, pchan); +} + +/* Allocate a logical channel. + * + * Dynamic channel types: we always prefer a dedicated TS, and only pick + + * switch a dynamic TS if no pure TS of the requested PCHAN is available. + * + * TCH_F/PDCH: if we pick a PDCH ACT style dynamic TS as TCH/F channel, PDCH + * will be disabled in rsl_chan_activate_lchan(); there is no need to check + * whether PDCH mode is currently active, here. + */ +struct gsm_lchan *lchan_alloc(struct gsm_bts *bts, enum gsm_chan_t type, + int allow_bigger) +{ + struct gsm_lchan *lchan = NULL; + enum gsm_phys_chan_config first, first_cbch, second, second_cbch; + + LOGP(DRLL, LOGL_DEBUG, "(bts=%d) lchan_alloc(%s)\n", bts->nr, gsm_lchant_name(type)); + + switch (type) { + case GSM_LCHAN_SDCCH: + if (bts->chan_alloc_reverse) { + first = GSM_PCHAN_SDCCH8_SACCH8C; + first_cbch = GSM_PCHAN_SDCCH8_SACCH8C_CBCH; + second = GSM_PCHAN_CCCH_SDCCH4; + second_cbch = GSM_PCHAN_CCCH_SDCCH4_CBCH; + } else { + first = GSM_PCHAN_CCCH_SDCCH4; + first_cbch = GSM_PCHAN_CCCH_SDCCH4_CBCH; + second = GSM_PCHAN_SDCCH8_SACCH8C; + second_cbch = GSM_PCHAN_SDCCH8_SACCH8C_CBCH; + } + + lchan = _lc_find_bts(bts, first); + if (lchan == NULL) + lchan = _lc_find_bts(bts, first_cbch); + if (lchan == NULL) + lchan = _lc_find_bts(bts, second); + if (lchan == NULL) + lchan = _lc_find_bts(bts, second_cbch); + + /* allow to assign bigger channels */ + if (allow_bigger) { + if (lchan == NULL) { + lchan = _lc_find_bts(bts, GSM_PCHAN_TCH_H); + if (lchan) + type = GSM_LCHAN_TCH_H; + } + + if (lchan == NULL) { + lchan = _lc_find_bts(bts, GSM_PCHAN_TCH_F); + if (lchan) + type = GSM_LCHAN_TCH_F; + } + + /* try dynamic TCH/F_PDCH */ + if (lchan == NULL) { + lchan = _lc_dyn_find_bts(bts, GSM_PCHAN_TCH_F_PDCH, + GSM_PCHAN_TCH_F); + /* TCH/F_PDCH will be used as TCH/F */ + if (lchan) + type = GSM_LCHAN_TCH_F; + } + + /* try fully dynamic TCH/F_TCH/H_PDCH */ + if (lchan == NULL) { + lchan = _lc_dyn_find_bts(bts, GSM_PCHAN_TCH_F_TCH_H_PDCH, + GSM_PCHAN_TCH_H); + if (lchan) + type = GSM_LCHAN_TCH_H; + } + /* + * No need to check fully dynamic channels for TCH/F: + * if no TCH/H was available, neither will be TCH/F. + */ + } + break; + case GSM_LCHAN_TCH_F: + lchan = _lc_find_bts(bts, GSM_PCHAN_TCH_F); + /* If we don't have TCH/F available, fall-back to TCH/H */ + if (!lchan) { + lchan = _lc_find_bts(bts, GSM_PCHAN_TCH_H); + if (lchan) + type = GSM_LCHAN_TCH_H; + } + /* If we don't have TCH/H either, try dynamic TCH/F_PDCH */ + if (!lchan) { + lchan = _lc_dyn_find_bts(bts, GSM_PCHAN_TCH_F_PDCH, + GSM_PCHAN_TCH_F); + /* TCH/F_PDCH used as TCH/F -- here, type is already + * set to GSM_LCHAN_TCH_F, but for clarity's sake... */ + if (lchan) + type = GSM_LCHAN_TCH_F; + } + + /* Try fully dynamic TCH/F_TCH/H_PDCH as TCH/F... */ + if (!lchan && bts->network->dyn_ts_allow_tch_f) { + lchan = _lc_dyn_find_bts(bts, + GSM_PCHAN_TCH_F_TCH_H_PDCH, + GSM_PCHAN_TCH_F); + if (lchan) + type = GSM_LCHAN_TCH_F; + } + /* ...and as TCH/H. */ + if (!lchan) { + lchan = _lc_dyn_find_bts(bts, + GSM_PCHAN_TCH_F_TCH_H_PDCH, + GSM_PCHAN_TCH_H); + if (lchan) + type = GSM_LCHAN_TCH_H; + } + break; + case GSM_LCHAN_TCH_H: + lchan = _lc_find_bts(bts, GSM_PCHAN_TCH_H); + /* If we don't have TCH/H available, fall-back to TCH/F */ + if (!lchan) { + lchan = _lc_find_bts(bts, GSM_PCHAN_TCH_F); + if (lchan) + type = GSM_LCHAN_TCH_F; + } + /* No dedicated TCH/x available -- try fully dynamic + * TCH/F_TCH/H_PDCH */ + if (!lchan) { + lchan = _lc_dyn_find_bts(bts, + GSM_PCHAN_TCH_F_TCH_H_PDCH, + GSM_PCHAN_TCH_H); + if (lchan) + type = GSM_LCHAN_TCH_H; + } + /* + * No need to check TCH/F_TCH/H_PDCH channels for TCH/F: + * if no TCH/H was available, neither will be TCH/F. + */ + /* If we don't have TCH/F either, try dynamic TCH/F_PDCH */ + if (!lchan) { + lchan = _lc_dyn_find_bts(bts, GSM_PCHAN_TCH_F_PDCH, + GSM_PCHAN_TCH_F); + if (lchan) + type = GSM_LCHAN_TCH_F; + } + break; + default: + LOGP(DRLL, LOGL_ERROR, "Unknown gsm_chan_t %u\n", type); + } + + if (lchan) { + lchan->type = type; + + LOGP(DRLL, LOGL_INFO, "%s Allocating lchan=%u as %s\n", + gsm_ts_and_pchan_name(lchan->ts), + lchan->nr, gsm_lchant_name(lchan->type)); + + /* reset measurement report counter and index */ + lchan->meas_rep_count = 0; + lchan->meas_rep_idx = 0; + lchan->meas_rep_last_seen_nr = 255; + + /* clear sapis */ + memset(lchan->sapis, 0, ARRAY_SIZE(lchan->sapis)); + + /* clear multi rate config */ + memset(&lchan->mr_ms_lv, 0, sizeof(lchan->mr_ms_lv)); + memset(&lchan->mr_bts_lv, 0, sizeof(lchan->mr_bts_lv)); + lchan->broken_reason = ""; + } else { + struct challoc_signal_data sig; + + LOGP(DRLL, LOGL_ERROR, "(bts=%d) Failed to allocate %s channel\n", + bts->nr, gsm_lchant_name(type)); + + sig.bts = bts; + sig.type = type; + osmo_signal_dispatch(SS_CHALLOC, S_CHALLOC_ALLOC_FAIL, &sig); + } + + return lchan; +} + +/* Free a logical channel */ +void lchan_free(struct gsm_lchan *lchan) +{ + struct challoc_signal_data sig; + int i; + + sig.type = lchan->type; + lchan->type = GSM_LCHAN_NONE; + + + if (lchan->conn + && !(lchan->ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH + && lchan->ts->dyn.pchan_is != lchan->ts->dyn.pchan_want)) { + struct lchan_signal_data sig; + + /* We might kill an active channel... */ + sig.lchan = lchan; + sig.mr = NULL; + osmo_signal_dispatch(SS_LCHAN, S_LCHAN_UNEXPECTED_RELEASE, &sig); + } + + /* stop the timer */ + osmo_timer_del(&lchan->T3101); + + /* clear cached measuement reports */ + lchan->meas_rep_idx = 0; + for (i = 0; i < ARRAY_SIZE(lchan->meas_rep); i++) { + lchan->meas_rep[i].flags = 0; + lchan->meas_rep[i].nr = 0; + } + for (i = 0; i < ARRAY_SIZE(lchan->neigh_meas); i++) + lchan->neigh_meas[i].arfcn = 0; + + if (lchan->rqd_ref) { + talloc_free(lchan->rqd_ref); + lchan->rqd_ref = NULL; + lchan->rqd_ta = 0; + } + + sig.lchan = lchan; + sig.bts = lchan->ts->trx->bts; + osmo_signal_dispatch(SS_CHALLOC, S_CHALLOC_FREED, &sig); + + if (lchan->conn + && !(lchan->ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH + && lchan->ts->dyn.pchan_is != lchan->ts->dyn.pchan_want)) { + LOGP(DRLL, LOGL_ERROR, "the subscriber connection should be gone.\n"); + lchan->conn = NULL; + } + + /* FIXME: ts_free() the timeslot, if we're the last logical + * channel using it */ +} + +/* + * There was an error with the TRX and we need to forget + * any state so that a lchan can be allocated again after + * the trx is fully usable. + * + * This should be called after lchan_free to force a channel + * be available for allocation again. This means that this + * method will stop the "delay after error"-timer and set the + * state to LCHAN_S_NONE. + */ +void lchan_reset(struct gsm_lchan *lchan) +{ + osmo_timer_del(&lchan->T3101); + osmo_timer_del(&lchan->T3109); + osmo_timer_del(&lchan->T3111); + osmo_timer_del(&lchan->error_timer); + + lchan->type = GSM_LCHAN_NONE; + rsl_lchan_set_state(lchan, LCHAN_S_NONE); +} + +/* Drive the release process of the lchan */ +static void _lchan_handle_release(struct gsm_lchan *lchan, + int sacch_deact, int mode) +{ + /* Release all SAPIs on the local end and continue */ + rsl_release_sapis_from(lchan, 1, RSL_REL_LOCAL_END); + + /* + * Shall we send a RR Release, start T3109 and wait for the + * release indication from the BTS or just take it down (e.g. + * on assignment requests) + */ + if (sacch_deact) { + gsm48_send_rr_release(lchan); + + /* Deactivate the SACCH on the BTS side */ + rsl_deact_sacch(lchan); + rsl_start_t3109(lchan); + } else if (lchan->sapis[0] == LCHAN_SAPI_UNUSED) { + rsl_direct_rf_release(lchan); + } else { + rsl_release_request(lchan, 0, mode); + } +} + +/* Consider releasing the channel now */ +int lchan_release(struct gsm_lchan *lchan, int sacch_deact, enum rsl_rel_mode mode) +{ + DEBUGP(DRLL, "%s starting release sequence\n", gsm_lchan_name(lchan)); + rsl_lchan_set_state(lchan, LCHAN_S_REL_REQ); + + lchan->conn = NULL; + _lchan_handle_release(lchan, sacch_deact, mode); + return 1; +} + +void bts_chan_load(struct pchan_load *cl, const struct gsm_bts *bts) +{ + struct gsm_bts_trx *trx; + + llist_for_each_entry(trx, &bts->trx_list, list) { + int i; + + /* skip administratively deactivated tranxsceivers */ + if (!nm_is_running(&trx->mo.nm_state) || + !nm_is_running(&trx->bb_transc.mo.nm_state)) + continue; + + for (i = 0; i < ARRAY_SIZE(trx->ts); i++) { + struct gsm_bts_trx_ts *ts = &trx->ts[i]; + struct load_counter *pl = &cl->pchan[ts->pchan]; + int j; + int subslots; + + /* skip administratively deactivated timeslots */ + if (!nm_is_running(&ts->mo.nm_state)) + continue; + + subslots = ts_subslots(ts); + for (j = 0; j < subslots; j++) { + struct gsm_lchan *lchan = &ts->lchan[j]; + + pl->total++; + + switch (lchan->state) { + case LCHAN_S_NONE: + break; + default: + pl->used++; + break; + } + } + } + } +} + +void network_chan_load(struct pchan_load *pl, struct gsm_network *net) +{ + struct gsm_bts *bts; + + memset(pl, 0, sizeof(*pl)); + + llist_for_each_entry(bts, &net->bts_list, list) + bts_chan_load(pl, bts); +} + +/* Update T3122 wait indicator based on samples of BTS channel load. */ +void +bts_update_t3122_chan_load(struct gsm_bts *bts) +{ + struct pchan_load pl; + uint64_t used = 0; + uint32_t total = 0; + uint64_t load; + uint64_t wait_ind; + static const uint8_t min_wait_ind = GSM_T3122_DEFAULT; + static const uint8_t max_wait_ind = 128; /* max wait ~2 minutes */ + int i; + + /* Ignore BTS that are not in operation, in order to not flood the log with "bogus channel load" + * messages */ + if (!trx_is_usable(bts->c0)) + return; + + /* Sum up current load across all channels. */ + memset(&pl, 0, sizeof(pl)); + bts_chan_load(&pl, bts); + for (i = 0; i < ARRAY_SIZE(pl.pchan); i++) { + struct load_counter *lc = &pl.pchan[i]; + + /* Ignore samples too large for fixed-point calculations (shouldn't happen). */ + if (lc->used > UINT16_MAX || lc->total > UINT16_MAX) { + LOGP(DRLL, LOGL_NOTICE, "(bts=%d) numbers in channel load sample " + "too large (used=%u / total=%u)\n", bts->nr, lc->used, lc->total); + continue; + } + + used += lc->used; + total += lc->total; + } + + /* Check for invalid samples (shouldn't happen). */ + if (total == 0 || used > total) { + LOGP(DRLL, LOGL_NOTICE, "(bts=%d) bogus channel load sample (used=%"PRIu64" / total=%"PRIu32")\n", + bts->nr, used, total); + bts->T3122 = 0; /* disable override of network-wide default value */ + bts->chan_load_samples_idx = 0; /* invalidate other samples collected so far */ + return; + } + + /* If we haven't got enough samples yet, store measurement for later use. */ + if (bts->chan_load_samples_idx < ARRAY_SIZE(bts->chan_load_samples)) { + struct load_counter *sample = &bts->chan_load_samples[bts->chan_load_samples_idx++]; + sample->total = (unsigned int)total; + sample->used = (unsigned int)used; + return; + } + + /* We have enough samples and will overwrite our current samples later. */ + bts->chan_load_samples_idx = 0; + + /* Add all previous samples to the current sample. */ + for (i = 0; i < ARRAY_SIZE(bts->chan_load_samples); i++) { + struct load_counter *sample = &bts->chan_load_samples[i]; + total += sample->total; + used += sample->used; + } + + used <<= 8; /* convert to fixed-point */ + + /* Log channel load average. */ + load = ((used / total) * 100); + LOGP(DRLL, LOGL_DEBUG, "(bts=%d) channel load average is %"PRIu64".%.2"PRIu64"%%\n", + bts->nr, (load & 0xffffff00) >> 8, (load & 0xff) / 10); + bts->chan_load_avg = ((load & 0xffffff00) >> 8); + OSMO_ASSERT(bts->chan_load_avg <= 100); + osmo_stat_item_set(bts->bts_statg->items[BTS_STAT_CHAN_LOAD_AVERAGE], bts->chan_load_avg); + + /* Calculate new T3122 wait indicator. */ + wait_ind = ((used / total) * max_wait_ind); + wait_ind >>= 8; /* convert from fixed-point to integer */ + if (wait_ind < min_wait_ind) + wait_ind = min_wait_ind; + else if (wait_ind > max_wait_ind) + wait_ind = max_wait_ind; + + LOGP(DRLL, LOGL_DEBUG, "(bts=%d) T3122 wait indicator set to %"PRIu64" seconds\n", bts->nr, wait_ind); + bts->T3122 = (uint8_t)wait_ind; + osmo_stat_item_set(bts->bts_statg->items[BTS_STAT_T3122], wait_ind); +} diff --git a/src/osmo-bsc/e1_config.c b/src/osmo-bsc/e1_config.c new file mode 100644 index 000000000..e7398ed9c --- /dev/null +++ b/src/osmo-bsc/e1_config.c @@ -0,0 +1,299 @@ +/* OpenBSC E1 Input code */ + +/* (C) 2008-2010 by Harald Welte <laforge@gnumonks.org> + * 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 <string.h> +#include <errno.h> +#include <time.h> +#include <netinet/in.h> + +#include <osmocom/bsc/gsm_data.h> +#include <osmocom/abis/e1_input.h> +#include <osmocom/bsc/misdn.h> +#include <osmocom/abis/ipaccess.h> +#include <osmocom/core/talloc.h> +#include <osmocom/bsc/debug.h> +#include <osmocom/bsc/abis_rsl.h> + +#define SAPI_L2ML 0 +#define SAPI_OML 62 +#define SAPI_RSL 0 /* 63 ? */ + +/* The e1_reconfig_*() functions below take the configuration present in the + * bts/trx/ts data structures and ensure the E1 configuration reflects the + * timeslot/subslot/TEI configuration */ + +int e1_reconfig_ts(struct gsm_bts_trx_ts *ts) +{ + struct gsm_e1_subslot *e1_link = &ts->e1_link; + struct e1inp_line *line; + + DEBUGP(DLMI, "e1_reconfig_ts(%u,%u,%u)\n", ts->trx->bts->nr, ts->trx->nr, ts->nr); + + if (!e1_link->e1_ts) { + LOGP(DLINP, LOGL_ERROR, "TS (%u/%u/%u) without E1 timeslot?\n", + ts->nr, ts->trx->nr, ts->trx->bts->nr); + return 0; + } + + line = e1inp_line_find(e1_link->e1_nr); + if (!line) { + LOGP(DLINP, LOGL_ERROR, "TS (%u/%u/%u) referring to " + "non-existing E1 line %u\n", ts->nr, ts->trx->nr, + ts->trx->bts->nr, e1_link->e1_nr); + return -ENOMEM; + } + + return 0; +} + +int e1_reconfig_trx(struct gsm_bts_trx *trx) +{ + struct gsm_e1_subslot *e1_link = &trx->rsl_e1_link; + struct e1inp_ts *sign_ts; + struct e1inp_line *line; + struct e1inp_sign_link *rsl_link; + int i; + + if (!e1_link->e1_ts) { + LOGP(DLINP, LOGL_ERROR, "TRX (%u/%u) RSL link without " + "timeslot?\n", trx->bts->nr, trx->nr); + return -EINVAL; + } + + /* RSL Link */ + line = e1inp_line_find(e1_link->e1_nr); + if (!line) { + LOGP(DLINP, LOGL_ERROR, "TRX (%u/%u) RSL link referring " + "to non-existing E1 line %u\n", trx->bts->nr, + trx->nr, e1_link->e1_nr); + return -ENOMEM; + } + sign_ts = &line->ts[e1_link->e1_ts-1]; + e1inp_ts_config_sign(sign_ts, line); + /* Ericsson RBS have a per-TRX OML link in parallel to RSL */ + if (trx->bts->type == GSM_BTS_TYPE_RBS2000) { + struct e1inp_sign_link *oml_link; + oml_link = e1inp_sign_link_create(sign_ts, E1INP_SIGN_OML, trx, + trx->rsl_tei, SAPI_OML); + if (!oml_link) { + LOGP(DLINP, LOGL_ERROR, "TRX (%u/%u) OML link creation " + "failed\n", trx->bts->nr, trx->nr); + return -ENOMEM; + } + if (trx->oml_link) + e1inp_sign_link_destroy(trx->oml_link); + trx->oml_link = oml_link; + } + rsl_link = e1inp_sign_link_create(sign_ts, E1INP_SIGN_RSL, + trx, trx->rsl_tei, SAPI_RSL); + if (!rsl_link) { + LOGP(DLINP, LOGL_ERROR, "TRX (%u/%u) RSL link creation " + "failed\n", trx->bts->nr, trx->nr); + return -ENOMEM; + } + if (trx->rsl_link) + e1inp_sign_link_destroy(trx->rsl_link); + trx->rsl_link = rsl_link; + + for (i = 0; i < TRX_NR_TS; i++) + e1_reconfig_ts(&trx->ts[i]); + + return 0; +} + +/* this is the generic callback for all ISDN-based BTS. */ +static int bts_isdn_sign_link(struct msgb *msg) +{ + int ret = -EINVAL; + struct e1inp_sign_link *link = msg->dst; + struct gsm_bts *bts; + + switch (link->type) { + case E1INP_SIGN_OML: + bts = link->trx->bts; + ret = bts->model->oml_rcvmsg(msg); + break; + case E1INP_SIGN_RSL: + if (link->trx->mo.nm_state.administrative == NM_STATE_LOCKED) { + LOGP(DLMI, LOGL_ERROR, "(bts=%d/trx=%d) discarding RSL message received " + "in locked administrative state\n", link->trx->bts->nr, link->trx->nr); + msgb_free(msg); + break; + } + ret = abis_rsl_rcvmsg(msg); + break; + default: + LOGP(DLMI, LOGL_ERROR, "unknown link type %u\n", link->type); + msgb_free(msg); + break; + } + return ret; +} + +struct e1inp_line_ops bts_isdn_e1inp_line_ops = { + .sign_link = bts_isdn_sign_link, +}; + +int e1_reconfig_bts(struct gsm_bts *bts) +{ + struct gsm_e1_subslot *e1_link = &bts->oml_e1_link; + struct e1inp_ts *sign_ts; + struct e1inp_line *line; + struct e1inp_sign_link *oml_link; + struct gsm_bts_trx *trx; + struct timespec tp; + int rc; + + DEBUGP(DLMI, "e1_reconfig_bts(%u)\n", bts->nr); + + line = e1inp_line_find(e1_link->e1_nr); + if (!line) { + LOGP(DLINP, LOGL_ERROR, "BTS %u OML link referring to " + "non-existing E1 line %u\n", bts->nr, e1_link->e1_nr); + return -ENOMEM; + } + + if (!bts->model->e1line_bind_ops) { + LOGP(DLINP, LOGL_ERROR, "no callback to bind E1 line operations\n"); + return -EINVAL; + } + if (!line->ops) + bts->model->e1line_bind_ops(line); + + /* skip signal link initialization, this is done later for these BTS. */ + if (bts->type == GSM_BTS_TYPE_NANOBTS || + bts->type == GSM_BTS_TYPE_OSMOBTS) + return e1inp_line_update(line); + + /* OML link */ + if (!e1_link->e1_ts) { + LOGP(DLINP, LOGL_ERROR, "BTS %u OML link without timeslot?\n", + bts->nr); + return -EINVAL; + } + + sign_ts = &line->ts[e1_link->e1_ts-1]; + e1inp_ts_config_sign(sign_ts, line); + oml_link = e1inp_sign_link_create(sign_ts, E1INP_SIGN_OML, + bts->c0, bts->oml_tei, SAPI_OML); + if (!oml_link) { + LOGP(DLINP, LOGL_ERROR, "BTS %u OML link creation failed\n", + bts->nr); + return -ENOMEM; + } + if (bts->oml_link) + e1inp_sign_link_destroy(bts->oml_link); + bts->oml_link = oml_link; + rc = clock_gettime(CLOCK_MONOTONIC, &tp); + bts->uptime = (rc < 0) ? 0 : tp.tv_sec; /* we don't need sub-second precision for uptime */ + + llist_for_each_entry(trx, &bts->trx_list, list) + e1_reconfig_trx(trx); + + /* notify E1 input something has changed */ + return e1inp_line_update(line); +} + +#if 0 +/* do some compiled-in configuration for our BTS/E1 setup */ +int e1_config(struct gsm_bts *bts, int cardnr, int release_l2) +{ + struct e1inp_line *line; + struct e1inp_ts *sign_ts; + struct e1inp_sign_link *oml_link, *rsl_link; + struct gsm_bts_trx *trx = bts->c0; + int base_ts; + + switch (bts->nr) { + case 0: + /* First BTS uses E1 TS 01,02,03,04,05 */ + base_ts = HARDCODED_BTS0_TS - 1; + break; + case 1: + /* Second BTS uses E1 TS 06,07,08,09,10 */ + base_ts = HARDCODED_BTS1_TS - 1; + break; + case 2: + /* Third BTS uses E1 TS 11,12,13,14,15 */ + base_ts = HARDCODED_BTS2_TS - 1; + default: + return -EINVAL; + } + + line = talloc_zero(tall_bsc_ctx, struct e1inp_line); + if (!line) + return -ENOMEM; + + /* create E1 timeslots for signalling and TRAU frames */ + e1inp_ts_config(&line->ts[base_ts+1-1], line, E1INP_TS_TYPE_SIGN); + e1inp_ts_config(&line->ts[base_ts+2-1], line, E1INP_TS_TYPE_TRAU); + e1inp_ts_config(&line->ts[base_ts+3-1], line, E1INP_TS_TYPE_TRAU); + + /* create signalling links for TS1 */ + sign_ts = &line->ts[base_ts+1-1]; + oml_link = e1inp_sign_link_create(sign_ts, E1INP_SIGN_OML, + trx, TEI_OML, SAPI_OML); + rsl_link = e1inp_sign_link_create(sign_ts, E1INP_SIGN_RSL, + trx, TEI_RSL, SAPI_RSL); + + /* create back-links from bts/trx */ + bts->oml_link = oml_link; + trx->rsl_link = rsl_link; + + /* enable subchannel demuxer on TS2 */ + subch_demux_activate(&line->ts[base_ts+2-1].trau.demux, 1); + subch_demux_activate(&line->ts[base_ts+2-1].trau.demux, 2); + subch_demux_activate(&line->ts[base_ts+2-1].trau.demux, 3); + + /* enable subchannel demuxer on TS3 */ + subch_demux_activate(&line->ts[base_ts+3-1].trau.demux, 0); + subch_demux_activate(&line->ts[base_ts+3-1].trau.demux, 1); + subch_demux_activate(&line->ts[base_ts+3-1].trau.demux, 2); + subch_demux_activate(&line->ts[base_ts+3-1].trau.demux, 3); + + trx = gsm_bts_trx_num(bts, 1); + if (trx) { + /* create E1 timeslots for TRAU frames of TRX1 */ + e1inp_ts_config(&line->ts[base_ts+4-1], line, E1INP_TS_TYPE_TRAU); + e1inp_ts_config(&line->ts[base_ts+5-1], line, E1INP_TS_TYPE_TRAU); + + /* create RSL signalling link for TRX1 */ + sign_ts = &line->ts[base_ts+1-1]; + rsl_link = e1inp_sign_link_create(sign_ts, E1INP_SIGN_RSL, + trx, TEI_RSL+1, SAPI_RSL); + /* create back-links from trx */ + trx->rsl_link = rsl_link; + + /* enable subchannel demuxer on TS2 */ + subch_demux_activate(&line->ts[base_ts+4-1].trau.demux, 0); + subch_demux_activate(&line->ts[base_ts+4-1].trau.demux, 1); + subch_demux_activate(&line->ts[base_ts+4-1].trau.demux, 2); + subch_demux_activate(&line->ts[base_ts+4-1].trau.demux, 3); + + /* enable subchannel demuxer on TS3 */ + subch_demux_activate(&line->ts[base_ts+5-1].trau.demux, 0); + subch_demux_activate(&line->ts[base_ts+5-1].trau.demux, 1); + subch_demux_activate(&line->ts[base_ts+5-1].trau.demux, 2); + subch_demux_activate(&line->ts[base_ts+5-1].trau.demux, 3); + } + + return mi_setup(cardnr, line, release_l2); +} +#endif diff --git a/src/osmo-bsc/gsm_04_08_utils.c b/src/osmo-bsc/gsm_04_08_utils.c new file mode 100644 index 000000000..5bfdf97ff --- /dev/null +++ b/src/osmo-bsc/gsm_04_08_utils.c @@ -0,0 +1,705 @@ +/* GSM Mobile Radio Interface Layer 3 messages on the A-bis interface + * 3GPP TS 04.08 version 7.21.0 Release 1998 / ETSI TS 100 940 V7.21.0 + * utility functions + */ + +/* (C) 2008-2009 by Harald Welte <laforge@gnumonks.org> + * (C) 2008, 2009 by Holger Hans Peter Freyther <zecke@selfish.org> + * + * 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 <stdio.h> +#include <stdlib.h> +#include <errno.h> +#include <netinet/in.h> + +#include <osmocom/core/msgb.h> +#include <osmocom/gsm/gsm48.h> + +#include <osmocom/bsc/abis_rsl.h> +#include <osmocom/bsc/debug.h> +#include <osmocom/bsc/paging.h> +#include <osmocom/bsc/signal.h> +#include <osmocom/bsc/bsc_api.h> +#include <osmocom/bsc/gsm_04_08_utils.h> + +/* should ip.access BTS use direct RTP streams between each other (1), + * or should OpenBSC always act as RTP relay/proxy in between (0) ? */ +int ipacc_rtp_direct = 1; + +static int gsm48_sendmsg(struct msgb *msg) +{ + if (msg->lchan) + msg->dst = msg->lchan->ts->trx->rsl_link; + + msg->l3h = msg->data; + return rsl_data_request(msg, 0); +} + +/* Section 9.1.8 / Table 9.9 */ +struct chreq { + uint8_t val; + uint8_t mask; + enum chreq_type type; +}; + +/* If SYSTEM INFORMATION TYPE 4 NECI bit == 1 */ +static const struct chreq chreq_type_neci1[] = { + { 0xa0, 0xe0, CHREQ_T_EMERG_CALL }, + { 0xc0, 0xe0, CHREQ_T_CALL_REEST_TCH_F }, + { 0x68, 0xfc, CHREQ_T_CALL_REEST_TCH_H }, + { 0x6c, 0xfc, CHREQ_T_CALL_REEST_TCH_H_DBL }, + { 0xe0, 0xe0, CHREQ_T_TCH_F }, + { 0x40, 0xf0, CHREQ_T_VOICE_CALL_TCH_H }, + { 0x50, 0xf0, CHREQ_T_DATA_CALL_TCH_H }, + { 0x00, 0xf0, CHREQ_T_LOCATION_UPD }, + { 0x10, 0xf0, CHREQ_T_SDCCH }, + { 0x80, 0xe0, CHREQ_T_PAG_R_ANY_NECI1 }, + { 0x20, 0xf0, CHREQ_T_PAG_R_TCH_F }, + { 0x30, 0xf0, CHREQ_T_PAG_R_TCH_FH }, + { 0x67, 0xff, CHREQ_T_LMU }, + { 0x60, 0xf9, CHREQ_T_RESERVED_SDCCH }, + { 0x61, 0xfb, CHREQ_T_RESERVED_SDCCH }, + { 0x63, 0xff, CHREQ_T_RESERVED_SDCCH }, + { 0x70, 0xf8, CHREQ_T_PDCH_TWO_PHASE }, + { 0x78, 0xfc, CHREQ_T_PDCH_ONE_PHASE }, + { 0x78, 0xfa, CHREQ_T_PDCH_ONE_PHASE }, + { 0x78, 0xf9, CHREQ_T_PDCH_ONE_PHASE }, + { 0x7f, 0xff, CHREQ_T_RESERVED_IGNORE }, +}; + +/* If SYSTEM INFORMATION TYPE 4 NECI bit == 0 */ +static const struct chreq chreq_type_neci0[] = { + { 0xa0, 0xe0, CHREQ_T_EMERG_CALL }, + { 0xc0, 0xe0, CHREQ_T_CALL_REEST_TCH_H }, + { 0xe0, 0xe0, CHREQ_T_TCH_F }, + { 0x50, 0xf0, CHREQ_T_DATA_CALL_TCH_H }, + { 0x00, 0xe0, CHREQ_T_LOCATION_UPD }, + { 0x80, 0xe0, CHREQ_T_PAG_R_ANY_NECI0 }, + { 0x20, 0xf0, CHREQ_T_PAG_R_TCH_F }, + { 0x30, 0xf0, CHREQ_T_PAG_R_TCH_FH }, + { 0x67, 0xff, CHREQ_T_LMU }, + { 0x60, 0xf9, CHREQ_T_RESERVED_SDCCH }, + { 0x61, 0xfb, CHREQ_T_RESERVED_SDCCH }, + { 0x63, 0xff, CHREQ_T_RESERVED_SDCCH }, + { 0x70, 0xf8, CHREQ_T_PDCH_TWO_PHASE }, + { 0x78, 0xfc, CHREQ_T_PDCH_ONE_PHASE }, + { 0x78, 0xfa, CHREQ_T_PDCH_ONE_PHASE }, + { 0x78, 0xf9, CHREQ_T_PDCH_ONE_PHASE }, + { 0x7f, 0xff, CHREQ_T_RESERVED_IGNORE }, +}; + +static const enum gsm_chan_t ctype_by_chreq[] = { + [CHREQ_T_EMERG_CALL] = GSM_LCHAN_TCH_F, + [CHREQ_T_CALL_REEST_TCH_F] = GSM_LCHAN_TCH_F, + [CHREQ_T_CALL_REEST_TCH_H] = GSM_LCHAN_TCH_H, + [CHREQ_T_CALL_REEST_TCH_H_DBL] = GSM_LCHAN_TCH_H, + [CHREQ_T_SDCCH] = GSM_LCHAN_SDCCH, + [CHREQ_T_TCH_F] = GSM_LCHAN_TCH_F, + [CHREQ_T_VOICE_CALL_TCH_H] = GSM_LCHAN_TCH_H, + [CHREQ_T_DATA_CALL_TCH_H] = GSM_LCHAN_TCH_H, + [CHREQ_T_LOCATION_UPD] = GSM_LCHAN_SDCCH, + [CHREQ_T_PAG_R_ANY_NECI1] = GSM_LCHAN_SDCCH, + [CHREQ_T_PAG_R_ANY_NECI0] = GSM_LCHAN_SDCCH, + [CHREQ_T_PAG_R_TCH_F] = GSM_LCHAN_TCH_F, + [CHREQ_T_PAG_R_TCH_FH] = GSM_LCHAN_TCH_H, + [CHREQ_T_LMU] = GSM_LCHAN_SDCCH, + [CHREQ_T_RESERVED_SDCCH] = GSM_LCHAN_SDCCH, + [CHREQ_T_PDCH_ONE_PHASE] = GSM_LCHAN_PDTCH, + [CHREQ_T_PDCH_TWO_PHASE] = GSM_LCHAN_PDTCH, + [CHREQ_T_RESERVED_IGNORE] = GSM_LCHAN_UNKNOWN, +}; + +static const enum gsm_chreq_reason_t reason_by_chreq[] = { + [CHREQ_T_EMERG_CALL] = GSM_CHREQ_REASON_EMERG, + [CHREQ_T_CALL_REEST_TCH_F] = GSM_CHREQ_REASON_CALL, + [CHREQ_T_CALL_REEST_TCH_H] = GSM_CHREQ_REASON_CALL, + [CHREQ_T_CALL_REEST_TCH_H_DBL] = GSM_CHREQ_REASON_CALL, + [CHREQ_T_SDCCH] = GSM_CHREQ_REASON_OTHER, + [CHREQ_T_TCH_F] = GSM_CHREQ_REASON_OTHER, + [CHREQ_T_VOICE_CALL_TCH_H] = GSM_CHREQ_REASON_CALL, + [CHREQ_T_DATA_CALL_TCH_H] = GSM_CHREQ_REASON_OTHER, + [CHREQ_T_LOCATION_UPD] = GSM_CHREQ_REASON_LOCATION_UPD, + [CHREQ_T_PAG_R_ANY_NECI1] = GSM_CHREQ_REASON_PAG, + [CHREQ_T_PAG_R_ANY_NECI0] = GSM_CHREQ_REASON_PAG, + [CHREQ_T_PAG_R_TCH_F] = GSM_CHREQ_REASON_PAG, + [CHREQ_T_PAG_R_TCH_FH] = GSM_CHREQ_REASON_PAG, + [CHREQ_T_LMU] = GSM_CHREQ_REASON_OTHER, + [CHREQ_T_PDCH_ONE_PHASE] = GSM_CHREQ_REASON_PDCH, + [CHREQ_T_PDCH_TWO_PHASE] = GSM_CHREQ_REASON_PDCH, + [CHREQ_T_RESERVED_SDCCH] = GSM_CHREQ_REASON_OTHER, + [CHREQ_T_RESERVED_IGNORE] = GSM_CHREQ_REASON_OTHER, +}; + +/* verify that the two tables match */ +osmo_static_assert(sizeof(ctype_by_chreq) == + sizeof(((struct gsm_network *) NULL)->ctype_by_chreq), assert_size); + +/* + * Update channel types for request based on policy. E.g. in the + * case of a TCH/H network/bsc use TCH/H for the emergency calls, + * for early assignment assign a SDCCH and some other options. + */ +void gsm_net_update_ctype(struct gsm_network *network) +{ + /* copy over the data */ + memcpy(network->ctype_by_chreq, ctype_by_chreq, sizeof(ctype_by_chreq)); + + /* + * Use TCH/H for emergency calls when this cell allows TCH/H. Maybe it + * is better to iterate over the BTS/TRX and check if no TCH/F is available + * and then set it to TCH/H. + */ + if (network->neci) + network->ctype_by_chreq[CHREQ_T_EMERG_CALL] = GSM_LCHAN_TCH_H; + + if (network->pag_any_tch) { + if (network->neci) { + network->ctype_by_chreq[CHREQ_T_PAG_R_ANY_NECI0] = GSM_LCHAN_TCH_H; + network->ctype_by_chreq[CHREQ_T_PAG_R_ANY_NECI1] = GSM_LCHAN_TCH_H; + } else { + network->ctype_by_chreq[CHREQ_T_PAG_R_ANY_NECI0] = GSM_LCHAN_TCH_F; + network->ctype_by_chreq[CHREQ_T_PAG_R_ANY_NECI1] = GSM_LCHAN_TCH_F; + } + } +} + +enum gsm_chan_t get_ctype_by_chreq(struct gsm_network *network, uint8_t ra) +{ + int i; + int length; + const struct chreq *chreq; + + if (network->neci) { + chreq = chreq_type_neci1; + length = ARRAY_SIZE(chreq_type_neci1); + } else { + chreq = chreq_type_neci0; + length = ARRAY_SIZE(chreq_type_neci0); + } + + + for (i = 0; i < length; i++) { + const struct chreq *chr = &chreq[i]; + if ((ra & chr->mask) == chr->val) + return network->ctype_by_chreq[chr->type]; + } + LOGP(DRR, LOGL_ERROR, "Unknown CHANNEL REQUEST RQD 0x%02x\n", ra); + return GSM_LCHAN_SDCCH; +} + +int get_reason_by_chreq(uint8_t ra, int neci) +{ + int i; + int length; + const struct chreq *chreq; + + if (neci) { + chreq = chreq_type_neci1; + length = ARRAY_SIZE(chreq_type_neci1); + } else { + chreq = chreq_type_neci0; + length = ARRAY_SIZE(chreq_type_neci0); + } + + for (i = 0; i < length; i++) { + const struct chreq *chr = &chreq[i]; + if ((ra & chr->mask) == chr->val) + return reason_by_chreq[chr->type]; + } + LOGP(DRR, LOGL_ERROR, "Unknown CHANNEL REQUEST REASON 0x%02x\n", ra); + return GSM_CHREQ_REASON_OTHER; +} + +static void mr_config_for_ms(struct gsm_lchan *lchan, struct msgb *msg) +{ + if (lchan->tch_mode == GSM48_CMODE_SPEECH_AMR) + msgb_tlv_put(msg, GSM48_IE_MUL_RATE_CFG, lchan->mr_ms_lv[0], + lchan->mr_ms_lv + 1); +} + +/* 7.1.7 and 9.1.7: RR CHANnel RELease */ +int gsm48_send_rr_release(struct gsm_lchan *lchan) +{ + struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 RR REL"); + struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); + uint8_t *cause; + + msg->lchan = lchan; + gh->proto_discr = GSM48_PDISC_RR; + gh->msg_type = GSM48_MT_RR_CHAN_REL; + + cause = msgb_put(msg, 1); + cause[0] = GSM48_RR_CAUSE_NORMAL; + + DEBUGP(DRR, "Sending Channel Release: Chan: Number: %d Type: %d\n", + lchan->nr, lchan->type); + + /* Send actual release request to MS */ + return gsm48_sendmsg(msg); +} + +int send_siemens_mrpci(struct gsm_lchan *lchan, + uint8_t *classmark2_lv) +{ + struct rsl_mrpci mrpci; + + if (classmark2_lv[0] < 2) + return -EINVAL; + + mrpci.power_class = classmark2_lv[1] & 0x7; + mrpci.vgcs_capable = classmark2_lv[2] & (1 << 1); + mrpci.vbs_capable = classmark2_lv[2] & (1 <<2); + mrpci.gsm_phase = (classmark2_lv[1]) >> 5 & 0x3; + + return rsl_siemens_mrpci(lchan, &mrpci); +} + +/* Chapter 9.1.9: Ciphering Mode Command */ +int gsm48_send_rr_ciph_mode(struct gsm_lchan *lchan, int want_imeisv) +{ + struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 CIPH"); + struct gsm48_hdr *gh; + uint8_t ciph_mod_set; + + msg->lchan = lchan; + + DEBUGP(DRR, "TX CIPHERING MODE CMD\n"); + + if (lchan->encr.alg_id <= RSL_ENC_ALG_A5(0)) + ciph_mod_set = 0; + else + ciph_mod_set = (lchan->encr.alg_id-2)<<1 | 1; + + gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh) + 1); + gh->proto_discr = GSM48_PDISC_RR; + gh->msg_type = GSM48_MT_RR_CIPH_M_CMD; + gh->data[0] = (want_imeisv & 0x1) << 4 | (ciph_mod_set & 0xf); + + return rsl_encryption_cmd(msg); +} + +static void gsm48_cell_desc(struct gsm48_cell_desc *cd, + const struct gsm_bts *bts) +{ + cd->ncc = (bts->bsic >> 3 & 0x7); + cd->bcc = (bts->bsic & 0x7); + cd->arfcn_hi = bts->c0->arfcn >> 8; + cd->arfcn_lo = bts->c0->arfcn & 0xff; +} + +/*! \brief Encode a TS 04.08 multirate config LV according to 10.5.2.21aa + * \param[out] lv caller-allocated buffer of 7 bytes. First octet is IS length + * \param[in] mr multi-rate configuration to encode + * \param[in] modes array describing the AMR modes + * \returns 0 on success */ +int gsm48_multirate_config(uint8_t *lv, const struct amr_multirate_conf *mr, const struct amr_mode *modes) +{ + int num = 0, i; + + for (i = 0; i < 8; i++) { + if (((mr->gsm48_ie[1] >> i) & 1)) + num++; + } + if (num > 4) { + LOGP(DRR, LOGL_ERROR, "BUG: Using multirate codec with too " + "many modes in config.\n"); + num = 4; + } + if (num < 1) { + LOGP(DRR, LOGL_ERROR, "BUG: Using multirate codec with no " + "mode in config.\n"); + num = 1; + } + + lv[0] = (num == 1) ? 2 : (num + 2); + memcpy(lv + 1, mr->gsm48_ie, 2); + if (num == 1) + return 0; + + lv[3] = modes[0].threshold & 0x3f; + lv[4] = modes[0].hysteresis << 4; + if (num == 2) + return 0; + lv[4] |= (modes[1].threshold & 0x3f) >> 2; + lv[5] = modes[1].threshold << 6; + lv[5] |= (modes[1].hysteresis & 0x0f) << 2; + if (num == 3) + return 0; + lv[5] |= (modes[2].threshold & 0x3f) >> 4; + lv[6] = modes[2].threshold << 4; + lv[6] |= modes[2].hysteresis & 0x0f; + + return 0; +} + +#define GSM48_HOCMD_CCHDESC_LEN 16 + +/* Chapter 9.1.15: Handover Command */ +int gsm48_send_ho_cmd(struct gsm_lchan *old_lchan, struct gsm_lchan *new_lchan, + uint8_t power_command, uint8_t ho_ref) +{ + struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 HO CMD"); + struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); + struct gsm48_ho_cmd *ho = + (struct gsm48_ho_cmd *) msgb_put(msg, sizeof(*ho)); + + msg->lchan = old_lchan; + gh->proto_discr = GSM48_PDISC_RR; + gh->msg_type = GSM48_MT_RR_HANDO_CMD; + + /* mandatory bits */ + gsm48_cell_desc(&ho->cell_desc, new_lchan->ts->trx->bts); + gsm48_lchan2chan_desc(&ho->chan_desc, new_lchan); + ho->ho_ref = ho_ref; + ho->power_command = power_command; + + if (new_lchan->ts->hopping.enabled) { + struct gsm_bts *bts = new_lchan->ts->trx->bts; + struct gsm48_system_information_type_1 *si1; + uint8_t *cur; + + si1 = GSM_BTS_SI(bts, SYSINFO_TYPE_1); + /* Copy the Cell Chan Desc (ARFCNS in this cell) */ + msgb_put_u8(msg, GSM48_IE_CELL_CH_DESC); + cur = msgb_put(msg, GSM48_HOCMD_CCHDESC_LEN); + memcpy(cur, si1->cell_channel_description, + GSM48_HOCMD_CCHDESC_LEN); + /* Copy the Mobile Allocation */ + msgb_tlv_put(msg, GSM48_IE_MA_BEFORE, + new_lchan->ts->hopping.ma_len, + new_lchan->ts->hopping.ma_data); + } + /* FIXME: optional bits for type of synchronization? */ + + msgb_tv_put(msg, GSM48_IE_CHANMODE_1, new_lchan->tch_mode); + + /* in case of multi rate we need to attach a config */ + if (new_lchan->tch_mode == GSM48_CMODE_SPEECH_AMR) + msgb_tlv_put(msg, GSM48_IE_MUL_RATE_CFG, new_lchan->mr_ms_lv[0], + new_lchan->mr_ms_lv + 1); + + return gsm48_sendmsg(msg); +} + +/* Chapter 9.1.2: Assignment Command */ +int gsm48_send_rr_ass_cmd(struct gsm_lchan *dest_lchan, struct gsm_lchan *lchan, uint8_t power_command) +{ + struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 ASS CMD"); + struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); + struct gsm48_ass_cmd *ass = + (struct gsm48_ass_cmd *) msgb_put(msg, sizeof(*ass)); + + DEBUGP(DRR, "-> ASSIGNMENT COMMAND tch_mode=0x%02x\n", lchan->tch_mode); + + msg->lchan = dest_lchan; + gh->proto_discr = GSM48_PDISC_RR; + gh->msg_type = GSM48_MT_RR_ASS_CMD; + + /* + * fill the channel information element, this code + * should probably be shared with rsl_rx_chan_rqd(), + * gsm48_lchan_modify(). But beware that 10.5.2.5 + * 10.5.2.5.a have slightly different semantic for + * the chan_desc. But as long as multi-slot configurations + * are not used we seem to be fine. + */ + gsm48_lchan2chan_desc(&ass->chan_desc, lchan); + ass->power_command = power_command; + + /* optional: cell channel description */ + + msgb_tv_put(msg, GSM48_IE_CHANMODE_1, lchan->tch_mode); + + /* mobile allocation in case of hopping */ + if (lchan->ts->hopping.enabled) { + msgb_tlv_put(msg, GSM48_IE_MA_BEFORE, lchan->ts->hopping.ma_len, + lchan->ts->hopping.ma_data); + } + + /* in case of multi rate we need to attach a config */ + mr_config_for_ms(lchan, msg); + + return gsm48_sendmsg(msg); +} + +/* 9.1.5 Channel mode modify: Modify the mode on the MS side */ +int gsm48_lchan_modify(struct gsm_lchan *lchan, uint8_t mode) +{ + struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 CHN MOD"); + struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); + struct gsm48_chan_mode_modify *cmm = + (struct gsm48_chan_mode_modify *) msgb_put(msg, sizeof(*cmm)); + + DEBUGP(DRR, "-> CHANNEL MODE MODIFY mode=0x%02x\n", mode); + + lchan->tch_mode = mode; + msg->lchan = lchan; + gh->proto_discr = GSM48_PDISC_RR; + gh->msg_type = GSM48_MT_RR_CHAN_MODE_MODIF; + + /* fill the channel information element, this code + * should probably be shared with rsl_rx_chan_rqd() */ + gsm48_lchan2chan_desc(&cmm->chan_desc, lchan); + cmm->mode = mode; + + /* in case of multi rate we need to attach a config */ + mr_config_for_ms(lchan, msg); + + return gsm48_sendmsg(msg); +} + +int gsm48_rx_rr_modif_ack(struct msgb *msg) +{ + int rc; + struct gsm48_hdr *gh = msgb_l3(msg); + struct gsm48_chan_mode_modify *mod = + (struct gsm48_chan_mode_modify *) gh->data; + + DEBUGP(DRR, "CHANNEL MODE MODIFY ACK\n"); + + if (mod->mode != msg->lchan->tch_mode) { + LOGP(DRR, LOGL_ERROR, "CHANNEL MODE change failed. Wanted: %d Got: %d\n", + msg->lchan->tch_mode, mod->mode); + return -1; + } + + /* update the channel type */ + switch (mod->mode) { + case GSM48_CMODE_SIGN: + msg->lchan->rsl_cmode = RSL_CMOD_SPD_SIGN; + break; + case GSM48_CMODE_SPEECH_V1: + case GSM48_CMODE_SPEECH_EFR: + case GSM48_CMODE_SPEECH_AMR: + msg->lchan->rsl_cmode = RSL_CMOD_SPD_SPEECH; + break; + case GSM48_CMODE_DATA_14k5: + case GSM48_CMODE_DATA_12k0: + case GSM48_CMODE_DATA_6k0: + case GSM48_CMODE_DATA_3k6: + msg->lchan->rsl_cmode = RSL_CMOD_SPD_DATA; + break; + } + + /* We've successfully modified the MS side of the channel, + * now go on to modify the BTS side of the channel */ + rc = rsl_chan_mode_modify_req(msg->lchan); + + /* FIXME: we not only need to do this after mode modify, but + * also after channel activation */ + if (is_ipaccess_bts(msg->lchan->ts->trx->bts) && mod->mode != GSM48_CMODE_SIGN) + rsl_ipacc_crcx(msg->lchan); + return rc; +} + +int gsm48_parse_meas_rep(struct gsm_meas_rep *rep, struct msgb *msg) +{ + struct gsm48_hdr *gh = msgb_l3(msg); + uint8_t *data = gh->data; + struct gsm_bts *bts = msg->lchan->ts->trx->bts; + struct bitvec *nbv = &bts->si_common.neigh_list; + struct gsm_meas_rep_cell *mrc; + + if (gh->msg_type != GSM48_MT_RR_MEAS_REP) + return -EINVAL; + + if (data[0] & 0x80) + rep->flags |= MEAS_REP_F_BA1; + if (data[0] & 0x40) + rep->flags |= MEAS_REP_F_UL_DTX; + if ((data[1] & 0x40) == 0x00) + rep->flags |= MEAS_REP_F_DL_VALID; + + rep->dl.full.rx_lev = data[0] & 0x3f; + rep->dl.sub.rx_lev = data[1] & 0x3f; + rep->dl.full.rx_qual = (data[2] >> 4) & 0x7; + rep->dl.sub.rx_qual = (data[2] >> 1) & 0x7; + + rep->num_cell = ((data[3] >> 6) & 0x3) | ((data[2] & 0x01) << 2); + if (rep->num_cell < 1 || rep->num_cell > 6) { + /* There are no neighbor cell reports present. */ + rep->num_cell = 0; + return 0; + } + + /* an encoding nightmare in perfection */ + mrc = &rep->cell[0]; + mrc->rxlev = data[3] & 0x3f; + mrc->neigh_idx = data[4] >> 3; + mrc->arfcn = bitvec_get_nth_set_bit(nbv, mrc->neigh_idx + 1); + mrc->bsic = ((data[4] & 0x07) << 3) | (data[5] >> 5); + if (rep->num_cell < 2) + return 0; + + mrc = &rep->cell[1]; + mrc->rxlev = ((data[5] & 0x1f) << 1) | (data[6] >> 7); + mrc->neigh_idx = (data[6] >> 2) & 0x1f; + mrc->arfcn = bitvec_get_nth_set_bit(nbv, mrc->neigh_idx + 1); + mrc->bsic = ((data[6] & 0x03) << 4) | (data[7] >> 4); + if (rep->num_cell < 3) + return 0; + + mrc = &rep->cell[2]; + mrc->rxlev = ((data[7] & 0x0f) << 2) | (data[8] >> 6); + mrc->neigh_idx = (data[8] >> 1) & 0x1f; + mrc->arfcn = bitvec_get_nth_set_bit(nbv, mrc->neigh_idx + 1); + mrc->bsic = ((data[8] & 0x01) << 5) | (data[9] >> 3); + if (rep->num_cell < 4) + return 0; + + mrc = &rep->cell[3]; + mrc->rxlev = ((data[9] & 0x07) << 3) | (data[10] >> 5); + mrc->neigh_idx = data[10] & 0x1f; + mrc->arfcn = bitvec_get_nth_set_bit(nbv, mrc->neigh_idx + 1); + mrc->bsic = data[11] >> 2; + if (rep->num_cell < 5) + return 0; + + mrc = &rep->cell[4]; + mrc->rxlev = ((data[11] & 0x03) << 4) | (data[12] >> 4); + mrc->neigh_idx = ((data[12] & 0xf) << 1) | (data[13] >> 7); + mrc->arfcn = bitvec_get_nth_set_bit(nbv, mrc->neigh_idx + 1); + mrc->bsic = (data[13] >> 1) & 0x3f; + if (rep->num_cell < 6) + return 0; + + mrc = &rep->cell[5]; + mrc->rxlev = ((data[13] & 0x01) << 5) | (data[14] >> 3); + mrc->neigh_idx = ((data[14] & 0x07) << 2) | (data[15] >> 6); + mrc->arfcn = bitvec_get_nth_set_bit(nbv, mrc->neigh_idx + 1); + mrc->bsic = data[15] & 0x3f; + + return 0; +} + +/* 9.2.5 CM service accept */ +int gsm48_tx_mm_serv_ack(struct gsm_subscriber_connection *conn) +{ + struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 SERV ACK"); + struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); + + msg->lchan = conn->lchan; + + gh->proto_discr = GSM48_PDISC_MM; + gh->msg_type = GSM48_MT_MM_CM_SERV_ACC; + + DEBUGP(DMM, "-> CM SERVICE ACK\n"); + + return gsm0808_submit_dtap(conn, msg, 0, 0); +} + +/* 9.2.6 CM service reject */ +int gsm48_tx_mm_serv_rej(struct gsm_subscriber_connection *conn, + enum gsm48_reject_value value) +{ + struct msgb *msg; + + msg = gsm48_create_mm_serv_rej(value); + if (!msg) { + LOGP(DMM, LOGL_ERROR, "Failed to allocate CM Service Reject.\n"); + return -1; + } + + DEBUGP(DMM, "-> CM SERVICE Reject cause: %d\n", value); + + return gsm0808_submit_dtap(conn, msg, 0, 0); +} + +/* 9.1.29 RR Status */ +struct msgb *gsm48_create_rr_status(uint8_t cause) +{ + struct msgb *msg; + struct gsm48_hdr *gh; + + msg = gsm48_msgb_alloc_name("GSM 04.08 RR STATUS"); + if (!msg) + return NULL; + + gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh) + 1); + gh->proto_discr = GSM48_PDISC_RR; + gh->msg_type = GSM48_MT_RR_STATUS; + gh->data[0] = cause; + + return msg; +} + +/* 9.1.29 RR Status */ +int gsm48_tx_rr_status(struct gsm_subscriber_connection *conn, uint8_t cause) +{ + struct msgb *msg = gsm48_create_rr_status(cause); + if (!msg) + return -1; + return gsm0808_submit_dtap(conn, msg, 0, 0); +} + +struct msgb *gsm48_create_mm_serv_rej(enum gsm48_reject_value value) +{ + struct msgb *msg; + struct gsm48_hdr *gh; + + msg = gsm48_msgb_alloc_name("GSM 04.08 SERV REJ"); + if (!msg) + return NULL; + + gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh) + 1); + gh->proto_discr = GSM48_PDISC_MM; + gh->msg_type = GSM48_MT_MM_CM_SERV_REJ; + gh->data[0] = value; + + return msg; +} + +struct msgb *gsm48_create_loc_upd_rej(uint8_t cause) +{ + struct gsm48_hdr *gh; + struct msgb *msg; + + msg = gsm48_msgb_alloc_name("GSM 04.08 LOC UPD REJ"); + if (!msg) + return NULL; + + gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh) + 1); + gh->proto_discr = GSM48_PDISC_MM; + gh->msg_type = GSM48_MT_MM_LOC_UPD_REJECT; + gh->data[0] = cause; + return msg; +} + +int gsm48_extract_mi(uint8_t *classmark2_lv, int length, char *mi_string, uint8_t *mi_type) +{ + /* Check the size for the classmark */ + if (length < 1 + *classmark2_lv) + return -1; + + uint8_t *mi_lv = classmark2_lv + *classmark2_lv + 1; + if (length < 2 + *classmark2_lv + mi_lv[0]) + return -2; + + *mi_type = mi_lv[1] & GSM_MI_TYPE_MASK; + return gsm48_mi_to_string(mi_string, GSM48_MI_SIZE, mi_lv+1, *mi_lv); +} + +int gsm48_paging_extract_mi(struct gsm48_pag_resp *resp, int length, + char *mi_string, uint8_t *mi_type) +{ + static const uint32_t classmark_offset = + offsetof(struct gsm48_pag_resp, classmark2); + uint8_t *classmark2_lv = (uint8_t *) &resp->classmark2; + return gsm48_extract_mi(classmark2_lv, length - classmark_offset, + mi_string, mi_type); +} diff --git a/src/osmo-bsc/gsm_04_80_utils.c b/src/osmo-bsc/gsm_04_80_utils.c new file mode 100644 index 000000000..d67f3c568 --- /dev/null +++ b/src/osmo-bsc/gsm_04_80_utils.c @@ -0,0 +1,40 @@ +/* OpenBSC utility functions for 3GPP TS 04.80 */ + +/* (C) 2016 by sysmocom s.m.f.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 <osmocom/gsm/gsm0480.h> +#include <osmocom/bsc/bsc_api.h> + +int bsc_send_ussd_notify(struct gsm_subscriber_connection *conn, int level, + const char *text) +{ + struct msgb *msg = gsm0480_create_ussd_notify(level, text); + if (!msg) + return -1; + return gsm0808_submit_dtap(conn, msg, 0, 0); +} + +int bsc_send_ussd_release_complete(struct gsm_subscriber_connection *conn) +{ + struct msgb *msg = gsm0480_create_ussd_release_complete(); + if (!msg) + return -1; + return gsm0808_submit_dtap(conn, msg, 0, 0); +} diff --git a/src/osmo-bsc/gsm_data.c b/src/osmo-bsc/gsm_data.c new file mode 100644 index 000000000..0f062d25a --- /dev/null +++ b/src/osmo-bsc/gsm_data.c @@ -0,0 +1,1305 @@ +/* (C) 2008-2018 by Harald Welte <laforge@gnumonks.org> + * + * 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 <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <ctype.h> +#include <stdbool.h> +#include <netinet/in.h> + +#include <osmocom/core/linuxlist.h> +#include <osmocom/core/talloc.h> +#include <osmocom/gsm/gsm_utils.h> +#include <osmocom/gsm/abis_nm.h> +#include <osmocom/core/statistics.h> +#include <osmocom/gsm/protocol/gsm_04_08.h> +#include <osmocom/gsm/gsm48.h> + +#include <osmocom/bsc/gsm_data.h> +#include <osmocom/bsc/bsc_msc_data.h> +#include <osmocom/bsc/abis_nm.h> +#include <osmocom/bsc/handover_cfg.h> + +void *tall_bsc_ctx = NULL; + +static LLIST_HEAD(bts_models); + +void set_ts_e1link(struct gsm_bts_trx_ts *ts, uint8_t e1_nr, + uint8_t e1_ts, uint8_t e1_ts_ss) +{ + ts->e1_link.e1_nr = e1_nr; + ts->e1_link.e1_ts = e1_ts; + ts->e1_link.e1_ts_ss = e1_ts_ss; +} + +static struct gsm_bts_model *bts_model_find(enum gsm_bts_type type) +{ + struct gsm_bts_model *model; + + llist_for_each_entry(model, &bts_models, list) { + if (model->type == type) + return model; + } + + return NULL; +} + +int gsm_bts_model_register(struct gsm_bts_model *model) +{ + if (bts_model_find(model->type)) + return -EEXIST; + + tlv_def_patch(&model->nm_att_tlvdef, &abis_nm_att_tlvdef); + llist_add_tail(&model->list, &bts_models); + return 0; +} + +const struct value_string bts_type_descs[_NUM_GSM_BTS_TYPE+1] = { + { GSM_BTS_TYPE_UNKNOWN, "Unknown BTS Type" }, + { GSM_BTS_TYPE_BS11, "Siemens BTS (BS-11 or compatible)" }, + { GSM_BTS_TYPE_NANOBTS, "ip.access nanoBTS or compatible" }, + { GSM_BTS_TYPE_RBS2000, "Ericsson RBS2000 Series" }, + { GSM_BTS_TYPE_NOKIA_SITE, "Nokia {Metro,Ultra,In}Site" }, + { GSM_BTS_TYPE_OSMOBTS, "sysmocom sysmoBTS" }, + { 0, NULL } +}; + +struct gsm_bts_trx *gsm_bts_trx_by_nr(struct gsm_bts *bts, int nr) +{ + struct gsm_bts_trx *trx; + + llist_for_each_entry(trx, &bts->trx_list, list) { + if (trx->nr == nr) + return trx; + } + return NULL; +} + +/* Search for a BTS in the given Location Area; optionally start searching + * with start_bts (for continuing to search after the first result) */ +struct gsm_bts *gsm_bts_by_lac(struct gsm_network *net, unsigned int lac, + struct gsm_bts *start_bts) +{ + int i; + struct gsm_bts *bts; + int skip = 0; + + if (start_bts) + skip = 1; + + for (i = 0; i < net->num_bts; i++) { + bts = gsm_bts_num(net, i); + + if (skip) { + if (start_bts == bts) + skip = 0; + continue; + } + + if (lac == GSM_LAC_RESERVED_ALL_BTS || bts->location_area_code == lac) + return bts; + } + return NULL; +} + +static const struct value_string bts_gprs_mode_names[] = { + { BTS_GPRS_NONE, "none" }, + { BTS_GPRS_GPRS, "gprs" }, + { BTS_GPRS_EGPRS, "egprs" }, + { 0, NULL } +}; + +enum bts_gprs_mode bts_gprs_mode_parse(const char *arg, int *valid) +{ + int rc; + + rc = get_string_value(bts_gprs_mode_names, arg); + if (valid) + *valid = rc != -EINVAL; + return rc; +} + +const char *bts_gprs_mode_name(enum bts_gprs_mode mode) +{ + return get_value_string(bts_gprs_mode_names, mode); +} + +int bts_gprs_mode_is_compat(struct gsm_bts *bts, enum bts_gprs_mode mode) +{ + if (mode != BTS_GPRS_NONE && + !osmo_bts_has_feature(&bts->model->features, BTS_FEAT_GPRS)) { + return 0; + } + if (mode == BTS_GPRS_EGPRS && + !osmo_bts_has_feature(&bts->model->features, BTS_FEAT_EGPRS)) { + return 0; + } + + return 1; +} + +int gsm_set_bts_type(struct gsm_bts *bts, enum gsm_bts_type type) +{ + struct gsm_bts_model *model; + + model = bts_model_find(type); + if (!model) + return -EINVAL; + + bts->type = type; + bts->model = model; + + if (model->start && !model->started) { + int ret = model->start(bts->network); + if (ret < 0) + return ret; + + model->started = true; + } + + switch (bts->type) { + case GSM_BTS_TYPE_NANOBTS: + case GSM_BTS_TYPE_OSMOBTS: + /* Set the default OML Stream ID to 0xff */ + bts->oml_tei = 0xff; + bts->c0->nominal_power = 23; + break; + case GSM_BTS_TYPE_RBS2000: + INIT_LLIST_HEAD(&bts->rbs2000.is.conn_groups); + INIT_LLIST_HEAD(&bts->rbs2000.con.conn_groups); + break; + case GSM_BTS_TYPE_BS11: + case GSM_BTS_TYPE_UNKNOWN: + case GSM_BTS_TYPE_NOKIA_SITE: + /* Set default BTS reset timer */ + bts->nokia.bts_reset_timer_cnf = 15; + case _NUM_GSM_BTS_TYPE: + break; + } + + return 0; +} + +struct gsm_bts *gsm_bts_alloc_register(struct gsm_network *net, enum gsm_bts_type type, + uint8_t bsic) +{ + struct gsm_bts_model *model = bts_model_find(type); + struct gsm_bts *bts; + + if (!model && type != GSM_BTS_TYPE_UNKNOWN) + return NULL; + + bts = gsm_bts_alloc(net, net->num_bts); + if (!bts) + return NULL; + + net->num_bts++; + + bts->type = type; + bts->model = model; + bts->bsic = bsic; + + llist_add_tail(&bts->list, &net->bts_list); + + return bts; +} + +void gprs_ra_id_by_bts(struct gprs_ra_id *raid, struct gsm_bts *bts) +{ + *raid = (struct gprs_ra_id){ + .mcc = bts->network->plmn.mcc, + .mnc = bts->network->plmn.mnc, + .mnc_3_digits = bts->network->plmn.mnc_3_digits, + .lac = bts->location_area_code, + .rac = bts->gprs.rac, + }; +} + +void gsm48_ra_id_by_bts(struct gsm48_ra_id *buf, struct gsm_bts *bts) +{ + struct gprs_ra_id raid; + + gprs_ra_id_by_bts(&raid, bts); + gsm48_encode_ra(buf, &raid); +} + +int gsm_parse_reg(void *ctx, regex_t *reg, char **str, int argc, const char **argv) +{ + int ret; + + ret = 0; + if (*str) { + talloc_free(*str); + *str = NULL; + } + regfree(reg); + + if (argc > 0) { + *str = talloc_strdup(ctx, argv[0]); + ret = regcomp(reg, argv[0], 0); + + /* handle compilation failures */ + if (ret != 0) { + talloc_free(*str); + *str = NULL; + } + } + + return ret; +} + +/* Assume there are only 256 possible bts */ +osmo_static_assert(sizeof(((struct gsm_bts *) 0)->nr) == 1, _bts_nr_is_256); +static void depends_calc_index_bit(int bts_nr, int *idx, int *bit) +{ + *idx = bts_nr / (8 * 4); + *bit = bts_nr % (8 * 4); +} + +void bts_depend_mark(struct gsm_bts *bts, int dep) +{ + int idx, bit; + depends_calc_index_bit(dep, &idx, &bit); + + bts->depends_on[idx] |= 1 << bit; +} + +void bts_depend_clear(struct gsm_bts *bts, int dep) +{ + int idx, bit; + depends_calc_index_bit(dep, &idx, &bit); + + bts->depends_on[idx] &= ~(1 << bit); +} + +int bts_depend_is_depedency(struct gsm_bts *base, struct gsm_bts *other) +{ + int idx, bit; + depends_calc_index_bit(other->nr, &idx, &bit); + + /* Check if there is a depends bit */ + return (base->depends_on[idx] & (1 << bit)) > 0; +} + +static int bts_is_online(struct gsm_bts *bts) +{ + /* TODO: support E1 BTS too */ + if (!is_ipaccess_bts(bts)) + return 1; + + if (!bts->oml_link) + return 0; + + return bts->mo.nm_state.operational == NM_OPSTATE_ENABLED; +} + +int bts_depend_check(struct gsm_bts *bts) +{ + struct gsm_bts *other_bts; + + llist_for_each_entry(other_bts, &bts->network->bts_list, list) { + if (!bts_depend_is_depedency(bts, other_bts)) + continue; + if (bts_is_online(other_bts)) + continue; + return 0; + } + return 1; +} + +/* get the radio link timeout (based on SACCH decode errors, according + * to algorithm specified in TS 05.08 section 5.2. A value of -1 + * indicates we should use an infinitely long timeout, which only works + * with OsmoBTS as the BTS implementation */ +int gsm_bts_get_radio_link_timeout(const struct gsm_bts *bts) +{ + const struct gsm48_cell_options *cell_options = &bts->si_common.cell_options; + + if (bts->infinite_radio_link_timeout) + return -1; + else { + /* Encoding as per Table 10.5.21 of TS 04.08 */ + return (cell_options->radio_link_timeout + 1) << 2; + } +} + +/* set the radio link timeout (based on SACCH decode errors, according + * to algorithm specified in TS 05.08 Section 5.2. A value of -1 + * indicates we should use an infinitely long timeout, which only works + * with OsmoBTS as the BTS implementation */ +void gsm_bts_set_radio_link_timeout(struct gsm_bts *bts, int value) +{ + struct gsm48_cell_options *cell_options = &bts->si_common.cell_options; + + if (value < 0) + bts->infinite_radio_link_timeout = true; + else { + bts->infinite_radio_link_timeout = false; + /* Encoding as per Table 10.5.21 of TS 04.08 */ + if (value < 4) + value = 4; + if (value > 64) + value = 64; + cell_options->radio_link_timeout = (value >> 2) - 1; + } +} + +bool classmark_is_r99(struct gsm_classmark *cm) +{ + int rev_lev = 0; + if (cm->classmark1_set) + rev_lev = cm->classmark1.rev_lev; + else if (cm->classmark2_len > 0) + rev_lev = (cm->classmark2[0] >> 5) & 0x3; + return rev_lev >= 2; +} + +static const struct osmo_stat_item_desc bts_stat_desc[] = { + { "chanloadavg", "Channel load average.", "%", 16, 0 }, + { "T3122", "T3122 IMMEDIATE ASSIGNMENT REJECT wait indicator.", "s", 16, GSM_T3122_DEFAULT }, +}; + +static const struct osmo_stat_item_group_desc bts_statg_desc = { + .group_name_prefix = "bts", + .group_description = "base transceiver station", + .class_id = OSMO_STATS_CLASS_GLOBAL, + .num_items = ARRAY_SIZE(bts_stat_desc), + .item_desc = bts_stat_desc, +}; + +void gsm_abis_mo_reset(struct gsm_abis_mo *mo) +{ + mo->nm_state.operational = NM_OPSTATE_NULL; + mo->nm_state.availability = NM_AVSTATE_POWER_OFF; +} + +static void gsm_mo_init(struct gsm_abis_mo *mo, struct gsm_bts *bts, + uint8_t obj_class, uint8_t p1, uint8_t p2, uint8_t p3) +{ + mo->bts = bts; + mo->obj_class = obj_class; + mo->obj_inst.bts_nr = p1; + mo->obj_inst.trx_nr = p2; + mo->obj_inst.ts_nr = p3; + gsm_abis_mo_reset(mo); +} + +const struct value_string bts_attribute_names[] = { + OSMO_VALUE_STRING(BTS_TYPE_VARIANT), + OSMO_VALUE_STRING(BTS_SUB_MODEL), + OSMO_VALUE_STRING(TRX_PHY_VERSION), + { 0, NULL } +}; + +enum bts_attribute str2btsattr(const char *s) +{ + return get_string_value(bts_attribute_names, s); +} + +const char *btsatttr2str(enum bts_attribute v) +{ + return get_value_string(bts_attribute_names, v); +} + +const struct value_string osmo_bts_variant_names[_NUM_BTS_VARIANT + 1] = { + { BTS_UNKNOWN, "unknown" }, + { BTS_OSMO_LITECELL15, "osmo-bts-lc15" }, + { BTS_OSMO_OCTPHY, "osmo-bts-octphy" }, + { BTS_OSMO_SYSMO, "osmo-bts-sysmo" }, + { BTS_OSMO_TRX, "omso-bts-trx" }, + { 0, NULL } +}; + +enum gsm_bts_type_variant str2btsvariant(const char *arg) +{ + return get_string_value(osmo_bts_variant_names, arg); +} + +const char *btsvariant2str(enum gsm_bts_type_variant v) +{ + return get_value_string(osmo_bts_variant_names, v); +} + +const struct value_string bts_type_names[_NUM_GSM_BTS_TYPE + 1] = { + { GSM_BTS_TYPE_UNKNOWN, "unknown" }, + { GSM_BTS_TYPE_BS11, "bs11" }, + { GSM_BTS_TYPE_NANOBTS, "nanobts" }, + { GSM_BTS_TYPE_RBS2000, "rbs2000" }, + { GSM_BTS_TYPE_NOKIA_SITE, "nokia_site" }, + { GSM_BTS_TYPE_OSMOBTS, "sysmobts" }, + { 0, NULL } +}; + +enum gsm_bts_type str2btstype(const char *arg) +{ + return get_string_value(bts_type_names, arg); +} + +const char *btstype2str(enum gsm_bts_type type) +{ + return get_value_string(bts_type_names, type); +} + +const struct value_string gsm_chreq_descs[] = { + { GSM_CHREQ_REASON_EMERG, "emergency call" }, + { GSM_CHREQ_REASON_PAG, "answer to paging" }, + { GSM_CHREQ_REASON_CALL, "call re-establishment" }, + { GSM_CHREQ_REASON_LOCATION_UPD,"Location updating" }, + { GSM_CHREQ_REASON_PDCH, "one phase packet access" }, + { GSM_CHREQ_REASON_OTHER, "other" }, + { 0, NULL } +}; + +const struct value_string gsm_pchant_names[13] = { + { GSM_PCHAN_NONE, "NONE" }, + { GSM_PCHAN_CCCH, "CCCH" }, + { GSM_PCHAN_CCCH_SDCCH4,"CCCH+SDCCH4" }, + { GSM_PCHAN_TCH_F, "TCH/F" }, + { GSM_PCHAN_TCH_H, "TCH/H" }, + { GSM_PCHAN_SDCCH8_SACCH8C, "SDCCH8" }, + { GSM_PCHAN_PDCH, "PDCH" }, + { GSM_PCHAN_TCH_F_PDCH, "TCH/F_PDCH" }, + { GSM_PCHAN_UNKNOWN, "UNKNOWN" }, + { GSM_PCHAN_CCCH_SDCCH4_CBCH, "CCCH+SDCCH4+CBCH" }, + { GSM_PCHAN_SDCCH8_SACCH8C_CBCH, "SDCCH8+CBCH" }, + { GSM_PCHAN_TCH_F_TCH_H_PDCH, "TCH/F_TCH/H_PDCH" }, + { 0, NULL } +}; + +const struct value_string gsm_pchant_descs[13] = { + { GSM_PCHAN_NONE, "Physical Channel not configured" }, + { GSM_PCHAN_CCCH, "FCCH + SCH + BCCH + CCCH (Comb. IV)" }, + { GSM_PCHAN_CCCH_SDCCH4, + "FCCH + SCH + BCCH + CCCH + 4 SDCCH + 2 SACCH (Comb. V)" }, + { GSM_PCHAN_TCH_F, "TCH/F + FACCH/F + SACCH (Comb. I)" }, + { GSM_PCHAN_TCH_H, "2 TCH/H + 2 FACCH/H + 2 SACCH (Comb. II)" }, + { GSM_PCHAN_SDCCH8_SACCH8C, "8 SDCCH + 4 SACCH (Comb. VII)" }, + { GSM_PCHAN_PDCH, "Packet Data Channel for GPRS/EDGE" }, + { GSM_PCHAN_TCH_F_PDCH, "Dynamic TCH/F or GPRS PDCH" }, + { GSM_PCHAN_UNKNOWN, "Unknown / Unsupported channel combination" }, + { GSM_PCHAN_CCCH_SDCCH4_CBCH, "FCCH + SCH + BCCH + CCCH + CBCH + 3 SDCCH + 2 SACCH (Comb. V)" }, + { GSM_PCHAN_SDCCH8_SACCH8C_CBCH, "7 SDCCH + 4 SACCH + CBCH (Comb. VII)" }, + { GSM_PCHAN_TCH_F_TCH_H_PDCH, "Dynamic TCH/F or TCH/H or GPRS PDCH" }, + { 0, NULL } +}; + +const char *gsm_pchan_name(enum gsm_phys_chan_config c) +{ + return get_value_string(gsm_pchant_names, c); +} + +enum gsm_phys_chan_config gsm_pchan_parse(const char *name) +{ + return get_string_value(gsm_pchant_names, name); +} + +/* TODO: move to libosmocore, next to gsm_chan_t_names? */ +const char *gsm_lchant_name(enum gsm_chan_t c) +{ + return get_value_string(gsm_chan_t_names, c); +} + +static const struct value_string lchan_s_names[] = { + { LCHAN_S_NONE, "NONE" }, + { LCHAN_S_ACT_REQ, "ACTIVATION REQUESTED" }, + { LCHAN_S_ACTIVE, "ACTIVE" }, + { LCHAN_S_INACTIVE, "INACTIVE" }, + { LCHAN_S_REL_REQ, "RELEASE REQUESTED" }, + { LCHAN_S_REL_ERR, "RELEASE DUE ERROR" }, + { LCHAN_S_BROKEN, "BROKEN UNUSABLE" }, + { 0, NULL } +}; + +const char *gsm_lchans_name(enum gsm_lchan_state s) +{ + return get_value_string(lchan_s_names, s); +} + +static const struct value_string chreq_names[] = { + { GSM_CHREQ_REASON_EMERG, "EMERGENCY" }, + { GSM_CHREQ_REASON_PAG, "PAGING" }, + { GSM_CHREQ_REASON_CALL, "CALL" }, + { GSM_CHREQ_REASON_LOCATION_UPD,"LOCATION_UPDATE" }, + { GSM_CHREQ_REASON_OTHER, "OTHER" }, + { 0, NULL } +}; + +const char *gsm_chreq_name(enum gsm_chreq_reason_t c) +{ + return get_value_string(chreq_names, c); +} + +struct gsm_bts *gsm_bts_num(struct gsm_network *net, int num) +{ + struct gsm_bts *bts; + + if (num >= net->num_bts) + return NULL; + + llist_for_each_entry(bts, &net->bts_list, list) { + if (bts->nr == num) + return bts; + } + + return NULL; +} + +struct gsm_bts_trx *gsm_bts_trx_alloc(struct gsm_bts *bts) +{ + struct gsm_bts_trx *trx = talloc_zero(bts, struct gsm_bts_trx); + int k; + + if (!trx) + return NULL; + + trx->bts = bts; + trx->nr = bts->num_trx++; + trx->mo.nm_state.administrative = NM_STATE_UNLOCKED; + + gsm_mo_init(&trx->mo, bts, NM_OC_RADIO_CARRIER, + bts->nr, trx->nr, 0xff); + gsm_mo_init(&trx->bb_transc.mo, bts, NM_OC_BASEB_TRANSC, + bts->nr, trx->nr, 0xff); + + for (k = 0; k < TRX_NR_TS; k++) { + struct gsm_bts_trx_ts *ts = &trx->ts[k]; + int l; + + ts->trx = trx; + ts->nr = k; + ts->pchan = GSM_PCHAN_NONE; + ts->dyn.pchan_is = GSM_PCHAN_NONE; + ts->dyn.pchan_want = GSM_PCHAN_NONE; + ts->tsc = -1; + + gsm_mo_init(&ts->mo, bts, NM_OC_CHANNEL, + bts->nr, trx->nr, ts->nr); + + ts->hopping.arfcns.data_len = sizeof(ts->hopping.arfcns_data); + ts->hopping.arfcns.data = ts->hopping.arfcns_data; + ts->hopping.ma.data_len = sizeof(ts->hopping.ma_data); + ts->hopping.ma.data = ts->hopping.ma_data; + + for (l = 0; l < TS_MAX_LCHAN; l++) { + struct gsm_lchan *lchan; + char *name; + lchan = &ts->lchan[l]; + + lchan->ts = ts; + lchan->nr = l; + lchan->type = GSM_LCHAN_NONE; + + name = gsm_lchan_name_compute(lchan); + lchan->name = talloc_strdup(trx, name); + } + } + + if (trx->nr != 0) + trx->nominal_power = bts->c0->nominal_power; + + llist_add_tail(&trx->list, &bts->trx_list); + + return trx; +} + + +static const uint8_t bts_nse_timer_default[] = { 3, 3, 3, 3, 30, 3, 10 }; +static const uint8_t bts_cell_timer_default[] = + { 3, 3, 3, 3, 3, 10, 3, 10, 3, 10, 3 }; +static const struct gprs_rlc_cfg rlc_cfg_default = { + .parameter = { + [RLC_T3142] = 20, + [RLC_T3169] = 5, + [RLC_T3191] = 5, + [RLC_T3193] = 160, /* 10ms */ + [RLC_T3195] = 5, + [RLC_N3101] = 10, + [RLC_N3103] = 4, + [RLC_N3105] = 8, + [CV_COUNTDOWN] = 15, + [T_DL_TBF_EXT] = 250 * 10, /* ms */ + [T_UL_TBF_EXT] = 250 * 10, /* ms */ + }, + .paging = { + .repeat_time = 5 * 50, /* ms */ + .repeat_count = 3, + }, + .cs_mask = 0x1fff, + .initial_cs = 2, + .initial_mcs = 6, +}; + +/* Initialize those parts that don't require osmo-bsc specific dependencies. + * This part is shared among the thin programs in osmo-bsc/src/utils/. + * osmo-bsc requires further initialization that pulls in more dependencies (see + * bsc_bts_alloc_register()). */ +struct gsm_bts *gsm_bts_alloc(struct gsm_network *net, uint8_t bts_num) +{ + struct gsm_bts *bts = talloc_zero(net, struct gsm_bts); + int i; + + if (!bts) + return NULL; + + bts->nr = bts_num; + bts->num_trx = 0; + INIT_LLIST_HEAD(&bts->trx_list); + bts->network = net; + + bts->ms_max_power = 15; /* dBm */ + + gsm_mo_init(&bts->mo, bts, NM_OC_BTS, + bts->nr, 0xff, 0xff); + gsm_mo_init(&bts->site_mgr.mo, bts, NM_OC_SITE_MANAGER, + 0xff, 0xff, 0xff); + + for (i = 0; i < ARRAY_SIZE(bts->gprs.nsvc); i++) { + bts->gprs.nsvc[i].bts = bts; + bts->gprs.nsvc[i].id = i; + gsm_mo_init(&bts->gprs.nsvc[i].mo, bts, NM_OC_GPRS_NSVC, + bts->nr, i, 0xff); + } + memcpy(&bts->gprs.nse.timer, bts_nse_timer_default, + sizeof(bts->gprs.nse.timer)); + gsm_mo_init(&bts->gprs.nse.mo, bts, NM_OC_GPRS_NSE, + bts->nr, 0xff, 0xff); + memcpy(&bts->gprs.cell.timer, bts_cell_timer_default, + sizeof(bts->gprs.cell.timer)); + gsm_mo_init(&bts->gprs.cell.mo, bts, NM_OC_GPRS_CELL, + bts->nr, 0xff, 0xff); + memcpy(&bts->gprs.cell.rlc_cfg, &rlc_cfg_default, + sizeof(bts->gprs.cell.rlc_cfg)); + + /* init statistics */ + bts->bts_ctrs = rate_ctr_group_alloc(bts, &bts_ctrg_desc, bts->nr); + if (!bts->bts_ctrs) { + talloc_free(bts); + return NULL; + } + bts->bts_statg = osmo_stat_item_group_alloc(bts, &bts_statg_desc, 0); + + /* create our primary TRX */ + bts->c0 = gsm_bts_trx_alloc(bts); + if (!bts->c0) { + rate_ctr_group_free(bts->bts_ctrs); + osmo_stat_item_group_free(bts->bts_statg); + talloc_free(bts); + return NULL; + } + bts->c0->ts[0].pchan = GSM_PCHAN_CCCH_SDCCH4; + + bts->rach_b_thresh = -1; + bts->rach_ldavg_slots = -1; + + bts->paging.free_chans_need = -1; + INIT_LLIST_HEAD(&bts->paging.pending_requests); + + bts->features.data = &bts->_features_data[0]; + bts->features.data_len = sizeof(bts->_features_data); + + /* si handling */ + bts->bcch_change_mark = 1; + bts->chan_load_avg = 0; + + /* timer overrides */ + bts->T3122 = 0; /* not overriden by default */ + + bts->dtxu = GSM48_DTX_SHALL_NOT_BE_USED; + bts->dtxd = false; + bts->gprs.ctrl_ack_type_use_block = true; /* use RLC/MAC control block */ + bts->neigh_list_manual_mode = 0; + bts->early_classmark_allowed_3g = true; /* 3g Early Classmark Sending controlled by bts->early_classmark_allowed param */ + bts->si_common.cell_sel_par.cell_resel_hyst = 2; /* 4 dB */ + bts->si_common.cell_sel_par.rxlev_acc_min = 0; + bts->si_common.si2quater_neigh_list.arfcn = bts->si_common.data.earfcn_list; + bts->si_common.si2quater_neigh_list.meas_bw = bts->si_common.data.meas_bw_list; + bts->si_common.si2quater_neigh_list.length = MAX_EARFCN_LIST; + bts->si_common.si2quater_neigh_list.thresh_hi = 0; + osmo_earfcn_init(&bts->si_common.si2quater_neigh_list); + bts->si_common.neigh_list.data = bts->si_common.data.neigh_list; + bts->si_common.neigh_list.data_len = + sizeof(bts->si_common.data.neigh_list); + bts->si_common.si5_neigh_list.data = bts->si_common.data.si5_neigh_list; + bts->si_common.si5_neigh_list.data_len = + sizeof(bts->si_common.data.si5_neigh_list); + bts->si_common.cell_alloc.data = bts->si_common.data.cell_alloc; + bts->si_common.cell_alloc.data_len = + sizeof(bts->si_common.data.cell_alloc); + bts->si_common.rach_control.re = 1; /* no re-establishment */ + bts->si_common.rach_control.tx_integer = 9; /* 12 slots spread - 217/115 slots delay */ + bts->si_common.rach_control.max_trans = 3; /* 7 retransmissions */ + bts->si_common.rach_control.t2 = 4; /* no emergency calls */ + bts->si_common.chan_desc.att = 1; /* attachment required */ + bts->si_common.chan_desc.bs_pa_mfrms = RSL_BS_PA_MFRMS_5; /* paging frames */ + bts->si_common.chan_desc.bs_ag_blks_res = 1; /* reserved AGCH blocks */ + bts->si_common.chan_desc.t3212 = net->t3212; /* Use network's current value */ + gsm_bts_set_radio_link_timeout(bts, 32); /* Use RADIO LINK TIMEOUT of 32 */ + + INIT_LLIST_HEAD(&bts->abis_queue); + INIT_LLIST_HEAD(&bts->loc_list); + + return bts; +} + +/* reset the state of all MO in the BTS */ +void gsm_bts_mo_reset(struct gsm_bts *bts) +{ + struct gsm_bts_trx *trx; + unsigned int i; + + gsm_abis_mo_reset(&bts->mo); + gsm_abis_mo_reset(&bts->site_mgr.mo); + for (i = 0; i < ARRAY_SIZE(bts->gprs.nsvc); i++) + gsm_abis_mo_reset(&bts->gprs.nsvc[i].mo); + gsm_abis_mo_reset(&bts->gprs.nse.mo); + gsm_abis_mo_reset(&bts->gprs.cell.mo); + + llist_for_each_entry(trx, &bts->trx_list, list) { + gsm_abis_mo_reset(&trx->mo); + gsm_abis_mo_reset(&trx->bb_transc.mo); + + for (i = 0; i < ARRAY_SIZE(trx->ts); i++) { + struct gsm_bts_trx_ts *ts = &trx->ts[i]; + gsm_abis_mo_reset(&ts->mo); + } + } +} + +struct gsm_bts_trx *gsm_bts_trx_num(const struct gsm_bts *bts, int num) +{ + struct gsm_bts_trx *trx; + + if (num >= bts->num_trx) + return NULL; + + llist_for_each_entry(trx, &bts->trx_list, list) { + if (trx->nr == num) + return trx; + } + + return NULL; +} + +static char ts2str[255]; + +char *gsm_trx_name(const struct gsm_bts_trx *trx) +{ + if (!trx) + snprintf(ts2str, sizeof(ts2str), "(trx=NULL)"); + else + snprintf(ts2str, sizeof(ts2str), "(bts=%d,trx=%d)", + trx->bts->nr, trx->nr); + + return ts2str; +} + + +char *gsm_ts_name(const struct gsm_bts_trx_ts *ts) +{ + snprintf(ts2str, sizeof(ts2str), "(bts=%d,trx=%d,ts=%d)", + ts->trx->bts->nr, ts->trx->nr, ts->nr); + + return ts2str; +} + +/*! Log timeslot number with full pchan information */ +char *gsm_ts_and_pchan_name(const struct gsm_bts_trx_ts *ts) +{ + switch (ts->pchan) { + case GSM_PCHAN_TCH_F_TCH_H_PDCH: + if (ts->dyn.pchan_is == ts->dyn.pchan_want) + snprintf(ts2str, sizeof(ts2str), + "(bts=%d,trx=%d,ts=%d,pchan=%s as %s)", + ts->trx->bts->nr, ts->trx->nr, ts->nr, + gsm_pchan_name(ts->pchan), + gsm_pchan_name(ts->dyn.pchan_is)); + else + snprintf(ts2str, sizeof(ts2str), + "(bts=%d,trx=%d,ts=%d,pchan=%s" + " switching %s -> %s)", + ts->trx->bts->nr, ts->trx->nr, ts->nr, + gsm_pchan_name(ts->pchan), + gsm_pchan_name(ts->dyn.pchan_is), + gsm_pchan_name(ts->dyn.pchan_want)); + break; + case GSM_PCHAN_TCH_F_PDCH: + if ((ts->flags & TS_F_PDCH_PENDING_MASK) == 0) + snprintf(ts2str, sizeof(ts2str), + "(bts=%d,trx=%d,ts=%d,pchan=%s as %s)", + ts->trx->bts->nr, ts->trx->nr, ts->nr, + gsm_pchan_name(ts->pchan), + (ts->flags & TS_F_PDCH_ACTIVE)? "PDCH" + : "TCH/F"); + else + snprintf(ts2str, sizeof(ts2str), + "(bts=%d,trx=%d,ts=%d,pchan=%s" + " switching %s -> %s)", + ts->trx->bts->nr, ts->trx->nr, ts->nr, + gsm_pchan_name(ts->pchan), + (ts->flags & TS_F_PDCH_ACTIVE)? "PDCH" + : "TCH/F", + (ts->flags & TS_F_PDCH_ACT_PENDING)? "PDCH" + : "TCH/F"); + break; + default: + snprintf(ts2str, sizeof(ts2str), "(bts=%d,trx=%d,ts=%d,pchan=%s)", + ts->trx->bts->nr, ts->trx->nr, ts->nr, + gsm_pchan_name(ts->pchan)); + break; + } + + return ts2str; +} + +char *gsm_lchan_name_compute(const struct gsm_lchan *lchan) +{ + struct gsm_bts_trx_ts *ts = lchan->ts; + + snprintf(ts2str, sizeof(ts2str), "(bts=%d,trx=%d,ts=%d,ss=%d)", + ts->trx->bts->nr, ts->trx->nr, ts->nr, lchan->nr); + + return ts2str; +} + +/* obtain the MO structure for a given object instance */ +static inline struct gsm_abis_mo * +gsm_objclass2mo(struct gsm_bts *bts, uint8_t obj_class, + const struct abis_om_obj_inst *obj_inst) +{ + struct gsm_bts_trx *trx; + struct gsm_abis_mo *mo = NULL; + + switch (obj_class) { + case NM_OC_BTS: + mo = &bts->mo; + break; + case NM_OC_RADIO_CARRIER: + if (obj_inst->trx_nr >= bts->num_trx) { + return NULL; + } + trx = gsm_bts_trx_num(bts, obj_inst->trx_nr); + mo = &trx->mo; + break; + case NM_OC_BASEB_TRANSC: + if (obj_inst->trx_nr >= bts->num_trx) { + return NULL; + } + trx = gsm_bts_trx_num(bts, obj_inst->trx_nr); + mo = &trx->bb_transc.mo; + break; + case NM_OC_CHANNEL: + if (obj_inst->trx_nr >= bts->num_trx) { + return NULL; + } + trx = gsm_bts_trx_num(bts, obj_inst->trx_nr); + if (obj_inst->ts_nr >= TRX_NR_TS) + return NULL; + mo = &trx->ts[obj_inst->ts_nr].mo; + break; + case NM_OC_SITE_MANAGER: + mo = &bts->site_mgr.mo; + break; + case NM_OC_BS11: + switch (obj_inst->bts_nr) { + case BS11_OBJ_CCLK: + mo = &bts->bs11.cclk.mo; + break; + case BS11_OBJ_BBSIG: + if (obj_inst->ts_nr > bts->num_trx) + return NULL; + trx = gsm_bts_trx_num(bts, obj_inst->trx_nr); + mo = &trx->bs11.bbsig.mo; + break; + case BS11_OBJ_PA: + if (obj_inst->ts_nr > bts->num_trx) + return NULL; + trx = gsm_bts_trx_num(bts, obj_inst->trx_nr); + mo = &trx->bs11.pa.mo; + break; + default: + return NULL; + } + break; + case NM_OC_BS11_RACK: + mo = &bts->bs11.rack.mo; + break; + case NM_OC_BS11_ENVABTSE: + if (obj_inst->trx_nr >= ARRAY_SIZE(bts->bs11.envabtse)) + return NULL; + mo = &bts->bs11.envabtse[obj_inst->trx_nr].mo; + break; + case NM_OC_GPRS_NSE: + mo = &bts->gprs.nse.mo; + break; + case NM_OC_GPRS_CELL: + mo = &bts->gprs.cell.mo; + break; + case NM_OC_GPRS_NSVC: + if (obj_inst->trx_nr >= ARRAY_SIZE(bts->gprs.nsvc)) + return NULL; + mo = &bts->gprs.nsvc[obj_inst->trx_nr].mo; + break; + } + return mo; +} + +/* obtain the gsm_nm_state data structure for a given object instance */ +struct gsm_nm_state * +gsm_objclass2nmstate(struct gsm_bts *bts, uint8_t obj_class, + const struct abis_om_obj_inst *obj_inst) +{ + struct gsm_abis_mo *mo; + + mo = gsm_objclass2mo(bts, obj_class, obj_inst); + if (!mo) + return NULL; + + return &mo->nm_state; +} + +/* obtain the in-memory data structure of a given object instance */ +void * +gsm_objclass2obj(struct gsm_bts *bts, uint8_t obj_class, + const struct abis_om_obj_inst *obj_inst) +{ + struct gsm_bts_trx *trx; + void *obj = NULL; + + switch (obj_class) { + case NM_OC_BTS: + obj = bts; + break; + case NM_OC_RADIO_CARRIER: + if (obj_inst->trx_nr >= bts->num_trx) { + return NULL; + } + trx = gsm_bts_trx_num(bts, obj_inst->trx_nr); + obj = trx; + break; + case NM_OC_BASEB_TRANSC: + if (obj_inst->trx_nr >= bts->num_trx) { + return NULL; + } + trx = gsm_bts_trx_num(bts, obj_inst->trx_nr); + obj = &trx->bb_transc; + break; + case NM_OC_CHANNEL: + if (obj_inst->trx_nr >= bts->num_trx) { + return NULL; + } + trx = gsm_bts_trx_num(bts, obj_inst->trx_nr); + if (obj_inst->ts_nr >= TRX_NR_TS) + return NULL; + obj = &trx->ts[obj_inst->ts_nr]; + break; + case NM_OC_SITE_MANAGER: + obj = &bts->site_mgr; + break; + case NM_OC_GPRS_NSE: + obj = &bts->gprs.nse; + break; + case NM_OC_GPRS_CELL: + obj = &bts->gprs.cell; + break; + case NM_OC_GPRS_NSVC: + if (obj_inst->trx_nr >= ARRAY_SIZE(bts->gprs.nsvc)) + return NULL; + obj = &bts->gprs.nsvc[obj_inst->trx_nr]; + break; + } + return obj; +} + +/* See Table 10.5.25 of GSM04.08 */ +uint8_t gsm_pchan2chan_nr(enum gsm_phys_chan_config pchan, + uint8_t ts_nr, uint8_t lchan_nr) +{ + uint8_t cbits, chan_nr; + + switch (pchan) { + case GSM_PCHAN_TCH_F: + case GSM_PCHAN_TCH_F_PDCH: + OSMO_ASSERT(lchan_nr == 0); + cbits = 0x01; + break; + case GSM_PCHAN_PDCH: + OSMO_ASSERT(lchan_nr == 0); + cbits = RSL_CHAN_OSMO_PDCH >> 3; + break; + case GSM_PCHAN_TCH_H: + OSMO_ASSERT(lchan_nr < 2); + cbits = 0x02; + cbits += lchan_nr; + break; + case GSM_PCHAN_CCCH_SDCCH4: + case GSM_PCHAN_CCCH_SDCCH4_CBCH: + /* + * As a special hack for BCCH, lchan_nr == 4 may be passed + * here. This should never be sent in an RSL message. + * See osmo-bts-xxx/oml.c:opstart_compl(). + */ + if (lchan_nr == CCCH_LCHAN) + chan_nr = 0; + else + OSMO_ASSERT(lchan_nr < 4); + cbits = 0x04; + cbits += lchan_nr; + break; + case GSM_PCHAN_SDCCH8_SACCH8C: + case GSM_PCHAN_SDCCH8_SACCH8C_CBCH: + OSMO_ASSERT(lchan_nr < 8); + cbits = 0x08; + cbits += lchan_nr; + break; + default: + case GSM_PCHAN_CCCH: + OSMO_ASSERT(lchan_nr == 0); + cbits = 0x10; + break; + } + + chan_nr = (cbits << 3) | (ts_nr & 0x7); + + return chan_nr; +} + +uint8_t gsm_lchan2chan_nr(const struct gsm_lchan *lchan) +{ + enum gsm_phys_chan_config pchan = lchan->ts->pchan; + if (pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH) + return gsm_lchan_as_pchan2chan_nr(lchan, + lchan->ts->dyn.pchan_is); + return gsm_pchan2chan_nr(lchan->ts->pchan, lchan->ts->nr, lchan->nr); +} + +uint8_t gsm_lchan_as_pchan2chan_nr(const struct gsm_lchan *lchan, + enum gsm_phys_chan_config as_pchan) +{ + if (lchan->ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH + && as_pchan == GSM_PCHAN_PDCH) + return RSL_CHAN_OSMO_PDCH | (lchan->ts->nr & ~RSL_CHAN_NR_MASK); + return gsm_pchan2chan_nr(as_pchan, lchan->ts->nr, lchan->nr); +} + +/* return the gsm_lchan for the CBCH (if it exists at all) */ +struct gsm_lchan *gsm_bts_get_cbch(struct gsm_bts *bts) +{ + struct gsm_lchan *lchan = NULL; + struct gsm_bts_trx *trx = bts->c0; + + if (trx->ts[0].pchan == GSM_PCHAN_CCCH_SDCCH4_CBCH) + lchan = &trx->ts[0].lchan[2]; + else { + int i; + for (i = 0; i < 8; i++) { + if (trx->ts[i].pchan == GSM_PCHAN_SDCCH8_SACCH8C_CBCH) { + lchan = &trx->ts[i].lchan[2]; + break; + } + } + } + + return lchan; +} + +/* determine logical channel based on TRX and channel number IE */ +struct gsm_lchan *rsl_lchan_lookup(struct gsm_bts_trx *trx, uint8_t chan_nr, + int *rc) +{ + uint8_t ts_nr = chan_nr & 0x07; + uint8_t cbits = chan_nr >> 3; + uint8_t lch_idx; + struct gsm_bts_trx_ts *ts = &trx->ts[ts_nr]; + bool ok = true; + + if (rc) + *rc = -EINVAL; + + if (cbits == 0x01) { + lch_idx = 0; /* TCH/F */ + if (ts->pchan != GSM_PCHAN_TCH_F && + ts->pchan != GSM_PCHAN_PDCH && + ts->pchan != GSM_PCHAN_TCH_F_PDCH + && !(ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH + && (ts->dyn.pchan_is == GSM_PCHAN_TCH_F + || ts->dyn.pchan_want == GSM_PCHAN_TCH_F))) + ok = false; + } else if ((cbits & 0x1e) == 0x02) { + lch_idx = cbits & 0x1; /* TCH/H */ + if (ts->pchan != GSM_PCHAN_TCH_H + && !(ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH + && (ts->dyn.pchan_is == GSM_PCHAN_TCH_H + || ts->dyn.pchan_want == GSM_PCHAN_TCH_H))) + ok = false; + } else if ((cbits & 0x1c) == 0x04) { + lch_idx = cbits & 0x3; /* SDCCH/4 */ + if (ts->pchan != GSM_PCHAN_CCCH_SDCCH4 && + ts->pchan != GSM_PCHAN_CCCH_SDCCH4_CBCH) + ok = false; + } else if ((cbits & 0x18) == 0x08) { + lch_idx = cbits & 0x7; /* SDCCH/8 */ + if (ts->pchan != GSM_PCHAN_SDCCH8_SACCH8C && + ts->pchan != GSM_PCHAN_SDCCH8_SACCH8C_CBCH) + ok = false; + } else if (cbits == 0x10 || cbits == 0x11 || cbits == 0x12) { + lch_idx = 0; + if (ts->pchan != GSM_PCHAN_CCCH && + ts->pchan != GSM_PCHAN_CCCH_SDCCH4 && + ts->pchan != GSM_PCHAN_CCCH_SDCCH4_CBCH) + ok = false; + /* FIXME: we should not return first sdcch4 !!! */ + } else if ((chan_nr & RSL_CHAN_NR_MASK) == RSL_CHAN_OSMO_PDCH) { + lch_idx = 0; + if (ts->pchan != GSM_PCHAN_TCH_F_TCH_H_PDCH) + ok = false; + } else + return NULL; + + if (rc && ok) + *rc = 0; + + return &ts->lchan[lch_idx]; +} + +static const uint8_t subslots_per_pchan[] = { + [GSM_PCHAN_NONE] = 0, + [GSM_PCHAN_CCCH] = 0, + [GSM_PCHAN_PDCH] = 0, + [GSM_PCHAN_CCCH_SDCCH4] = 4, + [GSM_PCHAN_TCH_F] = 1, + [GSM_PCHAN_TCH_H] = 2, + [GSM_PCHAN_SDCCH8_SACCH8C] = 8, + [GSM_PCHAN_CCCH_SDCCH4_CBCH] = 4, + [GSM_PCHAN_SDCCH8_SACCH8C_CBCH] = 8, + /* + * GSM_PCHAN_TCH_F_PDCH and GSM_PCHAN_TCH_F_TCH_H_PDCH should not be + * part of this, those TS are handled according to their dynamic state. + */ +}; + +/*! Return the actual pchan type, also heeding dynamic TS. */ +enum gsm_phys_chan_config ts_pchan(struct gsm_bts_trx_ts *ts) +{ + switch (ts->pchan) { + case GSM_PCHAN_TCH_F_TCH_H_PDCH: + return ts->dyn.pchan_is; + case GSM_PCHAN_TCH_F_PDCH: + if (ts->flags & TS_F_PDCH_ACTIVE) + return GSM_PCHAN_PDCH; + else + return GSM_PCHAN_TCH_F; + default: + return ts->pchan; + } +} + +/*! According to ts->pchan and possibly ts->dyn_pchan, return the number of + * logical channels available in the timeslot. */ +uint8_t ts_subslots(struct gsm_bts_trx_ts *ts) +{ + return subslots_per_pchan[ts_pchan(ts)]; +} + +static bool pchan_is_tch(enum gsm_phys_chan_config pchan) +{ + switch (pchan) { + case GSM_PCHAN_TCH_F: + case GSM_PCHAN_TCH_H: + return true; + default: + return false; + } +} + +bool ts_is_tch(struct gsm_bts_trx_ts *ts) +{ + return pchan_is_tch(ts_pchan(ts)); +} + +bool trx_is_usable(const struct gsm_bts_trx *trx) +{ + /* FIXME: How does this behave for BS-11 ? */ + if (is_ipaccess_bts(trx->bts)) { + if (!nm_is_running(&trx->mo.nm_state) || + !nm_is_running(&trx->bb_transc.mo.nm_state)) + return false; + } + + return true; +} + +void gsm_trx_mark_all_ts_uninitialized(struct gsm_bts_trx *trx) +{ + int i; + for (i = 0; i < ARRAY_SIZE(trx->ts); i++) { + struct gsm_bts_trx_ts *ts = &trx->ts[i]; + ts->initialized = false; + } +} + +void gsm_bts_mark_all_ts_uninitialized(struct gsm_bts *bts) +{ + struct gsm_bts_trx *trx; + llist_for_each_entry(trx, &bts->trx_list, list) + gsm_trx_mark_all_ts_uninitialized(trx); +} + +/* Trigger initial timeslot actions iff both OML and RSL are setup. */ +void gsm_ts_check_init(struct gsm_bts_trx_ts *ts) +{ + struct gsm_bts *bts = ts->trx->bts; + if (bts->model->oml_is_ts_ready + && !bts->model->oml_is_ts_ready(ts)) + return; + if (!ts->trx->rsl_link) + return; + if (ts->initialized) + return; + ts->initialized = on_gsm_ts_init(ts); +} + +void gsm48_lchan2chan_desc(struct gsm48_chan_desc *cd, + const struct gsm_lchan *lchan) +{ + uint16_t arfcn = lchan->ts->trx->arfcn & 0x3ff; + + cd->chan_nr = gsm_lchan2chan_nr(lchan); + if (!lchan->ts->hopping.enabled) { + cd->h0.tsc = gsm_ts_tsc(lchan->ts); + cd->h0.h = 0; + cd->h0.arfcn_high = arfcn >> 8; + cd->h0.arfcn_low = arfcn & 0xff; + } else { + cd->h1.tsc = gsm_ts_tsc(lchan->ts); + cd->h1.h = 1; + cd->h1.maio_high = lchan->ts->hopping.maio >> 2; + cd->h1.maio_low = lchan->ts->hopping.maio & 0x03; + cd->h1.hsn = lchan->ts->hopping.hsn; + } +} + +bool nm_is_running(const struct gsm_nm_state *s) { + return (s->operational == NM_OPSTATE_ENABLED) && ( + (s->availability == NM_AVSTATE_OK) || + (s->availability == 0xff) + ); +} diff --git a/src/osmo-bsc/handover_cfg.c b/src/osmo-bsc/handover_cfg.c new file mode 100644 index 000000000..14f5d8e84 --- /dev/null +++ b/src/osmo-bsc/handover_cfg.c @@ -0,0 +1,86 @@ +/* OsmoBSC handover configuration implementation */ +/* (C) 2009-2010 by Andreas Eversberg <jolly@eversberg.eu> + * (C) 2009-2010 by Harald Welte <laforge@gnumonks.org> + * (C) 2017-2018 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> + * + * All Rights Reserved + * + * Author: Andreas Eversberg <jolly@eversberg.eu> + * Neels Hofmeyr <nhofmeyr@sysmocom.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/>. + * + */ + +#include <stdbool.h> +#include <talloc.h> + +#include <osmocom/bsc/vty.h> +#include <osmocom/bsc/handover_cfg.h> +#include <osmocom/bsc/gsm_data.h> + +struct handover_cfg { + struct handover_cfg *higher_level_cfg; + +#define HO_CFG_ONE_MEMBER(TYPE, NAME, DEFAULT_VAL, VTY0, VTY1, VTY2, VTY3, VTY4, VTY5, VTY6) \ + TYPE NAME; \ + bool has_##NAME; + + HO_CFG_ALL_MEMBERS +#undef HO_CFG_ONE_MEMBER +}; + +struct handover_cfg *ho_cfg_init(void *ctx, struct handover_cfg *higher_level_cfg) +{ + struct handover_cfg *ho = talloc_zero(ctx, struct handover_cfg); + OSMO_ASSERT(ho); + ho->higher_level_cfg = higher_level_cfg; + return ho; +} + +#define HO_CFG_ONE_MEMBER(TYPE, NAME, DEFAULT_VAL, VTY0, VTY1, VTY2, VTY_ARG_EVAL, VTY4, VTY5, VTY6) \ +TYPE ho_get_##NAME(struct handover_cfg *ho) \ +{ \ + if (ho->has_##NAME) \ + return ho->NAME; \ + if (ho->higher_level_cfg) \ + return ho_get_##NAME(ho->higher_level_cfg); \ + return VTY_ARG_EVAL(#DEFAULT_VAL); \ +} \ +\ +void ho_set_##NAME(struct handover_cfg *ho, TYPE value) \ +{ \ + ho->NAME = value; \ + ho->has_##NAME = true; \ +} \ +\ +bool ho_isset_##NAME(struct handover_cfg *ho) \ +{ \ + return ho->has_##NAME; \ +} \ +\ +void ho_clear_##NAME(struct handover_cfg *ho) \ +{ \ + ho->has_##NAME = false; \ +} \ +\ +bool ho_isset_on_parent_##NAME(struct handover_cfg *ho) \ +{ \ + return ho->higher_level_cfg \ + && (ho_isset_##NAME(ho->higher_level_cfg) \ + || ho_isset_on_parent_##NAME(ho->higher_level_cfg)); \ +} + +HO_CFG_ALL_MEMBERS +#undef HO_CFG_ONE_MEMBER diff --git a/src/osmo-bsc/handover_decision.c b/src/osmo-bsc/handover_decision.c new file mode 100644 index 000000000..887c2993f --- /dev/null +++ b/src/osmo-bsc/handover_decision.c @@ -0,0 +1,343 @@ +/* Handover Decision making for Inter-BTS (Intra-BSC) Handover. This + * only implements the handover algorithm/decision, but not execution + * of it */ + +/* (C) 2009 by Harald Welte <laforge@gnumonks.org> + * + * 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 <stdlib.h> +#include <errno.h> + +#include <osmocom/core/msgb.h> +#include <osmocom/bsc/debug.h> +#include <osmocom/bsc/gsm_data.h> +#include <osmocom/bsc/meas_rep.h> +#include <osmocom/bsc/signal.h> +#include <osmocom/core/talloc.h> +#include <osmocom/gsm/gsm_utils.h> + +#include <osmocom/bsc/handover.h> +#include <osmocom/bsc/handover_cfg.h> + +/* Find BTS by ARFCN and BSIC */ +struct gsm_bts *bts_by_arfcn_bsic(const struct gsm_network *net, + uint16_t arfcn, uint8_t bsic) +{ + struct gsm_bts *bts; + + llist_for_each_entry(bts, &net->bts_list, list) { + if (bts->c0->arfcn == arfcn && + bts->bsic == bsic) + return bts; + } + + return NULL; +} + + +/* issue handover to a cell identified by ARFCN and BSIC */ +static int handover_to_arfcn_bsic(struct gsm_lchan *lchan, + uint16_t arfcn, uint8_t bsic) +{ + struct gsm_bts *new_bts; + + /* resolve the gsm_bts structure for the best neighbor */ + /* FIXME: use some better heuristics here to determine which cell + * using this ARFCN really is closest to the target cell. For + * now we simply assume that each ARFCN will only be used by one + * cell */ + new_bts = bts_by_arfcn_bsic(lchan->ts->trx->bts->network, arfcn, bsic); + if (!new_bts) { + LOGP(DHODEC, LOGL_NOTICE, "unable to determine neighbor BTS " + "for ARFCN %u BSIC %u ?!?\n", arfcn, bsic); + return -EINVAL; + } + + /* and actually try to handover to that cell */ + return bsc_handover_start(HODEC1, lchan, new_bts, lchan->type); +} + +/* did we get a RXLEV for a given cell in the given report? */ +static int rxlev_for_cell_in_rep(struct gsm_meas_rep *mr, + uint16_t arfcn, uint8_t bsic) +{ + int i; + + for (i = 0; i < mr->num_cell; i++) { + struct gsm_meas_rep_cell *mrc = &mr->cell[i]; + + /* search for matching report */ + if (!(mrc->arfcn == arfcn && mrc->bsic == bsic)) + continue; + + mrc->flags |= MRC_F_PROCESSED; + return mrc->rxlev; + } + return -ENODEV; +} + +/* obtain averaged rxlev for given neighbor */ +static int neigh_meas_avg(struct neigh_meas_proc *nmp, int window) +{ + unsigned int i, idx; + int avg = 0; + + /* reduce window to the actual number of existing measurements */ + if (window > nmp->rxlev_cnt) + window = nmp->rxlev_cnt; + /* this should never happen */ + if (window <= 0) { + LOGP(DHODEC, LOGL_ERROR, "Requested Neighbor RxLev for invalid window size of %d\n", window); + return 0; + } + + idx = calc_initial_idx(ARRAY_SIZE(nmp->rxlev), + nmp->rxlev_cnt % ARRAY_SIZE(nmp->rxlev), + window); + + for (i = 0; i < window; i++) { + int j = (idx+i) % ARRAY_SIZE(nmp->rxlev); + + avg += nmp->rxlev[j]; + } + + return avg / window; +} + +/* find empty or evict bad neighbor */ +static struct neigh_meas_proc *find_evict_neigh(struct gsm_lchan *lchan) +{ + int j, worst = 999999; + struct neigh_meas_proc *nmp_worst = NULL; + + /* first try to find an empty/unused slot */ + for (j = 0; j < ARRAY_SIZE(lchan->neigh_meas); j++) { + struct neigh_meas_proc *nmp = &lchan->neigh_meas[j]; + if (!nmp->arfcn) + return nmp; + } + + /* no empty slot found. evict worst neighbor from list */ + for (j = 0; j < ARRAY_SIZE(lchan->neigh_meas); j++) { + struct neigh_meas_proc *nmp = &lchan->neigh_meas[j]; + int avg = neigh_meas_avg(nmp, MAX_WIN_NEIGH_AVG); + if (!nmp_worst || avg < worst) { + worst = avg; + nmp_worst = nmp; + } + } + + return nmp_worst; +} + +/* process neighbor cell measurement reports */ +static void process_meas_neigh(struct gsm_meas_rep *mr) +{ + int i, j, idx; + + /* for each reported cell, try to update global state */ + for (j = 0; j < ARRAY_SIZE(mr->lchan->neigh_meas); j++) { + struct neigh_meas_proc *nmp = &mr->lchan->neigh_meas[j]; + unsigned int idx; + int rxlev; + + /* skip unused entries */ + if (!nmp->arfcn) + continue; + + rxlev = rxlev_for_cell_in_rep(mr, nmp->arfcn, nmp->bsic); + idx = nmp->rxlev_cnt % ARRAY_SIZE(nmp->rxlev); + if (rxlev >= 0) { + nmp->rxlev[idx] = rxlev; + nmp->last_seen_nr = mr->nr; + } else + nmp->rxlev[idx] = 0; + nmp->rxlev_cnt++; + } + + /* iterate over list of reported cells, check if we did not + * process all of them */ + for (i = 0; i < mr->num_cell; i++) { + struct gsm_meas_rep_cell *mrc = &mr->cell[i]; + struct neigh_meas_proc *nmp; + + if (mrc->flags & MRC_F_PROCESSED) + continue; + + nmp = find_evict_neigh(mr->lchan); + + nmp->arfcn = mrc->arfcn; + nmp->bsic = mrc->bsic; + + nmp->rxlev_cnt = 0; + idx = nmp->rxlev_cnt % ARRAY_SIZE(nmp->rxlev); + nmp->rxlev[idx] = mrc->rxlev; + nmp->rxlev_cnt++; + nmp->last_seen_nr = mr->nr; + + mrc->flags |= MRC_F_PROCESSED; + } +} + +/* attempt to do a handover */ +static int attempt_handover(struct gsm_meas_rep *mr) +{ + struct gsm_bts *bts = mr->lchan->ts->trx->bts; + struct neigh_meas_proc *best_cell = NULL; + unsigned int best_better_db = 0; + int i, rc; + + /* find the best cell in this report that is at least RXLEV_HYST + * better than the current serving cell */ + + for (i = 0; i < ARRAY_SIZE(mr->lchan->neigh_meas); i++) { + struct neigh_meas_proc *nmp = &mr->lchan->neigh_meas[i]; + int avg, better; + + /* skip empty slots */ + if (nmp->arfcn == 0) + continue; + + /* caculate average rxlev for this cell over the window */ + avg = neigh_meas_avg(nmp, ho_get_hodec1_rxlev_neigh_avg_win(bts->ho)); + + /* check if hysteresis is fulfilled */ + if (avg < mr->dl.full.rx_lev + ho_get_hodec1_pwr_hysteresis(bts->ho)) + continue; + + better = avg - mr->dl.full.rx_lev; + if (better > best_better_db) { + best_cell = nmp; + best_better_db = better; + } + } + + if (!best_cell) + return 0; + + LOGP(DHODEC, LOGL_INFO, "%s: Cell on ARFCN %u is better: ", + gsm_ts_name(mr->lchan->ts), best_cell->arfcn); + if (!ho_get_ho_active(bts->ho)) { + LOGPC(DHODEC, LOGL_INFO, "Skipping, Handover disabled\n"); + return 0; + } + + rc = handover_to_arfcn_bsic(mr->lchan, best_cell->arfcn, best_cell->bsic); + switch (rc) { + case 0: + LOGPC(DHODEC, LOGL_INFO, "Starting handover: meas report number %d \n", mr->nr); + break; + case -ENOSPC: + LOGPC(DHODEC, LOGL_INFO, "No channel available\n"); + break; + case -EBUSY: + LOGPC(DHODEC, LOGL_INFO, "Handover already active\n"); + break; + default: + LOGPC(DHODEC, LOGL_ERROR, "Unknown error\n"); + } + return rc; +} + +/* process an already parsed measurement report and decide if we want to + * attempt a handover */ +static void on_measurement_report(struct gsm_meas_rep *mr) +{ + struct gsm_bts *bts = mr->lchan->ts->trx->bts; + enum meas_rep_field dlev, dqual; + int av_rxlev; + unsigned int pwr_interval; + + /* If this cell does not use handover algorithm 1, then we're not responsible. */ + if (ho_get_algorithm(bts->ho) != 1) + return; + + /* we currently only do handover for TCH channels */ + switch (mr->lchan->type) { + case GSM_LCHAN_TCH_F: + case GSM_LCHAN_TCH_H: + break; + default: + return; + } + + if (mr->flags & MEAS_REP_F_DL_DTX) { + dlev = MEAS_REP_DL_RXLEV_SUB; + dqual = MEAS_REP_DL_RXQUAL_SUB; + } else { + dlev = MEAS_REP_DL_RXLEV_FULL; + dqual = MEAS_REP_DL_RXQUAL_FULL; + } + + /* parse actual neighbor cell info */ + if (mr->num_cell > 0 && mr->num_cell < 7) + process_meas_neigh(mr); + + av_rxlev = get_meas_rep_avg(mr->lchan, dlev, + ho_get_hodec1_rxlev_avg_win(bts->ho)); + + /* Interference HO */ + if (rxlev2dbm(av_rxlev) > -85 && + meas_rep_n_out_of_m_be(mr->lchan, dqual, 3, 4, 5)) { + LOGPC(DHO, LOGL_INFO, "HO cause: Interference HO av_rxlev=%d dBm\n", + rxlev2dbm(av_rxlev)); + attempt_handover(mr); + return; + } + + /* Bad Quality */ + if (meas_rep_n_out_of_m_be(mr->lchan, dqual, 3, 4, 5)) { + LOGPC(DHO, LOGL_INFO, "HO cause: Bad Quality av_rxlev=%d dBm\n", rxlev2dbm(av_rxlev)); + attempt_handover(mr); + return; + } + + /* Low Level */ + if (rxlev2dbm(av_rxlev) <= -110) { + LOGPC(DHO, LOGL_INFO, "HO cause: Low Level av_rxlev=%d dBm\n", rxlev2dbm(av_rxlev)); + attempt_handover(mr); + return; + } + + /* Distance */ + if (mr->ms_l1.ta > ho_get_hodec1_max_distance(bts->ho)) { + LOGPC(DHO, LOGL_INFO, "HO cause: Distance av_rxlev=%d dBm ta=%d \n", + rxlev2dbm(av_rxlev), mr->ms_l1.ta); + attempt_handover(mr); + return; + } + + /* Power Budget AKA Better Cell */ + pwr_interval = ho_get_hodec1_pwr_interval(bts->ho); + /* handover_cfg.h defines pwr_interval as [1..99], but since we're using it in a modulo below, + * assert non-zero to clarify. */ + OSMO_ASSERT(pwr_interval); + if ((mr->nr % pwr_interval) == pwr_interval - 1) + attempt_handover(mr); +} + +struct handover_decision_callbacks hodec1_callbacks = { + .hodec_id = HODEC1, + .on_measurement_report = on_measurement_report, +}; + +void handover_decision_1_init(void) +{ + handover_decision_callbacks_register(&hodec1_callbacks); +} diff --git a/src/osmo-bsc/handover_decision_2.c b/src/osmo-bsc/handover_decision_2.c new file mode 100644 index 000000000..7ac54df95 --- /dev/null +++ b/src/osmo-bsc/handover_decision_2.c @@ -0,0 +1,1830 @@ +/* Handover Decision Algorithm 2 for intra-BSC (inter-BTS) handover, public API for OsmoBSC. */ + +/* (C) 2009 by Andreas Eversberg <jolly@eversberg.eu> + * (C) 2017-2018 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> + * + * All Rights Reserved + * + * Author: Andreas Eversberg <jolly@eversberg.eu> + * Neels Hofmeyr <nhofmeyr@sysmocom.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/>. + */ + +#include <stdbool.h> +#include <errno.h> + +#include <osmocom/bsc/debug.h> +#include <osmocom/bsc/gsm_data.h> +#include <osmocom/bsc/handover.h> +#include <osmocom/bsc/handover_decision.h> +#include <osmocom/bsc/handover_decision_2.h> +#include <osmocom/bsc/handover_cfg.h> +#include <osmocom/bsc/bsc_subscriber.h> +#include <osmocom/bsc/chan_alloc.h> +#include <osmocom/bsc/signal.h> +#include <osmocom/bsc/penalty_timers.h> + +#define LOGPHOBTS(bts, level, fmt, args...) \ + LOGP(DHODEC, level, "(BTS %u) " fmt, bts->nr, ## args) + +#define LOGPHOLCHAN(lchan, level, fmt, args...) \ + LOGP(DHODEC, level, "(lchan %u.%u%u%u %s) (subscr %s) " fmt, \ + lchan->ts->trx->bts->nr, \ + lchan->ts->trx->nr, \ + lchan->ts->nr, \ + lchan->nr, \ + gsm_pchan_name(lchan->ts->pchan), \ + bsc_subscr_name(lchan->conn? lchan->conn->bsub : NULL), \ + ## args) + +#define LOGPHOLCHANTOBTS(lchan, new_bts, level, fmt, args...) \ + LOGP(DHODEC, level, "(lchan %u.%u%u%u %s)->(BTS %u) (subscr %s) " fmt, \ + lchan->ts->trx->bts->nr, \ + lchan->ts->trx->nr, \ + lchan->ts->nr, \ + lchan->nr, \ + gsm_pchan_name(lchan->ts->pchan), \ + new_bts->nr, \ + bsc_subscr_name(lchan->conn? lchan->conn->bsub : NULL), \ + ## args) + +#define REQUIREMENT_A_TCHF 0x01 +#define REQUIREMENT_B_TCHF 0x02 +#define REQUIREMENT_C_TCHF 0x04 +#define REQUIREMENT_A_TCHH 0x10 +#define REQUIREMENT_B_TCHH 0x20 +#define REQUIREMENT_C_TCHH 0x40 +#define REQUIREMENT_TCHF_MASK (REQUIREMENT_A_TCHF | REQUIREMENT_B_TCHF | REQUIREMENT_C_TCHF) +#define REQUIREMENT_TCHH_MASK (REQUIREMENT_A_TCHH | REQUIREMENT_B_TCHH | REQUIREMENT_C_TCHH) +#define REQUIREMENT_A_MASK (REQUIREMENT_A_TCHF | REQUIREMENT_A_TCHH) +#define REQUIREMENT_B_MASK (REQUIREMENT_B_TCHF | REQUIREMENT_B_TCHH) +#define REQUIREMENT_C_MASK (REQUIREMENT_C_TCHF | REQUIREMENT_C_TCHH) + +struct ho_candidate { + struct gsm_lchan *lchan; /* candidate for whom */ + struct gsm_bts *bts; /* target BTS */ + uint8_t requirements; /* what is fulfilled */ + int avg; /* average RX level */ +}; + +enum ho_reason { + HO_REASON_INTERFERENCE, + HO_REASON_BAD_QUALITY, + HO_REASON_LOW_RXLEVEL, + HO_REASON_MAX_DISTANCE, + HO_REASON_BETTER_CELL, + HO_REASON_CONGESTION, +}; + +static const struct value_string ho_reason_names[] = { + { HO_REASON_INTERFERENCE, "interference (bad quality)" }, + { HO_REASON_BAD_QUALITY, "bad quality" }, + { HO_REASON_LOW_RXLEVEL, "low rxlevel" }, + { HO_REASON_MAX_DISTANCE, "maximum allowed distance" }, + { HO_REASON_BETTER_CELL, "better cell" }, + { HO_REASON_CONGESTION, "congestion" }, + {0, NULL} +}; + +static const char *ho_reason_name(int value) +{ + return get_value_string(ho_reason_names, value); +} + + +static bool hodec2_initialized = false; +static enum ho_reason global_ho_reason; + +static void congestion_check_cb(void *arg); + +/* This function gets called on ho2 init, whenever the congestion check interval is changed, and also + * when the timer has fired to trigger again after the next congestion check timeout. */ +static void reinit_congestion_timer(struct gsm_network *net) +{ + int congestion_check_interval_s; + bool was_active; + + /* Don't setup timers from VTY config parsing before the main program has actually initialized + * the data structures. */ + if (!hodec2_initialized) + return; + + was_active = net->hodec2.congestion_check_timer.active; + if (was_active) + osmo_timer_del(&net->hodec2.congestion_check_timer); + + congestion_check_interval_s = net->hodec2.congestion_check_interval_s; + if (congestion_check_interval_s < 1) { + if (was_active) + LOGP(DHODEC, LOGL_NOTICE, "HO algorithm 2: Disabling congestion check\n"); + return; + } + + LOGP(DHODEC, LOGL_DEBUG, "HO algorithm 2: next periodical congestion check in %u seconds\n", + congestion_check_interval_s); + + osmo_timer_setup(&net->hodec2.congestion_check_timer, + congestion_check_cb, net); + osmo_timer_schedule(&net->hodec2.congestion_check_timer, + congestion_check_interval_s, 0); +} + +void hodec2_on_change_congestion_check_interval(struct gsm_network *net, unsigned int new_interval) +{ + net->hodec2.congestion_check_interval_s = new_interval; + reinit_congestion_timer(net); +} + +static void conn_penalty_time_add(struct gsm_subscriber_connection *conn, struct gsm_bts *bts, + int penalty_time) +{ + if (!conn->hodec2.penalty_timers) { + conn->hodec2.penalty_timers = penalty_timers_init(conn); + OSMO_ASSERT(conn->hodec2.penalty_timers); + } + penalty_timers_add(conn->hodec2.penalty_timers, bts, penalty_time); +} + +static unsigned int conn_penalty_time_remaining(struct gsm_subscriber_connection *conn, + struct gsm_bts *bts) +{ + if (!conn->hodec2.penalty_timers) + return 0; + return penalty_timers_remaining(conn->hodec2.penalty_timers, bts); +} + +/* did we get a RXLEV for a given cell in the given report? Mark matches as MRC_F_PROCESSED. */ +static struct gsm_meas_rep_cell *cell_in_rep(struct gsm_meas_rep *mr, uint16_t arfcn, uint8_t bsic) +{ + int i; + + for (i = 0; i < mr->num_cell; i++) { + struct gsm_meas_rep_cell *mrc = &mr->cell[i]; + + if (mrc->arfcn != arfcn) + continue; + if (mrc->bsic != bsic) + continue; + + return mrc; + } + return NULL; +} + +/* obtain averaged rxlev for given neighbor */ +static int neigh_meas_avg(struct neigh_meas_proc *nmp, int window) +{ + unsigned int i, idx; + int avg = 0; + + /* reduce window to the actual number of existing measurements */ + if (window > nmp->rxlev_cnt) + window = nmp->rxlev_cnt; + /* this should never happen */ + if (window <= 0) + return 0; + + idx = calc_initial_idx(ARRAY_SIZE(nmp->rxlev), + nmp->rxlev_cnt % ARRAY_SIZE(nmp->rxlev), + window); + + for (i = 0; i < window; i++) { + int j = (idx+i) % ARRAY_SIZE(nmp->rxlev); + + avg += nmp->rxlev[j]; + } + + return avg / window; +} + +/* Find empty slot or the worst neighbor. */ +static struct neigh_meas_proc *find_unused_or_worst_neigh(struct gsm_lchan *lchan) +{ + struct neigh_meas_proc *nmp_worst = NULL; + int worst; + int j; + + /* First try to find an empty/unused slot. */ + for (j = 0; j < ARRAY_SIZE(lchan->neigh_meas); j++) { + struct neigh_meas_proc *nmp = &lchan->neigh_meas[j]; + if (!nmp->arfcn) + return nmp; + } + + /* No empty slot found. Return worst neighbor to be evicted. */ + worst = 0; /* (overwritten on first loop, but avoid compiler warning) */ + for (j = 0; j < ARRAY_SIZE(lchan->neigh_meas); j++) { + struct neigh_meas_proc *nmp = &lchan->neigh_meas[j]; + int avg = neigh_meas_avg(nmp, MAX_WIN_NEIGH_AVG); + if (nmp_worst && avg >= worst) + continue; + worst = avg; + nmp_worst = nmp; + } + + return nmp_worst; +} + +/* process neighbor cell measurement reports */ +static void process_meas_neigh(struct gsm_meas_rep *mr) +{ + int i, j, idx; + + /* For each reported cell, try to update measurements we already have from previous reports. */ + for (j = 0; j < ARRAY_SIZE(mr->lchan->neigh_meas); j++) { + struct neigh_meas_proc *nmp = &mr->lchan->neigh_meas[j]; + unsigned int idx; + struct gsm_meas_rep_cell *mrc; + + /* skip unused entries */ + if (!nmp->arfcn) + continue; + + mrc = cell_in_rep(mr, nmp->arfcn, nmp->bsic); + idx = nmp->rxlev_cnt % ARRAY_SIZE(nmp->rxlev); + if (mrc) { + nmp->rxlev[idx] = mrc->rxlev; + nmp->last_seen_nr = mr->nr; + LOGPHOLCHAN(mr->lchan, LOGL_DEBUG, "neigh %u rxlev=%d last_seen_nr=%u\n", + nmp->arfcn, mrc->rxlev, nmp->last_seen_nr); + mrc->flags |= MRC_F_PROCESSED; + } else { + nmp->rxlev[idx] = 0; + LOGPHOLCHAN(mr->lchan, LOGL_DEBUG, "neigh %u not in report (last_seen_nr=%u)\n", + nmp->arfcn, nmp->last_seen_nr); + } + nmp->rxlev_cnt++; + } + + /* Add cells that we don't know about yet, if necessary overwriting previous records that reflect + * cells with worse receive levels */ + for (i = 0; i < mr->num_cell; i++) { + struct gsm_meas_rep_cell *mrc = &mr->cell[i]; + struct neigh_meas_proc *nmp; + + if (mrc->flags & MRC_F_PROCESSED) + continue; + + nmp = find_unused_or_worst_neigh(mr->lchan); + + nmp->arfcn = mrc->arfcn; + nmp->bsic = mrc->bsic; + + nmp->rxlev_cnt = 0; + idx = nmp->rxlev_cnt % ARRAY_SIZE(nmp->rxlev); + nmp->rxlev[idx] = mrc->rxlev; + nmp->rxlev_cnt++; + nmp->last_seen_nr = mr->nr; + LOGPHOLCHAN(mr->lchan, LOGL_DEBUG, "neigh %u new in report rxlev=%d last_seen_nr=%u\n", + nmp->arfcn, mrc->rxlev, nmp->last_seen_nr); + + mrc->flags |= MRC_F_PROCESSED; + } +} + +static bool codec_type_is_supported(struct gsm_subscriber_connection *conn, + enum gsm0808_speech_codec_type type) +{ + int i; + struct gsm0808_speech_codec_list *clist = &conn->codec_list; + + if (!conn->codec_list_present) { + /* We don't have a list of supported codecs. This should never happen. */ + LOGPHOLCHAN(conn->lchan, LOGL_ERROR, + "No Speech Codec List present, accepting all codecs\n"); + return true; + } + + for (i = 0; i < clist->len; i++) { + if (clist->codec[i].type == type) { + LOGPHOLCHAN(conn->lchan, LOGL_DEBUG, "%s supported\n", + gsm0808_speech_codec_type_name(type)); + return true; + } + } + LOGPHOLCHAN(conn->lchan, LOGL_DEBUG, "Codec not supported by MS or not allowed by MSC: %s\n", + gsm0808_speech_codec_type_name(type)); + return false; +} + +/* + * Check what requirements the given cell fulfills. + * A bit mask of fulfilled requirements is returned. + * + * Target cell requirement A -- ability to service the call + * + * In order to successfully handover/assign to a better cell, the target cell + * must be able to continue the current call. Therefore the cell must fulfill + * the following criteria: + * + * * The handover must be enabled for the target cell, if it differs from the + * originating cell. + * * The assignment must be enabled for the cell, if it equals the current + * cell. + * * The handover penalty timer must not run for the cell. + * * If FR, EFR or HR codec is used, the cell must support this codec. + * * If FR or EFR codec is used, the cell must have a TCH/F slot type + * available. + * * If HR codec is used, the cell must have a TCH/H slot type available. + * * If AMR codec is used, the cell must have a TCH/F slot available, if AFS + * is supported by mobile and BTS. + * * If AMR codec is used, the cell must have a TCH/H slot available, if AHS + * is supported by mobile and BTS. + * * osmo-nitb with built-in MNCC application: + * o If AMR codec is used, the cell must support AMR codec with equal codec + * rate or rates. (not meaning TCH types) + * * If defined, the number of maximum unsynchronized handovers to this cell + * may not be exceeded. (This limits processing load for random access + * bursts.) + * + * + * Target cell requirement B -- avoid congestion + * + * In order to prevent congestion of a target cell, the cell must fulfill the + * requirement A, but also: + * + * * The minimum free channels, that are defined for that cell must be + * maintained after handover/assignment. + * * The minimum free channels are defined for TCH/F and TCH/H slot types + * individually. + * + * + * Target cell requirement C -- balance congestion + * + * In order to balance congested cells, the target cell must fulfill the + * requirement A, but also: + * + * * The target cell (which is congested also) must have more or equal free + * slots after handover/assignment. + * * The number of free slots are checked for TCH/F and TCH/H slot types + * individually. + */ +static uint8_t check_requirements(struct gsm_lchan *lchan, struct gsm_bts *bts, int tchf_count, int tchh_count) +{ + int count; + uint8_t requirement = 0; + unsigned int penalty_time; + struct gsm_bts *current_bts = lchan->ts->trx->bts; + + /* Requirement A */ + + /* the handover/assignment must not be disabled */ + if (current_bts == bts) { + if (!ho_get_hodec2_as_active(bts->ho)) { + LOGPHOLCHAN(lchan, LOGL_DEBUG, "Assignment disabled\n"); + return 0; + } + } else { + if (!ho_get_ho_active(bts->ho)) { + LOGPHOLCHANTOBTS(lchan, bts, LOGL_DEBUG, + "not a candidate, handover is disabled in target BTS\n"); + return 0; + } + } + + /* the handover penalty timer must not run for this bts */ + penalty_time = conn_penalty_time_remaining(lchan->conn, bts); + if (penalty_time) { + LOGPHOLCHANTOBTS(lchan, bts, LOGL_DEBUG, "not a candidate, target BTS still in penalty time" + " (%u seconds left)\n", penalty_time); + return 0; + } + + LOGPHOLCHANTOBTS(lchan, bts, LOGL_DEBUG, "tch_mode='%s' type='%s'\n", + get_value_string(gsm48_chan_mode_names, lchan->tch_mode), + gsm_lchant_name(lchan->type)); + + /* compatibility check for codecs. + * if so, the candidates for full rate and half rate are selected */ + switch (lchan->tch_mode) { + case GSM48_CMODE_SPEECH_V1: + switch (lchan->type) { + case GSM_LCHAN_TCH_F: /* mandatory */ + requirement |= REQUIREMENT_A_TCHF; + LOGPHOLCHANTOBTS(lchan, bts, LOGL_DEBUG, "tch_mode='%s' type='%s' supported\n", + get_value_string(gsm48_chan_mode_names, lchan->tch_mode), + gsm_lchant_name(lchan->type)); + break; + case GSM_LCHAN_TCH_H: + if (!bts->codec.hr) { + LOGPHOLCHANTOBTS(lchan, bts, LOGL_DEBUG, + "tch_mode='%s' type='%s' not supported\n", + get_value_string(gsm48_chan_mode_names, + lchan->tch_mode), + gsm_lchant_name(lchan->type)); + break; + } + if (codec_type_is_supported(lchan->conn, GSM0808_SCT_HR1)) + requirement |= REQUIREMENT_A_TCHH; + break; + default: + LOGPHOLCHAN(lchan, LOGL_ERROR, "Unexpected channel type: neither TCH/F nor TCH/H for %s\n", + get_value_string(gsm48_chan_mode_names, lchan->tch_mode)); + return 0; + } + break; + case GSM48_CMODE_SPEECH_EFR: + if (!bts->codec.efr) { + LOGPHOBTS(bts, LOGL_DEBUG, "EFR not supported\n"); + break; + } + if (codec_type_is_supported(lchan->conn, GSM0808_SCT_FR2)) + requirement |= REQUIREMENT_A_TCHF; + break; + case GSM48_CMODE_SPEECH_AMR: + if (!bts->codec.amr) { + LOGPHOBTS(bts, LOGL_DEBUG, "AMR not supported\n"); + break; + } + if (codec_type_is_supported(lchan->conn, GSM0808_SCT_FR3)) + requirement |= REQUIREMENT_A_TCHF; + if (codec_type_is_supported(lchan->conn, GSM0808_SCT_HR3)) + requirement |= REQUIREMENT_A_TCHH; + break; + default: + LOGPHOLCHANTOBTS(lchan, bts, LOGL_DEBUG, "Not even considering: src is not a SPEECH mode lchan\n"); + return 0; + } + + /* no candidate, because new cell is incompatible */ + if (!requirement) { + LOGPHOLCHANTOBTS(lchan, bts, LOGL_DEBUG, "not a candidate, because codec of MS and BTS are incompatible\n"); + return 0; + } + + /* remove slot types that are not available */ + if (!tchf_count && requirement & REQUIREMENT_A_TCHF) { + LOGPHOLCHANTOBTS(lchan, bts, LOGL_DEBUG, + "removing TCH/F, since all TCH/F lchans are in use\n"); + requirement &= ~(REQUIREMENT_A_TCHF); + } + if (!tchh_count && requirement & REQUIREMENT_A_TCHH) { + LOGPHOLCHANTOBTS(lchan, bts, LOGL_DEBUG, + "removing TCH/H, since all TCH/H lchans are in use\n"); + requirement &= ~(REQUIREMENT_A_TCHH); + } + + if (!requirement) { + LOGPHOLCHANTOBTS(lchan, bts, LOGL_DEBUG, "not a candidate, because no suitable slots available\n"); + return 0; + } + + /* omit same channel type on same BTS (will not change anything) */ + if (bts == current_bts) { + switch (lchan->type) { + case GSM_LCHAN_TCH_F: + LOGPHOLCHANTOBTS(lchan, bts, LOGL_DEBUG, + "removing TCH/F, already on TCH/F in this cell\n"); + requirement &= ~(REQUIREMENT_A_TCHF); + break; + case GSM_LCHAN_TCH_H: + LOGPHOLCHANTOBTS(lchan, bts, LOGL_DEBUG, + "removing TCH/H, already on TCH/H in this cell\n"); + requirement &= ~(REQUIREMENT_A_TCHH); + break; + default: + break; + } + + if (!requirement) { + LOGPHOLCHAN(lchan, LOGL_DEBUG, + "Reassignment within cell not an option, no differing channel types available\n"); + return 0; + } + } + +#ifdef LEGACY + // This was useful in osmo-nitb. We're in osmo-bsc now and have no idea whether the osmo-msc does + // internal or external call control. Maybe a future config switch wants to add this behavior? + /* Built-in call control requires equal codec rates. Remove rates that are not equal. */ + if (lchan->tch_mode == GSM48_CMODE_SPEECH_AMR + && current_bts->network->mncc_recv != mncc_sock_from_cc) { + switch (lchan->type) { + case GSM_LCHAN_TCH_F: + if ((requirement & REQUIREMENT_A_TCHF) + && !!memcmp(¤t_bts->mr_full, &bts->mr_full, + sizeof(struct amr_multirate_conf))) + requirement &= ~(REQUIREMENT_A_TCHF); + if ((requirement & REQUIREMENT_A_TCHH) + && !!memcmp(¤t_bts->mr_full, &bts->mr_half, + sizeof(struct amr_multirate_conf))) + requirement &= ~(REQUIREMENT_A_TCHH); + break; + case GSM_LCHAN_TCH_H: + if ((requirement & REQUIREMENT_A_TCHF) + && !!memcmp(¤t_bts->mr_half, &bts->mr_full, + sizeof(struct amr_multirate_conf))) + requirement &= ~(REQUIREMENT_A_TCHF); + if ((requirement & REQUIREMENT_A_TCHH) + && !!memcmp(¤t_bts->mr_half, &bts->mr_half, + sizeof(struct amr_multirate_conf))) + requirement &= ~(REQUIREMENT_A_TCHH); + break; + default: + break; + } + + if (!requirement) { + LOGPHOLCHANTOBTS(lchan, bts, LOGL_DEBUG, + "not a candidate, cannot provide identical codec rate\n"); + return 0; + } + } +#endif + + /* the maximum number of unsynchonized handovers must no be exceeded */ + if (current_bts != bts + && bsc_ho_count(bts, true) >= ho_get_hodec2_ho_max(bts->ho)) { + LOGPHOLCHANTOBTS(lchan, bts, LOGL_DEBUG, + "not a candidate, number of allowed handovers (%d) would be exceeded\n", + ho_get_hodec2_ho_max(bts->ho)); + return 0; + } + + /* Requirement B */ + + /* the minimum free timeslots that are defined for this cell must + * be maintained _after_ handover/assignment */ + if (requirement & REQUIREMENT_A_TCHF) { + if (tchf_count - 1 >= ho_get_hodec2_tchf_min_slots(bts->ho)) { + LOGPHOLCHANTOBTS(lchan, bts, LOGL_DEBUG, + "TCH/F would not be congested after HO\n"); + requirement |= REQUIREMENT_B_TCHF; + } else { + LOGPHOLCHANTOBTS(lchan, bts, LOGL_DEBUG, + "TCH/F would be congested after HO\n"); + } + } + if (requirement & REQUIREMENT_A_TCHH) { + if (tchh_count - 1 >= ho_get_hodec2_tchh_min_slots(bts->ho)) { + LOGPHOLCHANTOBTS(lchan, bts, LOGL_DEBUG, + "TCH/H would not be congested after HO\n"); + requirement |= REQUIREMENT_B_TCHH; + } else { + LOGPHOLCHANTOBTS(lchan, bts, LOGL_DEBUG, + "TCH/H would be congested after HO\n"); + } + } + + /* Requirement C */ + + /* the nr of free timeslots of the target cell must be >= the + * free slots of the current cell _after_ handover/assignment */ + count = bts_count_free_ts(current_bts, + (lchan->type == GSM_LCHAN_TCH_H) ? + GSM_PCHAN_TCH_H : GSM_PCHAN_TCH_F); + if (requirement & REQUIREMENT_A_TCHF) { + if (tchf_count - 1 >= count + 1) { + LOGPHOLCHANTOBTS(lchan, bts, LOGL_DEBUG, + "TCH/F would be less congested in target than source cell after HO\n"); + requirement |= REQUIREMENT_C_TCHF; + } else { + LOGPHOLCHANTOBTS(lchan, bts, LOGL_DEBUG, + "TCH/F would not be less congested in target than source cell after HO\n"); + } + } + if (requirement & REQUIREMENT_A_TCHH) { + if (tchh_count - 1 >= count + 1) { + LOGPHOLCHANTOBTS(lchan, bts, LOGL_DEBUG, + "TCH/H would be less congested in target than source cell after HO\n"); + requirement |= REQUIREMENT_C_TCHH; + } else { + LOGPHOLCHANTOBTS(lchan, bts, LOGL_DEBUG, + "TCH/H would not be less congested in target than source cell after HO\n"); + } + } + + LOGPHOLCHANTOBTS(lchan, bts, LOGL_DEBUG, "requirements=0x%x\n", requirement); + + /* return mask of fulfilled requirements */ + return requirement; +} + +/* Trigger handover or assignment depending on the target BTS */ +static int trigger_handover_or_assignment(struct gsm_lchan *lchan, struct gsm_bts *new_bts, uint8_t requirements) +{ + struct gsm_bts *current_bts = lchan->ts->trx->bts; + int afs_bias = 0; + bool full_rate = false; + + if (current_bts == new_bts) + LOGPHOLCHAN(lchan, LOGL_NOTICE, "Triggering Assignment\n"); + else + LOGPHOLCHANTOBTS(lchan, new_bts, LOGL_NOTICE, "Triggering Handover\n"); + + /* afs_bias becomes > 0, if AFS is used and is improved */ + if (lchan->tch_mode == GSM48_CMODE_SPEECH_AMR) + afs_bias = ho_get_hodec2_afs_bias_rxlev(new_bts->ho); + + /* select TCH rate, prefer TCH/F if AFS is improved */ + switch (lchan->type) { + case GSM_LCHAN_TCH_F: + /* keep on full rate, if TCH/F is a candidate */ + if ((requirements & REQUIREMENT_TCHF_MASK)) { + if (current_bts == new_bts) { + LOGPHOLCHAN(lchan, LOGL_INFO, "Not performing assignment: Already on target type\n"); + return 0; + } + full_rate = true; + break; + } + /* change to half rate */ + if (!(requirements & REQUIREMENT_TCHH_MASK)) { + LOGPHOLCHANTOBTS(lchan, new_bts, LOGL_ERROR, + "neither TCH/F nor TCH/H requested, aborting ho/as\n"); + return -EINVAL; + } + break; + case GSM_LCHAN_TCH_H: + /* change to full rate if AFS is improved and a candidate */ + if (afs_bias > 0 && (requirements & REQUIREMENT_TCHF_MASK)) { + full_rate = true; + LOGPHOLCHAN(lchan, LOGL_DEBUG, "[Improve AHS->AFS]\n"); + break; + } + /* change to full rate if the only candidate */ + if ((requirements & REQUIREMENT_TCHF_MASK) + && !(requirements & REQUIREMENT_TCHH_MASK)) { + full_rate = true; + break; + } + /* keep on half rate */ + if (!(requirements & REQUIREMENT_TCHH_MASK)) { + LOGPHOLCHANTOBTS(lchan, new_bts, LOGL_ERROR, + "neither TCH/F nor TCH/H requested, aborting ho/as\n"); + return -EINVAL; + } + if (current_bts == new_bts) { + LOGPHOLCHAN(lchan, LOGL_INFO, "Not performing assignment: Already on target type\n"); + return 0; + } + break; + default: + LOGPHOLCHANTOBTS(lchan, new_bts, LOGL_ERROR, "lchan is neither TCH/F nor TCH/H, aborting ho/as\n"); + return -EINVAL; + } + + /* trigger handover or assignment */ + if (current_bts == new_bts) + LOGPHOLCHAN(lchan, LOGL_NOTICE, "Triggering assignment to %s, due to %s\n", + full_rate ? "TCH/F" : "TCH/H", + ho_reason_name(global_ho_reason)); + else + LOGPHOLCHANTOBTS(lchan, new_bts, LOGL_NOTICE, + "Triggering handover to %s, due to %s\n", + full_rate ? "TCH/F" : "TCH/H", + ho_reason_name(global_ho_reason)); + + return bsc_handover_start(HODEC2, lchan, current_bts == new_bts? NULL : new_bts, + full_rate? GSM_LCHAN_TCH_F : GSM_LCHAN_TCH_H); +} + +/* debug collected candidates */ +static inline void debug_candidate(struct ho_candidate *candidate, + int neighbor, int8_t rxlev, int tchf_count, int tchh_count) +{ + if (neighbor) + LOGP(DHODEC, LOGL_DEBUG, " - neighbor BTS %d, RX level " + "%d -> %d\n", candidate->bts->nr, rxlev2dbm(rxlev), + rxlev2dbm(candidate->avg)); + else + LOGP(DHODEC, LOGL_DEBUG, " - current BTS %d, RX level %d\n", + candidate->bts->nr, rxlev2dbm(candidate->avg)); + + LOGP(DHODEC, LOGL_DEBUG, " o free TCH/F slots %d, minimum required " + "%d\n", tchf_count, ho_get_hodec2_tchf_min_slots(candidate->bts->ho)); + LOGP(DHODEC, LOGL_DEBUG, " o free TCH/H slots %d, minimum required " + "%d\n", tchh_count, ho_get_hodec2_tchh_min_slots(candidate->bts->ho)); + + if ((candidate->requirements & REQUIREMENT_TCHF_MASK)) + LOGP(DHODEC, LOGL_DEBUG, " o requirement "); + else + LOGP(DHODEC, LOGL_DEBUG, " o no requirement "); + if ((candidate->requirements & REQUIREMENT_A_TCHF)) + LOGPC(DHODEC, LOGL_DEBUG, "A "); + if ((candidate->requirements & REQUIREMENT_B_TCHF)) + LOGPC(DHODEC, LOGL_DEBUG, "B "); + if ((candidate->requirements & REQUIREMENT_C_TCHF)) + LOGPC(DHODEC, LOGL_DEBUG, "C "); + LOGPC(DHODEC, LOGL_DEBUG, "fulfilled for TCHF"); + if (!(candidate->requirements & REQUIREMENT_TCHF_MASK)) /* nothing */ + LOGPC(DHODEC, LOGL_DEBUG, " (no %s possible)\n", + (neighbor) ? "handover" : "assignment"); + else if ((candidate->requirements & REQUIREMENT_TCHF_MASK) + == REQUIREMENT_A_TCHF) /* only A */ + LOGPC(DHODEC, LOGL_DEBUG, " (more congestion after %s)\n", + (neighbor) ? "handover" : "assignment"); + else if ((candidate->requirements & REQUIREMENT_B_TCHF)) /* B incl. */ + LOGPC(DHODEC, LOGL_DEBUG, " (not congested after %s)\n", + (neighbor) ? "handover" : "assignment"); + else /* so it must include C */ + LOGPC(DHODEC, LOGL_DEBUG, " (less or equally congested after " + "%s)\n", (neighbor) ? "handover" : "assignment"); + + if ((candidate->requirements & REQUIREMENT_TCHH_MASK)) + LOGP(DHODEC, LOGL_DEBUG, " o requirement "); + else + LOGP(DHODEC, LOGL_DEBUG, " o no requirement "); + if ((candidate->requirements & REQUIREMENT_A_TCHH)) + LOGPC(DHODEC, LOGL_DEBUG, "A "); + if ((candidate->requirements & REQUIREMENT_B_TCHH)) + LOGPC(DHODEC, LOGL_DEBUG, "B "); + if ((candidate->requirements & REQUIREMENT_C_TCHH)) + LOGPC(DHODEC, LOGL_DEBUG, "C "); + LOGPC(DHODEC, LOGL_DEBUG, "fulfilled for TCHH"); + if (!(candidate->requirements & REQUIREMENT_TCHH_MASK)) /* nothing */ + LOGPC(DHODEC, LOGL_DEBUG, " (no %s possible)\n", + (neighbor) ? "handover" : "assignment"); + else if ((candidate->requirements & REQUIREMENT_TCHH_MASK) + == REQUIREMENT_A_TCHH) /* only A */ + LOGPC(DHODEC, LOGL_DEBUG, " (more congestion after %s)\n", + (neighbor) ? "handover" : "assignment"); + else if ((candidate->requirements & REQUIREMENT_B_TCHH)) /* B incl. */ + LOGPC(DHODEC, LOGL_DEBUG, " (not congested after %s)\n", + (neighbor) ? "handover" : "assignment"); + else /* so it must include C */ + LOGPC(DHODEC, LOGL_DEBUG, " (less or equally congested after " + "%s)\n", (neighbor) ? "handover" : "assignment"); +} + +/* add candidate for re-assignment within the current cell */ +static void collect_assignment_candidate(struct gsm_lchan *lchan, struct ho_candidate *clist, + unsigned int *candidates, int av_rxlev) +{ + struct gsm_bts *bts = lchan->ts->trx->bts; + int tchf_count, tchh_count; + struct ho_candidate *c; + + tchf_count = bts_count_free_ts(bts, GSM_PCHAN_TCH_F); + tchh_count = bts_count_free_ts(bts, GSM_PCHAN_TCH_H); + + c = &clist[*candidates]; + c->lchan = lchan; + c->bts = bts; + c->requirements = check_requirements(lchan, bts, tchf_count, tchh_count); + c->avg = av_rxlev; + debug_candidate(c, 0, 0, tchf_count, tchh_count); + (*candidates)++; +} + +/* add candidates for handover to all neighbor cells */ +static void collect_handover_candidate(struct gsm_lchan *lchan, struct neigh_meas_proc *nmp, + struct ho_candidate *clist, unsigned int *candidates, + bool include_weaker_rxlev, int av_rxlev, + int *neighbors_count) +{ + struct gsm_bts *bts = lchan->ts->trx->bts; + int tchf_count, tchh_count; + struct gsm_bts *neighbor_bts; + int avg; + struct ho_candidate *c; + int min_rxlev; + + /* skip empty slots */ + if (nmp->arfcn == 0) + return; + + if (neighbors_count) + (*neighbors_count)++; + + /* skip if measurement report is old */ + if (nmp->last_seen_nr != lchan->meas_rep_last_seen_nr) { + LOGPHOLCHAN(lchan, LOGL_DEBUG, "neighbor ARFCN %u measurement report is old" + " (nmp->last_seen_nr=%u lchan->meas_rep_last_seen_nr=%u)\n", + nmp->arfcn, nmp->last_seen_nr, lchan->meas_rep_last_seen_nr); + return; + } + + neighbor_bts = bts_by_arfcn_bsic(bts->network, nmp->arfcn, nmp->bsic); + if (!neighbor_bts) { + LOGPHOBTS(bts, LOGL_DEBUG, "neighbor ARFCN %u does not belong to this network\n", + nmp->arfcn); + return; + } + + /* in case we have measurements of our bts, due to misconfiguration */ + if (neighbor_bts == bts) { + LOGPHOBTS(bts, LOGL_ERROR, "Configuration error: this BTS appears as its own neighbor\n"); + return; + } + + /* caculate average rxlev for this cell over the window */ + avg = neigh_meas_avg(nmp, ho_get_hodec2_rxlev_neigh_avg_win(bts->ho)); + + /* Heed rxlev hysteresis only if the RXLEV/RXQUAL/TA levels of the MS aren't critically bad and + * we're just looking for an improvement. If levels are critical, we desperately need a handover + * and thus skip the hysteresis check. */ + if (!include_weaker_rxlev) { + unsigned int pwr_hyst = ho_get_hodec2_pwr_hysteresis(bts->ho); + if (avg <= (av_rxlev + pwr_hyst)) { + LOGPHOLCHAN(lchan, LOGL_DEBUG, + "BTS %d is not a candidate, because RX level (%d) is lower" + " or equal than current RX level (%d) + hysteresis (%d)\n", + neighbor_bts->nr, rxlev2dbm(avg), rxlev2dbm(av_rxlev), pwr_hyst); + return; + } + } + + /* if the minimum level is not reached */ + min_rxlev = ho_get_hodec2_min_rxlev(neighbor_bts->ho); + if (rxlev2dbm(avg) < min_rxlev) { + LOGPHOLCHAN(lchan, LOGL_DEBUG, + "BTS %d is not a candidate, because RX level (%d) is lower" + " than its minimum required RX level (%d)\n", + neighbor_bts->nr, rxlev2dbm(avg), min_rxlev); + return; + } + + tchf_count = bts_count_free_ts(neighbor_bts, GSM_PCHAN_TCH_F); + tchh_count = bts_count_free_ts(neighbor_bts, GSM_PCHAN_TCH_H); + c = &clist[*candidates]; + c->lchan = lchan; + c->bts = neighbor_bts; + c->requirements = check_requirements(lchan, neighbor_bts, tchf_count, + tchh_count); + c->avg = avg; + debug_candidate(c, 1, av_rxlev, tchf_count, tchh_count); + (*candidates)++; +} + +static void collect_candidates_for_lchan(struct gsm_lchan *lchan, + struct ho_candidate *clist, unsigned int *candidates, + int *_av_rxlev, bool include_weaker_rxlev) +{ + struct gsm_bts *bts = lchan->ts->trx->bts; + int av_rxlev; + unsigned int candidates_was; + bool assignment; + bool handover; + int neighbors_count = 0; + unsigned int rxlev_avg_win = ho_get_hodec2_rxlev_avg_win(bts->ho); + + OSMO_ASSERT(candidates); + candidates_was = *candidates; + + /* caculate average rxlev for this cell over the window */ + av_rxlev = get_meas_rep_avg(lchan, + ho_get_hodec2_full_tdma(bts->ho) ? + MEAS_REP_DL_RXLEV_FULL : MEAS_REP_DL_RXLEV_SUB, + rxlev_avg_win); + if (_av_rxlev) + *_av_rxlev = av_rxlev; + + /* in case there is no measurment report (yet) */ + if (av_rxlev < 0) { + LOGPHOLCHAN(lchan, LOGL_DEBUG, "Not collecting candidates, not enough measurements" + " (got %d, want %u)\n", + lchan->meas_rep_count, rxlev_avg_win); + return; + } + + assignment = ho_get_hodec2_as_active(bts->ho); + handover = ho_get_ho_active(bts->ho); + + LOGPHOLCHAN(lchan, LOGL_DEBUG, "Collecting candidates for%s%s%s\n", + assignment ? " Assignment" : "", + assignment && handover ? " and" : "", + handover ? " Handover" : ""); + + if (assignment) + collect_assignment_candidate(lchan, clist, candidates, av_rxlev); + + if (handover) { + int i; + for (i = 0; i < ARRAY_SIZE(lchan->neigh_meas); i++) { + collect_handover_candidate(lchan, &lchan->neigh_meas[i], + clist, candidates, + include_weaker_rxlev, av_rxlev, &neighbors_count); + } + } + + LOGPHOLCHAN(lchan, LOGL_DEBUG, "adding %u candidates from %u neighbors, total %u\n", + *candidates - candidates_was, neighbors_count, *candidates); +} + +/* + * Search for a alternative / better cell. + * + * Do not trigger handover/assignment on slots which have already ongoing + * handover/assignment processes. If no AFS improvement offset is given, try to + * maintain the same TCH rate, if available. + * Do not perform this process, if handover and assignment are disabled for + * the current cell. + * Do not perform handover, if the minimum acceptable RX level + * is not reched for this cell. + * + * If one or more 'better cells' are available, check the current and neighbor + * cell measurements in descending order of their RX levels (down-link): + * + * * Select the best candidate that fulfills requirement B (no congestion + * after handover/assignment) and trigger handover or assignment. + * * If no candidate fulfills requirement B, select the best candidate that + * fulfills requirement C (less or equally congested cells after handover) + * and trigger handover or assignment. + * * If no candidate fulfills requirement C, do not perform handover nor + * assignment. + * + * If the RX level (down-link) or RX quality (down-link) of the current cell is + * below minimum acceptable level, or if the maximum allowed timing advance is + * reached or exceeded, check the RX levels (down-link) of the current and + * neighbor cells in descending order of their levels: (bad BTS case) + * + * * Select the best candidate that fulfills requirement B (no congestion after + * handover/assignment) and trigger handover or assignment. + * * If no candidate fulfills requirement B, select the best candidate that + * fulfills requirement C (less or equally congested cells after handover) + * and trigger handover or assignment. + * * If no candidate fulfills requirement C, select the best candidate that + * fulfills requirement A (ignore congestion after handover or assignment) + * and trigger handover or assignment. + * * If no candidate fulfills requirement A, do not perform handover nor + * assignment. + * + * RX levels (down-link) of current and neighbor cells: + * + * * The RX levels of the current cell and neighbor cells are improved by a + * given offset, if AFS (AMR on TCH/F) is used or is a candidate for + * handover/assignment. + * * If AMR is used, the requirement for handover is checked for TCH/F and + * TCH/H. Both results (if any) are used as a candidate. + * * If AMR is used, the requirement for assignment to a different TCH slot + * rate is checked. The result (if available) is used as a candidate. + * + * If minimum RXLEV, minimum RXQUAL or maximum TA are exceeded, the caller should pass + * include_weaker_rxlev=true so that handover is performed despite congestion. + */ +static int find_alternative_lchan(struct gsm_lchan *lchan, bool include_weaker_rxlev) +{ + struct gsm_bts *bts = lchan->ts->trx->bts; + int ahs = (lchan->tch_mode == GSM48_CMODE_SPEECH_AMR + && lchan->type == GSM_LCHAN_TCH_H); + int av_rxlev; + struct ho_candidate clist[1 + ARRAY_SIZE(lchan->neigh_meas)]; + unsigned int candidates = 0; + int i; + struct ho_candidate *best_cand = NULL; + unsigned int best_better_db; + bool best_applied_afs_bias = false; + int better; + + /* check for disabled handover/assignment at the current cell */ + if (!ho_get_hodec2_as_active(bts->ho) + && !ho_get_ho_active(bts->ho)) { + LOGP(DHODEC, LOGL_INFO, "Skipping, Handover and Assignment both disabled in this cell\n"); + return 0; + } + + collect_candidates_for_lchan(lchan, clist, &candidates, &av_rxlev, include_weaker_rxlev); + + /* If assignment is disabled and no neighbor cell report exists, or no neighbor cell qualifies, + * we may not even have any candidates. */ + if (!candidates) + goto no_candidates; + + /* select best candidate that fulfills requirement B: no congestion after HO */ + best_better_db = 0; + for (i = 0; i < candidates; i++) { + int afs_bias; + if (!(clist[i].requirements & REQUIREMENT_B_MASK)) + continue; + + better = clist[i].avg - av_rxlev; + /* Apply AFS bias? */ + afs_bias = 0; + if (ahs && (clist[i].requirements & REQUIREMENT_B_TCHF)) + afs_bias = ho_get_hodec2_afs_bias_rxlev(clist[i].bts->ho); + better += afs_bias; + if (better > best_better_db) { + best_cand = &clist[i]; + best_better_db = better; + best_applied_afs_bias = afs_bias? true : false; + } + } + + /* perform handover, if there is a candidate */ + if (best_cand) { + LOGPHOLCHANTOBTS(lchan, best_cand->bts, LOGL_INFO, "Best candidate, RX level %d%s\n", + rxlev2dbm(best_cand->avg), + best_applied_afs_bias ? " (applied AHS -> AFS rxlev bias)" : ""); + return trigger_handover_or_assignment(lchan, best_cand->bts, + best_cand->requirements & REQUIREMENT_B_MASK); + } + + /* select best candidate that fulfills requirement C: less or equal congestion after HO */ + best_better_db = 0; + for (i = 0; i < candidates; i++) { + int afs_bias; + if (!(clist[i].requirements & REQUIREMENT_C_MASK)) + continue; + + better = clist[i].avg - av_rxlev; + /* Apply AFS bias? */ + afs_bias = 0; + if (ahs && (clist[i].requirements & REQUIREMENT_C_TCHF)) + afs_bias = ho_get_hodec2_afs_bias_rxlev(clist[i].bts->ho); + better += afs_bias; + if (better > best_better_db) { + best_cand = &clist[i]; + best_better_db = better; + best_applied_afs_bias = afs_bias? true : false; + } + } + + /* perform handover, if there is a candidate */ + if (best_cand) { + LOGPHOLCHANTOBTS(lchan, best_cand->bts, LOGL_INFO, "Best candidate, RX level %d%s\n", + rxlev2dbm(best_cand->avg), + best_applied_afs_bias? " (applied AHS -> AFS rxlev bias)" : ""); + return trigger_handover_or_assignment(lchan, best_cand->bts, + best_cand->requirements & REQUIREMENT_C_MASK); + } + + /* we are done in case the MS RXLEV/RXQUAL/TA aren't critical and we're avoiding congestion. */ + if (!include_weaker_rxlev) + goto no_candidates; + + /* Select best candidate that fulfills requirement A: can service the call. + * From above we know that there are no options that avoid congestion. Here we're trying to find + * *any* free lchan that has no critically low RXLEV and is able to handle the MS. */ + best_better_db = 0; + for (i = 0; i < candidates; i++) { + int afs_bias; + if (!(clist[i].requirements & REQUIREMENT_A_MASK)) + continue; + + better = clist[i].avg - av_rxlev; + /* Apply AFS bias? */ + afs_bias = 0; + if (ahs && (clist[i].requirements & REQUIREMENT_A_TCHF)) + afs_bias = ho_get_hodec2_afs_bias_rxlev(clist[i].bts->ho); + better += afs_bias; + if (better > best_better_db) { + best_cand = &clist[i]; + best_better_db = better; + best_applied_afs_bias = afs_bias? true : false; + } + } + + /* perform handover, if there is a candidate */ + if (best_cand) { + LOGPHOLCHANTOBTS(lchan, best_cand->bts, LOGL_INFO, "Best candidate, RX level %d" + " with greater congestion found%s\n", + rxlev2dbm(best_cand->avg), + best_applied_afs_bias ? " (applied AHS -> AFS rxlev bias)" : ""); + return trigger_handover_or_assignment(lchan, best_cand->bts, + best_cand->requirements & REQUIREMENT_A_MASK); + } + + /* Damn, all is congested, has too low RXLEV or cannot service the voice call due to codec + * restrictions or because all lchans are taken. */ + +no_candidates: + if (include_weaker_rxlev) + LOGPHOLCHAN(lchan, LOGL_INFO, "No alternative lchan found\n"); + else + LOGPHOLCHAN(lchan, LOGL_INFO, "No better/less congested neighbor cell found\n"); + + return 0; +} + +/* + * Handover/assignment check, if measurement report is received + * + * Do not trigger handover/assignment on slots which have already ongoing + * handover/assignment processes. + * + * In case of handover triggered because maximum allowed timing advance is + * exceeded, the handover penalty timer is started for the originating cell. + * + */ +static void on_measurement_report(struct gsm_meas_rep *mr) +{ + struct gsm_lchan *lchan = mr->lchan; + struct gsm_bts *bts = lchan->ts->trx->bts; + int av_rxlev = -EINVAL, av_rxqual = -EINVAL; + unsigned int pwr_interval; + + /* we currently only do handover for TCH channels */ + switch (mr->lchan->type) { + case GSM_LCHAN_TCH_F: + case GSM_LCHAN_TCH_H: + break; + default: + return; + } + + if (log_check_level(DHODEC, LOGL_DEBUG)) { + int i; + LOGPHOLCHAN(lchan, LOGL_DEBUG, "MEASUREMENT REPORT (%d neighbors)\n", + mr->num_cell); + for (i = 0; i < mr->num_cell; i++) { + struct gsm_meas_rep_cell *mrc = &mr->cell[i]; + LOGPHOLCHAN(lchan, LOGL_DEBUG, + " %d: arfcn=%u bsic=%u neigh_idx=%u rxlev=%u flags=%x\n", + i, mrc->arfcn, mrc->bsic, mrc->neigh_idx, mrc->rxlev, mrc->flags); + } + } + + /* parse actual neighbor cell info */ + if (mr->num_cell > 0 && mr->num_cell < 7) + process_meas_neigh(mr); + + /* check for ongoing handover/assignment */ + if (!lchan->conn) { + LOGPHOLCHAN(lchan, LOGL_ERROR, "Skipping, No subscriber connection???\n"); + return; + } + if (lchan->conn->secondary_lchan) { + LOGPHOLCHAN(lchan, LOGL_INFO, "Skipping, Initial Assignment is still ongoing\n"); + return; + } + if (lchan->conn->ho) { + LOGPHOLCHAN(lchan, LOGL_INFO, "Skipping, Handover already triggered\n"); + return; + } + + LOGPHOLCHAN(lchan, LOGL_DEBUG, "HODEC2: evaluating measurement report\n"); + + /* get average levels. if not enought measurements yet, value is < 0 */ + av_rxlev = get_meas_rep_avg(lchan, + ho_get_hodec2_full_tdma(bts->ho) ? + MEAS_REP_DL_RXLEV_FULL : MEAS_REP_DL_RXLEV_SUB, + ho_get_hodec2_rxlev_avg_win(bts->ho)); + av_rxqual = get_meas_rep_avg(lchan, + ho_get_hodec2_full_tdma(bts->ho) ? + MEAS_REP_DL_RXQUAL_FULL : MEAS_REP_DL_RXQUAL_SUB, + ho_get_hodec2_rxqual_avg_win(bts->ho)); + if (av_rxlev < 0 && av_rxqual < 0) { + LOGPHOLCHAN(lchan, LOGL_INFO, "Skipping, Not enough recent measurements\n"); + return; + } + if (av_rxlev >= 0) { + LOGPHOLCHAN(lchan, LOGL_DEBUG, "Measurement report: average RX level = %d\n", + rxlev2dbm(av_rxlev)); + } + if (av_rxqual >= 0) { + LOGPHOLCHAN(lchan, LOGL_DEBUG, "Measurement report: average RX quality = %d\n", + av_rxqual); + } + + /* improve levels in case of AFS, if defined */ + if (lchan->type == GSM_LCHAN_TCH_F + && lchan->tch_mode == GSM48_CMODE_SPEECH_AMR) { + int rxlev_bias = ho_get_hodec2_afs_bias_rxlev(bts->ho); + int rxqual_bias = ho_get_hodec2_afs_bias_rxqual(bts->ho); + if (av_rxlev >= 0 && rxlev_bias) { + int imp = av_rxlev + rxlev_bias; + LOGPHOLCHAN(lchan, LOGL_INFO, "Virtually improving RX level from %d to %d," + " due to AFS bias\n", rxlev2dbm(av_rxlev), rxlev2dbm(imp)); + av_rxlev = imp; + } + if (av_rxqual >= 0 && rxqual_bias) { + int imp = av_rxqual - rxqual_bias; + if (imp < 0) + imp = 0; + LOGPHOLCHAN(lchan, LOGL_INFO, "Virtually improving RX quality from %d to %d," + " due to AFS bias\n", rxlev2dbm(av_rxqual), rxlev2dbm(imp)); + av_rxqual = imp; + } + } + + /* Bad Quality */ + if (av_rxqual >= 0 && av_rxqual > ho_get_hodec2_min_rxqual(bts->ho)) { + if (rxlev2dbm(av_rxlev) > -85) { + global_ho_reason = HO_REASON_INTERFERENCE; + LOGPHOLCHAN(lchan, LOGL_INFO, "Trying handover/assignment" + " due to interference (bad quality)\n"); + } else { + global_ho_reason = HO_REASON_BAD_QUALITY; + LOGPHOLCHAN(lchan, LOGL_INFO, "Trying handover/assignment due to bad quality\n"); + } + find_alternative_lchan(lchan, true); + return; + } + + /* Low Level */ + if (av_rxlev >= 0 && rxlev2dbm(av_rxlev) < ho_get_hodec2_min_rxlev(bts->ho)) { + global_ho_reason = HO_REASON_LOW_RXLEVEL; + LOGPHOLCHAN(lchan, LOGL_INFO, "Attempting handover/assignment due to low rxlev\n"); + find_alternative_lchan(lchan, true); + return; + } + + /* Max Distance */ + if (lchan->meas_rep_count > 0 + && lchan->rqd_ta > ho_get_hodec2_max_distance(bts->ho)) { + global_ho_reason = HO_REASON_MAX_DISTANCE; + LOGPHOLCHAN(lchan, LOGL_INFO, "Attempting handover due to high TA\n"); + /* start penalty timer to prevent comming back too + * early. it must be started before selecting a better cell, + * so there is no assignment selected, due to running + * penalty timer. */ + conn_penalty_time_add(lchan->conn, bts, ho_get_hodec2_penalty_max_dist(bts->ho)); + find_alternative_lchan(lchan, true); + return; + } + + /* pwr_interval's range is 1-99, clarifying that no div-zero shall happen in modulo below: */ + pwr_interval = ho_get_hodec2_pwr_interval(bts->ho); + OSMO_ASSERT(pwr_interval); + + /* try handover to a better cell */ + if (av_rxlev >= 0 && (mr->nr % pwr_interval) == 0) { + LOGPHOLCHAN(lchan, LOGL_INFO, "Looking whether a cell has better RXLEV\n"); + global_ho_reason = HO_REASON_BETTER_CELL; + find_alternative_lchan(lchan, false); + } +} + +/* + * Handover/assignment check after timer timeout: + * + * Even if handover process tries to prevent a congestion, a cell might get + * congested due to new call setups or handovers to prevent loss of radio link. + * A cell is congested, if not the minimum number of free slots are available. + * The minimum number can be defined for TCH/F and TCH/H individually. + * + * Do not perform congestion check, if no minimum free slots are defined for + * a cell. + * Do not trigger handover/assignment on slots which have already ongoing + * handover/assignment processes. If no AFS improvement offset is given, try to + * maintain the same TCH rate, if available. + * Do not perform this process, if handover and assignment are disabled for + * the current cell. + * Do not perform handover, if the minimum acceptable RX level + * is not reched for this cell. + * Only check candidates that will solve/reduce congestion. + * + * If a cell is congested, all slots are checked for all their RX levels + * (down-link) of the current and neighbor cell measurements in descending + * order of their RX levels: + * + * * Select the best candidate that fulfills requirement B (no congestion after + * handover/assignment), trigger handover or assignment. Candidates that will + * cause an assignment from AHS (AMR on TCH/H) to AFS (AMR on TCH/F) are + * omitted. + * o This process repeated until the minimum required number of free slots + * are restored or if all cell measurements are checked. The process ends + * then, otherwise: + * * Select the worst candidate that fulfills requirement B, trigger + * assignment. Note that only assignment candidates for changing from AHS to + * AFS are left. + * o This process repeated until the minimum required number of free slots + * are restored or if all cell measurements are checked. The process ends + * then, otherwise: + * * Select the best candidates that fulfill requirement C (less or equally + * congested cells after handover/assignment), trigger handover or + * assignment. Candidates that will cause an assignment from AHS (AMR on + * TCH/H) to AFS (AMR on TCH/F) are omitted. + * o This process repeated until the minimum required number of free slots + * are restored or if all cell measurements are checked. The process ends + * then, otherwise: + * * Select the worst candidate that fulfills requirement C, trigger + * assignment. Note that only assignment candidates for changing from AHS to + * AFS are left. + * o This process repeated until the minimum required number of free slots + * are restored or if all cell measurements are checked. + */ +static int bts_resolve_congestion(struct gsm_bts *bts, int tchf_congestion, int tchh_congestion) +{ + struct gsm_lchan *lc; + struct gsm_bts_trx *trx; + struct gsm_bts_trx_ts *ts; + int i, j; + struct ho_candidate *clist; + unsigned int candidates; + struct ho_candidate *best_cand = NULL, *worst_cand = NULL; + struct gsm_lchan *delete_lchan = NULL; + unsigned int best_avg_db, worst_avg_db; + int avg; + int rc = 0; + int any_ho = 0; + int is_improved = 0; + + if (tchf_congestion < 0) + tchf_congestion = 0; + if (tchh_congestion < 0) + tchh_congestion = 0; + + LOGPHOBTS(bts, LOGL_INFO, "congested: %d TCH/F and %d TCH/H should be moved\n", + tchf_congestion, tchh_congestion); + + /* allocate array of all bts */ + clist = talloc_zero_array(tall_bsc_ctx, struct ho_candidate, + bts->num_trx * 8 * 2 * (1 + ARRAY_SIZE(lc->neigh_meas))); + if (!clist) + return 0; + + candidates = 0; + + /* loop through all active lchan and collect candidates */ + llist_for_each_entry(trx, &bts->trx_list, list) { + if (!trx_is_usable(trx)) + continue; + + for (i = 0; i < 8; i++) { + ts = &trx->ts[i]; + if (!ts_is_usable(ts)) + continue; + + /* (Do not consider dynamic TS that are in PDCH mode) */ + switch (ts_pchan(ts)) { + case GSM_PCHAN_TCH_F: + lc = &ts->lchan[0]; + /* omit if channel not active */ + if (lc->type != GSM_LCHAN_TCH_F + || lc->state != LCHAN_S_ACTIVE) + break; + /* omit if there is an ongoing ho/as */ + if (!lc->conn || lc->conn->secondary_lchan + || lc->conn->ho) + break; + /* We desperately want to resolve congestion, ignore rxlev when + * collecting candidates by passing include_weaker_rxlev=true. */ + collect_candidates_for_lchan(lc, clist, &candidates, NULL, true); + break; + case GSM_PCHAN_TCH_H: + for (j = 0; j < 2; j++) { + lc = &ts->lchan[j]; + /* omit if channel not active */ + if (lc->type != GSM_LCHAN_TCH_H + || lc->state != LCHAN_S_ACTIVE) + continue; + /* omit of there is an ongoing ho/as */ + if (!lc->conn + || lc->conn->secondary_lchan + || lc->conn->ho) + continue; + /* We desperately want to resolve congestion, ignore rxlev when + * collecting candidates by passing include_weaker_rxlev=true. */ + collect_candidates_for_lchan(lc, clist, &candidates, NULL, true); + } + break; + default: + break; + } + } + } + + if (!candidates) { + LOGPHOBTS(bts, LOGL_DEBUG, "No neighbor cells qualify to solve congestion\n"); + goto exit; + } + if (log_check_level(DHODEC, LOGL_DEBUG)) { + LOGPHOBTS(bts, LOGL_DEBUG, "Considering %u candidates to solve congestion:\n", candidates); + for (i = 0; i < candidates; i++) { + LOGPHOLCHANTOBTS(clist[i].lchan, clist[i].bts, LOGL_DEBUG, + "#%d: req=0x%x avg-rxlev=%d\n", + i, clist[i].requirements, clist[i].avg); + } + } + +#if 0 +next_b1: +#endif + /* select best candidate that fulfills requirement B, + * omit change from AHS to AFS */ + best_avg_db = 0; + for (i = 0; i < candidates; i++) { + /* delete subscriber that just have handovered */ + if (clist[i].lchan == delete_lchan) + clist[i].lchan = NULL; + /* omit all subscribers that are handovered */ + if (!clist[i].lchan) + continue; + + if (!(clist[i].requirements & REQUIREMENT_B_MASK)) + continue; + /* omit assignment from AHS to AFS */ + if (clist[i].lchan->ts->trx->bts == clist[i].bts + && clist[i].lchan->type == GSM_LCHAN_TCH_H + && (clist[i].requirements & REQUIREMENT_B_TCHF)) + continue; + /* omit candidates that will not solve/reduce congestion */ + if (clist[i].lchan->type == GSM_LCHAN_TCH_F + && tchf_congestion <= 0) + continue; + if (clist[i].lchan->type == GSM_LCHAN_TCH_H + && tchh_congestion <= 0) + continue; + + avg = clist[i].avg; + /* improve AHS */ + if (clist[i].lchan->tch_mode == GSM48_CMODE_SPEECH_AMR + && clist[i].lchan->type == GSM_LCHAN_TCH_H + && (clist[i].requirements & REQUIREMENT_B_TCHF)) { + avg += ho_get_hodec2_afs_bias_rxlev(clist[i].bts->ho); + is_improved = 1; + } else + is_improved = 0; + LOGP(DHODEC, LOGL_DEBUG, "candidate %d: avg=%d best_avg_db=%d\n", i, avg, best_avg_db); + if (avg > best_avg_db) { + best_cand = &clist[i]; + best_avg_db = avg; + } + } + + /* perform handover, if there is a candidate */ + if (best_cand) { + any_ho = 1; + LOGPHOLCHAN(best_cand->lchan, LOGL_INFO, + "Best candidate BTS %u (RX level %d) without congestion found\n", + best_cand->bts->nr, rxlev2dbm(best_cand->avg)); + if (is_improved) + LOGP(DHODEC, LOGL_INFO, "(is improved due to " + "AHS -> AFS)\n"); + trigger_handover_or_assignment(best_cand->lchan, best_cand->bts, + best_cand->requirements & REQUIREMENT_B_MASK); +#if 0 + /* if there is still congestion, mark lchan as deleted + * and redo this process */ + if (best_cand->lchan->type == GSM_LCHAN_TCH_H) + tchh_congestion--; + else + tchf_congestion--; + if (tchf_congestion > 0 || tchh_congestion > 0) { + delete_lchan = best_cand->lchan; + best_cand = NULL; + goto next_b1; + } +#else + /* must exit here, because triggering handover/assignment + * will cause change in requirements. more check for this + * bts is performed in the next iteration. + */ +#endif + goto exit; + } + + LOGPHOBTS(bts, LOGL_DEBUG, "Did not find a best candidate that fulfills requirement B" + " (omitting change from AHS to AFS)\n"); + +#if 0 +next_b2: +#endif + /* select worst candidate that fulfills requirement B, + * select candidates that change from AHS to AFS only */ + if (tchh_congestion > 0) { + /* since this will only check half rate channels, it will + * only need to be checked, if tchh is congested */ + worst_avg_db = 999; + for (i = 0; i < candidates; i++) { + /* delete subscriber that just have handovered */ + if (clist[i].lchan == delete_lchan) + clist[i].lchan = NULL; + /* omit all subscribers that are handovered */ + if (!clist[i].lchan) + continue; + + if (!(clist[i].requirements & REQUIREMENT_B_MASK)) + continue; + /* omit all but assignment from AHS to AFS */ + if (clist[i].lchan->ts->trx->bts != clist[i].bts + || clist[i].lchan->type != GSM_LCHAN_TCH_H + || !(clist[i].requirements & REQUIREMENT_B_TCHF)) + continue; + + avg = clist[i].avg; + /* improve AHS */ + if (clist[i].lchan->tch_mode == GSM48_CMODE_SPEECH_AMR + && clist[i].lchan->type == GSM_LCHAN_TCH_H) { + avg += ho_get_hodec2_afs_bias_rxlev(clist[i].bts->ho); + is_improved = 1; + } else + is_improved = 0; + LOGP(DHODEC, LOGL_DEBUG, "candidate %d: avg=%d worst_avg_db=%d\n", i, avg, + worst_avg_db); + if (avg < worst_avg_db) { + worst_cand = &clist[i]; + worst_avg_db = avg; + } + } + } + + /* perform handover, if there is a candidate */ + if (worst_cand) { + any_ho = 1; + LOGP(DHODEC, LOGL_INFO, "Worst candidate for assignment " + "(RX level %d) from TCH/H -> TCH/F without congestion " + "found\n", rxlev2dbm(worst_cand->avg)); + if (is_improved) + LOGP(DHODEC, LOGL_INFO, "(is improved due to " + "AHS -> AFS)\n"); + trigger_handover_or_assignment(worst_cand->lchan, + worst_cand->bts, + worst_cand->requirements & REQUIREMENT_B_MASK); +#if 0 + /* if there is still congestion, mark lchan as deleted + * and redo this process */ + tchh_congestion--; + if (tchh_congestion > 0) { + delete_lchan = worst_cand->lchan; + best_cand = NULL; + goto next_b2; + } +#else + /* must exit here, because triggering handover/assignment + * will cause change in requirements. more check for this + * bts is performed in the next iteration. + */ +#endif + goto exit; + } + + LOGPHOBTS(bts, LOGL_DEBUG, "Did not find a worst candidate that fulfills requirement B," + " selecting candidates that change from AHS to AFS only\n"); + +#if 0 +next_c1: +#endif + /* select best candidate that fulfills requirement C, + * omit change from AHS to AFS */ + best_avg_db = 0; + for (i = 0; i < candidates; i++) { + /* delete subscriber that just have handovered */ + if (clist[i].lchan == delete_lchan) + clist[i].lchan = NULL; + /* omit all subscribers that are handovered */ + if (!clist[i].lchan) + continue; + + if (!(clist[i].requirements & REQUIREMENT_C_MASK)) + continue; + /* omit assignment from AHS to AFS */ + if (clist[i].lchan->ts->trx->bts == clist[i].bts + && clist[i].lchan->type == GSM_LCHAN_TCH_H + && (clist[i].requirements & REQUIREMENT_C_TCHF)) + continue; + /* omit candidates that will not solve/reduce congestion */ + if (clist[i].lchan->type == GSM_LCHAN_TCH_F + && tchf_congestion <= 0) + continue; + if (clist[i].lchan->type == GSM_LCHAN_TCH_H + && tchh_congestion <= 0) + continue; + + avg = clist[i].avg; + /* improve AHS */ + if (clist[i].lchan->tch_mode == GSM48_CMODE_SPEECH_AMR + && clist[i].lchan->type == GSM_LCHAN_TCH_H + && (clist[i].requirements & REQUIREMENT_C_TCHF)) { + avg += ho_get_hodec2_afs_bias_rxlev(clist[i].bts->ho); + is_improved = 1; + } else + is_improved = 0; + LOGP(DHODEC, LOGL_DEBUG, "candidate %d: avg=%d best_avg_db=%d\n", i, avg, best_avg_db); + if (avg > best_avg_db) { + best_cand = &clist[i]; + best_avg_db = avg; + } + } + + /* perform handover, if there is a candidate */ + if (best_cand) { + any_ho = 1; + LOGP(DHODEC, LOGL_INFO, "Best candidate BTS %d (RX level %d) " + "with less or equal congestion found\n", + best_cand->bts->nr, rxlev2dbm(best_cand->avg)); + if (is_improved) + LOGP(DHODEC, LOGL_INFO, "(is improved due to " + "AHS -> AFS)\n"); + trigger_handover_or_assignment(best_cand->lchan, best_cand->bts, + best_cand->requirements & REQUIREMENT_C_MASK); +#if 0 + /* if there is still congestion, mark lchan as deleted + * and redo this process */ + if (best_cand->lchan->type == GSM_LCHAN_TCH_H) + tchh_congestion--; + else + tchf_congestion--; + if (tchf_congestion > 0 || tchh_congestion > 0) { + delete_lchan = best_cand->lchan; + best_cand = NULL; + goto next_c1; + } +#else + /* must exit here, because triggering handover/assignment + * will cause change in requirements. more check for this + * bts is performed in the next iteration. + */ +#endif + goto exit; + } + + LOGPHOBTS(bts, LOGL_DEBUG, "Did not find a best candidate that fulfills requirement C" + " (omitting change from AHS to AFS)\n"); + +#if 0 +next_c2: +#endif + /* select worst candidate that fulfills requirement C, + * select candidates that change from AHS to AFS only */ + if (tchh_congestion > 0) { + /* since this will only check half rate channels, it will + * only need to be checked, if tchh is congested */ + worst_avg_db = 999; + for (i = 0; i < candidates; i++) { + /* delete subscriber that just have handovered */ + if (clist[i].lchan == delete_lchan) + clist[i].lchan = NULL; + /* omit all subscribers that are handovered */ + if (!clist[i].lchan) + continue; + + if (!(clist[i].requirements & REQUIREMENT_C_MASK)) + continue; + /* omit all but assignment from AHS to AFS */ + if (clist[i].lchan->ts->trx->bts != clist[i].bts + || clist[i].lchan->type != GSM_LCHAN_TCH_H + || !(clist[i].requirements & REQUIREMENT_C_TCHF)) + continue; + + avg = clist[i].avg; + /* improve AHS */ + if (clist[i].lchan->tch_mode == GSM48_CMODE_SPEECH_AMR + && clist[i].lchan->type == GSM_LCHAN_TCH_H) { + avg += ho_get_hodec2_afs_bias_rxlev(clist[i].bts->ho); + is_improved = 1; + } else + is_improved = 0; + LOGP(DHODEC, LOGL_DEBUG, "candidate %d: avg=%d worst_avg_db=%d\n", i, avg, + worst_avg_db); + if (avg < worst_avg_db) { + worst_cand = &clist[i]; + worst_avg_db = avg; + } + } + } + + /* perform handover, if there is a candidate */ + if (worst_cand) { + any_ho = 1; + LOGP(DHODEC, LOGL_INFO, "Worst candidate for assignment " + "(RX level %d) from TCH/H -> TCH/F with less or equal " + "congestion found\n", rxlev2dbm(worst_cand->avg)); + if (is_improved) + LOGP(DHODEC, LOGL_INFO, "(is improved due to " + "AHS -> AFS)\n"); + trigger_handover_or_assignment(worst_cand->lchan, + worst_cand->bts, + worst_cand->requirements & REQUIREMENT_C_MASK); +#if 0 + /* if there is still congestion, mark lchan as deleted + * and redo this process */ + tchh_congestion--; + if (tchh_congestion > 0) { + delete_lchan = worst_cand->lchan; + worst_cand = NULL; + goto next_c2; + } +#else + /* must exit here, because triggering handover/assignment + * will cause change in requirements. more check for this + * bts is performed in the next iteration. + */ +#endif + goto exit; + } + LOGPHOBTS(bts, LOGL_DEBUG, "Did not find a worst candidate that fulfills requirement C," + " selecting candidates that change from AHS to AFS only\n"); + + +exit: + /* free array */ + talloc_free(clist); + + if (tchf_congestion <= 0 && tchh_congestion <= 0) + LOGP(DHODEC, LOGL_INFO, "Congestion at BTS %d solved!\n", + bts->nr); + else if (any_ho) + LOGP(DHODEC, LOGL_INFO, "Congestion at BTS %d reduced!\n", + bts->nr); + else + LOGP(DHODEC, LOGL_INFO, "Congestion at BTS %d can't be reduced/solved!\n", bts->nr); + + return rc; +} + +static void bts_congestion_check(struct gsm_bts *bts) +{ + int min_free_tchf, min_free_tchh; + int tchf_count, tchh_count; + + global_ho_reason = HO_REASON_CONGESTION; + + /* only check BTS if TRX 0 is usable */ + if (!trx_is_usable(bts->c0)) { + LOGPHOBTS(bts, LOGL_DEBUG, "No congestion check: TRX 0 not usable\n"); + return; + } + + /* only check BTS if handover or assignment is enabled */ + if (!ho_get_hodec2_as_active(bts->ho) + && !ho_get_ho_active(bts->ho)) { + LOGPHOBTS(bts, LOGL_DEBUG, "No congestion check: Assignment and Handover both disabled\n"); + return; + } + + min_free_tchf = ho_get_hodec2_tchf_min_slots(bts->ho); + min_free_tchh = ho_get_hodec2_tchh_min_slots(bts->ho); + + /* only check BTS with congestion level set */ + if (!min_free_tchf && !min_free_tchh) { + LOGPHOBTS(bts, LOGL_DEBUG, "No congestion check: no minimum for free TCH/F nor TCH/H set\n"); + return; + } + + tchf_count = bts_count_free_ts(bts, GSM_PCHAN_TCH_F); + tchh_count = bts_count_free_ts(bts, GSM_PCHAN_TCH_H); + LOGPHOBTS(bts, LOGL_INFO, "Congestion check: (free/want-free) TCH/F=%d/%d TCH/H=%d/%d\n", + tchf_count, min_free_tchf, tchh_count, min_free_tchh); + + /* only check BTS if congested */ + if (tchf_count >= min_free_tchf && tchh_count >= min_free_tchh) { + LOGPHOBTS(bts, LOGL_DEBUG, "Not congested\n"); + return; + } + + LOGPHOBTS(bts, LOGL_DEBUG, "Attempting to resolve congestion...\n"); + bts_resolve_congestion(bts, min_free_tchf - tchf_count, min_free_tchh - tchh_count); +} + +void hodec2_congestion_check(struct gsm_network *net) +{ + struct gsm_bts *bts; + + llist_for_each_entry(bts, &net->bts_list, list) + bts_congestion_check(bts); +} + +static void congestion_check_cb(void *arg) +{ + struct gsm_network *net = arg; + hodec2_congestion_check(net); + reinit_congestion_timer(net); +} + +void on_ho_chan_activ_nack(struct bsc_handover *ho) +{ + struct gsm_bts *new_bts = ho->new_lchan->ts->trx->bts; + + LOGPHO(ho, LOGL_ERROR, "Channel Activate Nack for %s, starting penalty timer\n", ho->inter_cell? "Handover" : "Assignment"); + + /* if channel failed, wait 10 seconds before allowing to retry handover */ + conn_penalty_time_add(ho->old_lchan->conn, new_bts, 10); /* FIXME configurable */ +} + +void on_ho_failure(struct bsc_handover *ho) +{ + struct gsm_bts *old_bts = ho->old_lchan->ts->trx->bts; + struct gsm_bts *new_bts = ho->new_lchan->ts->trx->bts; + struct gsm_subscriber_connection *conn = ho->old_lchan->conn; + + if (!conn) { + LOGPHO(ho, LOGL_ERROR, "HO failure, but no conn"); + return; + } + + if (conn->hodec2.failures >= ho_get_hodec2_retries(old_bts->ho)) { + int penalty = ho->inter_cell + ? ho_get_hodec2_penalty_failed_ho(old_bts->ho) + : ho_get_hodec2_penalty_failed_as(old_bts->ho); + LOGPHO(ho, LOGL_NOTICE, "%s failed, starting penalty timer (%d s)\n", + ho->inter_cell ? "Handover" : "Assignment", + penalty); + conn->hodec2.failures = 0; + conn_penalty_time_add(conn, new_bts, penalty); + } else { + conn->hodec2.failures++; + LOGPHO(ho, LOGL_NOTICE, "%s failed, allowing handover decision to try again" + " (%d/%d attempts)\n", + ho->inter_cell ? "Handover" : "Assignment", + conn->hodec2.failures, ho_get_hodec2_retries(old_bts->ho)); + } +} + +struct handover_decision_callbacks hodec2_callbacks = { + .hodec_id = 2, + .on_measurement_report = on_measurement_report, + .on_ho_chan_activ_nack = on_ho_chan_activ_nack, + .on_ho_failure = on_ho_failure, +}; + +void hodec2_init(struct gsm_network *net) +{ + handover_decision_callbacks_register(&hodec2_callbacks); + hodec2_initialized = true; + reinit_congestion_timer(net); +} diff --git a/src/osmo-bsc/handover_logic.c b/src/osmo-bsc/handover_logic.c new file mode 100644 index 000000000..960bf6993 --- /dev/null +++ b/src/osmo-bsc/handover_logic.c @@ -0,0 +1,473 @@ +/* Handover Logic for Inter-BTS (Intra-BSC) Handover. This does not + * actually implement the handover algorithm/decision, but executes a + * handover decision */ + +/* (C) 2009 by Harald Welte <laforge@gnumonks.org> + * + * 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 <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <time.h> +#include <netinet/in.h> + +#include <osmocom/core/msgb.h> +#include <osmocom/bsc/debug.h> +#include <osmocom/bsc/gsm_data.h> +#include <osmocom/gsm/gsm_utils.h> +#include <osmocom/bsc/abis_rsl.h> +#include <osmocom/bsc/chan_alloc.h> +#include <osmocom/bsc/signal.h> +#include <osmocom/core/talloc.h> +#include <osmocom/bsc/bsc_subscriber.h> +#include <osmocom/bsc/gsm_04_08_utils.h> +#include <osmocom/bsc/handover.h> +#include <osmocom/bsc/handover_cfg.h> +#include <osmocom/bsc/bsc_subscr_conn_fsm.h> + +static LLIST_HEAD(bsc_handovers); +static LLIST_HEAD(handover_decision_callbacks); + +static void handover_free(struct bsc_handover *ho) +{ + osmo_timer_del(&ho->T3103); + llist_del(&ho->list); + talloc_free(ho); +} + +static struct bsc_handover *bsc_ho_by_new_lchan(struct gsm_lchan *new_lchan) +{ + struct bsc_handover *ho; + + llist_for_each_entry(ho, &bsc_handovers, list) { + if (ho->new_lchan == new_lchan) + return ho; + } + + return NULL; +} + +static struct bsc_handover *bsc_ho_by_old_lchan(struct gsm_lchan *old_lchan) +{ + struct bsc_handover *ho; + + llist_for_each_entry(ho, &bsc_handovers, list) { + if (ho->old_lchan == old_lchan) + return ho; + } + + return NULL; +} + +/*! Hand over the specified logical channel to the specified new BTS and possibly change the lchan type. + * This is the main entry point for the actual handover algorithm, after the decision whether to initiate + * HO to a specific BTS. To not change the lchan type, pass old_lchan->type. */ +int bsc_handover_start(enum hodec_id from_hodec_id, struct gsm_lchan *old_lchan, struct gsm_bts *new_bts, + enum gsm_chan_t new_lchan_type) +{ + struct gsm_subscriber_connection *conn; + struct bsc_handover *ho; + static uint8_t ho_ref = 0; + bool do_assignment; + + OSMO_ASSERT(old_lchan); + + /* don't attempt multiple handovers for the same lchan at + * the same time */ + if (bsc_ho_by_old_lchan(old_lchan)) + return -EBUSY; + + conn = old_lchan->conn; + if (!conn) { + LOGP(DHO, LOGL_ERROR, "Old lchan lacks connection data.\n"); + return -ENOSPC; + } + + if (!new_bts) + new_bts = old_lchan->ts->trx->bts; + OSMO_ASSERT(new_bts); + + do_assignment = (new_bts == old_lchan->ts->trx->bts); + + ho = talloc_zero(conn, struct bsc_handover); + if (!ho) { + LOGP(DHO, LOGL_FATAL, "Out of Memory\n"); + return -ENOMEM; + } + ho->from_hodec_id = from_hodec_id; + ho->old_lchan = old_lchan; + ho->new_bts = new_bts; + ho->new_lchan_type = new_lchan_type; + ho->ho_ref = ho_ref++; + ho->inter_cell = !do_assignment; + ho->async = true; + llist_add(&ho->list, &bsc_handovers); + + conn->ho = ho; + + DEBUGP(DHO, "(BTS %u trx %u ts %u lchan %u %s)->(BTS %u lchan %s) Initiating %s...\n", + old_lchan->ts->trx->bts->nr, + old_lchan->ts->trx->nr, + old_lchan->ts->nr, + old_lchan->nr, + gsm_pchan_name(old_lchan->ts->pchan), + new_bts->nr, + gsm_lchant_name(new_lchan_type), + do_assignment ? "Assignment" : "Handover"); + + return osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_HO_START, NULL); +} + +/*! Start actual handover. Call bsc_handover_start() instead; The only legal caller is the GSCON FSM in + * bsc_subscr_conn_fsm.c. */ +int bsc_handover_start_gscon(struct gsm_subscriber_connection *conn) +{ + int rc; + struct gsm_network *network = conn->network; + struct bsc_handover *ho = conn->ho; + struct gsm_lchan *old_lchan; + struct gsm_lchan *new_lchan; + + if (!ho) { + LOGP(DHO, LOGL_ERROR, "%s: Requested to start handover, but conn->ho is NULL\n", + bsc_subscr_name(conn->bsub)); + return -EINVAL; + } + + OSMO_ASSERT(ho->old_lchan && ho->new_bts); + + if (ho->old_lchan->conn != conn) { + LOGP(DHO, LOGL_ERROR, + "%s: Requested to start handover, but the lchan does not belong to this conn\n", + bsc_subscr_name(conn->bsub)); + return -EINVAL; + } + + rate_ctr_inc(&network->bsc_ctrs->ctr[BSC_CTR_HANDOVER_ATTEMPTED]); + + ho->new_lchan = lchan_alloc(ho->new_bts, ho->new_lchan_type, 0); + if (!ho->new_lchan) { + LOGP(DHO, LOGL_NOTICE, "No free channel for %s\n", gsm_lchant_name(ho->new_lchan_type)); + rate_ctr_inc(&network->bsc_ctrs->ctr[BSC_CTR_HANDOVER_NO_CHANNEL]); + return -ENOSPC; + } + + LOGPHO(ho, LOGL_INFO, "Triggering %s\n", ho->inter_cell? "Handover" : "Assignment"); + + /* copy some parameters from old lchan */ + old_lchan = ho->old_lchan; + new_lchan = ho->new_lchan; + memcpy(&new_lchan->encr, &old_lchan->encr, sizeof(new_lchan->encr)); + if (!ho->inter_cell) { + new_lchan->ms_power = old_lchan->ms_power; + new_lchan->rqd_ta = old_lchan->rqd_ta; + } else { + new_lchan->ms_power = + ms_pwr_ctl_lvl(ho->new_bts->band, ho->new_bts->ms_max_power); + /* FIXME: do we have a better idea of the timing advance? */ + //new_lchan->rqd_ta = old_lchan->rqd_ta; + } + new_lchan->bs_power = old_lchan->bs_power; + new_lchan->rsl_cmode = old_lchan->rsl_cmode; + new_lchan->tch_mode = old_lchan->tch_mode; + memcpy(&new_lchan->mr_ms_lv, &old_lchan->mr_ms_lv, sizeof(new_lchan->mr_ms_lv)); + memcpy(&new_lchan->mr_bts_lv, &old_lchan->mr_bts_lv, sizeof(new_lchan->mr_bts_lv)); + + new_lchan->conn = conn; + + rc = rsl_chan_activate_lchan(new_lchan, + ho->async ? RSL_ACT_INTER_ASYNC : RSL_ACT_INTER_SYNC, + ho->ho_ref); + if (rc < 0) { + LOGPHO(ho, LOGL_INFO, "%s Failure: activate lchan rc = %d\n", + ho->inter_cell? "Handover" : "Assignment", rc); + lchan_free(new_lchan); + ho->new_lchan = NULL; + bsc_clear_handover(conn, 0); + return rc; + } + + rsl_lchan_set_state(new_lchan, LCHAN_S_ACT_REQ); + /* we continue in the SS_LCHAN handler / ho_chan_activ_ack */ + + return 0; +} + +/* clear any operation for this connection */ +void bsc_clear_handover(struct gsm_subscriber_connection *conn, int free_lchan) +{ + struct bsc_handover *ho = conn->ho; + + if (!ho) + return; + + if (ho->new_lchan) { + ho->new_lchan->conn = NULL; + if (free_lchan) + lchan_release(ho->new_lchan, 0, RSL_REL_LOCAL_END); + ho->new_lchan = NULL; + } + + handover_free(ho); + conn->ho = NULL; +} + +/* T3103 expired: Handover has failed without HO COMPLETE or HO FAIL */ +static void ho_T3103_cb(void *_ho) +{ + struct bsc_handover *ho = _ho; + struct gsm_network *net = ho->new_lchan->ts->trx->bts->network; + + DEBUGP(DHO, "HO T3103 expired\n"); + rate_ctr_inc(&net->bsc_ctrs->ctr[BSC_CTR_HANDOVER_TIMEOUT]); + + /* Inform the GSCON FSM about the timed out handover */ + osmo_fsm_inst_dispatch(ho->old_lchan->conn->fi, GSCON_EV_HO_TIMEOUT, NULL); + + bsc_clear_handover(ho->old_lchan->conn, 1); +} + +/* RSL has acknowledged activation of the new lchan */ +static int ho_chan_activ_ack(struct gsm_lchan *new_lchan) +{ + struct bsc_handover *ho; + + /* we need to check if this channel activation is related to + * a handover at all (and if, which particular handover) */ + ho = bsc_ho_by_new_lchan(new_lchan); + if (!ho) + return -ENODEV; + + LOGPHO(ho, LOGL_INFO, "Channel Activate Ack, send %s COMMAND\n", ho->inter_cell? "HANDOVER" : "ASSIGNMENT"); + + /* we can now send the 04.08 HANDOVER COMMAND to the MS + * using the old lchan */ + + gsm48_send_ho_cmd(ho->old_lchan, new_lchan, new_lchan->ms_power, ho->ho_ref); + + /* start T3103. We can continue either with T3103 expiration, + * 04.08 HANDOVER COMPLETE or 04.08 HANDOVER FAIL */ + osmo_timer_setup(&ho->T3103, ho_T3103_cb, ho); + osmo_timer_schedule(&ho->T3103, 10, 0); + + /* create a RTP connection */ + if (is_ipaccess_bts(new_lchan->ts->trx->bts)) + rsl_ipacc_crcx(new_lchan); + + return 0; +} + +/* RSL has not acknowledged activation of the new lchan */ +static int ho_chan_activ_nack(struct gsm_lchan *new_lchan) +{ + struct bsc_handover *ho; + struct handover_decision_callbacks *hdc; + + ho = bsc_ho_by_new_lchan(new_lchan); + if (!ho) { + /* This lchan is not involved in a handover. */ + return 0; + } + + hdc = handover_decision_callbacks_get(ho->from_hodec_id); + if (hdc && hdc->on_ho_chan_activ_nack) + hdc->on_ho_chan_activ_nack(ho); + + bsc_clear_handover(new_lchan->conn, 0); + return 0; +} + +/* GSM 04.08 HANDOVER COMPLETE has been received on new channel */ +static int ho_gsm48_ho_compl(struct gsm_lchan *new_lchan) +{ + struct gsm_network *net; + struct bsc_handover *ho; + + ho = bsc_ho_by_new_lchan(new_lchan); + if (!ho) { + LOGP(DHO, LOGL_ERROR, "unable to find HO record\n"); + return -ENODEV; + } + + net = new_lchan->ts->trx->bts->network; + + LOGPHO(ho, LOGL_INFO, "%s Complete\n", ho->inter_cell ? "Handover" : "Assignment"); + + rate_ctr_inc(&net->bsc_ctrs->ctr[BSC_CTR_HANDOVER_COMPLETED]); + + osmo_timer_del(&ho->T3103); + + /* Replace the ho lchan with the primary one */ + if (ho->old_lchan != new_lchan->conn->lchan) + LOGPHO(ho, LOGL_ERROR, "Primary lchan changed during handover.\n"); + + if (new_lchan->conn->ho != ho) + LOGPHO(ho, LOGL_ERROR, "Handover channel changed during this handover.\n"); + + new_lchan->conn->lchan = new_lchan; + ho->old_lchan->conn = NULL; + + lchan_release(ho->old_lchan, 0, RSL_REL_LOCAL_END); + + handover_free(ho); + new_lchan->conn->ho = NULL; + + /* Inform the GSCON FSM that the handover is complete */ + osmo_fsm_inst_dispatch(new_lchan->conn->fi, GSCON_EV_HO_COMPL, NULL); + return 0; +} + +/* GSM 04.08 HANDOVER FAIL has been received */ +static int ho_gsm48_ho_fail(struct gsm_lchan *old_lchan) +{ + struct gsm_network *net = old_lchan->ts->trx->bts->network; + struct bsc_handover *ho; + struct handover_decision_callbacks *hdc; + + ho = bsc_ho_by_old_lchan(old_lchan); + if (!ho) { + LOGP(DHO, LOGL_ERROR, "unable to find HO record\n"); + return -ENODEV; + } + + hdc = handover_decision_callbacks_get(ho->from_hodec_id); + if (hdc && hdc->on_ho_failure) + hdc->on_ho_failure(ho); + + rate_ctr_inc(&net->bsc_ctrs->ctr[BSC_CTR_HANDOVER_FAILED]); + + bsc_clear_handover(ho->new_lchan->conn, 1); + + /* Inform the GSCON FSM that the handover failed */ + osmo_fsm_inst_dispatch(old_lchan->conn->fi, GSCON_EV_HO_FAIL, NULL); + return 0; +} + +/* GSM 08.58 HANDOVER DETECT has been received */ +static int ho_rsl_detect(struct gsm_lchan *new_lchan) +{ + struct bsc_handover *ho; + + ho = bsc_ho_by_new_lchan(new_lchan); + if (!ho) { + LOGP(DHO, LOGL_ERROR, "unable to find HO record\n"); + return -ENODEV; + } + + LOGPHO(ho, LOGL_DEBUG, "Handover RACH detected\n"); + + /* This is just for logging on the DHO category. The actual MGCP switchover happens in + * osmo_bsc_mgcp.c by receiving the same S_LCHAN_HANDOVER_DETECT signal. + * (Calling mgcp_handover() directly currently breaks linking in utils/...) */ + + return 0; +} + +static int ho_meas_rep(struct gsm_meas_rep *mr) +{ + struct handover_decision_callbacks *hdc; + enum hodec_id hodec_id = ho_get_algorithm(mr->lchan->ts->trx->bts->ho); + + hdc = handover_decision_callbacks_get(hodec_id); + if (!hdc || !hdc->on_measurement_report) + return 0; + hdc->on_measurement_report(mr); + return 0; +} + +static int ho_logic_sig_cb(unsigned int subsys, unsigned int signal, + void *handler_data, void *signal_data) +{ + struct lchan_signal_data *lchan_data; + struct gsm_lchan *lchan; + + lchan_data = signal_data; + switch (subsys) { + case SS_LCHAN: + lchan = lchan_data->lchan; + switch (signal) { + case S_LCHAN_ACTIVATE_ACK: + return ho_chan_activ_ack(lchan); + case S_LCHAN_ACTIVATE_NACK: + return ho_chan_activ_nack(lchan); + case S_LCHAN_HANDOVER_DETECT: + return ho_rsl_detect(lchan); + case S_LCHAN_HANDOVER_COMPL: + return ho_gsm48_ho_compl(lchan); + case S_LCHAN_HANDOVER_FAIL: + return ho_gsm48_ho_fail(lchan); + case S_LCHAN_MEAS_REP: + return ho_meas_rep(lchan_data->mr); + } + break; + default: + break; + } + + return 0; +} + +/* Return the old lchan or NULL. This is meant for audio handling */ +struct gsm_lchan *bsc_handover_pending(struct gsm_lchan *new_lchan) +{ + struct bsc_handover *ho; + ho = bsc_ho_by_new_lchan(new_lchan); + if (!ho) + return NULL; + return ho->old_lchan; +} + +static __attribute__((constructor)) void on_dso_load_ho_logic(void) +{ + osmo_signal_register_handler(SS_LCHAN, ho_logic_sig_cb, NULL); +} + +/* Count number of currently ongoing handovers + * inter_cell: if true, count only handovers between two cells. If false, count only handovers within one + * cell. */ +int bsc_ho_count(struct gsm_bts *bts, bool inter_cell) +{ + struct bsc_handover *ho; + int count = 0; + + llist_for_each_entry(ho, &bsc_handovers, list) { + if (ho->inter_cell != inter_cell) + continue; + if (ho->new_lchan->ts->trx->bts == bts) + count++; + } + + return count; +} + +void handover_decision_callbacks_register(struct handover_decision_callbacks *hdc) +{ + llist_add_tail(&hdc->entry, &handover_decision_callbacks); +} + +struct handover_decision_callbacks *handover_decision_callbacks_get(int hodec_id) +{ + struct handover_decision_callbacks *hdc; + llist_for_each_entry(hdc, &handover_decision_callbacks, entry) { + if (hdc->hodec_id == hodec_id) + return hdc; + } + return NULL; +} diff --git a/src/osmo-bsc/handover_vty.c b/src/osmo-bsc/handover_vty.c new file mode 100644 index 000000000..51e448e03 --- /dev/null +++ b/src/osmo-bsc/handover_vty.c @@ -0,0 +1,177 @@ +/* OsmoBSC interface to quagga VTY for handover parameters */ +/* (C) 2009-2010 by Andreas Eversberg <jolly@eversberg.eu> + * (C) 2009-2010 by Harald Welte <laforge@gnumonks.org> + * (C) 2017-2018 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> + * + * All Rights Reserved + * + * Author: Andreas Eversberg <jolly@eversberg.eu> + * Neels Hofmeyr <nhofmeyr@sysmocom.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/>. + * + */ + +#include <osmocom/bsc/gsm_data.h> +#include <osmocom/bsc/vty.h> +#include <osmocom/bsc/handover_cfg.h> +#include <osmocom/bsc/handover_decision_2.h> + +static struct handover_cfg *ho_cfg_from_vty(struct vty *vty) +{ + switch (vty->node) { + case GSMNET_NODE: + return gsmnet_from_vty(vty)->ho; + case BTS_NODE: + OSMO_ASSERT(vty->index); + return ((struct gsm_bts *)vty->index)->ho; + default: + OSMO_ASSERT(false); + } +} + + +#define HO_CFG_ONE_MEMBER(TYPE, NAME, DEFAULT_VAL, \ + VTY_CMD_PREFIX, VTY_CMD, VTY_CMD_ARG, VTY_ARG_EVAL, \ + VTY_WRITE_FMT, VTY_WRITE_CONV, \ + VTY_DOC) \ +DEFUN(cfg_ho_##NAME, cfg_ho_##NAME##_cmd, \ + VTY_CMD_PREFIX VTY_CMD " (" VTY_CMD_ARG "|default)", \ + VTY_DOC \ + "Use default (" #DEFAULT_VAL "), remove explicit setting on this node\n") \ +{ \ + struct handover_cfg *ho = ho_cfg_from_vty(vty); \ + const char *val = argv[0]; \ + if (!strcmp(val, "default")) { \ + const char *msg; \ + if (ho_isset_##NAME(ho)) {\ + ho_clear_##NAME(ho); \ + msg = "setting removed, now is"; \ + } else \ + msg = "already was unset, still is"; \ + vty_out(vty, "%% '" VTY_CMD_PREFIX VTY_CMD "' %s " VTY_WRITE_FMT "%s%s", \ + msg, VTY_WRITE_CONV( ho_get_##NAME(ho) ), \ + ho_isset_on_parent_##NAME(ho)? " (set on higher level node)" : "", \ + VTY_NEWLINE); \ + } \ + else \ + ho_set_##NAME(ho, VTY_ARG_EVAL(val)); \ + return CMD_SUCCESS; \ +} + +HO_CFG_ALL_MEMBERS +#undef HO_CFG_ONE_MEMBER + + +/* Aliases of 'handover' for 'handover1' for backwards compat */ +#define HO_CFG_ONE_MEMBER(TYPE, NAME, DEFAULT_VAL, \ + VTY_CMD_PREFIX, VTY_CMD, VTY_CMD_ARG, VTY_ARG_EVAL, \ + VTY_WRITE_FMT, VTY_WRITE_CONV, \ + VTY_DOC) \ +ALIAS_DEPRECATED(cfg_ho_##NAME, cfg_ho_##NAME##_cmd_alias, \ + "handover " VTY_CMD " (" VTY_CMD_ARG "|default)", \ + "Legacy alias for 'handover1': " VTY_DOC \ + "Use default (" #DEFAULT_VAL "), remove explicit setting on this node\n"); + +HODEC1_CFG_ALL_MEMBERS +#undef HO_CFG_ONE_MEMBER + +static inline const int a2congestion_check_interval(const char *arg) +{ + if (!strcmp(arg, "disabled")) + return 0; + return atoi(arg); +} + +static inline const char *congestion_check_interval2a(int val) +{ + static char str[9]; + if (val < 1 + || snprintf(str, sizeof(str), "%d", val) >= sizeof(str)) + return "disabled"; + return str; +} + +DEFUN(cfg_net_ho_congestion_check_interval, cfg_net_ho_congestion_check_interval_cmd, + "handover2 congestion-check (disabled|<1-999>|now)", + HO_CFG_STR_HANDOVER2 + "Configure congestion check interval" HO_CFG_STR_2 + "Disable congestion checking, do not handover based on cell overload\n" + "Congestion check interval in seconds (default " + OSMO_STRINGIFY_VAL(HO_CFG_CONGESTION_CHECK_DEFAULT) ")\n" + "Manually trigger a congestion check to run right now\n") +{ + if (!strcmp(argv[0], "now")) { + hodec2_congestion_check(gsmnet_from_vty(vty)); + return CMD_SUCCESS; + } + + hodec2_on_change_congestion_check_interval(gsmnet_from_vty(vty), + a2congestion_check_interval(argv[0])); + return CMD_SUCCESS; +} + +static void ho_vty_write(struct vty *vty, const char *indent, struct handover_cfg *ho) +{ +#define HO_CFG_ONE_MEMBER(TYPE, NAME, DEFAULT_VAL, \ + VTY_CMD_PREFIX, VTY_CMD, VTY_CMD_ARG, VTY_ARG_EVAL, \ + VTY_WRITE_FMT, VTY_WRITE_CONV, \ + VTY_DOC) \ + if (ho_isset_##NAME(ho)) \ + vty_out(vty, "%s" VTY_CMD_PREFIX VTY_CMD " " VTY_WRITE_FMT "%s", indent, \ + VTY_WRITE_CONV( ho_get_##NAME(ho) ), VTY_NEWLINE); + + HO_CFG_ALL_MEMBERS +#undef HO_CFG_ONE_MEMBER +} + +void ho_vty_write_bts(struct vty *vty, struct gsm_bts *bts) +{ + ho_vty_write(vty, " ", bts->ho); +} + +void ho_vty_write_net(struct vty *vty, struct gsm_network *net) +{ + ho_vty_write(vty, " ", net->ho); + + if (net->hodec2.congestion_check_interval_s != HO_CFG_CONGESTION_CHECK_DEFAULT) + vty_out(vty, " handover2 congestion-check %s%s", + congestion_check_interval2a(net->hodec2.congestion_check_interval_s), + VTY_NEWLINE); +} + +static void ho_vty_init_cmds(int parent_node) +{ +#define HO_CFG_ONE_MEMBER(TYPE, NAME, DEFAULT_VAL, VTY0, VTY1, VTY2, VTY3, VTY4, VTY5, VTY6) \ + install_element(parent_node, &cfg_ho_##NAME##_cmd); + + HO_CFG_ALL_MEMBERS +#undef HO_CFG_ONE_MEMBER + + /* Aliases of 'handover' for 'handover1' for backwards compat */ +#define HO_CFG_ONE_MEMBER(TYPE, NAME, DEFAULT_VAL, VTY0, VTY1, VTY2, VTY3, VTY4, VTY5, VTY6) \ + install_element(parent_node, &cfg_ho_##NAME##_cmd_alias); + +HODEC1_CFG_ALL_MEMBERS +#undef HO_CFG_ONE_MEMBER +} + +void ho_vty_init() +{ + ho_vty_init_cmds(GSMNET_NODE); + install_element(GSMNET_NODE, &cfg_net_ho_congestion_check_interval_cmd); + + ho_vty_init_cmds(BTS_NODE); +} + diff --git a/src/osmo-bsc/meas_feed.c b/src/osmo-bsc/meas_feed.c new file mode 100644 index 000000000..2e80754d4 --- /dev/null +++ b/src/osmo-bsc/meas_feed.c @@ -0,0 +1,185 @@ +/* UDP-Feed of measurement reports */ + +#include <unistd.h> + +#include <sys/socket.h> + +#include <osmocom/core/msgb.h> +#include <osmocom/core/socket.h> +#include <osmocom/core/write_queue.h> +#include <osmocom/core/talloc.h> +#include <osmocom/core/utils.h> + +#include <osmocom/vty/command.h> +#include <osmocom/vty/vty.h> + +#include <osmocom/bsc/meas_rep.h> +#include <osmocom/bsc/signal.h> +#include <osmocom/bsc/bsc_subscriber.h> +#include <osmocom/bsc/meas_feed.h> +#include <osmocom/bsc/vty.h> +#include <osmocom/bsc/debug.h> + +struct meas_feed_state { + struct osmo_wqueue wqueue; + char scenario[31+1]; + char *dst_host; + uint16_t dst_port; +}; + +static struct meas_feed_state g_mfs = {}; + +static int process_meas_rep(struct gsm_meas_rep *mr) +{ + struct msgb *msg; + struct meas_feed_meas *mfm; + struct bsc_subscr *bsub; + + /* ignore measurements as long as we don't know who it is */ + if (!mr->lchan) { + LOGP(DMEAS, LOGL_DEBUG, "meas_feed: no lchan, not sending report\n"); + return 0; + } + if (!mr->lchan->conn) { + LOGP(DMEAS, LOGL_DEBUG, "meas_feed: lchan without conn, not sending report\n"); + return 0; + } + + bsub = mr->lchan->conn->bsub; + + msg = msgb_alloc(sizeof(struct meas_feed_meas), "Meas. Feed"); + if (!msg) + return 0; + + /* fill in the header */ + mfm = (struct meas_feed_meas *) msgb_put(msg, sizeof(*mfm)); + mfm->hdr.msg_type = MEAS_FEED_MEAS; + mfm->hdr.version = MEAS_FEED_VERSION; + + /* fill in MEAS_FEED_MEAS specific header */ + if (bsub) + osmo_strlcpy(mfm->imsi, bsub->imsi, sizeof(mfm->imsi)); + /* This used to be a human readable meaningful name set in the old osmo-nitb's subscriber + * database. Now we're several layers away from that (and possibly don't even have a name in + * osmo-hlr either), hence this is a legacy item now that we should leave empty ... *but*: + * here in the BSC we often don't know the subscriber's full identity information. For example, + * we might only know the TMSI, and hence would pass an empty IMSI above. So after all, feed + * bsc_subscr_name(), which possibly will feed the IMSI again, but in case only the TMSI is known + * would add that to the information set as "TMSI:0x12345678". */ + osmo_strlcpy(mfm->name, bsc_subscr_name(bsub), sizeof(mfm->name)); + osmo_strlcpy(mfm->scenario, g_mfs.scenario, sizeof(mfm->scenario)); + + /* copy the entire measurement report */ + memcpy(&mfm->mr, mr, sizeof(mfm->mr)); + + /* copy channel information */ + /* we assume that the measurement report always belong to some timeslot */ + mfm->lchan_type = (uint8_t)mr->lchan->type; + mfm->pchan_type = (uint8_t)mr->lchan->ts->pchan; + mfm->bts_nr = mr->lchan->ts->trx->bts->nr; + mfm->trx_nr = mr->lchan->ts->trx->nr; + mfm->ts_nr = mr->lchan->ts->nr; + mfm->ss_nr = mr->lchan->nr; + + /* and send it to the socket */ + if (osmo_wqueue_enqueue(&g_mfs.wqueue, msg) != 0) { + LOGP(DMEAS, LOGL_ERROR, "meas_feed %s: sending measurement report failed\n", + gsm_lchan_name(mr->lchan)); + msgb_free(msg); + } else + LOGP(DMEAS, LOGL_DEBUG, "meas_feed %s: sent measurement report\n", + gsm_lchan_name(mr->lchan)); + + return 0; +} + +static int meas_feed_sig_cb(unsigned int subsys, unsigned int signal, + void *handler_data, void *signal_data) +{ + struct lchan_signal_data *sdata = signal_data; + + if (subsys != SS_LCHAN) + return 0; + + if (signal == S_LCHAN_MEAS_REP) + process_meas_rep(sdata->mr); + + return 0; +} + +static int feed_write_cb(struct osmo_fd *ofd, struct msgb *msg) +{ + return write(ofd->fd, msgb_data(msg), msgb_length(msg)); +} + +static int feed_read_cb(struct osmo_fd *ofd) +{ + int rc; + char buf[256]; + + rc = read(ofd->fd, buf, sizeof(buf)); + ofd->fd &= ~BSC_FD_READ; + + return rc; +} + +int meas_feed_cfg_set(const char *dst_host, uint16_t dst_port) +{ + int rc; + int already_initialized = 0; + + if (g_mfs.wqueue.bfd.fd) + already_initialized = 1; + + + if (already_initialized && + !strcmp(dst_host, g_mfs.dst_host) && + dst_port == g_mfs.dst_port) + return 0; + + if (!already_initialized) { + osmo_wqueue_init(&g_mfs.wqueue, 10); + g_mfs.wqueue.write_cb = feed_write_cb; + g_mfs.wqueue.read_cb = feed_read_cb; + osmo_signal_register_handler(SS_LCHAN, meas_feed_sig_cb, NULL); + LOGP(DMEAS, LOGL_DEBUG, "meas_feed: registered signal callback\n"); + } + + if (already_initialized) { + osmo_wqueue_clear(&g_mfs.wqueue); + osmo_fd_unregister(&g_mfs.wqueue.bfd); + close(g_mfs.wqueue.bfd.fd); + /* don't set to zero, as that would mean 'not yet initialized' */ + g_mfs.wqueue.bfd.fd = -1; + } + rc = osmo_sock_init_ofd(&g_mfs.wqueue.bfd, AF_UNSPEC, SOCK_DGRAM, + IPPROTO_UDP, dst_host, dst_port, + OSMO_SOCK_F_CONNECT); + if (rc < 0) + return rc; + + g_mfs.wqueue.bfd.when &= ~BSC_FD_READ; + + if (g_mfs.dst_host) + talloc_free(g_mfs.dst_host); + g_mfs.dst_host = talloc_strdup(NULL, dst_host); + g_mfs.dst_port = dst_port; + + return 0; +} + +void meas_feed_cfg_get(char **host, uint16_t *port) +{ + *port = g_mfs.dst_port; + *host = g_mfs.dst_host; +} + +void meas_feed_scenario_set(const char *name) +{ + osmo_strlcpy(g_mfs.scenario, name, sizeof(g_mfs.scenario)); +} + +const char *meas_feed_scenario_get(void) +{ + return g_mfs.scenario; +} diff --git a/src/osmo-bsc/meas_rep.c b/src/osmo-bsc/meas_rep.c new file mode 100644 index 000000000..73d9a1f21 --- /dev/null +++ b/src/osmo-bsc/meas_rep.c @@ -0,0 +1,134 @@ +/* Measurement Report Processing */ + +/* (C) 2009 by Harald Welte <laforge@gnumonks.org> + * + * 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 <errno.h> + +#include <osmocom/bsc/gsm_data.h> +#include <osmocom/bsc/meas_rep.h> + +static int get_field(const struct gsm_meas_rep *rep, + enum meas_rep_field field) +{ + switch (field) { + case MEAS_REP_DL_RXLEV_FULL: + if (!(rep->flags & MEAS_REP_F_DL_VALID)) + return -EINVAL; + return rep->dl.full.rx_lev; + case MEAS_REP_DL_RXLEV_SUB: + if (!(rep->flags & MEAS_REP_F_DL_VALID)) + return -EINVAL; + return rep->dl.sub.rx_lev; + case MEAS_REP_DL_RXQUAL_FULL: + if (!(rep->flags & MEAS_REP_F_DL_VALID)) + return -EINVAL; + return rep->dl.full.rx_qual; + case MEAS_REP_DL_RXQUAL_SUB: + if (!(rep->flags & MEAS_REP_F_DL_VALID)) + return -EINVAL; + return rep->dl.sub.rx_qual; + case MEAS_REP_UL_RXLEV_FULL: + return rep->ul.full.rx_lev; + case MEAS_REP_UL_RXLEV_SUB: + return rep->ul.sub.rx_lev; + case MEAS_REP_UL_RXQUAL_FULL: + return rep->ul.full.rx_qual; + case MEAS_REP_UL_RXQUAL_SUB: + return rep->ul.sub.rx_qual; + } + + return 0; +} + + +unsigned int calc_initial_idx(unsigned int array_size, + unsigned int meas_rep_idx, + unsigned int num_values) +{ + int offs, idx; + + /* from which element do we need to start if we're interested + * in an average of 'num' elements */ + offs = meas_rep_idx - num_values; + + if (offs < 0) + idx = array_size + offs; + else + idx = offs; + + return idx; +} + +/* obtain an average over the last 'num' fields in the meas reps */ +int get_meas_rep_avg(const struct gsm_lchan *lchan, + enum meas_rep_field field, unsigned int num) +{ + unsigned int i, idx; + int avg = 0, valid_num = 0; + + if (num < 1) + return -EINVAL; + + if (num > lchan->meas_rep_count) + return -EINVAL; + + idx = calc_initial_idx(ARRAY_SIZE(lchan->meas_rep), + lchan->meas_rep_idx, num); + + for (i = 0; i < num; i++) { + int j = (idx+i) % ARRAY_SIZE(lchan->meas_rep); + int val = get_field(&lchan->meas_rep[j], field); + + if (val >= 0) { + avg += val; + valid_num++; + } + } + + if (valid_num == 0) + return -EINVAL; + + return avg / valid_num; +} + +/* Check if N out of M last values for FIELD are >= bd */ +int meas_rep_n_out_of_m_be(const struct gsm_lchan *lchan, + enum meas_rep_field field, + unsigned int n, unsigned int m, int be) +{ + unsigned int i, idx; + int count = 0; + + idx = calc_initial_idx(ARRAY_SIZE(lchan->meas_rep), + lchan->meas_rep_idx, m); + + for (i = 0; i < m; i++) { + int j = (idx + i) % ARRAY_SIZE(lchan->meas_rep); + int val = get_field(&lchan->meas_rep[j], field); + + if (val >= be) /* implies that val < 0 will not count */ + count++; + + if (count >= n) + return 1; + } + + return 0; +} diff --git a/src/osmo-bsc/net_init.c b/src/osmo-bsc/net_init.c new file mode 100644 index 000000000..db84e2a53 --- /dev/null +++ b/src/osmo-bsc/net_init.c @@ -0,0 +1,69 @@ +/* (C) 2008-2010 by Harald Welte <laforge@gnumonks.org> + * + * 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 <osmocom/bsc/osmo_bsc.h> +#include <osmocom/bsc/bsc_msc_data.h> +#include <osmocom/bsc/gsm_04_08_utils.h> +#include <osmocom/bsc/handover_cfg.h> +#include <osmocom/bsc/chan_alloc.h> + +/* Initialize the bare minimum of struct gsm_network, minimizing required dependencies. + * This part is shared among the thin programs in osmo-bsc/src/utils/. + * osmo-bsc requires further initialization that pulls in more dependencies (see bsc_network_init()). */ +struct gsm_network *gsm_network_init(void *ctx) +{ + struct gsm_network *net = talloc_zero(ctx, struct gsm_network); + if (!net) + return NULL; + + net->plmn = (struct osmo_plmn_id){ + .mcc = 1, + .mnc = 1, + }; + + net->dyn_ts_allow_tch_f = true; + + /* Permit a compile-time default of A5/3 and A5/1 */ + net->a5_encryption_mask = (1 << 3) | (1 << 1); + + /* Use 30 min periodic update interval as sane default */ + net->t3212 = 5; + + INIT_LLIST_HEAD(&net->subscr_conns); + + net->bsc_subscribers = talloc_zero(net, struct llist_head); + INIT_LLIST_HEAD(net->bsc_subscribers); + + INIT_LLIST_HEAD(&net->bts_list); + net->num_bts = 0; + net->T3101 = GSM_T3101_DEFAULT; + net->T3103 = GSM_T3103_DEFAULT; + net->T3105 = GSM_T3105_DEFAULT; + net->T3107 = GSM_T3107_DEFAULT; + net->T3109 = GSM_T3109_DEFAULT; + net->T3111 = GSM_T3111_DEFAULT; + net->T3113 = GSM_T3113_DEFAULT; + net->T3115 = GSM_T3115_DEFAULT; + net->T3117 = GSM_T3117_DEFAULT; + net->T3119 = GSM_T3119_DEFAULT; + net->T3122 = GSM_T3122_DEFAULT; + net->T3141 = GSM_T3141_DEFAULT; + + return net; +} diff --git a/src/osmo-bsc/osmo_bsc_lcls.c b/src/osmo-bsc/osmo_bsc_lcls.c new file mode 100644 index 000000000..c2b076090 --- /dev/null +++ b/src/osmo-bsc/osmo_bsc_lcls.c @@ -0,0 +1,766 @@ +/* (C) 2018 by Harald Welte <laforge@gnumonks.org> + * 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 <osmocom/core/utils.h> +#include <osmocom/core/logging.h> +#include <osmocom/core/fsm.h> +#include <osmocom/core/linuxlist.h> +#include <osmocom/gsm/gsm0808.h> +#include <osmocom/core/msgb.h> +#include <osmocom/bsc/bsc_msc_data.h> +#include <osmocom/bsc/debug.h> +#include <osmocom/bsc/osmo_bsc.h> +#include <osmocom/bsc/bsc_subscr_conn_fsm.h> +#include <osmocom/bsc/gsm_data.h> +#include <osmocom/bsc/osmo_bsc_lcls.h> +#include <osmocom/mgcp_client/mgcp_client_fsm.h> + +struct value_string lcls_event_names[] = { + { LCLS_EV_UPDATE_CFG_CSC, "UPDATE_CFG_CSC" }, + { LCLS_EV_APPLY_CFG_CSC, "APPLY_CFG_CSC" }, + { LCLS_EV_CORRELATED, "CORRELATED" }, + { LCLS_EV_OTHER_ENABLED, "OTHER_ENABLED" }, + { LCLS_EV_OTHER_BREAK, "OTHER_BREAK" }, + { LCLS_EV_OTHER_DEAD, "OTHER_DEAD" }, + { 0, NULL } +}; + + +/*********************************************************************** + * Utility functions + ***********************************************************************/ + +enum gsm0808_lcls_status lcls_get_status(struct gsm_subscriber_connection *conn) +{ + if (!conn->lcls.fi) + return 0xff; + + switch (conn->lcls.fi->state) { + case ST_NO_LCLS: + return 0xff; + case ST_NOT_YET_LS: + return GSM0808_LCLS_STS_NOT_YET_LS; + case ST_NOT_POSSIBLE_LS: + return GSM0808_LCLS_STS_NOT_POSSIBLE_LS; + case ST_NO_LONGER_LS: + return GSM0808_LCLS_STS_NO_LONGER_LS; + case ST_REQ_LCLS_NOT_SUPP: + return GSM0808_LCLS_STS_REQ_LCLS_NOT_SUPP; + case ST_LOCALLY_SWITCHED: + case ST_LOCALLY_SWITCHED_WAIT_BREAK: + case ST_LOCALLY_SWITCHED_WAIT_OTHER_BREAK: + return GSM0808_LCLS_STS_LOCALLY_SWITCHED; + } + OSMO_ASSERT(0); +} + +static void lcls_send_notify(struct gsm_subscriber_connection *conn) +{ + enum gsm0808_lcls_status status = lcls_get_status(conn); + struct msgb *msg; + + if (status == 0xff) + return; + + LOGPFSM(conn->lcls.fi, "Sending BSSMAP LCLS NOTIFICATION (%s)\n", + gsm0808_lcls_status_name(status)); + msg = gsm0808_create_lcls_notification(status, false); + osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_TX_SCCP, msg); +} + +static struct gsm_subscriber_connection * +find_conn_with_same_gcr(struct gsm_subscriber_connection *conn_local) +{ + struct gsm_network *net = conn_local->network; + struct gsm_subscriber_connection *conn_other; + + llist_for_each_entry(conn_other, &net->subscr_conns, entry) { + /* don't report back the same connection */ + if (conn_other == conn_local) + continue; + /* don't consider any conn where GCR length is not the same as before */ + if (conn_other->lcls.global_call_ref_len != conn_local->lcls.global_call_ref_len) + continue; + if (!memcmp(conn_other->lcls.global_call_ref, conn_local->lcls.global_call_ref, + conn_local->lcls.global_call_ref_len)) + return conn_other; + } + return NULL; +} + +static bool lcls_is_supported_config(enum gsm0808_lcls_config cfg) +{ + /* this is the only configuration that we support for now */ + if (cfg == GSM0808_LCLS_CFG_BOTH_WAY) + return true; + else + return false; +} + +/* LCLS Call Leg Correlation as per 23.284 4.3 / 48.008 3.1.33.2.1 */ +static int lcls_perform_correlation(struct gsm_subscriber_connection *conn_local) +{ + struct gsm_subscriber_connection *conn_other; + + /* We can only correlate if a GCR is present */ + OSMO_ASSERT(conn_local->lcls.global_call_ref_len); + /* We can only correlate if we're not in active LS */ + OSMO_ASSERT(conn_local->lcls.fi->state != ST_LOCALLY_SWITCHED && + conn_local->lcls.fi->state != ST_LOCALLY_SWITCHED_WAIT_BREAK && + conn_local->lcls.fi->state != ST_LOCALLY_SWITCHED_WAIT_OTHER_BREAK); + + conn_other = conn_local->lcls.other; + if (conn_other) { + LOGPFSM(conn_local->lcls.fi, "Breaking previous correlation with %s\n", + osmo_fsm_inst_name(conn_other->lcls.fi)); + OSMO_ASSERT(conn_other->lcls.fi->state != ST_LOCALLY_SWITCHED && + conn_other->lcls.fi->state != ST_LOCALLY_SWITCHED_WAIT_BREAK && + conn_other->lcls.fi->state != ST_LOCALLY_SWITCHED_WAIT_OTHER_BREAK); + conn_local->lcls.other->lcls.other = NULL; + conn_local->lcls.other = NULL; + } + + conn_other = find_conn_with_same_gcr(conn_local); + if (!conn_other) { + /* we found no other call with same GCR: not possible */ + LOGPFSM(conn_local->lcls.fi, "Unsuccessful correlation\n"); + return -ENODEV; + } + + /* store pointer to "other" in "local" */ + conn_local->lcls.other = conn_other; + + LOGPFSM(conn_local->lcls.fi, "Successfully correlated with %s\n", + osmo_fsm_inst_name(conn_other->lcls.fi)); + + /* notify other conn about our correlation */ + osmo_fsm_inst_dispatch(conn_other->lcls.fi, LCLS_EV_CORRELATED, conn_local); + + return 0; +} + + +struct lcls_cfg_csc { + enum gsm0808_lcls_config config; + enum gsm0808_lcls_control control; +}; + +/* Update the connections LCLS configuration and return old/previous configuration. + * \returns (staticallly allocated) old configuration; NULL if new config not supported */ +static struct lcls_cfg_csc *update_lcls_cfg_csc(struct gsm_subscriber_connection *conn, + struct lcls_cfg_csc *new_cfg_csc) +{ + static struct lcls_cfg_csc old_cfg_csc; + old_cfg_csc.config = conn->lcls.config; + old_cfg_csc.control = conn->lcls.control; + + if (new_cfg_csc->config != 0xff) { + if (!lcls_is_supported_config(new_cfg_csc->config)) + return NULL; + if (conn->lcls.config != new_cfg_csc->config) { + /* TODO: logging */ + conn->lcls.config = new_cfg_csc->config; + } + } + if (new_cfg_csc->control != 0xff) { + if (conn->lcls.control != new_cfg_csc->control) { + /* TODO: logging */ + conn->lcls.control = new_cfg_csc->control; + } + } + + return &old_cfg_csc; +} + +/* Attempt to update conn->lcls with the new config/csc provided. If new config is + * unsupported, change into LCLS NOT SUPPORTED state and return -EINVAL. */ +static int lcls_handle_cfg_update(struct gsm_subscriber_connection *conn, void *data) +{ + struct lcls_cfg_csc *new_cfg_csc, *old_cfg_csc; + + new_cfg_csc = (struct lcls_cfg_csc *) data; + old_cfg_csc = update_lcls_cfg_csc(conn, new_cfg_csc); + if (!old_cfg_csc) { + osmo_fsm_inst_state_chg(conn->lcls.fi, ST_REQ_LCLS_NOT_SUPP, 0, 0); + return -EINVAL; + } + return 0; +} + +/* notify the LCLS FSM about new LCLS Config and/or CSC */ +void lcls_update_config(struct gsm_subscriber_connection *conn, + const uint8_t *config, const uint8_t *control) +{ + struct lcls_cfg_csc new_cfg = { + .config = 0xff, + .control = 0xff, + }; + /* nothing to update, skip it */ + if (!config && !control) + return; + if (config) + new_cfg.config = *config; + if (control) + new_cfg.control = *control; + osmo_fsm_inst_dispatch(conn->lcls.fi, LCLS_EV_UPDATE_CFG_CSC, &new_cfg); +} + +/* apply the configuration, may be changed before by lcls_update_config */ +void lcls_apply_config(struct gsm_subscriber_connection *conn) +{ + osmo_fsm_inst_dispatch(conn->lcls.fi, LCLS_EV_APPLY_CFG_CSC, NULL); +} + +static void lcls_break_local_switching(struct gsm_subscriber_connection *conn) +{ + struct mgcp_conn_peer peer; + struct sockaddr_in *sin; + + LOGPFSM(conn->lcls.fi, "=== HERE IS WHERE WE DISABLE LCLS\n"); + if (!conn->user_plane.fi_msc) { + /* the MGCP FSM has died, e.g. due to some MGCP/SDP parsing error */ + LOGPFSML(conn->lcls.fi, LOGL_NOTICE, "Cannot disable LCLS without MSC-side MGCP FSM\n"); + return; + } + + sin = (struct sockaddr_in *)&conn->user_plane.aoip_rtp_addr_remote; + OSMO_ASSERT(sin->sin_family == AF_INET); + + memset(&peer, 0, sizeof(peer)); + peer.port = htons(sin->sin_port); + osmo_strlcpy(peer.addr, inet_ntoa(sin->sin_addr), sizeof(peer.addr)); + mgcp_conn_modify(conn->user_plane.fi_msc, 0, &peer); +} + +static bool lcls_enable_possible(struct gsm_subscriber_connection *conn) +{ + struct gsm_subscriber_connection *other_conn = conn->lcls.other; + OSMO_ASSERT(other_conn); + + if (!lcls_is_supported_config(conn->lcls.config)) { + LOGPFSM(conn->lcls.fi, "Not enabling LS due to unsupported local config\n"); + return false; + } + + if (!lcls_is_supported_config(other_conn->lcls.config)) { + LOGPFSM(conn->lcls.fi, "Not enabling LS due to unsupported other config\n"); + return false; + } + + if (conn->lcls.control != GSM0808_LCLS_CSC_CONNECT) { + LOGPFSM(conn->lcls.fi, "Not enabling LS due to insufficient local control\n"); + return false; + } + + if (other_conn->lcls.control != GSM0808_LCLS_CSC_CONNECT) { + LOGPFSM(conn->lcls.fi, "Not enabling LS due to insufficient other control\n"); + return false; + } + + return true; +} + +/*********************************************************************** + * State callback functions + ***********************************************************************/ + +static void lcls_no_lcls_fn(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_subscriber_connection *conn = fi->priv; + + /* we're just starting and cannot yet have a correlated call */ + OSMO_ASSERT(conn->lcls.other == NULL); + + if (conn->sccp.msc->lcls_mode == BSC_LCLS_MODE_DISABLED) { + LOGPFSML(fi, LOGL_DEBUG, "LCLS disabled for this MSC, ignoring %s\n", + osmo_fsm_event_name(fi->fsm, event)); + return; + } + + /* If there's no GCR set, we can never leave this state */ + if (conn->lcls.global_call_ref_len == 0) { + LOGPFSML(fi, LOGL_NOTICE, "No GCR set, ignoring %s\n", + osmo_fsm_event_name(fi->fsm, event)); + return; + } + + switch (event) { + case LCLS_EV_UPDATE_CFG_CSC: + if (lcls_handle_cfg_update(conn, data) != 0) + return; + return; + case LCLS_EV_APPLY_CFG_CSC: + if (conn->lcls.config == 0xff) + return; + if (lcls_perform_correlation(conn) != 0) { + /* Correlation leads to no result: Not Possible to LS */ + osmo_fsm_inst_state_chg(fi, ST_NOT_POSSIBLE_LS, 0, 0); + return; + } + /* we now have two correlated calls */ + OSMO_ASSERT(conn->lcls.other); + if (lcls_enable_possible(conn)) { + /* Local Switching now active */ + osmo_fsm_inst_state_chg(fi, ST_LOCALLY_SWITCHED, 0, 0); + osmo_fsm_inst_dispatch(conn->lcls.other->lcls.fi, LCLS_EV_OTHER_ENABLED, conn); + } else { + /* Couldn't be enabled: Not yet LS */ + osmo_fsm_inst_state_chg(fi, ST_NOT_YET_LS, 0, 0); + } + break; + default: + OSMO_ASSERT(0); + break; + } +} + +static void lcls_not_yet_ls_fn(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_subscriber_connection *conn = fi->priv; + + /* not yet locally switched means that we have correlation but no instruction + * to actually connect them yet */ + OSMO_ASSERT(conn->lcls.other); + + switch (event) { + case LCLS_EV_UPDATE_CFG_CSC: + if (lcls_handle_cfg_update(conn, data) != 0) + return; + return; + case LCLS_EV_APPLY_CFG_CSC: + if (lcls_enable_possible(conn)) { + osmo_fsm_inst_state_chg(fi, ST_LOCALLY_SWITCHED, 0, 0); + osmo_fsm_inst_dispatch(conn->lcls.other->lcls.fi, LCLS_EV_OTHER_ENABLED, conn); + } + break; + case LCLS_EV_OTHER_ENABLED: + OSMO_ASSERT(conn->lcls.other == data); + if (lcls_enable_possible(conn)) { + osmo_fsm_inst_state_chg(fi, ST_LOCALLY_SWITCHED, 0, 0); + /* Send LCLS-NOTIFY to inform MSC */ + lcls_send_notify(conn); + } else { + /* we couldn't enable our side, so ask other side to break */ + osmo_fsm_inst_dispatch(conn->lcls.other->lcls.fi, LCLS_EV_OTHER_BREAK, conn); + } + break; + case LCLS_EV_CORRELATED: + /* other call informs us that he correlated with us */ + conn->lcls.other = data; + break; + case LCLS_EV_OTHER_DEAD: + OSMO_ASSERT(conn->lcls.other == data); + conn->lcls.other = NULL; + osmo_fsm_inst_state_chg(fi, ST_NOT_POSSIBLE_LS, 0, 0); + /* Send LCLS-NOTIFY to inform MSC */ + lcls_send_notify(conn); + break; + default: + OSMO_ASSERT(0); + break; + } +} + +static void lcls_not_possible_ls_fn(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_subscriber_connection *conn = fi->priv; + + OSMO_ASSERT(conn->lcls.other == NULL); + + switch (event) { + case LCLS_EV_UPDATE_CFG_CSC: + if (lcls_handle_cfg_update(conn, data) != 0) + return; + return; + case LCLS_EV_APPLY_CFG_CSC: + if (lcls_perform_correlation(conn) != 0) { + /* no correlation result: Remain in NOT_POSSIBLE_LS */ + return; + } + /* we now have two correlated calls */ + OSMO_ASSERT(conn->lcls.other); + if (lcls_enable_possible(conn)) { + osmo_fsm_inst_state_chg(fi, ST_LOCALLY_SWITCHED, 0, 0); + osmo_fsm_inst_dispatch(conn->lcls.other->lcls.fi, LCLS_EV_OTHER_ENABLED, conn); + } else { + osmo_fsm_inst_state_chg(fi, ST_NOT_YET_LS, 0, 0); + } + break; + case LCLS_EV_CORRELATED: + /* other call informs us that he correlated with us */ + conn->lcls.other = data; + osmo_fsm_inst_state_chg(fi, ST_NOT_YET_LS, 0, 0); + /* Send NOTIFY about the fact that correlation happened */ + lcls_send_notify(conn); + break; + case LCLS_EV_OTHER_DEAD: + OSMO_ASSERT(conn->lcls.other == data); + conn->lcls.other = NULL; + break; + default: + OSMO_ASSERT(0); + break; + } +} + +static void lcls_no_longer_ls_fn(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_subscriber_connection *conn = fi->priv; + + OSMO_ASSERT(conn->lcls.other); + + switch (event) { + case LCLS_EV_UPDATE_CFG_CSC: + if (lcls_handle_cfg_update(conn, data) != 0) + return; + if (lcls_enable_possible(conn)) { + osmo_fsm_inst_state_chg(fi, ST_LOCALLY_SWITCHED, 0, 0); + osmo_fsm_inst_dispatch(conn->lcls.other->lcls.fi, LCLS_EV_OTHER_ENABLED, conn); + } + break; + case LCLS_EV_OTHER_ENABLED: + OSMO_ASSERT(conn->lcls.other == data); + if (lcls_enable_possible(conn)) { + osmo_fsm_inst_state_chg(fi, ST_LOCALLY_SWITCHED, 0, 0); + /* Send LCLS-NOTIFY to inform MSC */ + lcls_send_notify(conn); + } else { + /* we couldn't enable our side, so ask other side to break */ + osmo_fsm_inst_dispatch(conn->lcls.other->lcls.fi, LCLS_EV_OTHER_BREAK, conn); + } + break; + case LCLS_EV_CORRELATED: + /* other call informs us that he correlated with us */ + conn->lcls.other = data; + break; + case LCLS_EV_OTHER_DEAD: + OSMO_ASSERT(conn->lcls.other == data); + conn->lcls.other = NULL; + osmo_fsm_inst_state_chg(fi, ST_NOT_POSSIBLE_LS, 0, 0); + /* Send LCLS-NOTIFY to inform MSC */ + lcls_send_notify(conn); + break; + default: + OSMO_ASSERT(0); + break; + } +} + +static void lcls_req_lcls_not_supp_fn(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_subscriber_connection *conn = fi->priv; + + /* we could have a correlated other call or not */ + + switch (event) { + case LCLS_EV_UPDATE_CFG_CSC: + if (lcls_handle_cfg_update(conn, data) != 0) + return; + //FIXME osmo_fsm_inst_state_chg(fi, + return; + case LCLS_EV_APPLY_CFG_CSC: + if (lcls_perform_correlation(conn) != 0) { + osmo_fsm_inst_state_chg(fi, ST_NOT_POSSIBLE_LS, 0, 0); + return; + } + /* we now have two correlated calls */ + OSMO_ASSERT(conn->lcls.other); + if (!lcls_is_supported_config(conn->lcls.config)) + return; + if (lcls_enable_possible(conn)) + osmo_fsm_inst_state_chg(fi, ST_LOCALLY_SWITCHED, 0, 0); + else + osmo_fsm_inst_state_chg(fi, ST_NOT_YET_LS, 0, 0); + break; + case LCLS_EV_CORRELATED: + /* other call informs us that he correlated with us */ + conn->lcls.other = data; + break; + case LCLS_EV_OTHER_DEAD: + OSMO_ASSERT(conn->lcls.other == data); + conn->lcls.other = NULL; + break; + default: + OSMO_ASSERT(0); + break; + } + +} + +static void lcls_locally_switched_fn(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_subscriber_connection *conn = fi->priv; + + OSMO_ASSERT(conn->lcls.other); + + switch (event) { + case LCLS_EV_UPDATE_CFG_CSC: + if (lcls_handle_cfg_update(conn, data) != 0) { + lcls_break_local_switching(conn); + return; + } + break; + case LCLS_EV_APPLY_CFG_CSC: + if (conn->lcls.control == GSM0808_LCLS_CSC_RELEASE_LCLS) { + osmo_fsm_inst_state_chg(fi, ST_LOCALLY_SWITCHED_WAIT_OTHER_BREAK, 0, 0); + osmo_fsm_inst_dispatch(conn->lcls.other->lcls.fi, LCLS_EV_OTHER_BREAK, conn); + /* FIXME: what if there's a new config included? */ + return; + } + /* TODO: Handle any changes of "config" once we support bi-casting etc. */ + break; + case LCLS_EV_OTHER_BREAK: + OSMO_ASSERT(conn->lcls.other == data); + osmo_fsm_inst_state_chg(fi, ST_LOCALLY_SWITCHED_WAIT_BREAK, 0, 0); + break; + case LCLS_EV_OTHER_DEAD: + OSMO_ASSERT(conn->lcls.other == data); + conn->lcls.other = NULL; + osmo_fsm_inst_state_chg(fi, ST_NOT_POSSIBLE_LS, 0, 0); + /* Send LCLS-NOTIFY to inform MSC */ + lcls_send_notify(conn); + break; + default: + OSMO_ASSERT(0); + break; + } +} + + +static void lcls_locally_switched_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct gsm_subscriber_connection *conn = fi->priv; + struct gsm_subscriber_connection *conn_other = conn->lcls.other; + struct mgcp_conn_peer peer; + struct sockaddr_in *sin; + + OSMO_ASSERT(conn_other); + + LOGPFSM(fi, "=== HERE IS WHERE WE ENABLE LCLS\n"); + if (!conn->user_plane.fi_msc) { + LOGPFSML(fi, LOGL_ERROR, "Cannot enable LCLS without MSC-side MGCP FSM. FIXME\n"); + return; + } + + sin = (struct sockaddr_in *)&conn_other->user_plane.aoip_rtp_addr_local; + OSMO_ASSERT(sin->sin_family == AF_INET); + + memset(&peer, 0, sizeof(peer)); + peer.port = htons(sin->sin_port); + osmo_strlcpy(peer.addr, inet_ntoa(sin->sin_addr), sizeof(peer.addr)); + mgcp_conn_modify(conn->user_plane.fi_msc, 0, &peer); + +} + +static void lcls_locally_switched_wait_break_fn(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_subscriber_connection *conn = fi->priv; + + OSMO_ASSERT(conn->lcls.other); + + switch (event) { + case LCLS_EV_UPDATE_CFG_CSC: + if (lcls_handle_cfg_update(conn, data) != 0) { + lcls_break_local_switching(conn); + return; + } + break; + case LCLS_EV_APPLY_CFG_CSC: + if (conn->lcls.control == GSM0808_LCLS_CSC_RELEASE_LCLS) { + lcls_break_local_switching(conn); + osmo_fsm_inst_state_chg(fi, ST_NO_LONGER_LS, 0, 0); + osmo_fsm_inst_dispatch(conn->lcls.other->lcls.fi, LCLS_EV_OTHER_BREAK, conn); + /* no NOTIFY here, as the caller will be returning status in LCLS-CTRL-ACK */ + /* FIXME: what if there's a new config included? */ + return; + } + /* TODO: Handle any changes of "config" once we support bi-casting etc. */ + break; + case LCLS_EV_OTHER_BREAK: + /* we simply ignore it, must be a re-transmission */ + break; + case LCLS_EV_OTHER_DEAD: + OSMO_ASSERT(conn->lcls.other == data); + conn->lcls.other = NULL; + break; + default: + lcls_locally_switched_fn(fi, event, data); + break; + } +} + +static void lcls_locally_switched_wait_other_break_fn(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_subscriber_connection *conn = fi->priv; + + OSMO_ASSERT(conn->lcls.other); + + switch (event) { + case LCLS_EV_UPDATE_CFG_CSC: + if (lcls_handle_cfg_update(conn, data) != 0) { + lcls_break_local_switching(conn); + return; + } + /* TODO: Handle any changes of "config" once we support bi-casting etc. */ + break; + case LCLS_EV_OTHER_BREAK: + case LCLS_EV_OTHER_DEAD: + OSMO_ASSERT(conn->lcls.other == data); + lcls_break_local_switching(conn); + osmo_fsm_inst_state_chg(fi, ST_NO_LONGER_LS, 0, 0); + /* Send LCLS-NOTIFY to inform MSC */ + lcls_send_notify(conn); + break; + default: + lcls_locally_switched_fn(fi, event, data); + break; + } +} + +static void lcls_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause) +{ + struct gsm_subscriber_connection *conn = fi->priv; + + if (conn->lcls.other) { + /* inform the "other" side that we're dead, so it can disabe LS and send NOTIFY */ + if (conn->lcls.other->fi) + osmo_fsm_inst_dispatch(conn->lcls.other->lcls.fi, LCLS_EV_OTHER_DEAD, conn); + conn->lcls.other = NULL; + } +} + + +/*********************************************************************** + * FSM Definition + ***********************************************************************/ + +#define S(x) (1 << (x)) + +static const struct osmo_fsm_state lcls_fsm_states[] = { + [ST_NO_LCLS] = { + .in_event_mask = S(LCLS_EV_UPDATE_CFG_CSC) | + S(LCLS_EV_APPLY_CFG_CSC), + .out_state_mask = S(ST_NO_LCLS) | + S(ST_NOT_YET_LS) | + S(ST_NOT_POSSIBLE_LS) | + S(ST_REQ_LCLS_NOT_SUPP) | + S(ST_LOCALLY_SWITCHED), + .name = "NO_LCLS", + .action = lcls_no_lcls_fn, + }, + [ST_NOT_YET_LS] = { + .in_event_mask = S(LCLS_EV_UPDATE_CFG_CSC) | + S(LCLS_EV_APPLY_CFG_CSC) | + S(LCLS_EV_CORRELATED) | + S(LCLS_EV_OTHER_ENABLED) | + S(LCLS_EV_OTHER_DEAD), + .out_state_mask = S(ST_NOT_YET_LS) | + S(ST_REQ_LCLS_NOT_SUPP) | + S(ST_LOCALLY_SWITCHED), + .name = "NOT_YET_LS", + .action = lcls_not_yet_ls_fn, + }, + [ST_NOT_POSSIBLE_LS] = { + .in_event_mask = S(LCLS_EV_UPDATE_CFG_CSC) | + S(LCLS_EV_APPLY_CFG_CSC) | + S(LCLS_EV_CORRELATED), + .out_state_mask = S(ST_NOT_YET_LS) | + S(ST_NOT_POSSIBLE_LS) | + S(ST_REQ_LCLS_NOT_SUPP) | + S(ST_LOCALLY_SWITCHED), + .name = "NOT_POSSIBLE_LS", + .action = lcls_not_possible_ls_fn, + }, + [ST_NO_LONGER_LS] = { + .in_event_mask = S(LCLS_EV_UPDATE_CFG_CSC) | + S(LCLS_EV_APPLY_CFG_CSC) | + S(LCLS_EV_CORRELATED) | + S(LCLS_EV_OTHER_ENABLED) | + S(LCLS_EV_OTHER_DEAD), + .out_state_mask = S(ST_NO_LONGER_LS) | + S(ST_REQ_LCLS_NOT_SUPP) | + S(ST_LOCALLY_SWITCHED), + .name = "NO_LONGER_LS", + .action = lcls_no_longer_ls_fn, + }, + [ST_REQ_LCLS_NOT_SUPP] = { + .in_event_mask = S(LCLS_EV_UPDATE_CFG_CSC) | + S(LCLS_EV_APPLY_CFG_CSC) | + S(LCLS_EV_CORRELATED) | + S(LCLS_EV_OTHER_DEAD), + .out_state_mask = S(ST_NOT_YET_LS) | + S(ST_REQ_LCLS_NOT_SUPP) | + S(ST_LOCALLY_SWITCHED), + .name = "REQ_LCLS_NOT_SUPP", + .action = lcls_req_lcls_not_supp_fn, + }, + [ST_LOCALLY_SWITCHED] = { + .in_event_mask = S(LCLS_EV_UPDATE_CFG_CSC) | + S(LCLS_EV_APPLY_CFG_CSC) | + S(LCLS_EV_OTHER_BREAK) | + S(LCLS_EV_OTHER_DEAD), + .out_state_mask = S(ST_NO_LONGER_LS) | + S(ST_NOT_POSSIBLE_LS) | + S(ST_REQ_LCLS_NOT_SUPP) | + S(ST_LOCALLY_SWITCHED_WAIT_BREAK) | + S(ST_LOCALLY_SWITCHED_WAIT_OTHER_BREAK) | + S(ST_LOCALLY_SWITCHED), + .name = "LOCALLY_SWITCHED", + .action = lcls_locally_switched_fn, + .onenter = lcls_locally_switched_onenter, + }, + /* received an "other" break, waiting for the local break */ + [ST_LOCALLY_SWITCHED_WAIT_BREAK] = { + .in_event_mask = S(LCLS_EV_UPDATE_CFG_CSC) | + S(LCLS_EV_APPLY_CFG_CSC) | + S(LCLS_EV_OTHER_BREAK) | + S(LCLS_EV_OTHER_DEAD), + .out_state_mask = S(ST_NO_LONGER_LS) | + S(ST_REQ_LCLS_NOT_SUPP) | + S(ST_LOCALLY_SWITCHED) | + S(ST_LOCALLY_SWITCHED_WAIT_BREAK), + .name = "LOCALLY_SWITCHED_WAIT_BREAK", + .action = lcls_locally_switched_wait_break_fn, + }, + /* received a local break, waiting for the "other" break */ + [ST_LOCALLY_SWITCHED_WAIT_OTHER_BREAK] = { + .in_event_mask = S(LCLS_EV_UPDATE_CFG_CSC) | + S(LCLS_EV_OTHER_BREAK) | + S(LCLS_EV_OTHER_DEAD), + .out_state_mask = S(ST_NO_LONGER_LS) | + S(ST_REQ_LCLS_NOT_SUPP) | + S(ST_LOCALLY_SWITCHED) | + S(ST_LOCALLY_SWITCHED_WAIT_OTHER_BREAK), + .name = "LOCALLY_SWITCHED_WAIT_OTHER_BREAK", + .action = lcls_locally_switched_wait_other_break_fn, + }, + + +}; + +struct osmo_fsm lcls_fsm = { + .name = "LCLS", + .states = lcls_fsm_states, + .num_states = ARRAY_SIZE(lcls_fsm_states), + .allstate_event_mask = 0, + .allstate_action = NULL, + .cleanup = lcls_fsm_cleanup, + .timer_cb = NULL, + .log_subsys = DLCLS, + .event_names = lcls_event_names, +}; diff --git a/src/osmo-bsc/osmo_bsc_main.c b/src/osmo-bsc/osmo_bsc_main.c index fefc04180..5c6a872b4 100644 --- a/src/osmo-bsc/osmo_bsc_main.c +++ b/src/osmo-bsc/osmo_bsc_main.c @@ -42,9 +42,15 @@ #include <osmocom/core/talloc.h> #include <osmocom/core/stats.h> #include <osmocom/gsm/protocol/gsm_12_21.h> +#include <osmocom/vty/telnet_interface.h> +#include <osmocom/vty/ports.h> #include <osmocom/abis/abis.h> #include <osmocom/bsc/abis_om2000.h> +#include <osmocom/bsc/abis_nm.h> +#include <osmocom/bsc/abis_rsl.h> +#include <osmocom/bsc/chan_alloc.h> +#include <osmocom/bsc/e1_config.h> #include <osmocom/mgcp_client/mgcp_client.h> @@ -147,6 +153,369 @@ static void handle_options(int argc, char **argv) } } +/* Callback function for NACK on the OML NM */ +static int oml_msg_nack(struct nm_nack_signal_data *nack) +{ + if (nack->mt == NM_MT_GET_ATTR_NACK) { + LOGP(DNM, LOGL_ERROR, "BTS%u does not support Get Attributes " + "OML message.\n", nack->bts->nr); + return 0; + } + + if (nack->mt == NM_MT_SET_BTS_ATTR_NACK) + LOGP(DNM, LOGL_ERROR, "Failed to set BTS attributes. That is fatal. " + "Was the bts type and frequency properly specified?\n"); + else + LOGP(DNM, LOGL_ERROR, "Got %s NACK going to drop the OML links.\n", + abis_nm_nack_name(nack->mt)); + + if (!nack->bts) { + LOGP(DNM, LOGL_ERROR, "Unknown bts. Can not drop it.\n"); + return 0; + } + + if (is_ipaccess_bts(nack->bts)) + ipaccess_drop_oml(nack->bts); + + return 0; +} + +/* Callback function to be called every time we receive a signal from NM */ +static int nm_sig_cb(unsigned int subsys, unsigned int signal, + void *handler_data, void *signal_data) +{ + struct nm_nack_signal_data *nack; + + switch (signal) { + case S_NM_NACK: + nack = signal_data; + return oml_msg_nack(nack); + default: + break; + } + return 0; +} + +/* Produce a MA as specified in 10.5.2.21 */ +static int generate_ma_for_ts(struct gsm_bts_trx_ts *ts) +{ + /* we have three bitvecs: the per-timeslot ARFCNs, the cell chan ARFCNs + * and the MA */ + struct bitvec *cell_chan = &ts->trx->bts->si_common.cell_alloc; + struct bitvec *ts_arfcn = &ts->hopping.arfcns; + struct bitvec *ma = &ts->hopping.ma; + unsigned int num_cell_arfcns, bitnum, n_chan; + int i; + + /* re-set the MA to all-zero */ + ma->cur_bit = 0; + ts->hopping.ma_len = 0; + memset(ma->data, 0, ma->data_len); + + if (!ts->hopping.enabled) + return 0; + + /* count the number of ARFCNs in the cell channel allocation */ + num_cell_arfcns = 0; + for (i = 0; i < 1024; i++) { + if (bitvec_get_bit_pos(cell_chan, i)) + num_cell_arfcns++; + } + + /* pad it to octet-aligned number of bits */ + ts->hopping.ma_len = num_cell_arfcns / 8; + if (num_cell_arfcns % 8) + ts->hopping.ma_len++; + + n_chan = 0; + for (i = 0; i < 1024; i++) { + if (!bitvec_get_bit_pos(cell_chan, i)) + continue; + /* set the corresponding bit in the MA */ + bitnum = (ts->hopping.ma_len * 8) - 1 - n_chan; + if (bitvec_get_bit_pos(ts_arfcn, i)) + bitvec_set_bit_pos(ma, bitnum, 1); + else + bitvec_set_bit_pos(ma, bitnum, 0); + n_chan++; + } + + /* ARFCN 0 is special: It is coded last in the bitmask */ + if (bitvec_get_bit_pos(cell_chan, 0)) { + n_chan++; + /* set the corresponding bit in the MA */ + bitnum = (ts->hopping.ma_len * 8) - 1 - n_chan; + if (bitvec_get_bit_pos(ts_arfcn, 0)) + bitvec_set_bit_pos(ma, bitnum, 1); + else + bitvec_set_bit_pos(ma, bitnum, 0); + } + + return 0; +} + +static void bootstrap_rsl(struct gsm_bts_trx *trx) +{ + unsigned int i; + + LOGP(DRSL, LOGL_NOTICE, "bootstrapping RSL for BTS/TRX (%u/%u) " + "on ARFCN %u using MCC-MNC %s LAC=%u CID=%u BSIC=%u\n", + trx->bts->nr, trx->nr, trx->arfcn, + osmo_plmn_name(&bsc_gsmnet->plmn), + trx->bts->location_area_code, + trx->bts->cell_identity, trx->bts->bsic); + + if (trx->bts->type == GSM_BTS_TYPE_NOKIA_SITE) { + rsl_nokia_si_begin(trx); + } + + /* + * Trigger ACC ramping before sending system information to BTS. + * This ensures that RACH control in system information is configured correctly. + * TRX 0 should be usable and unlocked, otherwise starting ACC ramping is pointless. + */ + if (trx_is_usable(trx) && trx->mo.nm_state.administrative == NM_STATE_UNLOCKED) + acc_ramp_trigger(&trx->bts->acc_ramp); + + gsm_bts_trx_set_system_infos(trx); + + if (trx->bts->type == GSM_BTS_TYPE_NOKIA_SITE) { + /* channel unspecific, power reduction in 2 dB steps */ + rsl_bs_power_control(trx, 0xFF, trx->max_power_red / 2); + rsl_nokia_si_end(trx); + } + + for (i = 0; i < ARRAY_SIZE(trx->ts); i++) { + struct gsm_bts_trx_ts *ts = &trx->ts[i]; + generate_ma_for_ts(ts); + gsm_ts_check_init(ts); + } +} + +/* Callback function to be called every time we receive a signal from INPUT */ +static int inp_sig_cb(unsigned int subsys, unsigned int signal, + void *handler_data, void *signal_data) +{ + struct input_signal_data *isd = signal_data; + struct gsm_bts_trx *trx = isd->trx; + int ts_no, lchan_no; + /* N. B: we rely on attribute order when parsing response in abis_nm_rx_get_attr_resp() */ + const uint8_t bts_attr[] = { NM_ATT_MANUF_ID, NM_ATT_SW_CONFIG, }; + const uint8_t trx_attr[] = { NM_ATT_MANUF_STATE, NM_ATT_SW_CONFIG, }; + + /* we should not request more attributes than we're ready to handle */ + OSMO_ASSERT(sizeof(bts_attr) < MAX_BTS_ATTR); + OSMO_ASSERT(sizeof(trx_attr) < MAX_BTS_ATTR); + + if (subsys != SS_L_INPUT) + return -EINVAL; + + LOGP(DLMI, LOGL_DEBUG, "%s(): Input signal '%s' received\n", __func__, + get_value_string(e1inp_signal_names, signal)); + switch (signal) { + case S_L_INP_TEI_UP: + if (isd->link_type == E1INP_SIGN_OML) { + /* TODO: this is required for the Nokia BTS, hopping is configured + during OML, other MA is not set. */ + struct gsm_bts_trx *cur_trx; + /* was static in system_information.c */ + extern int generate_cell_chan_list(uint8_t *chan_list, struct gsm_bts *bts); + uint8_t ca[20]; + /* has to be called before generate_ma_for_ts to + set bts->si_common.cell_alloc */ + generate_cell_chan_list(ca, trx->bts); + + /* Request generic BTS-level attributes */ + abis_nm_get_attr(trx->bts, NM_OC_BTS, 0xFF, 0xFF, 0xFF, bts_attr, sizeof(bts_attr)); + + llist_for_each_entry(cur_trx, &trx->bts->trx_list, list) { + int i; + /* Request TRX-level attributes */ + abis_nm_get_attr(cur_trx->bts, NM_OC_BASEB_TRANSC, 0, cur_trx->nr, 0xFF, + trx_attr, sizeof(trx_attr)); + for (i = 0; i < ARRAY_SIZE(cur_trx->ts); i++) + generate_ma_for_ts(&cur_trx->ts[i]); + } + } + if (isd->link_type == E1INP_SIGN_RSL) + bootstrap_rsl(trx); + break; + case S_L_INP_TEI_DN: + LOGP(DLMI, LOGL_ERROR, "Lost some E1 TEI link: %d %p\n", isd->link_type, trx); + + if (isd->link_type == E1INP_SIGN_OML) + rate_ctr_inc(&trx->bts->bts_ctrs->ctr[BTS_CTR_BTS_OML_FAIL]); + else if (isd->link_type == E1INP_SIGN_RSL) { + rate_ctr_inc(&trx->bts->bts_ctrs->ctr[BTS_CTR_BTS_RSL_FAIL]); + acc_ramp_abort(&trx->bts->acc_ramp); + } + + /* + * free all allocated channels. change the nm_state so the + * trx and trx_ts becomes unusable and chan_alloc.c can not + * allocate from it. + */ + for (ts_no = 0; ts_no < ARRAY_SIZE(trx->ts); ++ts_no) { + struct gsm_bts_trx_ts *ts = &trx->ts[ts_no]; + + for (lchan_no = 0; lchan_no < ARRAY_SIZE(ts->lchan); ++lchan_no) { + if (ts->lchan[lchan_no].state != LCHAN_S_NONE) + lchan_free(&ts->lchan[lchan_no]); + lchan_reset(&ts->lchan[lchan_no]); + } + } + + gsm_bts_mo_reset(trx->bts); + + abis_nm_clear_queue(trx->bts); + break; + default: + break; + } + + return 0; +} + +static int bootstrap_bts(struct gsm_bts *bts) +{ + int i, n; + + if (!bts->model) + return -EFAULT; + + if (bts->model->start && !bts->model->started) { + int ret = bts->model->start(bts->network); + if (ret < 0) + return ret; + + bts->model->started = true; + } + + /* FIXME: What about secondary TRX of a BTS? What about a BTS that has TRX + * in different bands? Why is 'band' a parameter of the BTS and not of the TRX? */ + switch (bts->band) { + case GSM_BAND_1800: + if (bts->c0->arfcn < 512 || bts->c0->arfcn > 885) { + LOGP(DNM, LOGL_ERROR, "GSM1800 channel must be between 512-885.\n"); + return -EINVAL; + } + break; + case GSM_BAND_1900: + if (bts->c0->arfcn < 512 || bts->c0->arfcn > 810) { + LOGP(DNM, LOGL_ERROR, "GSM1900 channel must be between 512-810.\n"); + return -EINVAL; + } + break; + case GSM_BAND_900: + if ((bts->c0->arfcn > 124 && bts->c0->arfcn < 955) || + bts->c0->arfcn > 1023) { + LOGP(DNM, LOGL_ERROR, "GSM900 channel must be between 0-124, 955-1023.\n"); + return -EINVAL; + } + break; + case GSM_BAND_850: + if (bts->c0->arfcn < 128 || bts->c0->arfcn > 251) { + LOGP(DNM, LOGL_ERROR, "GSM850 channel must be between 128-251.\n"); + return -EINVAL; + } + break; + default: + LOGP(DNM, LOGL_ERROR, "Unsupported frequency band.\n"); + return -EINVAL; + } + + /* Control Channel Description is set from vty/config */ + + /* T3212 is set from vty/config */ + + /* Set ccch config by looking at ts config */ + for (n=0, i=0; i<8; i++) + n += bts->c0->ts[i].pchan == GSM_PCHAN_CCCH ? 1 : 0; + + /* Indicate R99 MSC in SI3 */ + bts->si_common.chan_desc.mscr = 1; + + switch (n) { + case 0: + bts->si_common.chan_desc.ccch_conf = RSL_BCCH_CCCH_CONF_1_C; + /* Limit reserved block to 2 on combined channel according to + 3GPP TS 44.018 Table 10.5.2.11.1 */ + if (bts->si_common.chan_desc.bs_ag_blks_res > 2) { + LOGP(DNM, LOGL_NOTICE, "CCCH is combined with SDCCHs, " + "reducing BS-AG-BLKS-RES value %d -> 2\n", + bts->si_common.chan_desc.bs_ag_blks_res); + bts->si_common.chan_desc.bs_ag_blks_res = 2; + } + break; + case 1: + bts->si_common.chan_desc.ccch_conf = RSL_BCCH_CCCH_CONF_1_NC; + break; + case 2: + bts->si_common.chan_desc.ccch_conf = RSL_BCCH_CCCH_CONF_2_NC; + break; + case 3: + bts->si_common.chan_desc.ccch_conf = RSL_BCCH_CCCH_CONF_3_NC; + break; + case 4: + bts->si_common.chan_desc.ccch_conf = RSL_BCCH_CCCH_CONF_4_NC; + break; + default: + LOGP(DNM, LOGL_ERROR, "Unsupported CCCH timeslot configuration\n"); + return -EINVAL; + } + + bts->si_common.cell_options.pwrc = 0; /* PWRC not set */ + + bts->si_common.cell_sel_par.acs = 0; + + bts->si_common.ncc_permitted = 0xff; + + bts->chan_load_samples_idx = 0; + + /* ACC ramping is initialized from vty/config */ + + /* Initialize the BTS state */ + gsm_bts_mo_reset(bts); + + return 0; +} + +static int bsc_network_configure(const char *config_file) +{ + struct gsm_bts *bts; + int rc; + + rc = vty_read_config_file(config_file, NULL); + if (rc < 0) { + LOGP(DNM, LOGL_FATAL, "Failed to parse the config file: '%s'\n", config_file); + return rc; + } + + /* start telnet after reading config for vty_get_bind_addr() */ + rc = telnet_init_dynif(tall_bsc_ctx, bsc_gsmnet, vty_get_bind_addr(), + OSMO_VTY_PORT_NITB_BSC); + if (rc < 0) + return rc; + + osmo_signal_register_handler(SS_NM, nm_sig_cb, NULL); + osmo_signal_register_handler(SS_L_INPUT, inp_sig_cb, NULL); + + llist_for_each_entry(bts, &bsc_gsmnet->bts_list, list) { + rc = bootstrap_bts(bts); + if (rc < 0) { + LOGP(DNM, LOGL_FATAL, "Error bootstrapping BTS\n"); + return rc; + } + rc = e1_reconfig_bts(bts); + if (rc < 0) { + LOGP(DNM, LOGL_FATAL, "Error enabling E1 input driver\n"); + return rc; + } + } + + return 0; +} + static int bsc_vty_go_parent(struct vty *vty) { switch (vty->node) { diff --git a/src/osmo-bsc/paging.c b/src/osmo-bsc/paging.c new file mode 100644 index 000000000..d6bff2a42 --- /dev/null +++ b/src/osmo-bsc/paging.c @@ -0,0 +1,473 @@ +/* Paging helper and manager.... */ +/* (C) 2009,2013 by Holger Hans Peter Freyther <zecke@selfish.org> + * 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/>. + * + */ + +/* + * Relevant specs: + * 12.21: + * - 9.4.12 for CCCH Local Threshold + * + * 05.58: + * - 8.5.2 CCCH Load indication + * - 9.3.15 Paging Load + * + * Approach: + * - Send paging command to subscriber + * - On Channel Request we will remember the reason + * - After the ACK we will request the identity + * - Then we will send assign the gsm_subscriber and + * - and call a callback + */ + +#include <stdio.h> +#include <stdlib.h> +#include <assert.h> + +#include <osmocom/core/talloc.h> +#include <osmocom/gsm/gsm48.h> +#include <osmocom/gsm/gsm0502.h> + +#include <osmocom/bsc/bsc_subscriber.h> +#include <osmocom/bsc/paging.h> +#include <osmocom/bsc/debug.h> +#include <osmocom/bsc/signal.h> +#include <osmocom/bsc/abis_rsl.h> +#include <osmocom/bsc/gsm_data.h> +#include <osmocom/bsc/chan_alloc.h> +#include <osmocom/bsc/bsc_api.h> + +void *tall_paging_ctx = NULL; + +#define PAGING_TIMER 0, 500000 + +/* + * TODO MSCSPLIT: the paging in libbsc is closely tied to MSC land in that the + * MSC realm callback functions used to be invoked from the BSC/BTS level. So + * this entire file needs to be rewired for use with an A interface. + */ + +/* + * Kill one paging request update the internal list... + */ +static void paging_remove_request(struct gsm_bts_paging_state *paging_bts, + struct gsm_paging_request *to_be_deleted) +{ + osmo_timer_del(&to_be_deleted->T3113); + llist_del(&to_be_deleted->entry); + bsc_subscr_put(to_be_deleted->bsub); + talloc_free(to_be_deleted); +} + +static void page_ms(struct gsm_paging_request *request) +{ + uint8_t mi[128]; + unsigned int mi_len; + unsigned int page_group; + struct gsm_bts *bts = request->bts; + + log_set_context(LOG_CTX_BSC_SUBSCR, request->bsub); + + LOGP(DPAG, LOGL_INFO, "(bts=%d) Going to send paging commands: imsi: %s tmsi: " + "0x%08x for ch. type %d (attempt %d)\n", bts->nr, request->bsub->imsi, + request->bsub->tmsi, request->chan_type, request->attempts); + + if (request->bsub->tmsi == GSM_RESERVED_TMSI) + mi_len = gsm48_generate_mid_from_imsi(mi, request->bsub->imsi); + else + mi_len = gsm48_generate_mid_from_tmsi(mi, request->bsub->tmsi); + + page_group = gsm0502_calc_paging_group(&bts->si_common.chan_desc, + str_to_imsi(request->bsub->imsi)); + gsm0808_page(bts, page_group, mi_len, mi, request->chan_type); + log_set_context(LOG_CTX_BSC_SUBSCR, NULL); +} + +static void paging_schedule_if_needed(struct gsm_bts_paging_state *paging_bts) +{ + if (llist_empty(&paging_bts->pending_requests)) + return; + + if (!osmo_timer_pending(&paging_bts->work_timer)) + osmo_timer_schedule(&paging_bts->work_timer, PAGING_TIMER); +} + + +static void paging_handle_pending_requests(struct gsm_bts_paging_state *paging_bts); +static void paging_give_credit(void *data) +{ + struct gsm_bts_paging_state *paging_bts = data; + + LOGP(DPAG, LOGL_NOTICE, "(bts=%d) No PCH LOAD IND, adding 20 slots)\n", + paging_bts->bts->nr); + paging_bts->available_slots = 20; + paging_handle_pending_requests(paging_bts); +} + +/*! count the number of free channels for given RSL channel type required + * \param[in] BTS on which we shall count + * \param[in] rsl_type the RSL channel needed type + * \returns number of free channels matching \a rsl_type in \a bts */ +static int can_send_pag_req(struct gsm_bts *bts, int rsl_type) +{ + struct pchan_load pl; + int count; + + memset(&pl, 0, sizeof(pl)); + bts_chan_load(&pl, bts); + + switch (rsl_type) { + case RSL_CHANNEED_TCH_F: + case RSL_CHANNEED_TCH_ForH: + goto count_tch; + break; + case RSL_CHANNEED_SDCCH: + goto count_sdcch; + break; + case RSL_CHANNEED_ANY: + default: + if (bts->network->pag_any_tch) + goto count_tch; + else + goto count_sdcch; + break; + } + + return 0; + + /* could available SDCCH */ +count_sdcch: + count = 0; + count += pl.pchan[GSM_PCHAN_SDCCH8_SACCH8C].total + - pl.pchan[GSM_PCHAN_SDCCH8_SACCH8C].used; + count += pl.pchan[GSM_PCHAN_CCCH_SDCCH4].total + - pl.pchan[GSM_PCHAN_CCCH_SDCCH4].used; + return bts->paging.free_chans_need > count; + +count_tch: + count = 0; + count += pl.pchan[GSM_PCHAN_TCH_F].total + - pl.pchan[GSM_PCHAN_TCH_F].used; + if (bts->network->neci) + count += pl.pchan[GSM_PCHAN_TCH_H].total + - pl.pchan[GSM_PCHAN_TCH_H].used; + return bts->paging.free_chans_need > count; +} + +/* + * This is kicked by the periodic PAGING LOAD Indicator + * coming from abis_rsl.c + * + * We attempt to iterate once over the list of items but + * only upto available_slots. + */ +static void paging_handle_pending_requests(struct gsm_bts_paging_state *paging_bts) +{ + struct gsm_paging_request *request = NULL; + + /* + * Determine if the pending_requests list is empty and + * return then. + */ + if (llist_empty(&paging_bts->pending_requests)) { + /* since the list is empty, no need to reschedule the timer */ + return; + } + + /* + * In case the BTS does not provide us with load indication and we + * ran out of slots, call an autofill routine. It might be that the + * BTS did not like our paging messages and then we have counted down + * to zero and we do not get any messages. + */ + if (paging_bts->available_slots == 0) { + osmo_timer_setup(&paging_bts->credit_timer, paging_give_credit, + paging_bts); + osmo_timer_schedule(&paging_bts->credit_timer, 5, 0); + return; + } + + request = llist_entry(paging_bts->pending_requests.next, + struct gsm_paging_request, entry); + + /* we need to determine the number of free channels */ + if (paging_bts->free_chans_need != -1) { + if (can_send_pag_req(request->bts, request->chan_type) != 0) + goto skip_paging; + } + + /* Skip paging if the bts is down. */ + if (!request->bts->oml_link) + goto skip_paging; + + /* handle the paging request now */ + page_ms(request); + paging_bts->available_slots--; + request->attempts++; + + /* take the current and add it to the back */ + llist_del(&request->entry); + llist_add_tail(&request->entry, &paging_bts->pending_requests); + +skip_paging: + osmo_timer_schedule(&paging_bts->work_timer, PAGING_TIMER); +} + +static void paging_worker(void *data) +{ + struct gsm_bts_paging_state *paging_bts = data; + + paging_handle_pending_requests(paging_bts); +} + +/*! initialize the bts paging state, if it hasn't been initialized yet */ +static void paging_init_if_needed(struct gsm_bts *bts) +{ + if (bts->paging.bts) + return; + + bts->paging.bts = bts; + + /* This should be initialized only once. There is currently no code that sets bts->paging.bts + * back to NULL, so let's just assert this one instead of graceful handling. */ + OSMO_ASSERT(llist_empty(&bts->paging.pending_requests)); + + osmo_timer_setup(&bts->paging.work_timer, paging_worker, + &bts->paging); + + /* Large number, until we get a proper message */ + bts->paging.available_slots = 20; +} + +/*! do we have any pending paging requests for given subscriber? */ +static int paging_pending_request(struct gsm_bts_paging_state *bts, + struct bsc_subscr *bsub) +{ + struct gsm_paging_request *req; + + llist_for_each_entry(req, &bts->pending_requests, entry) { + if (bsub == req->bsub) + return 1; + } + + return 0; +} + +/*! Call-back once T3113 (paging timeout) expires for given paging_request */ +static void paging_T3113_expired(void *data) +{ + struct gsm_paging_request *req = (struct gsm_paging_request *)data; + + log_set_context(LOG_CTX_BSC_SUBSCR, req->bsub); + + LOGP(DPAG, LOGL_INFO, "T3113 expired for request %p (%s)\n", + req, bsc_subscr_name(req->bsub)); + + /* must be destroyed before calling cbfn, to prevent double free */ + rate_ctr_inc(&req->bts->bts_ctrs->ctr[BTS_CTR_PAGING_EXPIRED]); + + /* destroy it now. Do not access req afterwards */ + paging_remove_request(&req->bts->paging, req); +} + +/*! Start paging + paging timer for given subscriber on given BTS + * \param bts BTS on which to page + * \param[in] bsub subscriber we want to page + * \param[in] type type of radio channel we're requirign + * \param[in] msc MSC which has issue this paging + * \returns 0 on success, negative on error */ +static int _paging_request(struct gsm_bts *bts, struct bsc_subscr *bsub, int type, + struct bsc_msc_data *msc) +{ + struct gsm_bts_paging_state *bts_entry = &bts->paging; + struct gsm_paging_request *req; + + rate_ctr_inc(&bts->bts_ctrs->ctr[BTS_CTR_PAGING_ATTEMPTED]); + + if (paging_pending_request(bts_entry, bsub)) { + LOGP(DPAG, LOGL_INFO, "(bts=%d) Paging request already pending for %s\n", + bts->nr, bsc_subscr_name(bsub)); + rate_ctr_inc(&bts->bts_ctrs->ctr[BTS_CTR_PAGING_ALREADY]); + return -EEXIST; + } + + LOGP(DPAG, LOGL_DEBUG, "(bts=%d) Start paging of subscriber %s\n", bts->nr, + bsc_subscr_name(bsub)); + req = talloc_zero(tall_paging_ctx, struct gsm_paging_request); + OSMO_ASSERT(req); + req->bsub = bsc_subscr_get(bsub); + req->bts = bts; + req->chan_type = type; + req->msc = msc; + osmo_timer_setup(&req->T3113, paging_T3113_expired, req); + osmo_timer_schedule(&req->T3113, bts->network->T3113, 0); + llist_add_tail(&req->entry, &bts_entry->pending_requests); + paging_schedule_if_needed(bts_entry); + + return 0; +} + +/*! Handle PAGING request from MSC for one (matching) BTS + * \param bts BTS on which to page + * \param[in] bsub subscriber we want to page + * \param[in] type type of radio channel we're requirign + * \param[in] msc MSC which has issue this paging + * returns 1 on success; 0 in case of error (e.g. TRX down) */ +int paging_request_bts(struct gsm_bts *bts, struct bsc_subscr *bsub, int type, + struct bsc_msc_data *msc) +{ + int rc; + + /* skip all currently inactive TRX */ + if (!trx_is_usable(bts->c0)) + return 0; + + /* maybe it is the first time we use it */ + paging_init_if_needed(bts); + + /* Trigger paging, pass any error to the caller */ + rc = _paging_request(bts, bsub, type, msc); + if (rc < 0) + return 0; + return 1; +} + +/*! Stop paging a given subscriber on a given BTS. + * If \a conn is non-NULL, we also call the paging call-back function + * to notify the paging originator that paging has completed. + * \param[in] bts BTS on which we shall stop paging + * \param[in] bsub subscriber which we shall stop paging + * \param[in] conn connection to the subscriber (if any) + * \param[in] msg message received from subscrbier (if any) + * \returns 0 if an active paging request was stopped, an error code otherwise. */ +/* we consciously ignore the type of the request here */ +static int _paging_request_stop(struct gsm_bts *bts, struct bsc_subscr *bsub, + struct gsm_subscriber_connection *conn, + struct msgb *msg) +{ + struct gsm_bts_paging_state *bts_entry = &bts->paging; + struct gsm_paging_request *req, *req2; + + paging_init_if_needed(bts); + + llist_for_each_entry_safe(req, req2, &bts_entry->pending_requests, + entry) { + if (req->bsub == bsub) { + /* now give up the data structure */ + paging_remove_request(&bts->paging, req); + LOGP(DPAG, LOGL_DEBUG, "(bts=%d) Stop paging %s\n", bts->nr, + bsc_subscr_name(bsub)); + return 0; + } + } + + return -ENOENT; +} + +/*! Stop paging on all other bts' + * \param[in] bts_list list of BTSs to iterate + * \param[in] _bts BTS which has received a paging response + * \param[in] bsub subscriber + * \param[in] msgb L3 message that we have received from \a bsub on \a _bts */ +void paging_request_stop(struct llist_head *bts_list, + struct gsm_bts *_bts, struct bsc_subscr *bsub, + struct gsm_subscriber_connection *conn, + struct msgb *msg) +{ + struct gsm_bts *bts; + + log_set_context(LOG_CTX_BSC_SUBSCR, bsub); + + /* Stop this first and dispatch the request */ + if (_bts) { + if (_paging_request_stop(_bts, bsub, conn, msg) == 0) { + rate_ctr_inc(&_bts->bts_ctrs->ctr[BTS_CTR_PAGING_RESPONDED]); + rate_ctr_inc(&_bts->network->bsc_ctrs->ctr[BSC_CTR_PAGING_RESPONDED]); + } + } + + /* Make sure to cancel this everywhere else */ + llist_for_each_entry(bts, bts_list, list) { + /* Sort of an optimization. */ + if (bts == _bts) + continue; + _paging_request_stop(bts, bsub, NULL, NULL); + } +} + + +/*! Update the BTS paging buffer slots on given BTS */ +void paging_update_buffer_space(struct gsm_bts *bts, uint16_t free_slots) +{ + paging_init_if_needed(bts); + + osmo_timer_del(&bts->paging.credit_timer); + bts->paging.available_slots = free_slots; + paging_schedule_if_needed(&bts->paging); +} + +/*! Count the number of pending paging requests on given BTS */ +unsigned int paging_pending_requests_nr(struct gsm_bts *bts) +{ + unsigned int requests = 0; + struct gsm_paging_request *req; + + paging_init_if_needed(bts); + + llist_for_each_entry(req, &bts->paging.pending_requests, entry) + ++requests; + + return requests; +} + +/*! Find any paging data for the given subscriber at the given BTS. */ +struct bsc_msc_data *paging_get_msc(struct gsm_bts *bts, struct bsc_subscr *bsub) +{ + struct gsm_paging_request *req; + + llist_for_each_entry(req, &bts->paging.pending_requests, entry) + if (req->bsub == bsub) + return req->msc; + + return NULL; +} + +/*! Flush all paging requests at a given BTS for a given MSC (or NULL if all MSC should be flushed). */ +void paging_flush_bts(struct gsm_bts *bts, struct bsc_msc_data *msc) +{ + struct gsm_paging_request *req, *req2; + + paging_init_if_needed(bts); + + llist_for_each_entry_safe(req, req2, &bts->paging.pending_requests, entry) { + if (msc && req->msc != msc) + continue; + /* now give up the data structure */ + LOGP(DPAG, LOGL_DEBUG, "(bts=%d) Stop paging %s (flush)\n", bts->nr, + bsc_subscr_name(req->bsub)); + paging_remove_request(&bts->paging, req); + } +} + +/*! Flush all paging requests issued by \a msc on any BTS in \a net */ +void paging_flush_network(struct gsm_network *net, struct bsc_msc_data *msc) +{ + struct gsm_bts *bts; + + llist_for_each_entry(bts, &net->bts_list, list) + paging_flush_bts(bts, msc); +} diff --git a/src/osmo-bsc/pcu_sock.c b/src/osmo-bsc/pcu_sock.c new file mode 100644 index 000000000..64422c724 --- /dev/null +++ b/src/osmo-bsc/pcu_sock.c @@ -0,0 +1,740 @@ +/* pcu_sock.c: Connect from PCU via unix domain socket */ + +/* (C) 2008-2010 by Harald Welte <laforge@gnumonks.org> + * (C) 2009-2012 by Andreas Eversberg <jolly@eversberg.eu> + * (C) 2012 by Holger Hans Peter Freyther + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 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 General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#include <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <assert.h> +#include <sys/socket.h> +#include <sys/un.h> + +#include <osmocom/core/talloc.h> +#include <osmocom/core/select.h> +#include <osmocom/core/socket.h> +#include <osmocom/core/logging.h> +#include <osmocom/gsm/l1sap.h> +#include <osmocom/gsm/gsm0502.h> + +#include <osmocom/bsc/gsm_data.h> +#include <osmocom/bsc/pcu_if.h> +#include <osmocom/bsc/pcuif_proto.h> +#include <osmocom/bsc/signal.h> +#include <osmocom/bsc/debug.h> +#include <osmocom/bsc/abis_rsl.h> + +static int pcu_sock_send(struct gsm_bts *bts, struct msgb *msg); +uint32_t trx_get_hlayer1(struct gsm_bts_trx *trx); +int pcu_direct = 1; + +static const char *sapi_string[] = { + [PCU_IF_SAPI_RACH] = "RACH", + [PCU_IF_SAPI_AGCH] = "AGCH", + [PCU_IF_SAPI_PCH] = "PCH", + [PCU_IF_SAPI_BCCH] = "BCCH", + [PCU_IF_SAPI_PDTCH] = "PDTCH", + [PCU_IF_SAPI_PRACH] = "PRACH", + [PCU_IF_SAPI_PTCCH] = "PTCCH", + [PCU_IF_SAPI_AGCH_DT] = "AGCH_DT", +}; + +/* Check if BTS has a PCU connection */ +static bool pcu_connected(struct gsm_bts *bts) +{ + struct pcu_sock_state *state = bts->pcu_state; + + if (!state) + return false; + if (state->conn_bfd.fd <= 0) + return false; + return true; +} + +/* + * PCU messages + */ + +/* Set up an message buffer to package an pcu interface message */ +struct msgb *pcu_msgb_alloc(uint8_t msg_type, uint8_t bts_nr) +{ + struct msgb *msg; + struct gsm_pcu_if *pcu_prim; + + msg = msgb_alloc(sizeof(struct gsm_pcu_if), "pcu_sock_tx"); + if (!msg) + return NULL; + + msgb_put(msg, sizeof(struct gsm_pcu_if)); + pcu_prim = (struct gsm_pcu_if *) msg->data; + pcu_prim->msg_type = msg_type; + pcu_prim->bts_nr = bts_nr; + + return msg; +} + +/* Helper function exclusivly used by pcu_if_signal_cb() */ +static bool ts_should_be_pdch(struct gsm_bts_trx_ts *ts) { + if (ts->pchan == GSM_PCHAN_PDCH) + return true; + if (ts->pchan == GSM_PCHAN_TCH_F_PDCH) { + /* When we're busy deactivating the PDCH, we first set + * DEACT_PENDING, tell the PCU about it and wait for a + * response. So DEACT_PENDING means "no PDCH" to the PCU. + * Similarly, when we're activating PDCH, we set the + * ACT_PENDING and wait for an activation response from the + * PCU, so ACT_PENDING means "is PDCH". */ + if (ts->flags & TS_F_PDCH_ACTIVE) + return !(ts->flags & TS_F_PDCH_DEACT_PENDING); + else + return (ts->flags & TS_F_PDCH_ACT_PENDING); + } + if (ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH) { + /* + * When we're busy de-/activating the PDCH, we first set + * ts->dyn.pchan_want, tell the PCU about it and wait for a + * response. So only care about dyn.pchan_want here. + */ + return ts->dyn.pchan_want == GSM_PCHAN_PDCH; + } + return false; +} + +/* Send BTS properties to the PCU */ +static int pcu_tx_info_ind(struct gsm_bts *bts) +{ + struct msgb *msg; + struct gsm_pcu_if *pcu_prim; + struct gsm_pcu_if_info_ind *info_ind; + struct gprs_rlc_cfg *rlcc; + struct gsm_bts_gprs_nsvc *nsvc; + struct gsm_bts_trx *trx; + struct gsm_bts_trx_ts *ts; + int i, j; + + OSMO_ASSERT(bts); + OSMO_ASSERT(bts->network); + + LOGP(DPCU, LOGL_INFO, "Sending info for BTS %d\n",bts->nr); + + rlcc = &bts->gprs.cell.rlc_cfg; + + msg = pcu_msgb_alloc(PCU_IF_MSG_INFO_IND, bts->nr); + if (!msg) + return -ENOMEM; + + pcu_prim = (struct gsm_pcu_if *) msg->data; + info_ind = &pcu_prim->u.info_ind; + info_ind->version = PCU_IF_VERSION; + info_ind->flags |= PCU_IF_FLAG_ACTIVE; + + if (pcu_direct) + info_ind->flags |= PCU_IF_FLAG_SYSMO; + + /* RAI */ + info_ind->mcc = bts->network->plmn.mcc; + info_ind->mnc = bts->network->plmn.mnc; + info_ind->mnc_3_digits = bts->network->plmn.mnc_3_digits; + info_ind->lac = bts->location_area_code; + info_ind->rac = bts->gprs.rac; + + /* NSE */ + info_ind->nsei = bts->gprs.nse.nsei; + memcpy(info_ind->nse_timer, bts->gprs.nse.timer, 7); + memcpy(info_ind->cell_timer, bts->gprs.cell.timer, 11); + + /* cell attributes */ + info_ind->cell_id = bts->cell_identity; + info_ind->repeat_time = rlcc->paging.repeat_time; + info_ind->repeat_count = rlcc->paging.repeat_count; + info_ind->bvci = bts->gprs.cell.bvci; + info_ind->t3142 = rlcc->parameter[RLC_T3142]; + info_ind->t3169 = rlcc->parameter[RLC_T3169]; + info_ind->t3191 = rlcc->parameter[RLC_T3191]; + info_ind->t3193_10ms = rlcc->parameter[RLC_T3193]; + info_ind->t3195 = rlcc->parameter[RLC_T3195]; + info_ind->n3101 = rlcc->parameter[RLC_N3101]; + info_ind->n3103 = rlcc->parameter[RLC_N3103]; + info_ind->n3105 = rlcc->parameter[RLC_N3105]; + info_ind->cv_countdown = rlcc->parameter[CV_COUNTDOWN]; + if (rlcc->cs_mask & (1 << GPRS_CS1)) + info_ind->flags |= PCU_IF_FLAG_CS1; + if (rlcc->cs_mask & (1 << GPRS_CS2)) + info_ind->flags |= PCU_IF_FLAG_CS2; + if (rlcc->cs_mask & (1 << GPRS_CS3)) + info_ind->flags |= PCU_IF_FLAG_CS3; + if (rlcc->cs_mask & (1 << GPRS_CS4)) + info_ind->flags |= PCU_IF_FLAG_CS4; + if (bts->gprs.mode == BTS_GPRS_EGPRS) { + if (rlcc->cs_mask & (1 << GPRS_MCS1)) + info_ind->flags |= PCU_IF_FLAG_MCS1; + if (rlcc->cs_mask & (1 << GPRS_MCS2)) + info_ind->flags |= PCU_IF_FLAG_MCS2; + if (rlcc->cs_mask & (1 << GPRS_MCS3)) + info_ind->flags |= PCU_IF_FLAG_MCS3; + if (rlcc->cs_mask & (1 << GPRS_MCS4)) + info_ind->flags |= PCU_IF_FLAG_MCS4; + if (rlcc->cs_mask & (1 << GPRS_MCS5)) + info_ind->flags |= PCU_IF_FLAG_MCS5; + if (rlcc->cs_mask & (1 << GPRS_MCS6)) + info_ind->flags |= PCU_IF_FLAG_MCS6; + if (rlcc->cs_mask & (1 << GPRS_MCS7)) + info_ind->flags |= PCU_IF_FLAG_MCS7; + if (rlcc->cs_mask & (1 << GPRS_MCS8)) + info_ind->flags |= PCU_IF_FLAG_MCS8; + if (rlcc->cs_mask & (1 << GPRS_MCS9)) + info_ind->flags |= PCU_IF_FLAG_MCS9; + } +#warning "isn't dl_tbf_ext wrong?: * 10 and no ntohs" + info_ind->dl_tbf_ext = rlcc->parameter[T_DL_TBF_EXT]; +#warning "isn't ul_tbf_ext wrong?: * 10 and no ntohs" + info_ind->ul_tbf_ext = rlcc->parameter[T_UL_TBF_EXT]; + info_ind->initial_cs = rlcc->initial_cs; + info_ind->initial_mcs = rlcc->initial_mcs; + + /* NSVC */ + for (i = 0; i < ARRAY_SIZE(info_ind->nsvci); i++) { + nsvc = &bts->gprs.nsvc[i]; + info_ind->nsvci[i] = nsvc->nsvci; + info_ind->local_port[i] = nsvc->local_port; + info_ind->remote_port[i] = nsvc->remote_port; + info_ind->remote_ip[i] = nsvc->remote_ip; + } + + for (i = 0; i < ARRAY_SIZE(info_ind->trx); i++) { + trx = gsm_bts_trx_num(bts, i); + if (!trx) + continue; + info_ind->trx[i].hlayer1 = 0x2342; + info_ind->trx[i].pdch_mask = 0; + info_ind->trx[i].arfcn = trx->arfcn; + for (j = 0; j < ARRAY_SIZE(trx->ts); j++) { + ts = &trx->ts[j]; + if (ts->mo.nm_state.operational == NM_OPSTATE_ENABLED + && ts_should_be_pdch(ts)) { + info_ind->trx[i].pdch_mask |= (1 << j); + info_ind->trx[i].tsc[j] = + (ts->tsc >= 0) ? ts->tsc : bts->bsic & 7; + LOGP(DPCU, LOGL_INFO, "trx=%d ts=%d: " + "available (tsc=%d arfcn=%d)\n", + trx->nr, ts->nr, + info_ind->trx[i].tsc[j], + info_ind->trx[i].arfcn); + } + } + } + + return pcu_sock_send(bts, msg); +} + +void pcu_info_update(struct gsm_bts *bts) +{ + if (pcu_connected(bts)) + pcu_tx_info_ind(bts); +} + +/* Forward rach indication to PCU */ +int pcu_tx_rach_ind(struct gsm_bts *bts, int16_t qta, uint16_t ra, uint32_t fn, + uint8_t is_11bit, enum ph_burst_type burst_type) +{ + struct msgb *msg; + struct gsm_pcu_if *pcu_prim; + struct gsm_pcu_if_rach_ind *rach_ind; + + /* Bail if no PCU is connected */ + if (!pcu_connected(bts)) { + LOGP(DRSL, LOGL_ERROR, "BTS %d CHAN RQD(GPRS) but PCU not " + "connected!\n", bts->nr); + return -ENODEV; + } + + LOGP(DPCU, LOGL_INFO, "Sending RACH indication: qta=%d, ra=%d, " + "fn=%d\n", qta, ra, fn); + + msg = pcu_msgb_alloc(PCU_IF_MSG_RACH_IND, bts->nr); + if (!msg) + return -ENOMEM; + pcu_prim = (struct gsm_pcu_if *) msg->data; + rach_ind = &pcu_prim->u.rach_ind; + + rach_ind->sapi = PCU_IF_SAPI_RACH; + rach_ind->ra = ra; + rach_ind->qta = qta; + rach_ind->fn = fn; + rach_ind->is_11bit = is_11bit; + rach_ind->burst_type = burst_type; + + return pcu_sock_send(bts, msg); +} + +/* Confirm the sending of an immediate assignment to the pcu */ +int pcu_tx_imm_ass_sent(struct gsm_bts *bts, uint32_t tlli) +{ + struct msgb *msg; + struct gsm_pcu_if *pcu_prim; + struct gsm_pcu_if_data_cnf_dt *data_cnf_dt; + + LOGP(DPCU, LOGL_INFO, "Sending PCH confirm with direct TLLI\n"); + + msg = pcu_msgb_alloc(PCU_IF_MSG_DATA_CNF_DT, bts->nr); + if (!msg) + return -ENOMEM; + pcu_prim = (struct gsm_pcu_if *) msg->data; + data_cnf_dt = &pcu_prim->u.data_cnf_dt; + + data_cnf_dt->sapi = PCU_IF_SAPI_PCH; + data_cnf_dt->tlli = tlli; + + return pcu_sock_send(bts, msg); +} + +/* we need to decode the raw RR paging messsage (see PCU code + * Encoding::write_paging_request) and extract the mobile identity + * (P-TMSI) from it */ +static int pcu_rx_rr_paging(struct gsm_bts *bts, uint8_t paging_group, + const uint8_t *raw_rr_msg) +{ + struct gsm48_paging1 *p1 = (struct gsm48_paging1 *) raw_rr_msg; + uint8_t chan_needed; + unsigned int mi_len; + uint8_t *mi; + int rc; + + switch (p1->msg_type) { + case GSM48_MT_RR_PAG_REQ_1: + chan_needed = (p1->cneed2 << 2) | p1->cneed1; + mi_len = p1->data[0]; + mi = p1->data+1; + LOGP(DPCU, LOGL_ERROR, "PCU Sends paging " + "request type %02x (chan_needed=%02x, mi_len=%u, mi=%s)\n", + p1->msg_type, chan_needed, mi_len, + osmo_hexdump_nospc(mi,mi_len)); + /* NOTE: We will have to add 2 to mi_len and subtract 2 from + * the mi pointer because rsl_paging_cmd() will perform the + * reverse operations. This is because rsl_paging_cmd() is + * normally expected to chop off the element identifier (0xC0) + * and the length field. In our parameter, we do not have + * those fields included. */ + rc = rsl_paging_cmd(bts, paging_group, mi_len+2, mi-2, + chan_needed, true); + break; + case GSM48_MT_RR_PAG_REQ_2: + case GSM48_MT_RR_PAG_REQ_3: + LOGP(DPCU, LOGL_ERROR, "PCU Sends unsupported paging " + "request type %02x\n", p1->msg_type); + rc = -EINVAL; + break; + default: + LOGP(DPCU, LOGL_ERROR, "PCU Sends unknown paging " + "request type %02x\n", p1->msg_type); + rc = -EINVAL; + break; + } + + return rc; +} + +static int pcu_rx_data_req(struct gsm_bts *bts, uint8_t msg_type, + struct gsm_pcu_if_data *data_req) +{ + struct msgb *msg; + char imsi_digit_buf[4]; + uint32_t tlli = -1; + uint8_t pag_grp; + int rc = 0; + + LOGP(DPCU, LOGL_DEBUG, "Data request received: sapi=%s arfcn=%d " + "block=%d data=%s\n", sapi_string[data_req->sapi], + data_req->arfcn, data_req->block_nr, + osmo_hexdump(data_req->data, data_req->len)); + + switch (data_req->sapi) { + case PCU_IF_SAPI_PCH: + /* the first three bytes are the last three digits of + * the IMSI, which we need to compute the paging group */ + imsi_digit_buf[0] = data_req->data[0]; + imsi_digit_buf[1] = data_req->data[1]; + imsi_digit_buf[2] = data_req->data[2]; + imsi_digit_buf[3] = '\0'; + LOGP(DPCU, LOGL_DEBUG, "SAPI PCH imsi %s\n", imsi_digit_buf); + pag_grp = gsm0502_calc_paging_group(&bts->si_common.chan_desc, + str_to_imsi(imsi_digit_buf)); + pcu_rx_rr_paging(bts, pag_grp, data_req->data+3); + break; + case PCU_IF_SAPI_AGCH: + msg = msgb_alloc(data_req->len, "pcu_agch"); + if (!msg) { + rc = -ENOMEM; + break; + } + msg->l3h = msgb_put(msg, data_req->len); + memcpy(msg->l3h, data_req->data, data_req->len); + + if (rsl_imm_assign_cmd(bts, msg->len, msg->data)) { + msgb_free(msg); + rc = -EIO; + } + break; + case PCU_IF_SAPI_AGCH_DT: + /* DT = direct tlli. A tlli is prefixed */ + + if (data_req->len < 5) { + LOGP(DPCU, LOGL_ERROR, "Received PCU data request with " + "invalid/small length %d\n", data_req->len); + break; + } + memcpy(&tlli, data_req->data, 4); + + msg = msgb_alloc(data_req->len - 4, "pcu_agch"); + if (!msg) { + rc = -ENOMEM; + break; + } + msg->l3h = msgb_put(msg, data_req->len - 4); + memcpy(msg->l3h, data_req->data + 4, data_req->len - 4); + + if (bts->type == GSM_BTS_TYPE_RBS2000) + rc = rsl_ericsson_imm_assign_cmd(bts, tlli, msg->len, msg->data); + else + rc = rsl_imm_assign_cmd(bts, msg->len, msg->data); + + if (rc) { + msgb_free(msg); + rc = -EIO; + } + break; + default: + LOGP(DPCU, LOGL_ERROR, "Received PCU data request with " + "unsupported sapi %d\n", data_req->sapi); + rc = -EINVAL; + } + + return rc; +} + +static int pcu_rx(struct gsm_network *net, uint8_t msg_type, + struct gsm_pcu_if *pcu_prim) +{ + int rc = 0; + struct gsm_bts *bts; + + /* FIXME: allow multiple BTS */ + bts = llist_entry(net->bts_list.next, struct gsm_bts, list); + + switch (msg_type) { + case PCU_IF_MSG_DATA_REQ: + case PCU_IF_MSG_PAG_REQ: + rc = pcu_rx_data_req(bts, msg_type, &pcu_prim->u.data_req); + break; + default: + LOGP(DPCU, LOGL_ERROR, "Received unknwon PCU msg type %d\n", + msg_type); + rc = -EINVAL; + } + + return rc; +} + +/* + * PCU socket interface + */ + +static int pcu_sock_send(struct gsm_bts *bts, struct msgb *msg) +{ + struct pcu_sock_state *state = bts->pcu_state; + struct osmo_fd *conn_bfd; + struct gsm_pcu_if *pcu_prim = (struct gsm_pcu_if *) msg->data; + + if (!state) { + if (pcu_prim->msg_type != PCU_IF_MSG_TIME_IND) + LOGP(DPCU, LOGL_INFO, "PCU socket not created, " + "dropping message\n"); + msgb_free(msg); + return -EINVAL; + } + conn_bfd = &state->conn_bfd; + if (conn_bfd->fd <= 0) { + if (pcu_prim->msg_type != PCU_IF_MSG_TIME_IND) + LOGP(DPCU, LOGL_NOTICE, "PCU socket not connected, " + "dropping message\n"); + msgb_free(msg); + return -EIO; + } + msgb_enqueue(&state->upqueue, msg); + conn_bfd->when |= BSC_FD_WRITE; + + return 0; +} + +static void pcu_sock_close(struct pcu_sock_state *state) +{ + struct osmo_fd *bfd = &state->conn_bfd; + struct gsm_bts *bts; + struct gsm_bts_trx *trx; + struct gsm_bts_trx_ts *ts; + int i, j; + + /* FIXME: allow multiple BTS */ + bts = llist_entry(state->net->bts_list.next, struct gsm_bts, list); + + LOGP(DPCU, LOGL_NOTICE, "PCU socket has LOST connection\n"); + + close(bfd->fd); + bfd->fd = -1; + osmo_fd_unregister(bfd); + + /* re-enable the generation of ACCEPT for new connections */ + state->listen_bfd.when |= BSC_FD_READ; + +#if 0 + /* remove si13, ... */ + bts->si_valid &= ~(1 << SYSINFO_TYPE_13); + osmo_signal_dispatch(SS_GLOBAL, S_NEW_SYSINFO, bts); +#endif + + /* release PDCH */ + for (i = 0; i < 8; i++) { + trx = gsm_bts_trx_num(bts, i); + if (!trx) + break; + for (j = 0; j < 8; j++) { + ts = &trx->ts[j]; + if (ts->mo.nm_state.operational == NM_OPSTATE_ENABLED + && ts->pchan == GSM_PCHAN_PDCH) { + printf("l1sap_chan_rel(trx,gsm_lchan2chan_nr(ts->lchan));\n"); + } + } + } + + /* flush the queue */ + while (!llist_empty(&state->upqueue)) { + struct msgb *msg = msgb_dequeue(&state->upqueue); + msgb_free(msg); + } +} + +static int pcu_sock_read(struct osmo_fd *bfd) +{ + struct pcu_sock_state *state = (struct pcu_sock_state *)bfd->data; + struct gsm_pcu_if *pcu_prim; + struct msgb *msg; + int rc; + + msg = msgb_alloc(sizeof(*pcu_prim), "pcu_sock_rx"); + if (!msg) + return -ENOMEM; + + pcu_prim = (struct gsm_pcu_if *) msg->tail; + + rc = recv(bfd->fd, msg->tail, msgb_tailroom(msg), 0); + if (rc == 0) + goto close; + + if (rc < 0) { + if (errno == EAGAIN) + return 0; + goto close; + } + + rc = pcu_rx(state->net, pcu_prim->msg_type, pcu_prim); + + /* as we always synchronously process the message in pcu_rx() and + * its callbacks, we can free the message here. */ + msgb_free(msg); + + return rc; + +close: + msgb_free(msg); + pcu_sock_close(state); + return -1; +} + +static int pcu_sock_write(struct osmo_fd *bfd) +{ + struct pcu_sock_state *state = bfd->data; + int rc; + + while (!llist_empty(&state->upqueue)) { + struct msgb *msg, *msg2; + struct gsm_pcu_if *pcu_prim; + + /* peek at the beginning of the queue */ + msg = llist_entry(state->upqueue.next, struct msgb, list); + pcu_prim = (struct gsm_pcu_if *)msg->data; + + bfd->when &= ~BSC_FD_WRITE; + + /* bug hunter 8-): maybe someone forgot msgb_put(...) ? */ + if (!msgb_length(msg)) { + LOGP(DPCU, LOGL_ERROR, "message type (%d) with ZERO " + "bytes!\n", pcu_prim->msg_type); + goto dontsend; + } + + /* try to send it over the socket */ + rc = write(bfd->fd, msgb_data(msg), msgb_length(msg)); + if (rc == 0) + goto close; + if (rc < 0) { + if (errno == EAGAIN) { + bfd->when |= BSC_FD_WRITE; + break; + } + goto close; + } + +dontsend: + /* _after_ we send it, we can deueue */ + msg2 = msgb_dequeue(&state->upqueue); + assert(msg == msg2); + msgb_free(msg); + } + return 0; + +close: + pcu_sock_close(state); + + return -1; +} + +static int pcu_sock_cb(struct osmo_fd *bfd, unsigned int flags) +{ + int rc = 0; + + if (flags & BSC_FD_READ) + rc = pcu_sock_read(bfd); + if (rc < 0) + return rc; + + if (flags & BSC_FD_WRITE) + rc = pcu_sock_write(bfd); + + return rc; +} + +/* accept connection comming from PCU */ +static int pcu_sock_accept(struct osmo_fd *bfd, unsigned int flags) +{ + struct pcu_sock_state *state = (struct pcu_sock_state *)bfd->data; + struct osmo_fd *conn_bfd = &state->conn_bfd; + struct sockaddr_un un_addr; + socklen_t len; + int rc; + + len = sizeof(un_addr); + rc = accept(bfd->fd, (struct sockaddr *) &un_addr, &len); + if (rc < 0) { + LOGP(DPCU, LOGL_ERROR, "Failed to accept a new connection\n"); + return -1; + } + + if (conn_bfd->fd >= 0) { + LOGP(DPCU, LOGL_NOTICE, "PCU connects but we already have " + "another active connection ?!?\n"); + /* We already have one PCU connected, this is all we support */ + state->listen_bfd.when &= ~BSC_FD_READ; + close(rc); + return 0; + } + + conn_bfd->fd = rc; + conn_bfd->when = BSC_FD_READ; + conn_bfd->cb = pcu_sock_cb; + conn_bfd->data = state; + + if (osmo_fd_register(conn_bfd) != 0) { + LOGP(DPCU, LOGL_ERROR, "Failed to register new connection " + "fd\n"); + close(conn_bfd->fd); + conn_bfd->fd = -1; + return -1; + } + + LOGP(DPCU, LOGL_NOTICE, "PCU socket connected to external PCU\n"); + + return 0; +} + +/* Open connection to PCU */ +int pcu_sock_init(const char *path, struct gsm_bts *bts) +{ + struct pcu_sock_state *state; + struct osmo_fd *bfd; + int rc; + + state = talloc_zero(NULL, struct pcu_sock_state); + if (!state) + return -ENOMEM; + + INIT_LLIST_HEAD(&state->upqueue); + state->net = bts->network; + state->conn_bfd.fd = -1; + + bfd = &state->listen_bfd; + + bfd->fd = osmo_sock_unix_init(SOCK_SEQPACKET, 0, path, + OSMO_SOCK_F_BIND); + if (bfd->fd < 0) { + LOGP(DPCU, LOGL_ERROR, "Could not create unix socket: %s\n", + strerror(errno)); + talloc_free(state); + return -1; + } + + bfd->when = BSC_FD_READ; + bfd->cb = pcu_sock_accept; + bfd->data = state; + + rc = osmo_fd_register(bfd); + if (rc < 0) { + LOGP(DPCU, LOGL_ERROR, "Could not register listen fd: %d\n", + rc); + close(bfd->fd); + talloc_free(state); + return rc; + } + + bts->pcu_state = state; + return 0; +} + +/* Close connection to PCU */ +void pcu_sock_exit(struct gsm_bts *bts) +{ + struct pcu_sock_state *state = bts->pcu_state; + struct osmo_fd *bfd, *conn_bfd; + + if (!state) + return; + + conn_bfd = &state->conn_bfd; + if (conn_bfd->fd > 0) + pcu_sock_close(state); + bfd = &state->listen_bfd; + close(bfd->fd); + osmo_fd_unregister(bfd); + talloc_free(state); + bts->pcu_state = NULL; +} + diff --git a/src/osmo-bsc/penalty_timers.c b/src/osmo-bsc/penalty_timers.c new file mode 100644 index 000000000..b80fec946 --- /dev/null +++ b/src/osmo-bsc/penalty_timers.c @@ -0,0 +1,129 @@ +/* (C) 2018 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> + * + * All Rights Reserved + * + * Author: Neels Hofmeyr <nhofmeyr@sysmocom.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/>. + * + */ + +#include <talloc.h> +#include <time.h> +#include <stdint.h> + +#include <osmocom/core/linuxlist.h> + +#include <osmocom/bsc/penalty_timers.h> +#include <osmocom/bsc/gsm_data.h> + +struct penalty_timers { + struct llist_head timers; +}; + +struct penalty_timer { + struct llist_head entry; + void *for_object; + unsigned int timeout; +}; + +static unsigned int time_now(void) +{ + time_t now; + time(&now); + /* FIXME: use monotonic clock */ + return (unsigned int)now; +} + +struct penalty_timers *penalty_timers_init(void *ctx) +{ + struct penalty_timers *pt = talloc_zero(ctx, struct penalty_timers); + if (!pt) + return NULL; + INIT_LLIST_HEAD(&pt->timers); + return pt; +} + +void penalty_timers_add(struct penalty_timers *pt, void *for_object, int timeout) +{ + struct penalty_timer *timer; + unsigned int now; + unsigned int then; + now = time_now(); + + if (timeout <= 0) + return; + + then = now + timeout; + + /* timer already running for that BTS? */ + llist_for_each_entry(timer, &pt->timers, entry) { + if (timer->for_object != for_object) + continue; + /* raise, if running timer will timeout earlier or has timed + * out already, otherwise keep later timeout */ + if (timer->timeout < then) + timer->timeout = then; + return; + } + + /* add new timer */ + timer = talloc_zero(pt, struct penalty_timer); + if (!timer) + return; + + timer->for_object = for_object; + timer->timeout = then; + + llist_add_tail(&timer->entry, &pt->timers); +} + +unsigned int penalty_timers_remaining(struct penalty_timers *pt, void *for_object) +{ + struct penalty_timer *timer; + unsigned int now = time_now(); + unsigned int max_remaining = 0; + llist_for_each_entry(timer, &pt->timers, entry) { + unsigned int remaining; + if (timer->for_object != for_object) + continue; + if (now >= timer->timeout) + continue; + remaining = timer->timeout - now; + if (remaining > max_remaining) + max_remaining = remaining; + } + return max_remaining; +} + +void penalty_timers_clear(struct penalty_timers *pt, void *for_object) +{ + struct penalty_timer *timer, *timer2; + llist_for_each_entry_safe(timer, timer2, &pt->timers, entry) { + if (for_object && timer->for_object != for_object) + continue; + llist_del(&timer->entry); + talloc_free(timer); + } +} + +void penalty_timers_free(struct penalty_timers **pt_p) +{ + struct penalty_timers *pt = *pt_p; + if (!pt) + return; + penalty_timers_clear(pt, NULL); + talloc_free(pt); + *pt_p = NULL; +} diff --git a/src/osmo-bsc/rest_octets.c b/src/osmo-bsc/rest_octets.c new file mode 100644 index 000000000..9f2b4c0ab --- /dev/null +++ b/src/osmo-bsc/rest_octets.c @@ -0,0 +1,878 @@ +/* GSM Mobile Radio Interface Layer 3 messages on the A-bis interface, + * rest octet handling according to + * 3GPP TS 04.08 version 7.21.0 Release 1998 / ETSI TS 100 940 V7.21.0 */ + +/* (C) 2009 by Harald Welte <laforge@gnumonks.org> + * + * 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 <string.h> +#include <stdlib.h> +#include <errno.h> +#include <stdbool.h> + +#include <osmocom/bsc/debug.h> +#include <osmocom/bsc/gsm_data.h> +#include <osmocom/core/bitvec.h> +#include <osmocom/gsm/bitvec_gsm.h> +#include <osmocom/bsc/rest_octets.h> +#include <osmocom/bsc/arfcn_range_encode.h> +#include <osmocom/bsc/system_information.h> + +/* generate SI1 rest octets */ +int rest_octets_si1(uint8_t *data, uint8_t *nch_pos, int is1800_net) +{ + struct bitvec bv; + + memset(&bv, 0, sizeof(bv)); + bv.data = data; + bv.data_len = 1; + + if (nch_pos) { + bitvec_set_bit(&bv, H); + bitvec_set_uint(&bv, *nch_pos, 5); + } else + bitvec_set_bit(&bv, L); + + if (is1800_net) + bitvec_set_bit(&bv, L); + else + bitvec_set_bit(&bv, H); + + bitvec_spare_padding(&bv, 6); + return bv.data_len; +} + +/* Append Repeated E-UTRAN Neighbour Cell to bitvec: see 3GPP TS 44.018 Table 10.5.2.33b.1 */ +static inline bool append_eutran_neib_cell(struct bitvec *bv, struct gsm_bts *bts, uint8_t budget) +{ + const struct osmo_earfcn_si2q *e = &bts->si_common.si2quater_neigh_list; + unsigned i, skip = 0; + size_t offset = bts->e_offset; + int16_t rem = budget - 6; /* account for mandatory stop bit and THRESH_E-UTRAN_high */ + uint8_t earfcn_budget; + + if (budget <= 6) + return false; + + OSMO_ASSERT(budget <= SI2Q_MAX_LEN); + + /* first we have to properly adjust budget requirements */ + if (e->prio_valid) /* E-UTRAN_PRIORITY: 3GPP TS 45.008*/ + rem -= 4; + else + rem--; + + if (e->thresh_lo_valid) /* THRESH_E-UTRAN_low: */ + rem -= 6; + else + rem--; + + if (e->qrxlm_valid) /* E-UTRAN_QRXLEVMIN: */ + rem -= 6; + else + rem--; + + if (rem < 0) + return false; + + /* now we can proceed with actually adding EARFCNs within adjusted budget limit */ + for (i = 0; i < e->length; i++) { + if (e->arfcn[i] != OSMO_EARFCN_INVALID) { + if (skip < offset) { + skip++; /* ignore EARFCNs added on previous calls */ + } else { + earfcn_budget = 17; /* compute budget per-EARFCN */ + if (OSMO_EARFCN_MEAS_INVALID == e->meas_bw[i]) + earfcn_budget++; + else + earfcn_budget += 4; + + if (rem - earfcn_budget < 0) + break; + else { + bts->e_offset++; + rem -= earfcn_budget; + + if (rem < 0) + return false; + + bitvec_set_bit(bv, 1); /* EARFCN: */ + bitvec_set_uint(bv, e->arfcn[i], 16); + + if (OSMO_EARFCN_MEAS_INVALID == e->meas_bw[i]) + bitvec_set_bit(bv, 0); + else { /* Measurement Bandwidth: 9.1.54 */ + bitvec_set_bit(bv, 1); + bitvec_set_uint(bv, e->meas_bw[i], 3); + } + } + } + } + } + + /* stop bit - end of EARFCN + Measurement Bandwidth sequence */ + bitvec_set_bit(bv, 0); + + /* Note: we don't support different EARFCN arrays each with different priority, threshold etc. */ + + if (e->prio_valid) { + /* E-UTRAN_PRIORITY: 3GPP TS 45.008*/ + bitvec_set_bit(bv, 1); + bitvec_set_uint(bv, e->prio, 3); + } else + bitvec_set_bit(bv, 0); + + /* THRESH_E-UTRAN_high */ + bitvec_set_uint(bv, e->thresh_hi, 5); + + if (e->thresh_lo_valid) { + /* THRESH_E-UTRAN_low: */ + bitvec_set_bit(bv, 1); + bitvec_set_uint(bv, e->thresh_lo, 5); + } else + bitvec_set_bit(bv, 0); + + if (e->qrxlm_valid) { + /* E-UTRAN_QRXLEVMIN: */ + bitvec_set_bit(bv, 1); + bitvec_set_uint(bv, e->qrxlm, 5); + } else + bitvec_set_bit(bv, 0); + + return true; +} + +static inline void append_earfcn(struct bitvec *bv, struct gsm_bts *bts, uint8_t budget) +{ + bool appended; + unsigned int old = bv->cur_bit; /* save current position to make rollback possible */ + int rem = budget - 25; + if (rem <= 0) + return; + + OSMO_ASSERT(budget <= SI2Q_MAX_LEN); + + /* Additions in Rel-5: */ + bitvec_set_bit(bv, H); + /* No 3G Additional Measurement Param. Descr. */ + bitvec_set_bit(bv, 0); + /* No 3G ADDITIONAL MEASUREMENT Param. Descr. 2 */ + bitvec_set_bit(bv, 0); + /* Additions in Rel-6: */ + bitvec_set_bit(bv, H); + /* 3G_CCN_ACTIVE */ + bitvec_set_bit(bv, 0); + /* Additions in Rel-7: */ + bitvec_set_bit(bv, H); + /* No 700_REPORTING_OFFSET */ + bitvec_set_bit(bv, 0); + /* No 810_REPORTING_OFFSET */ + bitvec_set_bit(bv, 0); + /* Additions in Rel-8: */ + bitvec_set_bit(bv, H); + + /* Priority and E-UTRAN Parameters Description */ + bitvec_set_bit(bv, 1); + + /* No Serving Cell Priority Parameters Descr. */ + bitvec_set_bit(bv, 0); + /* No 3G Priority Parameters Description */ + bitvec_set_bit(bv, 0); + /* E-UTRAN Parameters Description */ + bitvec_set_bit(bv, 1); + + /* E-UTRAN_CCN_ACTIVE */ + bitvec_set_bit(bv, 0); + /* E-UTRAN_Start: 9.1.54 */ + bitvec_set_bit(bv, 1); + /* E-UTRAN_Stop: 9.1.54 */ + bitvec_set_bit(bv, 1); + + /* No E-UTRAN Measurement Parameters Descr. */ + bitvec_set_bit(bv, 0); + /* No GPRS E-UTRAN Measurement Param. Descr. */ + bitvec_set_bit(bv, 0); + + /* Note: each of next 3 "repeated" structures might be repeated any + (0, 1, 2...) times - we only support 1 and 0 */ + + /* Repeated E-UTRAN Neighbour Cells */ + bitvec_set_bit(bv, 1); + + appended = append_eutran_neib_cell(bv, bts, rem); + if (!appended) { /* appending is impossible within current budget: rollback */ + bv->cur_bit = old; + return; + } + + /* stop bit - end of Repeated E-UTRAN Neighbour Cells sequence: */ + bitvec_set_bit(bv, 0); + + /* Note: following 2 repeated structs are not supported ATM */ + /* stop bit - end of Repeated E-UTRAN Not Allowed Cells sequence: */ + bitvec_set_bit(bv, 0); + /* stop bit - end of Repeated E-UTRAN PCID to TA mapping sequence: */ + bitvec_set_bit(bv, 0); + + /* Priority and E-UTRAN Parameters Description ends here */ + /* No 3G CSG Description */ + bitvec_set_bit(bv, 0); + /* No E-UTRAN CSG Description */ + bitvec_set_bit(bv, 0); + /* No Additions in Rel-9: */ + bitvec_set_bit(bv, L); +} + +static inline int f0_helper(int *sc, size_t length, uint8_t *chan_list) +{ + int w[RANGE_ENC_MAX_ARFCNS] = { 0 }; + + return range_encode(ARFCN_RANGE_1024, sc, length, w, 0, chan_list); +} + +/* Estimate how many bits it'll take to append single FDD UARFCN */ +static inline int append_utran_fdd_length(uint16_t u, const int *sc, size_t sc_len, size_t length) +{ + uint8_t chan_list[16] = { 0 }; + int tmp[sc_len], f0; + + memcpy(tmp, sc, sizeof(tmp)); + + f0 = f0_helper(tmp, length, chan_list); + if (f0 < 0) + return f0; + + return 21 + range1024_p(length); +} + +/* Append single FDD UARFCN */ +static inline int append_utran_fdd(struct bitvec *bv, uint16_t u, int *sc, size_t length) +{ + uint8_t chan_list[16] = { 0 }; + int f0 = f0_helper(sc, length, chan_list); + + if (f0 < 0) + return f0; + + /* Repeated UTRAN FDD Neighbour Cells */ + bitvec_set_bit(bv, 1); + + /* FDD-ARFCN */ + bitvec_set_bit(bv, 0); + bitvec_set_uint(bv, u, 14); + + /* FDD_Indic0: parameter value '0000000000' is a member of the set? */ + bitvec_set_bit(bv, f0); + /* NR_OF_FDD_CELLS */ + bitvec_set_uint(bv, length, 5); + + f0 = bv->cur_bit; + bitvec_add_range1024(bv, (struct gsm48_range_1024 *)chan_list); + bv->cur_bit = f0 + range1024_p(length); + + return 21 + range1024_p(length); +} + +static inline int try_adding_uarfcn(struct bitvec *bv, struct gsm_bts *bts, uint16_t uarfcn, + uint8_t num_sc, uint8_t start_pos, uint8_t budget) +{ + int i, k, rc, a[bts->si_common.uarfcn_length]; + + if (budget < 23) + return -ENOMEM; + + /* copy corresponding Scrambling Codes: range encoder make in-place modifications */ + for (i = start_pos, k = 0; i < num_sc; a[k++] = bts->si_common.data.scramble_list[i++]); + + /* estimate bit length requirements */ + rc = append_utran_fdd_length(uarfcn, a, bts->si_common.uarfcn_length, k); + if (rc < 0) + return rc; /* range encoder failure */ + + if (budget - rc <= 0) + return -ENOMEM; /* we have ran out of budget in current SI2q */ + + /* compute next offset */ + bts->u_offset += k; + + return budget - append_utran_fdd(bv, uarfcn, a, k); +} + +/* Append multiple FDD UARFCNs */ +static inline void append_uarfcns(struct bitvec *bv, struct gsm_bts *bts, uint8_t budget) +{ + const uint16_t *u = bts->si_common.data.uarfcn_list; + int i, rem = budget - 7, st = bts->u_offset; /* account for constant bits right away */ + uint16_t cu = u[bts->u_offset]; /* caller ensures that length is positive */ + + OSMO_ASSERT(budget <= SI2Q_MAX_LEN); + + if (budget <= 7) + return; + + /* 3G Neighbour Cell Description */ + bitvec_set_bit(bv, 1); + /* No Index_Start_3G */ + bitvec_set_bit(bv, 0); + /* No Absolute_Index_Start_EMR */ + bitvec_set_bit(bv, 0); + + /* UTRAN FDD Description */ + bitvec_set_bit(bv, 1); + /* No Bandwidth_FDD */ + bitvec_set_bit(bv, 0); + + for (i = bts->u_offset; i <= bts->si_common.uarfcn_length; i++) + if (u[i] != cu) { /* we've reached new UARFCN */ + rem = try_adding_uarfcn(bv, bts, cu, i, st, rem); + if (rem < 0) + break; + + if (i < bts->si_common.uarfcn_length) { + cu = u[i]; + st = i; + } else + break; + } + + /* stop bit - end of Repeated UTRAN FDD Neighbour Cells */ + bitvec_set_bit(bv, 0); + + /* UTRAN TDD Description */ + bitvec_set_bit(bv, 0); +} + +/* generate SI2quater rest octets: 3GPP TS 44.018 § 10.5.2.33b */ +int rest_octets_si2quater(uint8_t *data, struct gsm_bts *bts) +{ + int rc; + struct bitvec bv; + + if (bts->si2q_count < bts->si2q_index) + return -EINVAL; + + bv.data = data; + bv.data_len = 20; + bitvec_zero(&bv); + + /* BA_IND: Set to '0' as that's what we use for SI2xxx type, + * whereas '1' is used for SI5xxx type messages. The point here + * is to be able to correlate whether a given MS measurement + * report was using the neighbor cells advertised in SI2 or in + * SI5, as those two could very well be different */ + bitvec_set_bit(&bv, 0); + /* 3G_BA_IND */ + bitvec_set_bit(&bv, 1); + /* MP_CHANGE_MARK */ + bitvec_set_bit(&bv, 0); + + /* SI2quater_INDEX */ + bitvec_set_uint(&bv, bts->si2q_index, 4); + /* SI2quater_COUNT */ + bitvec_set_uint(&bv, bts->si2q_count, 4); + + /* No Measurement_Parameters Description */ + bitvec_set_bit(&bv, 0); + /* No GPRS_Real Time Difference Description */ + bitvec_set_bit(&bv, 0); + /* No GPRS_BSIC Description */ + bitvec_set_bit(&bv, 0); + /* No GPRS_REPORT PRIORITY Description */ + bitvec_set_bit(&bv, 0); + /* No GPRS_MEASUREMENT_Parameters Description */ + bitvec_set_bit(&bv, 0); + /* No NC Measurement Parameters */ + bitvec_set_bit(&bv, 0); + /* No extension (length) */ + bitvec_set_bit(&bv, 0); + + rc = SI2Q_MAX_LEN - (bv.cur_bit + 3); + if (rc > 0 && bts->si_common.uarfcn_length - bts->u_offset > 0) + append_uarfcns(&bv, bts, rc); + else /* No 3G Neighbour Cell Description */ + bitvec_set_bit(&bv, 0); + + /* No 3G Measurement Parameters Description */ + bitvec_set_bit(&bv, 0); + /* No GPRS_3G_MEASUREMENT Parameters Descr. */ + bitvec_set_bit(&bv, 0); + + rc = SI2Q_MAX_LEN - bv.cur_bit; + if (rc > 0 && si2q_earfcn_count(&bts->si_common.si2quater_neigh_list) - bts->e_offset > 0) + append_earfcn(&bv, bts, rc); + else /* No Additions in Rel-5: */ + bitvec_set_bit(&bv, L); + + bitvec_spare_padding(&bv, (bv.data_len * 8) - 1); + return bv.data_len; +} + +/* Append selection parameters to bitvec */ +static void append_selection_params(struct bitvec *bv, + const struct gsm48_si_selection_params *sp) +{ + if (sp->present) { + bitvec_set_bit(bv, H); + bitvec_set_bit(bv, sp->cbq); + bitvec_set_uint(bv, sp->cell_resel_off, 6); + bitvec_set_uint(bv, sp->temp_offs, 3); + bitvec_set_uint(bv, sp->penalty_time, 5); + } else + bitvec_set_bit(bv, L); +} + +/* Append power offset to bitvec */ +static void append_power_offset(struct bitvec *bv, + const struct gsm48_si_power_offset *po) +{ + if (po->present) { + bitvec_set_bit(bv, H); + bitvec_set_uint(bv, po->power_offset, 2); + } else + bitvec_set_bit(bv, L); +} + +/* Append GPRS indicator to bitvec */ +static void append_gprs_ind(struct bitvec *bv, + const struct gsm48_si3_gprs_ind *gi) +{ + if (gi->present) { + bitvec_set_bit(bv, H); + bitvec_set_uint(bv, gi->ra_colour, 3); + /* 0 == SI13 in BCCH Norm, 1 == SI13 sent on BCCH Ext */ + bitvec_set_bit(bv, gi->si13_position); + } else + bitvec_set_bit(bv, L); +} + +/* Generate SI2ter Rest Octests 3GPP TS 44.018 Table 10.5.2.33a.1 */ +int rest_octets_si2ter(uint8_t *data) +{ + struct bitvec bv; + + memset(&bv, 0, sizeof(bv)); + bv.data = data; + bv.data_len = 4; + + /* No SI2ter_MP_CHANGE_MARK */ + bitvec_set_bit(&bv, L); + + bitvec_spare_padding(&bv, (bv.data_len * 8) - 1); + + return bv.data_len; +} + +/* Generate SI2bis Rest Octests 3GPP TS 44.018 Table 10.5.2.33.1 */ +int rest_octets_si2bis(uint8_t *data) +{ + struct bitvec bv; + + memset(&bv, 0, sizeof(bv)); + bv.data = data; + bv.data_len = 1; + + bitvec_spare_padding(&bv, (bv.data_len * 8) - 1); + + return bv.data_len; +} + +/* Generate SI3 Rest Octests (Chapter 10.5.2.34 / Table 10.4.72) */ +int rest_octets_si3(uint8_t *data, const struct gsm48_si_ro_info *si3) +{ + struct bitvec bv; + + memset(&bv, 0, sizeof(bv)); + bv.data = data; + bv.data_len = 4; + + /* Optional Selection Parameters */ + append_selection_params(&bv, &si3->selection_params); + + /* Optional Power Offset */ + append_power_offset(&bv, &si3->power_offset); + + /* Do we have a SI2ter on the BCCH? */ + if (si3->si2ter_indicator) + bitvec_set_bit(&bv, H); + else + bitvec_set_bit(&bv, L); + + /* Early Classmark Sending Control */ + if (si3->early_cm_ctrl) + bitvec_set_bit(&bv, H); + else + bitvec_set_bit(&bv, L); + + /* Do we have a SI Type 9 on the BCCH? */ + if (si3->scheduling.present) { + bitvec_set_bit(&bv, H); + bitvec_set_uint(&bv, si3->scheduling.where, 3); + } else + bitvec_set_bit(&bv, L); + + /* GPRS Indicator */ + append_gprs_ind(&bv, &si3->gprs_ind); + + /* 3G Early Classmark Sending Restriction. If H, then controlled by + * early_cm_ctrl above */ + if (si3->early_cm_restrict_3g) + bitvec_set_bit(&bv, L); + else + bitvec_set_bit(&bv, H); + + if (si3->si2quater_indicator) { + bitvec_set_bit(&bv, H); /* indicator struct present */ + bitvec_set_uint(&bv, 0, 1); /* message is sent on BCCH Norm */ + } + + bitvec_spare_padding(&bv, (bv.data_len*8)-1); + return bv.data_len; +} + +static int append_lsa_params(struct bitvec *bv, + const struct gsm48_lsa_params *lsa_params) +{ + /* FIXME */ + return -1; +} + +/* Generate SI4 Rest Octets (Chapter 10.5.2.35) */ +int rest_octets_si4(uint8_t *data, const struct gsm48_si_ro_info *si4, int len) +{ + struct bitvec bv; + + memset(&bv, 0, sizeof(bv)); + bv.data = data; + bv.data_len = len; + + /* SI4 Rest Octets O */ + append_selection_params(&bv, &si4->selection_params); + append_power_offset(&bv, &si4->power_offset); + append_gprs_ind(&bv, &si4->gprs_ind); + + if (0 /* FIXME */) { + /* H and SI4 Rest Octets S */ + bitvec_set_bit(&bv, H); + + /* LSA Parameters */ + if (si4->lsa_params.present) { + bitvec_set_bit(&bv, H); + append_lsa_params(&bv, &si4->lsa_params); + } else + bitvec_set_bit(&bv, L); + + /* Cell Identity */ + if (1) { + bitvec_set_bit(&bv, H); + bitvec_set_uint(&bv, si4->cell_id, 16); + } else + bitvec_set_bit(&bv, L); + + /* LSA ID Information */ + if (0) { + bitvec_set_bit(&bv, H); + /* FIXME */ + } else + bitvec_set_bit(&bv, L); + } else { + /* L and break indicator */ + bitvec_set_bit(&bv, L); + bitvec_set_bit(&bv, si4->break_ind ? H : L); + } + + return bv.data_len; +} + + +/* GSM 04.18 ETSI TS 101 503 V8.27.0 (2006-05) + +<SI6 rest octets> ::= +{L | H <PCH and NCH info>} +{L | H <VBS/VGCS options : bit(2)>} +{ < DTM_support : bit == L > I < DTM_support : bit == H > +< RAC : bit (8) > +< MAX_LAPDm : bit (3) > } +< Band indicator > +{ L | H < GPRS_MS_TXPWR_MAX_CCH : bit (5) > } +<implicit spare >; +*/ +int rest_octets_si6(uint8_t *data, bool is1800_net) +{ + struct bitvec bv; + + memset(&bv, 0, sizeof(bv)); + bv.data = data; + bv.data_len = 1; + + /* no PCH/NCH info */ + bitvec_set_bit(&bv, L); + /* no VBS/VGCS options */ + bitvec_set_bit(&bv, L); + /* no DTM_support */ + bitvec_set_bit(&bv, L); + /* band indicator */ + if (is1800_net) + bitvec_set_bit(&bv, L); + else + bitvec_set_bit(&bv, H); + /* no GPRS_MS_TXPWR_MAX_CCH */ + bitvec_set_bit(&bv, L); + + bitvec_spare_padding(&bv, (bv.data_len * 8) - 1); + return bv.data_len; +} + +/* GPRS Mobile Allocation as per TS 04.60 Chapter 12.10a: + < GPRS Mobile Allocation IE > ::= + < HSN : bit (6) > + { 0 | 1 < RFL number list : < RFL number list struct > > } + { 0 < MA_LENGTH : bit (6) > + < MA_BITMAP: bit (val(MA_LENGTH) + 1) > + | 1 { 0 | 1 <ARFCN index list : < ARFCN index list struct > > } } ; + + < RFL number list struct > :: = + < RFL_NUMBER : bit (4) > + { 0 | 1 < RFL number list struct > } ; + < ARFCN index list struct > ::= + < ARFCN_INDEX : bit(6) > + { 0 | 1 < ARFCN index list struct > } ; + */ +static int append_gprs_mobile_alloc(struct bitvec *bv) +{ + /* Hopping Sequence Number */ + bitvec_set_uint(bv, 0, 6); + + if (0) { + /* We want to use a RFL number list */ + bitvec_set_bit(bv, 1); + /* FIXME: RFL number list */ + } else + bitvec_set_bit(bv, 0); + + if (0) { + /* We want to use a MA_BITMAP */ + bitvec_set_bit(bv, 0); + /* FIXME: MA_LENGTH, MA_BITMAP, ... */ + } else { + bitvec_set_bit(bv, 1); + if (0) { + /* We want to provide an ARFCN index list */ + bitvec_set_bit(bv, 1); + /* FIXME */ + } else + bitvec_set_bit(bv, 0); + } + return 0; +} + +static int encode_t3192(unsigned int t3192) +{ + /* See also 3GPP TS 44.060 + Table 12.24.2: GPRS Cell Options information element details */ + if (t3192 == 0) + return 3; + else if (t3192 <= 80) + return 4; + else if (t3192 <= 120) + return 5; + else if (t3192 <= 160) + return 6; + else if (t3192 <= 200) + return 7; + else if (t3192 <= 500) + return 0; + else if (t3192 <= 1000) + return 1; + else if (t3192 <= 1500) + return 2; + else + return -EINVAL; +} + +static int encode_drx_timer(unsigned int drx) +{ + if (drx == 0) + return 0; + else if (drx == 1) + return 1; + else if (drx == 2) + return 2; + else if (drx <= 4) + return 3; + else if (drx <= 8) + return 4; + else if (drx <= 16) + return 5; + else if (drx <= 32) + return 6; + else if (drx <= 64) + return 7; + else + return -EINVAL; +} + +/* GPRS Cell Options as per TS 04.60 Chapter 12.24 + < GPRS Cell Options IE > ::= + < NMO : bit(2) > + < T3168 : bit(3) > + < T3192 : bit(3) > + < DRX_TIMER_MAX: bit(3) > + < ACCESS_BURST_TYPE: bit > + < CONTROL_ACK_TYPE : bit > + < BS_CV_MAX: bit(4) > + { 0 | 1 < PAN_DEC : bit(3) > + < PAN_INC : bit(3) > + < PAN_MAX : bit(3) > + { 0 | 1 < Extension Length : bit(6) > + < bit (val(Extension Length) + 1 + & { < Extension Information > ! { bit ** = <no string> } } ; + < Extension Information > ::= + { 0 | 1 < EGPRS_PACKET_CHANNEL_REQUEST : bit > + < BEP_PERIOD : bit(4) > } + < PFC_FEATURE_MODE : bit > + < DTM_SUPPORT : bit > + <BSS_PAGING_COORDINATION: bit > + <spare bit > ** ; + */ +static int append_gprs_cell_opt(struct bitvec *bv, + const struct gprs_cell_options *gco) +{ + int t3192, drx_timer_max; + + t3192 = encode_t3192(gco->t3192); + if (t3192 < 0) + return t3192; + + drx_timer_max = encode_drx_timer(gco->drx_timer_max); + if (drx_timer_max < 0) + return drx_timer_max; + + bitvec_set_uint(bv, gco->nmo, 2); + + /* See also 3GPP TS 44.060 + Table 12.24.2: GPRS Cell Options information element details */ + bitvec_set_uint(bv, gco->t3168 / 500 - 1, 3); + + bitvec_set_uint(bv, t3192, 3); + bitvec_set_uint(bv, drx_timer_max, 3); + /* ACCESS_BURST_TYPE: Hard-code 8bit */ + bitvec_set_bit(bv, 0); + /* CONTROL_ACK_TYPE: */ + bitvec_set_bit(bv, gco->ctrl_ack_type_use_block); + bitvec_set_uint(bv, gco->bs_cv_max, 4); + + if (0) { + /* hard-code no PAN_{DEC,INC,MAX} */ + bitvec_set_bit(bv, 0); + } else { + /* copied from ip.access BSC protocol trace */ + bitvec_set_bit(bv, 1); + bitvec_set_uint(bv, 1, 3); /* DEC */ + bitvec_set_uint(bv, 1, 3); /* INC */ + bitvec_set_uint(bv, 15, 3); /* MAX */ + } + + if (!gco->ext_info_present) { + /* no extension information */ + bitvec_set_bit(bv, 0); + } else { + /* extension information */ + bitvec_set_bit(bv, 1); + if (!gco->ext_info.egprs_supported) { + /* 6bit length of extension */ + bitvec_set_uint(bv, (1 + 3)-1, 6); + /* EGPRS supported in the cell */ + bitvec_set_bit(bv, 0); + } else { + /* 6bit length of extension */ + bitvec_set_uint(bv, (1 + 5 + 3)-1, 6); + /* EGPRS supported in the cell */ + bitvec_set_bit(bv, 1); + + /* 1bit EGPRS PACKET CHANNEL REQUEST */ + if (gco->supports_egprs_11bit_rach == 0) { + bitvec_set_bit(bv, + gco->ext_info.use_egprs_p_ch_req); + } else { + bitvec_set_bit(bv, 0); + } + + /* 4bit BEP PERIOD */ + bitvec_set_uint(bv, gco->ext_info.bep_period, 4); + } + bitvec_set_bit(bv, gco->ext_info.pfc_supported); + bitvec_set_bit(bv, gco->ext_info.dtm_supported); + bitvec_set_bit(bv, gco->ext_info.bss_paging_coordination); + } + + return 0; +} + +static void append_gprs_pwr_ctrl_pars(struct bitvec *bv, + const struct gprs_power_ctrl_pars *pcp) +{ + bitvec_set_uint(bv, pcp->alpha, 4); + bitvec_set_uint(bv, pcp->t_avg_w, 5); + bitvec_set_uint(bv, pcp->t_avg_t, 5); + bitvec_set_uint(bv, pcp->pc_meas_chan, 1); + bitvec_set_uint(bv, pcp->n_avg_i, 4); +} + +/* Generate SI13 Rest Octests (04.08 Chapter 10.5.2.37b) */ +int rest_octets_si13(uint8_t *data, const struct gsm48_si13_info *si13) +{ + struct bitvec bv; + + memset(&bv, 0, sizeof(bv)); + bv.data = data; + bv.data_len = 20; + + if (0) { + /* No rest octets */ + bitvec_set_bit(&bv, L); + } else { + bitvec_set_bit(&bv, H); + bitvec_set_uint(&bv, si13->bcch_change_mark, 3); + bitvec_set_uint(&bv, si13->si_change_field, 4); + if (1) { + bitvec_set_bit(&bv, 0); + } else { + bitvec_set_bit(&bv, 1); + bitvec_set_uint(&bv, si13->bcch_change_mark, 2); + append_gprs_mobile_alloc(&bv); + } + /* PBCCH not present in cell: + it shall never be indicated according to 3GPP TS 44.018 Table 10.5.2.37b.1 */ + bitvec_set_bit(&bv, 0); + bitvec_set_uint(&bv, si13->rac, 8); + bitvec_set_bit(&bv, si13->spgc_ccch_sup); + bitvec_set_uint(&bv, si13->prio_acc_thr, 3); + bitvec_set_uint(&bv, si13->net_ctrl_ord, 2); + append_gprs_cell_opt(&bv, &si13->cell_opts); + append_gprs_pwr_ctrl_pars(&bv, &si13->pwr_ctrl_pars); + + /* 3GPP TS 44.018 Release 6 / 10.5.2.37b */ + bitvec_set_bit(&bv, H); /* added Release 99 */ + /* claim our SGSN is compatible with Release 99, as EDGE and EGPRS + * was only added in this Release */ + bitvec_set_bit(&bv, 1); + } + bitvec_spare_padding(&bv, (bv.data_len*8)-1); + return bv.data_len; +} diff --git a/src/osmo-bsc/system_information.c b/src/osmo-bsc/system_information.c new file mode 100644 index 000000000..d99153f24 --- /dev/null +++ b/src/osmo-bsc/system_information.c @@ -0,0 +1,1210 @@ +/* GSM 04.08 System Information (SI) encoding and decoding + * 3GPP TS 04.08 version 7.21.0 Release 1998 / ETSI TS 100 940 V7.21.0 */ + +/* (C) 2008-2010 by Harald Welte <laforge@gnumonks.org> + * (C) 2012 Holger Hans Peter Freyther + * + * 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 <errno.h> +#include <string.h> +#include <stdio.h> +#include <netinet/in.h> +#include <stdbool.h> + +#include <osmocom/core/bitvec.h> +#include <osmocom/core/utils.h> +#include <osmocom/gsm/sysinfo.h> +#include <osmocom/gsm/gsm48_ie.h> +#include <osmocom/gsm/gsm48.h> + +#include <osmocom/bsc/debug.h> +#include <osmocom/bsc/gsm_data.h> +#include <osmocom/bsc/abis_rsl.h> +#include <osmocom/bsc/rest_octets.h> +#include <osmocom/bsc/arfcn_range_encode.h> +#include <osmocom/bsc/gsm_04_08_utils.h> +#include <osmocom/bsc/acc_ramp.h> + +/* + * DCS1800 and PCS1900 have overlapping ARFCNs. We would need to set the + * ARFCN_PCS flag on the 1900 ARFCNs but this would increase cell_alloc + * and other arrays to make sure (ARFCN_PCS + 1024)/8 ARFCNs fit into the + * array. DCS1800 and PCS1900 can not be used at the same time so conserve + * memory and do the below. + */ +static int band_compatible(const struct gsm_bts *bts, int arfcn) +{ + enum gsm_band band = gsm_arfcn2band(arfcn); + + /* normal case */ + if (band == bts->band) + return 1; + /* deal with ARFCN_PCS not set */ + if (band == GSM_BAND_1800 && bts->band == GSM_BAND_1900) + return 1; + + return 0; +} + +static int is_dcs_net(const struct gsm_bts *bts) +{ + if (bts->band == GSM_BAND_850) + return 0; + if (bts->band == GSM_BAND_1900) + return 0; + return 1; +} + +/* Return p(n) for given NR_OF_TDD_CELLS - see Table 9.1.54.1a, 3GPP TS 44.018 */ +unsigned range1024_p(unsigned n) +{ + switch (n) { + case 0: return 0; + case 1: return 10; + case 2: return 19; + case 3: return 28; + case 4: return 36; + case 5: return 44; + case 6: return 52; + case 7: return 60; + case 8: return 67; + case 9: return 74; + case 10: return 81; + case 11: return 88; + case 12: return 95; + case 13: return 102; + case 14: return 109; + case 15: return 116; + case 16: return 122; + default: return 0; + } +} + +/* Return q(m) for given NR_OF_TDD_CELLS - see Table 9.1.54.1b, 3GPP TS 44.018 */ +unsigned range512_q(unsigned m) +{ + switch (m) { + case 0: return 0; + case 1: return 9; + case 2: return 17; + case 3: return 25; + case 4: return 32; + case 5: return 39; + case 6: return 46; + case 7: return 53; + case 8: return 59; + case 9: return 65; + case 10: return 71; + case 11: return 77; + case 12: return 83; + case 13: return 89; + case 14: return 95; + case 15: return 101; + case 16: return 106; + case 17: return 111; + case 18: return 116; + case 19: return 121; + case 20: return 126; + default: return 0; + } +} + +size_t si2q_earfcn_count(const struct osmo_earfcn_si2q *e) +{ + unsigned i, ret = 0; + + if (!e) + return 0; + + for (i = 0; i < e->length; i++) + if (e->arfcn[i] != OSMO_EARFCN_INVALID) + ret++; + + return ret; +} + +/* generate SI2quater messages, return rest octets length of last generated message or negative error code */ +static int make_si2quaters(struct gsm_bts *bts, bool counting) +{ + int rc; + bool memory_exceeded = true; + struct gsm48_system_information_type_2quater *si2q; + + for (bts->si2q_index = 0; bts->si2q_index < SI2Q_MAX_NUM; bts->si2q_index++) { + si2q = GSM_BTS_SI2Q(bts, bts->si2q_index); + if (counting) { /* that's legitimate if we're called for counting purpose: */ + if (bts->si2q_count < bts->si2q_index) + bts->si2q_count = bts->si2q_index; + } else { + memset(si2q, GSM_MACBLOCK_PADDING, GSM_MACBLOCK_LEN); + + si2q->header.l2_plen = GSM48_LEN2PLEN(22); + si2q->header.rr_protocol_discriminator = GSM48_PDISC_RR; + si2q->header.skip_indicator = 0; + si2q->header.system_information = GSM48_MT_RR_SYSINFO_2quater; + } + + rc = rest_octets_si2quater(si2q->rest_octets, bts); + if (rc < 0) + return rc; + + if (bts->u_offset >= bts->si_common.uarfcn_length && + bts->e_offset >= si2q_earfcn_count(&bts->si_common.si2quater_neigh_list)) { + memory_exceeded = false; + break; + } + } + + if (memory_exceeded) + return -ENOMEM; + + return rc; +} + +/* we generate SI2q rest octets twice to get proper estimation but it's one time cost anyway */ +uint8_t si2q_num(struct gsm_bts *bts) +{ + int rc = make_si2quaters(bts, true); + uint8_t num = bts->si2q_index + 1; /* number of SI2quater messages */ + + /* N. B: si2q_num() should NEVER be called during actual SI2q rest octets generation + we're not re-entrant because of the following code: */ + bts->u_offset = 0; + bts->e_offset = 0; + + if (rc < 0) + return 0xFF; /* return impossible index as an indicator of error in generating SI2quater */ + + return num; +} + +/* 3GPP TS 44.018, Table 9.1.54.1 - prepend diversity bit to scrambling code */ +static inline uint16_t encode_fdd(uint16_t scramble, bool diversity) +{ + if (diversity) + return scramble | (1 << 9); + return scramble; +} + +int bts_earfcn_add(struct gsm_bts *bts, uint16_t earfcn, uint8_t thresh_hi, uint8_t thresh_lo, uint8_t prio, + uint8_t qrx, uint8_t meas_bw) +{ + struct osmo_earfcn_si2q *e = &bts->si_common.si2quater_neigh_list; + int r = osmo_earfcn_add(e, earfcn, (meas_bw < EARFCN_MEAS_BW_INVALID) ? meas_bw : OSMO_EARFCN_MEAS_INVALID); + + if (r < 0) + return r; + + if (e->thresh_hi && thresh_hi != e->thresh_hi) + r = 1; + + e->thresh_hi = thresh_hi; + + if (thresh_lo != EARFCN_THRESH_LOW_INVALID) { + if (e->thresh_lo_valid && e->thresh_lo != thresh_lo) + r = EARFCN_THRESH_LOW_INVALID; + e->thresh_lo = thresh_lo; + e->thresh_lo_valid = true; + } + + if (qrx != EARFCN_QRXLV_INVALID) { + if (e->qrxlm_valid && e->qrxlm != qrx) + r = EARFCN_QRXLV_INVALID + 1; + e->qrxlm = qrx; + e->qrxlm_valid = true; + } + + if (prio != EARFCN_PRIO_INVALID) { + if (e->prio_valid && e->prio != prio) + r = EARFCN_PRIO_INVALID; + e->prio = prio; + e->prio_valid = true; + } + + return r; +} + +/* Scrambling Code as defined in 3GPP TS 25.213 is 9 bit long so number below is unreacheable upper bound */ +#define SC_BOUND 600 + +/* Find position for a given UARFCN (take SC into consideration if it's available) in a sorted list + N. B: we rely on the assumption that (uarfcn, scramble) tuple is unique in the lists */ +static int uarfcn_sc_pos(const struct gsm_bts *bts, uint16_t uarfcn, uint16_t scramble) +{ + const uint16_t *sc = bts->si_common.data.scramble_list; + uint16_t i, scramble0 = encode_fdd(scramble, false), scramble1 = encode_fdd(scramble, true); + for (i = 0; i < bts->si_common.uarfcn_length; i++) + if (uarfcn == bts->si_common.data.uarfcn_list[i]) { + if (scramble < SC_BOUND) { + if (scramble0 == sc[i] || scramble1 == sc[i]) + return i; + } else + return i; + } + + return -1; +} + +int bts_uarfcn_del(struct gsm_bts *bts, uint16_t arfcn, uint16_t scramble) +{ + uint16_t *ual = bts->si_common.data.uarfcn_list, *scl = bts->si_common.data.scramble_list; + size_t len = bts->si_common.uarfcn_length; + int pos = uarfcn_sc_pos(bts, arfcn, scramble); + + if (pos < 0) + return -EINVAL; + + if (pos != len - 1) { /* move the tail if necessary */ + memmove(ual + pos, ual + pos + 1, 2 * (len - pos + 1)); + memmove(scl + pos, scl + pos + 1, 2 * (len - pos + 1)); + } + + bts->si_common.uarfcn_length--; + return 0; +} + +int bts_uarfcn_add(struct gsm_bts *bts, uint16_t arfcn, uint16_t scramble, bool diversity) +{ + size_t len = bts->si_common.uarfcn_length, i; + uint8_t si2q; + int pos = uarfcn_sc_pos(bts, arfcn, scramble); + uint16_t scr = diversity ? encode_fdd(scramble, true) : encode_fdd(scramble, false), + *ual = bts->si_common.data.uarfcn_list, + *scl = bts->si_common.data.scramble_list; + + if (len == MAX_EARFCN_LIST) + return -ENOMEM; + + if (pos >= 0) + return -EADDRINUSE; + + /* find the suitable position for arfcn if any */ + pos = uarfcn_sc_pos(bts, arfcn, SC_BOUND); + i = (pos < 0) ? len : pos; + + /* move the tail to make space for inserting if necessary */ + if (i < len) { + memmove(ual + i + 1, ual + i, (len - i) * 2); + memmove(scl + i + 1, scl + i, (len - i) * 2); + } + + /* insert into appropriate position */ + ual[i] = arfcn; + scl[i] = scr; + bts->si_common.uarfcn_length++; + /* try to generate SI2q */ + si2q = si2q_num(bts); + + if (si2q <= SI2Q_MAX_NUM) { + bts->si2q_count = si2q - 1; + return 0; + } + + /* rollback after unsuccessful generation */ + bts_uarfcn_del(bts, arfcn, scramble); + return -ENOSPC; +} + +static inline int use_arfcn(const struct gsm_bts *bts, const bool bis, const bool ter, + const bool pgsm, const int arfcn) +{ + if (bts->force_combined_si) + return !bis && !ter; + if (!bis && !ter && band_compatible(bts, arfcn)) + return 1; + /* Correct but somehow broken with either the nanoBTS or the iPhone5 */ + if (bis && pgsm && band_compatible(bts, arfcn) && (arfcn < 1 || arfcn > 124)) + return 1; + if (ter && !band_compatible(bts, arfcn)) + return 1; + return 0; +} + +/* Frequency Lists as per TS 04.08 10.5.2.13 */ + +/* 10.5.2.13.2: Bit map 0 format */ +static int freq_list_bm0_set_arfcn(uint8_t *chan_list, unsigned int arfcn) +{ + unsigned int byte, bit; + + if (arfcn > 124 || arfcn < 1) { + LOGP(DRR, LOGL_ERROR, "Bitmap 0 only supports ARFCN 1...124\n"); + return -EINVAL; + } + + /* the bitmask is from 1..124, not from 0..123 */ + arfcn--; + + byte = arfcn / 8; + bit = arfcn % 8; + + chan_list[GSM48_CELL_CHAN_DESC_SIZE-1-byte] |= (1 << bit); + + return 0; +} + +/* 10.5.2.13.7: Variable bit map format */ +static int freq_list_bmrel_set_arfcn(uint8_t *chan_list, unsigned int arfcn) +{ + unsigned int byte, bit; + unsigned int min_arfcn; + unsigned int bitno; + + min_arfcn = (chan_list[0] & 1) << 9; + min_arfcn |= chan_list[1] << 1; + min_arfcn |= (chan_list[2] >> 7) & 1; + + /* The lower end of our bitmaks is always implicitly included */ + if (arfcn == min_arfcn) + return 0; + + if (((arfcn - min_arfcn) & 1023) > 111) { + LOGP(DRR, LOGL_ERROR, "arfcn(%u) > min(%u) + 111\n", arfcn, min_arfcn); + return -EINVAL; + } + + bitno = (arfcn - min_arfcn) & 1023; + byte = bitno / 8; + bit = bitno % 8; + + chan_list[2 + byte] |= 1 << (7 - bit); + + return 0; +} + +/* generate a variable bitmap */ +static inline int enc_freq_lst_var_bitmap(uint8_t *chan_list, + struct bitvec *bv, const struct gsm_bts *bts, + bool bis, bool ter, int min, bool pgsm) +{ + int i; + + /* set it to 'Variable bitmap format' */ + chan_list[0] = 0x8e; + + chan_list[0] |= (min >> 9) & 1; + chan_list[1] = (min >> 1); + chan_list[2] = (min & 1) << 7; + + for (i = 0; i < bv->data_len*8; i++) { + /* see notes in bitvec2freq_list */ + if (bitvec_get_bit_pos(bv, i) + && ((!bis && !ter && band_compatible(bts,i)) + || (bis && pgsm && band_compatible(bts,i) && (i < 1 || i > 124)) + || (ter && !band_compatible(bts, i)))) { + int rc = freq_list_bmrel_set_arfcn(chan_list, i); + if (rc < 0) + return rc; + } + } + + return 0; +} + +int range_encode(enum gsm48_range r, int *arfcns, int arfcns_used, int *w, + int f0, uint8_t *chan_list) +{ + /* + * Manipulate the ARFCN list according to the rules in J4 depending + * on the selected range. + */ + int rc, f0_included; + + range_enc_filter_arfcns(arfcns, arfcns_used, f0, &f0_included); + + rc = range_enc_arfcns(r, arfcns, arfcns_used, w, 0); + if (rc < 0) + return rc; + + /* Select the range and the amount of bits needed */ + switch (r) { + case ARFCN_RANGE_128: + return range_enc_range128(chan_list, f0, w); + case ARFCN_RANGE_256: + return range_enc_range256(chan_list, f0, w); + case ARFCN_RANGE_512: + return range_enc_range512(chan_list, f0, w); + case ARFCN_RANGE_1024: + return range_enc_range1024(chan_list, f0, f0_included, w); + default: + return -ERANGE; + }; + + return f0_included; +} + +/* generate a frequency list with the range 512 format */ +static inline int enc_freq_lst_range(uint8_t *chan_list, + struct bitvec *bv, const struct gsm_bts *bts, + bool bis, bool ter, bool pgsm) +{ + int arfcns[RANGE_ENC_MAX_ARFCNS]; + int w[RANGE_ENC_MAX_ARFCNS]; + int arfcns_used = 0; + int i, range, f0; + + /* + * Select ARFCNs according to the rules in bitvec2freq_list + */ + for (i = 0; i < bv->data_len * 8; ++i) { + /* More ARFCNs than the maximum */ + if (arfcns_used > ARRAY_SIZE(arfcns)) + return -1; + /* Check if we can select it? */ + if (bitvec_get_bit_pos(bv, i) && use_arfcn(bts, bis, ter, pgsm, i)) + arfcns[arfcns_used++] = i; + } + + /* + * Check if the given list of ARFCNs can be encoded. + */ + range = range_enc_determine_range(arfcns, arfcns_used, &f0); + if (range == ARFCN_RANGE_INVALID) + return -2; + + memset(w, 0, sizeof(w)); + return range_encode(range, arfcns, arfcns_used, w, f0, chan_list); +} + +/* generate a cell channel list as per Section 10.5.2.1b of 04.08 */ +static int bitvec2freq_list(uint8_t *chan_list, struct bitvec *bv, + const struct gsm_bts *bts, bool bis, bool ter) +{ + int i, rc, min = -1, max = -1, arfcns = 0; + bool pgsm = false; + memset(chan_list, 0, 16); + + if (bts->band == GSM_BAND_900 + && bts->c0->arfcn >= 1 && bts->c0->arfcn <= 124) + pgsm = true; + /* P-GSM-only handsets only support 'bit map 0 format' */ + if (!bis && !ter && pgsm) { + chan_list[0] = 0; + + for (i = 0; i < bv->data_len*8; i++) { + if (i >= 1 && i <= 124 + && bitvec_get_bit_pos(bv, i)) { + rc = freq_list_bm0_set_arfcn(chan_list, i); + if (rc < 0) + return rc; + } + } + return 0; + } + + for (i = 0; i < bv->data_len*8; i++) { + /* in case of SI2 or SI5 allow all neighbours in same band + * in case of SI*bis, allow neighbours in same band ouside pgsm + * in case of SI*ter, allow neighbours in different bands + */ + if (!bitvec_get_bit_pos(bv, i)) + continue; + if (!use_arfcn(bts, bis, ter, pgsm, i)) + continue; + /* count the arfcns we want to carry */ + arfcns += 1; + + /* 955..1023 < 0..885 */ + if (min < 0) + min = i; + if (i >= 955 && min < 955) + min = i; + if (i >= 955 && min >= 955 && i < min) + min = i; + if (i < 955 && min < 955 && i < min) + min = i; + if (max < 0) + max = i; + if (i < 955 && max >= 955) + max = i; + if (i >= 955 && max >= 955 && i > max) + max = i; + if (i < 955 && max < 955 && i > max) + max = i; + } + + if (max == -1) { + /* Empty set, use 'bit map 0 format' */ + chan_list[0] = 0; + return 0; + } + + /* Now find the best encoding */ + if (((max - min) & 1023) <= 111) + return enc_freq_lst_var_bitmap(chan_list, bv, bts, bis, + ter, min, pgsm); + + /* Attempt to do the range encoding */ + rc = enc_freq_lst_range(chan_list, bv, bts, bis, ter, pgsm); + if (rc >= 0) + return 0; + + LOGP(DRR, LOGL_ERROR, "min_arfcn=%u, max_arfcn=%u, arfcns=%d " + "can not generate ARFCN list", min, max, arfcns); + return -EINVAL; +} + +/* generate a cell channel list as per Section 10.5.2.1b of 04.08 */ +/* static*/ int generate_cell_chan_list(uint8_t *chan_list, struct gsm_bts *bts) +{ + struct gsm_bts_trx *trx; + struct bitvec *bv = &bts->si_common.cell_alloc; + + /* Zero-initialize the bit-vector */ + memset(bv->data, 0, bv->data_len); + + /* first we generate a bitvec of all TRX ARFCN's in our BTS */ + llist_for_each_entry(trx, &bts->trx_list, list) { + unsigned int i, j; + /* Always add the TRX's ARFCN */ + bitvec_set_bit_pos(bv, trx->arfcn, 1); + for (i = 0; i < ARRAY_SIZE(trx->ts); i++) { + struct gsm_bts_trx_ts *ts = &trx->ts[i]; + /* Add any ARFCNs present in hopping channels */ + for (j = 0; j < 1024; j++) { + if (bitvec_get_bit_pos(&ts->hopping.arfcns, j)) + bitvec_set_bit_pos(bv, j, 1); + } + } + } + + /* then we generate a GSM 04.08 frequency list from the bitvec */ + return bitvec2freq_list(chan_list, bv, bts, false, false); +} + +/*! generate a cell channel list as per Section 10.5.2.22 of 04.08 + * \param[out] chan_list caller-provided output buffer + * \param[in] bts BTS descriptor used for input data + * \param[in] si5 Are we generating SI5xxx (true) or SI2xxx (false) + * \param[in] bis Are we generating SIXbis (true) or not (false) + * \param[in] ter Are we generating SIXter (true) or not (false) + */ +static int generate_bcch_chan_list(uint8_t *chan_list, struct gsm_bts *bts, + bool si5, bool bis, bool ter) +{ + struct gsm_bts *cur_bts; + struct bitvec *bv; + int rc; + + if (si5 && bts->neigh_list_manual_mode == NL_MODE_MANUAL_SI5SEP) + bv = &bts->si_common.si5_neigh_list; + else + bv = &bts->si_common.neigh_list; + + /* Generate list of neighbor cells if we are in automatic mode */ + if (bts->neigh_list_manual_mode == NL_MODE_AUTOMATIC) { + /* Zero-initialize the bit-vector */ + memset(bv->data, 0, bv->data_len); + + /* first we generate a bitvec of the BCCH ARFCN's in our BSC */ + llist_for_each_entry(cur_bts, &bts->network->bts_list, list) { + if (cur_bts == bts) + continue; + bitvec_set_bit_pos(bv, cur_bts->c0->arfcn, 1); + } + } + + /* then we generate a GSM 04.08 frequency list from the bitvec */ + rc = bitvec2freq_list(chan_list, bv, bts, bis, ter); + if (rc < 0) + return rc; + + /* Set BA-IND depending on whether we're generating SI2 or SI5. + * The point here is to be able to correlate whether a given MS + * measurement report was using the neighbor cells advertised in + * SI2 or in SI5, as those two could very well be different */ + if (si5) + chan_list[0] |= 0x10; + else + chan_list[0] &= ~0x10; + + return rc; +} + +static int list_arfcn(uint8_t *chan_list, uint8_t mask, char *text) +{ + int n = 0, i; + struct gsm_sysinfo_freq freq[1024]; + + memset(freq, 0, sizeof(freq)); + gsm48_decode_freq_list(freq, chan_list, 16, 0xce, 1); + for (i = 0; i < 1024; i++) { + if (freq[i].mask) { + if (!n) + LOGP(DRR, LOGL_INFO, "%s", text); + LOGPC(DRR, LOGL_INFO, " %d", i); + n++; + } + } + if (n) + LOGPC(DRR, LOGL_INFO, "\n"); + + return n; +} + +static int generate_si1(enum osmo_sysinfo_type t, struct gsm_bts *bts) +{ + int rc; + struct gsm48_system_information_type_1 *si1 = (struct gsm48_system_information_type_1 *) GSM_BTS_SI(bts, t); + + memset(si1, GSM_MACBLOCK_PADDING, GSM_MACBLOCK_LEN); + + si1->header.l2_plen = GSM48_LEN2PLEN(21); + si1->header.rr_protocol_discriminator = GSM48_PDISC_RR; + si1->header.skip_indicator = 0; + si1->header.system_information = GSM48_MT_RR_SYSINFO_1; + + rc = generate_cell_chan_list(si1->cell_channel_description, bts); + if (rc < 0) + return rc; + list_arfcn(si1->cell_channel_description, 0xce, "Serving cell:"); + + si1->rach_control = bts->si_common.rach_control; + if (acc_ramp_is_enabled(&bts->acc_ramp)) + acc_ramp_apply(&si1->rach_control, &bts->acc_ramp); + + /* + * SI1 Rest Octets (10.5.2.32), contains NCH position and band + * indicator but that is not in the 04.08. + */ + rc = rest_octets_si1(si1->rest_octets, NULL, is_dcs_net(bts)); + + return sizeof(*si1) + rc; +} + +static int generate_si2(enum osmo_sysinfo_type t, struct gsm_bts *bts) +{ + int rc; + struct gsm48_system_information_type_2 *si2 = (struct gsm48_system_information_type_2 *) GSM_BTS_SI(bts, t); + + memset(si2, GSM_MACBLOCK_PADDING, GSM_MACBLOCK_LEN); + + si2->header.l2_plen = GSM48_LEN2PLEN(22); + si2->header.rr_protocol_discriminator = GSM48_PDISC_RR; + si2->header.skip_indicator = 0; + si2->header.system_information = GSM48_MT_RR_SYSINFO_2; + + rc = generate_bcch_chan_list(si2->bcch_frequency_list, bts, false, false, false); + if (rc < 0) + return rc; + list_arfcn(si2->bcch_frequency_list, 0xce, + "SI2 Neighbour cells in same band:"); + + si2->ncc_permitted = bts->si_common.ncc_permitted; + si2->rach_control = bts->si_common.rach_control; + if (acc_ramp_is_enabled(&bts->acc_ramp)) + acc_ramp_apply(&si2->rach_control, &bts->acc_ramp); + + return sizeof(*si2); +} + +static int generate_si2bis(enum osmo_sysinfo_type t, struct gsm_bts *bts) +{ + int rc; + struct gsm48_system_information_type_2bis *si2b = + (struct gsm48_system_information_type_2bis *) GSM_BTS_SI(bts, t); + int n; + + memset(si2b, GSM_MACBLOCK_PADDING, GSM_MACBLOCK_LEN); + + si2b->header.l2_plen = GSM48_LEN2PLEN(22); + si2b->header.rr_protocol_discriminator = GSM48_PDISC_RR; + si2b->header.skip_indicator = 0; + si2b->header.system_information = GSM48_MT_RR_SYSINFO_2bis; + + rc = generate_bcch_chan_list(si2b->bcch_frequency_list, bts, false, true, false); + if (rc < 0) + return rc; + n = list_arfcn(si2b->bcch_frequency_list, 0xce, + "Neighbour cells in same band, but outside P-GSM:"); + if (n) { + /* indicate in SI2 and SI2bis: there is an extension */ + struct gsm48_system_information_type_2 *si2 = + (struct gsm48_system_information_type_2 *) GSM_BTS_SI(bts, SYSINFO_TYPE_2); + si2->bcch_frequency_list[0] |= 0x20; + si2b->bcch_frequency_list[0] |= 0x20; + } else + bts->si_valid &= ~(1 << SYSINFO_TYPE_2bis); + + si2b->rach_control = bts->si_common.rach_control; + if (acc_ramp_is_enabled(&bts->acc_ramp)) + acc_ramp_apply(&si2b->rach_control, &bts->acc_ramp); + + /* SI2bis Rest Octets as per 3GPP TS 44.018 §10.5.2.33 */ + rc = rest_octets_si2bis(si2b->rest_octets); + + return sizeof(*si2b) + rc; +} + +static int generate_si2ter(enum osmo_sysinfo_type t, struct gsm_bts *bts) +{ + int rc; + struct gsm48_system_information_type_2ter *si2t = + (struct gsm48_system_information_type_2ter *) GSM_BTS_SI(bts, t); + int n; + + memset(si2t, GSM_MACBLOCK_PADDING, GSM_MACBLOCK_LEN); + + si2t->header.l2_plen = GSM48_LEN2PLEN(22); + si2t->header.rr_protocol_discriminator = GSM48_PDISC_RR; + si2t->header.skip_indicator = 0; + si2t->header.system_information = GSM48_MT_RR_SYSINFO_2ter; + + rc = generate_bcch_chan_list(si2t->ext_bcch_frequency_list, bts, false, false, true); + if (rc < 0) + return rc; + n = list_arfcn(si2t->ext_bcch_frequency_list, 0x8e, + "Neighbour cells in different band:"); + if (!n) + bts->si_valid &= ~(1 << SYSINFO_TYPE_2ter); + + /* SI2ter Rest Octets as per 3GPP TS 44.018 §10.5.2.33a */ + rc = rest_octets_si2ter(si2t->rest_octets); + + return sizeof(*si2t) + rc; +} + +/* SI2quater messages are optional - we only generate them when neighbor UARFCNs or EARFCNs are configured */ +static inline bool si2quater_not_needed(struct gsm_bts *bts) +{ + unsigned i = MAX_EARFCN_LIST; + + if (bts->si_common.si2quater_neigh_list.arfcn) + for (i = 0; i < MAX_EARFCN_LIST; i++) + if (bts->si_common.si2quater_neigh_list.arfcn[i] != OSMO_EARFCN_INVALID) + break; + + if (!bts->si_common.uarfcn_length && i == MAX_EARFCN_LIST) { + bts->si_valid &= ~(1 << SYSINFO_TYPE_2quater); /* mark SI2q as invalid if no (E|U)ARFCNs are present */ + return true; + } + + return false; +} + +static int generate_si2quater(enum osmo_sysinfo_type t, struct gsm_bts *bts) +{ + int rc; + struct gsm48_system_information_type_2quater *si2q; + + if (si2quater_not_needed(bts)) /* generate rest_octets for SI2q only when necessary */ + return GSM_MACBLOCK_LEN; + + bts->u_offset = 0; + bts->e_offset = 0; + bts->si2q_index = 0; + bts->si2q_count = si2q_num(bts) - 1; + + rc = make_si2quaters(bts, false); + if (rc < 0) + return rc; + + OSMO_ASSERT(bts->si2q_count == bts->si2q_index); + OSMO_ASSERT(bts->si2q_count <= SI2Q_MAX_NUM); + + return sizeof(*si2q) + rc; +} + +static struct gsm48_si_ro_info si_info = { + .selection_params = { + .present = 0, + }, + .power_offset = { + .present = 0, + }, + .si2ter_indicator = false, + .early_cm_ctrl = true, + .scheduling = { + .present = 0, + }, + .gprs_ind = { + .si13_position = 0, + .ra_colour = 0, + .present = 1, + }, + .early_cm_restrict_3g = false, + .si2quater_indicator = false, + .lsa_params = { + .present = 0, + }, + .cell_id = 0, /* FIXME: doesn't the bts have this? */ + .break_ind = 0, +}; + +static int generate_si3(enum osmo_sysinfo_type t, struct gsm_bts *bts) +{ + int rc; + struct gsm48_system_information_type_3 *si3 = (struct gsm48_system_information_type_3 *) GSM_BTS_SI(bts, t); + + memset(si3, GSM_MACBLOCK_PADDING, GSM_MACBLOCK_LEN); + + si3->header.l2_plen = GSM48_LEN2PLEN(18); + si3->header.rr_protocol_discriminator = GSM48_PDISC_RR; + si3->header.skip_indicator = 0; + si3->header.system_information = GSM48_MT_RR_SYSINFO_3; + + si3->cell_identity = htons(bts->cell_identity); + gsm48_generate_lai2(&si3->lai, bts_lai(bts)); + si3->control_channel_desc = bts->si_common.chan_desc; + si3->cell_options = bts->si_common.cell_options; + si3->cell_sel_par = bts->si_common.cell_sel_par; + si3->rach_control = bts->si_common.rach_control; + if (acc_ramp_is_enabled(&bts->acc_ramp)) + acc_ramp_apply(&si3->rach_control, &bts->acc_ramp); + + /* allow/disallow DTXu */ + gsm48_set_dtx(&si3->cell_options, bts->dtxu, bts->dtxu, true); + + if (GSM_BTS_HAS_SI(bts, SYSINFO_TYPE_2ter)) { + LOGP(DRR, LOGL_INFO, "SI 2ter is included.\n"); + si_info.si2ter_indicator = true; + } else { + si_info.si2ter_indicator = false; + } + if (GSM_BTS_HAS_SI(bts, SYSINFO_TYPE_2quater)) { + LOGP(DRR, LOGL_INFO, "SI 2quater is included, based on %zu EARFCNs and %zu UARFCNs.\n", + si2q_earfcn_count(&bts->si_common.si2quater_neigh_list), bts->si_common.uarfcn_length); + si_info.si2quater_indicator = true; + } else { + si_info.si2quater_indicator = false; + } + si_info.early_cm_ctrl = bts->early_classmark_allowed; + si_info.early_cm_restrict_3g = !bts->early_classmark_allowed_3g; + + /* SI3 Rest Octets (10.5.2.34), containing + CBQ, CELL_RESELECT_OFFSET, TEMPORARY_OFFSET, PENALTY_TIME + Power Offset, 2ter Indicator, Early Classmark Sending, + Scheduling if and WHERE, GPRS Indicator, SI13 position */ + rc = rest_octets_si3(si3->rest_octets, &si_info); + + return sizeof(*si3) + rc; +} + +static int generate_si4(enum osmo_sysinfo_type t, struct gsm_bts *bts) +{ + int rc; + struct gsm48_system_information_type_4 *si4 = (struct gsm48_system_information_type_4 *) GSM_BTS_SI(bts, t); + struct gsm_lchan *cbch_lchan; + uint8_t *restoct = si4->data; + + /* length of all IEs present except SI4 rest octets and l2_plen */ + int l2_plen = sizeof(*si4) - 1; + + memset(si4, GSM_MACBLOCK_PADDING, GSM_MACBLOCK_LEN); + + si4->header.rr_protocol_discriminator = GSM48_PDISC_RR; + si4->header.skip_indicator = 0; + si4->header.system_information = GSM48_MT_RR_SYSINFO_4; + + gsm48_generate_lai2(&si4->lai, bts_lai(bts)); + si4->cell_sel_par = bts->si_common.cell_sel_par; + si4->rach_control = bts->si_common.rach_control; + if (acc_ramp_is_enabled(&bts->acc_ramp)) + acc_ramp_apply(&si4->rach_control, &bts->acc_ramp); + + /* Optional: CBCH Channel Description + CBCH Mobile Allocation */ + cbch_lchan = gsm_bts_get_cbch(bts); + if (cbch_lchan) { + struct gsm48_chan_desc cd; + gsm48_lchan2chan_desc(&cd, cbch_lchan); + tv_fixed_put(si4->data, GSM48_IE_CBCH_CHAN_DESC, 3, + (uint8_t *) &cd); + l2_plen += 3 + 1; + restoct += 3 + 1; + /* we don't use hopping and thus don't need a CBCH MA */ + } + + si4->header.l2_plen = GSM48_LEN2PLEN(l2_plen); + + /* SI4 Rest Octets (10.5.2.35), containing + Optional Power offset, GPRS Indicator, + Cell Identity, LSA ID, Selection Parameter */ + rc = rest_octets_si4(restoct, &si_info, (uint8_t *)GSM_BTS_SI(bts, t) + GSM_MACBLOCK_LEN - restoct); + + return l2_plen + 1 + rc; +} + +static int generate_si5(enum osmo_sysinfo_type t, struct gsm_bts *bts) +{ + struct gsm48_system_information_type_5 *si5; + uint8_t *output = GSM_BTS_SI(bts, t); + int rc, l2_plen = 18; + + memset(output, GSM_MACBLOCK_PADDING, GSM_MACBLOCK_LEN); + + /* ip.access nanoBTS needs l2_plen!! */ + switch (bts->type) { + case GSM_BTS_TYPE_NANOBTS: + case GSM_BTS_TYPE_OSMOBTS: + *output++ = GSM48_LEN2PLEN(l2_plen); + l2_plen++; + break; + default: + break; + } + + si5 = (struct gsm48_system_information_type_5 *) output; + + /* l2 pseudo length, not part of msg: 18 */ + si5->rr_protocol_discriminator = GSM48_PDISC_RR; + si5->skip_indicator = 0; + si5->system_information = GSM48_MT_RR_SYSINFO_5; + rc = generate_bcch_chan_list(si5->bcch_frequency_list, bts, true, false, false); + if (rc < 0) + return rc; + list_arfcn(si5->bcch_frequency_list, 0xce, + "SI5 Neighbour cells in same band:"); + + /* 04.08 9.1.37: L2 Pseudo Length of 18 */ + return l2_plen; +} + +static int generate_si5bis(enum osmo_sysinfo_type t, struct gsm_bts *bts) +{ + struct gsm48_system_information_type_5bis *si5b; + uint8_t *output = GSM_BTS_SI(bts, t); + int rc, l2_plen = 18; + int n; + + memset(output, GSM_MACBLOCK_PADDING, GSM_MACBLOCK_LEN); + + /* ip.access nanoBTS needs l2_plen!! */ + switch (bts->type) { + case GSM_BTS_TYPE_NANOBTS: + case GSM_BTS_TYPE_OSMOBTS: + *output++ = GSM48_LEN2PLEN(l2_plen); + l2_plen++; + break; + default: + break; + } + + si5b = (struct gsm48_system_information_type_5bis *) output; + + /* l2 pseudo length, not part of msg: 18 */ + si5b->rr_protocol_discriminator = GSM48_PDISC_RR; + si5b->skip_indicator = 0; + si5b->system_information = GSM48_MT_RR_SYSINFO_5bis; + rc = generate_bcch_chan_list(si5b->bcch_frequency_list, bts, true, true, false); + if (rc < 0) + return rc; + n = list_arfcn(si5b->bcch_frequency_list, 0xce, + "Neighbour cells in same band, but outside P-GSM:"); + if (n) { + /* indicate in SI5 and SI5bis: there is an extension */ + struct gsm48_system_information_type_5 *si5 = + (struct gsm48_system_information_type_5 *) GSM_BTS_SI(bts, SYSINFO_TYPE_5)+1; + si5->bcch_frequency_list[0] |= 0x20; + si5b->bcch_frequency_list[0] |= 0x20; + } else + bts->si_valid &= ~(1 << SYSINFO_TYPE_5bis); + + /* 04.08 9.1.37: L2 Pseudo Length of 18 */ + return l2_plen; +} + +static int generate_si5ter(enum osmo_sysinfo_type t, struct gsm_bts *bts) +{ + struct gsm48_system_information_type_5ter *si5t; + uint8_t *output = GSM_BTS_SI(bts, t); + int rc, l2_plen = 18; + int n; + + memset(output, GSM_MACBLOCK_PADDING, GSM_MACBLOCK_LEN); + + /* ip.access nanoBTS needs l2_plen!! */ + switch (bts->type) { + case GSM_BTS_TYPE_NANOBTS: + case GSM_BTS_TYPE_OSMOBTS: + *output++ = GSM48_LEN2PLEN(l2_plen); + l2_plen++; + break; + default: + break; + } + + si5t = (struct gsm48_system_information_type_5ter *) output; + + /* l2 pseudo length, not part of msg: 18 */ + si5t->rr_protocol_discriminator = GSM48_PDISC_RR; + si5t->skip_indicator = 0; + si5t->system_information = GSM48_MT_RR_SYSINFO_5ter; + rc = generate_bcch_chan_list(si5t->bcch_frequency_list, bts, true, false, true); + if (rc < 0) + return rc; + n = list_arfcn(si5t->bcch_frequency_list, 0x8e, + "Neighbour cells in different band:"); + if (!n) + bts->si_valid &= ~(1 << SYSINFO_TYPE_5ter); + + /* 04.08 9.1.37: L2 Pseudo Length of 18 */ + return l2_plen; +} + +static int generate_si6(enum osmo_sysinfo_type t, struct gsm_bts *bts) +{ + struct gsm48_system_information_type_6 *si6; + uint8_t *output = GSM_BTS_SI(bts, t); + int l2_plen = 11; + int rc; + + memset(output, GSM_MACBLOCK_PADDING, GSM_MACBLOCK_LEN); + + /* ip.access nanoBTS needs l2_plen!! */ + switch (bts->type) { + case GSM_BTS_TYPE_NANOBTS: + case GSM_BTS_TYPE_OSMOBTS: + *output++ = GSM48_LEN2PLEN(l2_plen); + l2_plen++; + break; + default: + break; + } + + si6 = (struct gsm48_system_information_type_6 *) output; + + /* l2 pseudo length, not part of msg: 11 */ + si6->rr_protocol_discriminator = GSM48_PDISC_RR; + si6->skip_indicator = 0; + si6->system_information = GSM48_MT_RR_SYSINFO_6; + si6->cell_identity = htons(bts->cell_identity); + gsm48_generate_lai2(&si6->lai, bts_lai(bts)); + si6->cell_options = bts->si_common.cell_options; + si6->ncc_permitted = bts->si_common.ncc_permitted; + /* allow/disallow DTXu */ + gsm48_set_dtx(&si6->cell_options, bts->dtxu, bts->dtxu, false); + + /* SI6 Rest Octets: 10.5.2.35a: PCH / NCH info, VBS/VGCS options */ + rc = rest_octets_si6(si6->rest_octets, is_dcs_net(bts)); + + return l2_plen + rc; +} + +static struct gsm48_si13_info si13_default = { + .cell_opts = { + .nmo = GPRS_NMO_II, + .t3168 = 2000, + .t3192 = 1500, + .drx_timer_max = 3, + .bs_cv_max = 15, + .ctrl_ack_type_use_block = true, + .ext_info_present = 0, + .supports_egprs_11bit_rach = 0, + .ext_info = { + /* The values below are just guesses ! */ + .egprs_supported = 0, + .use_egprs_p_ch_req = 1, + .bep_period = 5, + .pfc_supported = 0, + .dtm_supported = 0, + .bss_paging_coordination = 0, + }, + }, + .pwr_ctrl_pars = { + .alpha = 0, /* a = 0.0 */ + .t_avg_w = 16, + .t_avg_t = 16, + .pc_meas_chan = 0, /* downling measured on CCCH */ + .n_avg_i = 8, + }, + .bcch_change_mark = 1, + .si_change_field = 0, + .rac = 0, /* needs to be patched */ + .spgc_ccch_sup = 0, + .net_ctrl_ord = 0, + .prio_acc_thr = 6, +}; + +static int generate_si13(enum osmo_sysinfo_type t, struct gsm_bts *bts) +{ + struct gsm48_system_information_type_13 *si13 = + (struct gsm48_system_information_type_13 *) GSM_BTS_SI(bts, t); + int ret; + + memset(si13, GSM_MACBLOCK_PADDING, GSM_MACBLOCK_LEN); + + si13->header.rr_protocol_discriminator = GSM48_PDISC_RR; + si13->header.skip_indicator = 0; + si13->header.system_information = GSM48_MT_RR_SYSINFO_13; + + si13_default.rac = bts->gprs.rac; + si13_default.net_ctrl_ord = bts->gprs.net_ctrl_ord; + + si13_default.cell_opts.ctrl_ack_type_use_block = + bts->gprs.ctrl_ack_type_use_block; + + /* Information about the other SIs */ + si13_default.bcch_change_mark = bts->bcch_change_mark; + si13_default.cell_opts.supports_egprs_11bit_rach = + bts->gprs.supports_egprs_11bit_rach; + + ret = rest_octets_si13(si13->rest_octets, &si13_default); + if (ret < 0) + return ret; + + /* length is coded in bit 2 an up */ + si13->header.l2_plen = 0x01; + + return sizeof (*si13) + ret; +} + +typedef int (*gen_si_fn_t)(enum osmo_sysinfo_type t, struct gsm_bts *bts); + +static const gen_si_fn_t gen_si_fn[_MAX_SYSINFO_TYPE] = { + [SYSINFO_TYPE_1] = &generate_si1, + [SYSINFO_TYPE_2] = &generate_si2, + [SYSINFO_TYPE_2bis] = &generate_si2bis, + [SYSINFO_TYPE_2ter] = &generate_si2ter, + [SYSINFO_TYPE_2quater] = &generate_si2quater, + [SYSINFO_TYPE_3] = &generate_si3, + [SYSINFO_TYPE_4] = &generate_si4, + [SYSINFO_TYPE_5] = &generate_si5, + [SYSINFO_TYPE_5bis] = &generate_si5bis, + [SYSINFO_TYPE_5ter] = &generate_si5ter, + [SYSINFO_TYPE_6] = &generate_si6, + [SYSINFO_TYPE_13] = &generate_si13, +}; + +int gsm_generate_si(struct gsm_bts *bts, enum osmo_sysinfo_type si_type) +{ + gen_si_fn_t gen_si; + + switch (bts->gprs.mode) { + case BTS_GPRS_EGPRS: + si13_default.cell_opts.ext_info_present = 1; + si13_default.cell_opts.ext_info.egprs_supported = 1; + /* fallthrough */ + case BTS_GPRS_GPRS: + si_info.gprs_ind.present = 1; + break; + case BTS_GPRS_NONE: + si_info.gprs_ind.present = 0; + break; + } + + memcpy(&si_info.selection_params, + &bts->si_common.cell_ro_sel_par, + sizeof(struct gsm48_si_selection_params)); + + gen_si = gen_si_fn[si_type]; + if (!gen_si) + return -EINVAL; + + return gen_si(si_type, bts); +} |