diff options
Diffstat (limited to 'src')
207 files changed, 23034 insertions, 9771 deletions
diff --git a/src/Makefile.am b/src/Makefile.am index 70e4d968..b2870264 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -13,7 +13,7 @@ SUBDIRS += osmo-bts-octphy endif if ENABLE_LC15BTS -SUBDIRS += osmo-bts-litecell15 +SUBDIRS += osmo-bts-lc15 endif if ENABLE_OC2GBTS diff --git a/src/common/Makefile.am b/src/common/Makefile.am index 113ff2f4..d13415d1 100644 --- a/src/common/Makefile.am +++ b/src/common/Makefile.am @@ -1,17 +1,87 @@ -AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include -AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOTRAU_CFLAGS) $(LIBOSMOCODEC_CFLAGS) -LDADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOTRAU_LIBS) $(LIBOSMOCODEC_LIBS) +AM_CPPFLAGS = \ + $(all_includes) \ + -I$(top_srcdir)/include \ + -I$(top_builddir)/include \ + $(NULL) + +AM_CFLAGS = \ + -Wall \ + $(LIBOSMOCORE_CFLAGS) \ + $(LIBOSMOTRAU_CFLAGS) \ + $(LIBOSMOCODEC_CFLAGS) \ + $(LIBOSMONETIF_CFLAGS) \ + $(NULL) + +LDADD = \ + $(LIBOSMOCORE_LIBS) \ + $(LIBOSMOTRAU_LIBS) \ + $(LIBOSMOCODEC_LIBS) \ + $(LIBOSMONETIF_LIBS) \ + $(NULL) if ENABLE_LC15BTS AM_CFLAGS += -DENABLE_LC15BTS endif noinst_LIBRARIES = libbts.a libl1sched.a -libbts_a_SOURCES = gsm_data_shared.c sysinfo.c logging.c abis.c oml.c bts.c \ - rsl.c vty.c paging.c measurement.c amr.c lchan.c \ - load_indication.c pcu_sock.c handover.c msg_utils.c \ - tx_power.c bts_ctrl_commands.c bts_ctrl_lookup.c \ - l1sap.c cbch.c power_control.c main.c phy_link.c \ - dtx_dl_amr_fsm.c scheduler_mframe.c +libbts_a_SOURCES = \ + gsm_data.c \ + sysinfo.c \ + logging.c \ + abis.c \ + abis_osmo.c \ + oml.c \ + osmux.c \ + bts.c \ + bts_sm.c \ + bts_trx.c \ + rsl.c \ + rtp_input_preen.c \ + vty.c \ + paging.c \ + measurement.c \ + amr.c \ + asci.c \ + lchan.c \ + load_indication.c \ + pcu_sock.c \ + handover.c \ + msg_utils.c \ + tx_power.c \ + bts_ctrl_commands.c \ + bts_ctrl_lookup.c \ + bts_shutdown_fsm.c \ + csd_v110.c \ + l1sap.c \ + cbch.c \ + power_control.c \ + main.c \ + phy_link.c \ + dtx_dl_amr_fsm.c \ + scheduler_mframe.c \ + ta_control.c \ + nm_common_fsm.c \ + nm_bts_sm_fsm.c \ + nm_bts_fsm.c \ + nm_bb_transc_fsm.c \ + nm_channel_fsm.c \ + nm_gprs_cell_fsm.c \ + nm_gprs_nse_fsm.c \ + nm_gprs_nsvc_fsm.c \ + nm_radio_carrier_fsm.c \ + notification.c \ + probes.d \ + $(NULL) libl1sched_a_SOURCES = scheduler.c + +if ENABLE_SYSTEMTAP +probes.h: probes.d + $(DTRACE) -C -h -s $< -o $@ + +probes.lo: probes.d + $(LIBTOOL) --mode=compile $(AM_V_lt) --tag=CC env CFLAGS="$(CFLAGS)" $(DTRACE) -C -G -s $< -o $@ + +BUILT_SOURCES = probes.h probes.lo +libbts_la_LDADD = probes.lo +endif diff --git a/src/common/abis.c b/src/common/abis.c index 84a3a047..5629cf23 100644 --- a/src/common/abis.c +++ b/src/common/abis.c @@ -13,7 +13,7 @@ * 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. + * 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/>. @@ -38,46 +38,319 @@ #include <osmocom/core/msgb.h> #include <osmocom/core/signal.h> #include <osmocom/core/macaddr.h> +#include <osmocom/core/fsm.h> #include <osmocom/abis/abis.h> #include <osmocom/abis/e1_input.h> #include <osmocom/abis/ipaccess.h> #include <osmocom/gsm/ipa.h> +#include <osmo-bts/abis.h> #include <osmo-bts/logging.h> #include <osmo-bts/gsm_data.h> #include <osmo-bts/bts.h> #include <osmo-bts/rsl.h> #include <osmo-bts/oml.h> +#include <osmo-bts/abis_osmo.h> #include <osmo-bts/bts_model.h> +#include <osmo-bts/bts_trx.h> +#include <osmo-bts/bts_shutdown_fsm.h> static struct gsm_bts *g_bts; -int abis_oml_sendmsg(struct msgb *msg) +static struct e1inp_line_ops line_ops; + +static struct ipaccess_unit bts_dev_info; + +#define S(x) (1 << (x)) +#define OML_RETRY_TIMER 5 + +enum abis_link_fsm_state { + ABIS_LINK_ST_WAIT_RECONNECT, /* OML link has not yet been established */ + ABIS_LINK_ST_CONNECTING, /* OML link in process of been established */ + ABIS_LINK_ST_CONNECTED, /* OML link is established, RSL links may be established or not */ + ABIS_LINK_ST_FAILED, /* There used to be an active OML connection but it became broken */ +}; + +static const struct value_string abis_link_fsm_event_names[] = { + { ABIS_LINK_EV_SIGN_LINK_OML_UP, "SIGN_LINK_OML_UP", }, + { ABIS_LINK_EV_SIGN_LINK_DOWN, "SIGN_LINK_DOWN" }, + { ABIS_LINK_EV_VTY_RM_ADDR, "VTY_RM_ADDR" }, + {} +}; + +struct abis_link_fsm_priv { + struct bsc_oml_host *current_bsc; + struct gsm_bts *bts; + char *model_name; + bool reconnect_to_current_bsc; +}; + +static void reset_oml_link(struct gsm_bts *bts) { - struct gsm_bts *bts = msg->trx->bts; + if (bts->oml_link) { + struct timespec now; - if (!bts->oml_link) { - llist_add_tail(&msg->list, &bts->oml_queue); + e1inp_sign_link_destroy(bts->oml_link); + + /* Log a special notice if the OML connection was dropped relatively quickly. */ + if (bts->oml_conn_established_timestamp.tv_sec != 0 && clock_gettime(CLOCK_MONOTONIC, &now) == 0 && + bts->oml_conn_established_timestamp.tv_sec + OSMO_BTS_OML_CONN_EARLY_DISCONNECT >= now.tv_sec) { + LOGP(DABIS, LOGL_FATAL, "OML link was closed early within %" PRIu64 " seconds. " + "If this situation persists, please check your BTS and BSC configuration files for errors. " + "A common error is a mismatch between unit_id configuration parameters of BTS and BSC.\n", + (uint64_t) (now.tv_sec - bts->oml_conn_established_timestamp.tv_sec)); + } + bts->oml_link = NULL; + } + memset(&bts->oml_conn_established_timestamp, 0, sizeof(bts->oml_conn_established_timestamp)); + + /* Same for IPAC_PROTO_OSMO on the same ipa connection: */ + if (bts->osmo_link) { + e1inp_sign_link_destroy(bts->osmo_link); + bts->osmo_link = NULL; + } + +} + +static int pick_next_bsc(struct osmo_fsm_inst *fi) +{ + struct abis_link_fsm_priv *priv = fi->priv; + struct gsm_bts *bts = priv->bts; + struct bsc_oml_host *last; + + if (llist_empty(&bts->bsc_oml_hosts)) { + LOGPFSML(fi, LOGL_ERROR, "List of BSCs to connect to is empty!\n"); + return -1; + } + + /* Keep current pointer to priv->current_bsc: */ + if (priv->reconnect_to_current_bsc) { + OSMO_ASSERT(priv->current_bsc); + priv->reconnect_to_current_bsc = false; return 0; - } else { - /* osmo-bts uses msg->trx internally, but libosmo-abis uses - * the signalling link at msg->dst */ - msg->dst = bts->oml_link; - return abis_sendmsg(msg); } + + last = (struct bsc_oml_host *)llist_last_entry(&bts->bsc_oml_hosts, struct bsc_oml_host, list); + + if (!priv->current_bsc || priv->current_bsc == last) /* Pick first one (wrap around): */ + priv->current_bsc = (struct bsc_oml_host *)llist_first_entry(&bts->bsc_oml_hosts, struct bsc_oml_host, list); + else + priv->current_bsc = (struct bsc_oml_host *)llist_entry(priv->current_bsc->list.next, struct bsc_oml_host, list); + + return 0; } -static void drain_oml_queue(struct gsm_bts *bts) +static void abis_link_connecting_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) { - struct msgb *msg, *msg2; - - llist_for_each_entry_safe(msg, msg2, &bts->oml_queue, list) { - /* osmo-bts uses msg->trx internally, but libosmo-abis uses - * the signalling link at msg->dst */ - llist_del(&msg->list); - msg->dst = bts->oml_link; - abis_sendmsg(msg); + struct e1inp_line *line; + struct abis_link_fsm_priv *priv = fi->priv; + struct gsm_bts *bts = priv->bts; + + if (bts_shutdown_in_progress(bts)) { + LOGPFSML(fi, LOGL_NOTICE, "BTS is shutting down, delaying A-bis connection establishment to BSC\n"); + osmo_fsm_inst_state_chg(fi, ABIS_LINK_ST_WAIT_RECONNECT, OML_RETRY_TIMER, 0); + return; + } + + if (pick_next_bsc(fi) < 0) { + LOGPFSML(fi, LOGL_FATAL, "No BSC available, A-bis connection establishment failed\n"); + osmo_fsm_inst_state_chg(fi, ABIS_LINK_ST_FAILED, 0, 0); + return; } + + LOGP(DABIS, LOGL_NOTICE, "A-bis connection establishment to BSC (%s) in progress...\n", priv->current_bsc->addr); + + /* patch in various data from VTY and other sources */ + line_ops.cfg.ipa.addr = priv->current_bsc->addr; + osmo_get_macaddr(bts_dev_info.mac_addr, "eth0"); + bts_dev_info.site_id = bts->ip_access.site_id; + bts_dev_info.bts_id = bts->ip_access.bts_id; + bts_dev_info.unit_name = priv->model_name; + if (bts->description) + bts_dev_info.unit_name = bts->description; + bts_dev_info.location2 = priv->model_name; + + line = e1inp_line_find(0); + if (!line) + line = e1inp_line_create(0, "ipa"); + if (!line) { + osmo_fsm_inst_state_chg(fi, ABIS_LINK_ST_FAILED, 0, 0); + return; + } + /* Line always comes already with a "ctor" reference, enough to keep it alive forever. */ + + e1inp_line_bind_ops(line, &line_ops); + /* This will open the OML connection now */ + if (e1inp_line_update(line) < 0) { + osmo_fsm_inst_state_chg(fi, ABIS_LINK_ST_FAILED, 0, 0); + return; + } + + /* The TCP connection to the BSC is now in progress. + * Wait for OML Link UP to transition to CONNECTED. */ +} + +static void abis_link_connecting(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct abis_link_fsm_priv *priv = fi->priv; + struct gsm_bts *bts = priv->bts; + + switch (event) { + case ABIS_LINK_EV_SIGN_LINK_OML_UP: + osmo_fsm_inst_state_chg(fi, ABIS_LINK_ST_CONNECTED, 0, 0); + break; + case ABIS_LINK_EV_SIGN_LINK_DOWN: + reset_oml_link(bts); + osmo_fsm_inst_state_chg(fi, ABIS_LINK_ST_WAIT_RECONNECT, OML_RETRY_TIMER, 0); + break; + default: + OSMO_ASSERT(0); + } +} + +static void abis_link_connected_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + bts_link_estab(g_bts); +} + +static void abis_link_connected(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct abis_link_fsm_priv *priv = fi->priv; + struct gsm_bts *bts = priv->bts; + struct gsm_bts_trx *trx; + OSMO_ASSERT(event == ABIS_LINK_EV_SIGN_LINK_DOWN); + + /* First remove the OML signalling link */ + reset_oml_link(bts); + + /* Then iterate over the RSL signalling links */ + llist_for_each_entry(trx, &bts->trx_list, list) { + if (trx->bb_transc.rsl.link) { + e1inp_sign_link_destroy(trx->bb_transc.rsl.link); + trx->bb_transc.rsl.link = NULL; + if (trx == trx->bts->c0) + load_timer_stop(trx->bts); + } else { + /* If we have no rsl_link yet it may mean that lower + * layers are still establishing the socket (TCP, IPA). + * Let's tell it to stop connection establishment since + * we are shutting down. */ + struct e1inp_line *line = e1inp_line_find(0); + if (line) + e1inp_ipa_bts_rsl_close_n(line, trx->nr); + } + /* Note: Here we could send NM_EV_RSL_DOWN to each + * trx->(bb_transc.)mo.fi, but we are starting shutdown of the + * entire BTS anyway through bts_model_abis_close(), so simply + * let bts_shutdown FSM take care of slowly powering down all + * the TRX. It would make sense to send NM_EV_RSL_DOWN only if a + * RSL link TRX!=C0 was going down, in order to selectively stop + * that TRX only. But libosmo-abis expects us to drop the entire + * line when something goes wrong... */ + } + bts_model_abis_close(bts); + + /* We want to try reconnecting to the current BSC at least once before switching to a new one: */ + priv->reconnect_to_current_bsc = true; + osmo_fsm_inst_state_chg(fi, ABIS_LINK_ST_WAIT_RECONNECT, OML_RETRY_TIMER, 0); +} + +static void abis_link_failed_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct abis_link_fsm_priv *priv = fi->priv; + struct gsm_bts *bts = priv->bts; + + /* None of the configured BSCs was reachable or there was an existing + * OML/RSL connection that broke. Initiate BTS process shut down now. */ + bts_model_abis_close(bts); +} + +static void abis_link_allstate(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct abis_link_fsm_priv *priv = fi->priv; + struct gsm_bts *bts = priv->bts; + + OSMO_ASSERT(event == ABIS_LINK_EV_VTY_RM_ADDR); + + if (priv->current_bsc == data) { + if (llist_count(&bts->bsc_oml_hosts) <= 1) + priv->current_bsc = NULL; + else + pick_next_bsc(fi); + } +} + +int abis_link_fsm_timer_cb(struct osmo_fsm_inst *fi) +{ + switch (fi->state) { + case ABIS_LINK_ST_WAIT_RECONNECT: + osmo_fsm_inst_state_chg(fi, ABIS_LINK_ST_CONNECTING, 0, 0); + break; + default: + OSMO_ASSERT(0); + } + return 0; +} + + +static struct osmo_fsm_state abis_link_fsm_states[] = { + [ABIS_LINK_ST_WAIT_RECONNECT] = { + .name = "WAIT_RECONNECT", + .out_state_mask = + S(ABIS_LINK_ST_CONNECTING), + }, + [ABIS_LINK_ST_CONNECTING] = { + .name = "CONNECTING", + .in_event_mask = + S(ABIS_LINK_EV_SIGN_LINK_OML_UP) | + S(ABIS_LINK_EV_SIGN_LINK_DOWN), + .out_state_mask = + S(ABIS_LINK_ST_WAIT_RECONNECT) | + S(ABIS_LINK_ST_CONNECTED) | + S(ABIS_LINK_ST_FAILED), + .onenter = abis_link_connecting_onenter, + .action = abis_link_connecting, + }, + [ABIS_LINK_ST_CONNECTED] = { + .name = "CONNECTED", + .in_event_mask = + S(ABIS_LINK_EV_SIGN_LINK_DOWN), + .out_state_mask = + S(ABIS_LINK_ST_WAIT_RECONNECT), + .onenter = abis_link_connected_onenter, + .action = abis_link_connected, + }, + [ABIS_LINK_ST_FAILED] = { + .name = "FAILED", + .onenter = abis_link_failed_onenter, + }, +}; + +static struct osmo_fsm abis_link_fsm = { + .name = "abis_link", + .states = abis_link_fsm_states, + .num_states = ARRAY_SIZE(abis_link_fsm_states), + .log_subsys = DABIS, + .event_names = abis_link_fsm_event_names, + .allstate_action = abis_link_allstate, + .allstate_event_mask = S(ABIS_LINK_EV_VTY_RM_ADDR), + .timer_cb = abis_link_fsm_timer_cb, +}; + +int abis_oml_sendmsg(struct msgb *msg) +{ + struct gsm_bts *bts = msg->trx->bts; + + if (!bts->oml_link) { + LOGP(DABIS, LOGL_INFO, "Drop Tx OML msg, OML link is down\n"); + msgb_free(msg); + return 0; + } + + /* osmo-bts uses msg->trx internally, but libosmo-abis uses + * the signalling link at msg->dst */ + msg->dst = bts->oml_link; + return abis_sendmsg(msg); } int abis_bts_rsl_sendmsg(struct msgb *msg) @@ -91,33 +364,37 @@ int abis_bts_rsl_sendmsg(struct msgb *msg) /* osmo-bts uses msg->trx internally, but libosmo-abis uses * the signalling link at msg->dst */ - msg->dst = msg->trx->rsl_link; + msg->dst = msg->trx->bb_transc.rsl.link; return abis_sendmsg(msg); } static struct e1inp_sign_link *sign_link_up(void *unit, struct e1inp_line *line, enum e1inp_sign_type type) { - struct e1inp_sign_link *sign_link = NULL; + struct e1inp_ts *sign_ts; struct gsm_bts_trx *trx; int trx_nr; switch (type) { case E1INP_SIGN_OML: + sign_ts = e1inp_line_ipa_oml_ts(line); LOGP(DABIS, LOGL_INFO, "OML Signalling link up\n"); - e1inp_ts_config_sign(&line->ts[E1INP_SIGN_OML-1], line); - sign_link = g_bts->oml_link = - e1inp_sign_link_create(&line->ts[E1INP_SIGN_OML-1], - E1INP_SIGN_OML, NULL, 255, 0); + e1inp_ts_config_sign(sign_ts, line); + g_bts->oml_link = e1inp_sign_link_create(sign_ts, E1INP_SIGN_OML, + g_bts->c0, IPAC_PROTO_OML, 0); if (clock_gettime(CLOCK_MONOTONIC, &g_bts->oml_conn_established_timestamp) != 0) memset(&g_bts->oml_conn_established_timestamp, 0, sizeof(g_bts->oml_conn_established_timestamp)); - drain_oml_queue(g_bts); - sign_link->trx = g_bts->c0; - bts_link_estab(g_bts); - break; + g_bts->osmo_link = e1inp_sign_link_create(sign_ts, E1INP_SIGN_OSMO, + g_bts->c0, IPAC_PROTO_OSMO, 0); + osmo_fsm_inst_dispatch(g_bts->abis_link_fi, ABIS_LINK_EV_SIGN_LINK_OML_UP, NULL); + return g_bts->oml_link; + + case E1INP_SIGN_RSL: + /* fall through to default to catch TRXn having type = E1INP_SIGN_RSL + n */ default: trx_nr = type - E1INP_SIGN_RSL; + sign_ts = e1inp_line_ipa_rsl_ts(line, trx_nr); LOGP(DABIS, LOGL_INFO, "RSL Signalling link for TRX%d up\n", trx_nr); trx = gsm_bts_trx_num(g_bts, trx_nr); @@ -126,54 +403,23 @@ static struct e1inp_sign_link *sign_link_up(void *unit, struct e1inp_line *line, trx_nr); break; } - e1inp_ts_config_sign(&line->ts[type-1], line); - sign_link = trx->rsl_link = - e1inp_sign_link_create(&line->ts[type-1], - E1INP_SIGN_RSL, NULL, 0, 0); - sign_link->trx = trx; + e1inp_ts_config_sign(sign_ts, line); + trx->bb_transc.rsl.link = e1inp_sign_link_create(sign_ts, E1INP_SIGN_RSL, + trx, trx->bb_transc.rsl.tei, 0); trx_link_estab(trx); - break; + return trx->bb_transc.rsl.link; } - - return sign_link; + return NULL; } static void sign_link_down(struct e1inp_line *line) { - struct gsm_bts_trx *trx; - LOGP(DABIS, LOGL_ERROR, "Signalling link down\n"); - - /* First remove the OML signalling link */ - if (g_bts->oml_link) { - struct timespec now; - - e1inp_sign_link_destroy(g_bts->oml_link); - - /* Log a special notice if the OML connection was dropped relatively quickly. */ - if (g_bts->oml_conn_established_timestamp.tv_sec != 0 && clock_gettime(CLOCK_MONOTONIC, &now) == 0 && - g_bts->oml_conn_established_timestamp.tv_sec + OSMO_BTS_OML_CONN_EARLY_DISCONNECT >= now.tv_sec) { - LOGP(DABIS, LOGL_FATAL, "OML link was closed early within %" PRIu64 " seconds. " - "If this situation persists, please check your BTS and BSC configuration files for errors. " - "A common error is a mismatch between unit_id configuration parameters of BTS and BSC.\n", - (uint64_t)(now.tv_sec - g_bts->oml_conn_established_timestamp.tv_sec)); - } - } - g_bts->oml_link = NULL; - memset(&g_bts->oml_conn_established_timestamp, 0, sizeof(g_bts->oml_conn_established_timestamp)); - - /* Then iterate over the RSL signalling links */ - llist_for_each_entry(trx, &g_bts->trx_list, list) { - if (trx->rsl_link) { - e1inp_sign_link_destroy(trx->rsl_link); - trx->rsl_link = NULL; - } - } - - bts_model_abis_close(g_bts); + LOGPIL(line, DABIS, LOGL_ERROR, "Signalling link down\n"); + osmo_fsm_inst_dispatch(g_bts->abis_link_fi, ABIS_LINK_EV_SIGN_LINK_DOWN, NULL); } -/* callback for incoming mesages from A-bis/IP */ +/* callback for incoming messages from A-bis/IP */ static int sign_link_cb(struct msgb *msg) { struct e1inp_sign_link *link = msg->dst; @@ -190,6 +436,9 @@ static int sign_link_cb(struct msgb *msg) case E1INP_SIGN_RSL: down_rsl(link->trx, msg); break; + case E1INP_SIGN_OSMO: + down_osmo(link->trx->bts, msg); + break; default: msgb_free(msg); break; @@ -212,7 +461,7 @@ uint32_t get_signlink_remote_ip(struct e1inp_sign_link *link) return 0; } - /* we assume that the soket is AF_INET. As Abis/IP contains + /* we assume that the socket is AF_INET. As Abis/IP contains * lots of hard-coded IPv4 addresses, this safe */ OSMO_ASSERT(sin.sin_family == AF_INET); @@ -235,7 +484,7 @@ static int inp_s_cbfn(unsigned int subsys, unsigned int signal, static struct ipaccess_unit bts_dev_info = { - .unit_name = "sysmoBTS", + .unit_name = "osmo-bts", .equipvers = "", /* FIXME: read this from hw */ .swversion = PACKAGE_VERSION, .location1 = "", @@ -259,37 +508,36 @@ void abis_init(struct gsm_bts *bts) { g_bts = bts; - oml_init(&bts->mo); - libosmo_abis_init(NULL); + oml_init(); + libosmo_abis_init(tall_bts_ctx); osmo_signal_register_handler(SS_L_INPUT, &inp_s_cbfn, bts); } -struct e1inp_line *abis_open(struct gsm_bts *bts, char *dst_host, - char *model_name) +int abis_open(struct gsm_bts *bts, char *model_name) { - struct e1inp_line *line; + struct abis_link_fsm_priv *abis_link_fsm_priv; - /* patch in various data from VTY and othe sources */ - line_ops.cfg.ipa.addr = dst_host; - osmo_get_macaddr(bts_dev_info.mac_addr, "eth0"); - bts_dev_info.site_id = bts->ip_access.site_id; - bts_dev_info.bts_id = bts->ip_access.bts_id; - bts_dev_info.unit_name = model_name; - if (bts->description) - bts_dev_info.unit_name = bts->description; - bts_dev_info.location2 = model_name; + if (llist_empty(&bts->bsc_oml_hosts)) { + LOGP(DABIS, LOGL_FATAL, "No BSC configured, cannot start BTS without knowing BSC OML IP\n"); + return -EINVAL; + } - line = e1inp_line_find(0); - if (!line) - line = e1inp_line_create(0, "ipa"); - if (!line) - return NULL; - e1inp_line_bind_ops(line, &line_ops); + bts->abis_link_fi = osmo_fsm_inst_alloc(&abis_link_fsm, bts, NULL, LOGL_DEBUG, "abis_link"); + OSMO_ASSERT(bts->abis_link_fi); - /* This will open the OML connection now */ - if (e1inp_line_update(line) < 0) - return NULL; + abis_link_fsm_priv = talloc_zero(bts->abis_link_fi, struct abis_link_fsm_priv); + OSMO_ASSERT(abis_link_fsm_priv); + abis_link_fsm_priv->bts = bts; + abis_link_fsm_priv->model_name = model_name; + bts->abis_link_fi->priv = abis_link_fsm_priv; + + osmo_fsm_inst_state_chg(bts->abis_link_fi, ABIS_LINK_ST_CONNECTING, 0, 0); - return line; + return 0; +} + +static __attribute__((constructor)) void abis_link_fsm_init(void) +{ + OSMO_ASSERT(osmo_fsm_register(&abis_link_fsm) == 0); } diff --git a/src/common/abis_osmo.c b/src/common/abis_osmo.c new file mode 100644 index 00000000..beb9992d --- /dev/null +++ b/src/common/abis_osmo.c @@ -0,0 +1,135 @@ +/* OSMO extenion link associated to same line as oml_link: */ + +/* (C) 2021 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> + * Author: Pau Espin Pedrol <pespin@sysmocom.de> + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU 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/core/msgb.h> +#include <osmocom/gsm/ipa.h> +#include <osmocom/gsm/protocol/ipaccess.h> + +#include <osmo-bts/bts.h> +#include <osmo-bts/logging.h> +#include <osmo-bts/pcu_if.h> +#include <osmo-bts/pcuif_proto.h> +#include <osmo-bts/bts_sm.h> + +#define OM_HEADROOM_SIZE 128 + +//////////////////////////////////////// +// OSMO ABIS extensions (PCU) +/////////////////////////////////////// + +static struct msgb *abis_osmo_pcu_msgb_alloc(uint8_t msg_type, uint8_t bts_nr, size_t extra_size) +{ + struct msgb *msg; + struct gsm_pcu_if *pcu_prim; + msg = msgb_alloc_headroom(OM_HEADROOM_SIZE + sizeof(struct gsm_pcu_if) + extra_size, + OM_HEADROOM_SIZE, "IPA/ABIS/OSMO"); + /* Only header is filled, caller is responible for reserving + filling + * message type specific contents: */ + msgb_put(msg, PCUIF_HDR_SIZE); + pcu_prim = (struct gsm_pcu_if *) msgb_data(msg); + pcu_prim->msg_type = msg_type; + pcu_prim->bts_nr = bts_nr; + return msg; +} + +/* Send a OML NM Message from BSC to BTS */ +int abis_osmo_sendmsg(struct gsm_bts *bts, struct msgb *msg) +{ + msg->dst = bts->osmo_link; + msg->l2h = msg->data; + return abis_sendmsg(msg); +} + + +/* Send IPA/OSMO/PCU extension Abis message from PCU to BSC */ +static int abis_osmo_pcu_sendmsg(struct gsm_bts *bts, struct msgb *msg) +{ + ipa_prepend_header_ext(msg, IPAC_PROTO_EXT_PCU); + return abis_osmo_sendmsg(bts, msg); +} + +int abis_osmo_pcu_tx_container(struct gsm_bts *bts, const struct gsm_pcu_if_container *container) +{ + uint16_t data_length = osmo_load16be(&container->length); + struct msgb *msg = abis_osmo_pcu_msgb_alloc(PCU_IF_MSG_CONTAINER, bts->nr, data_length); + struct gsm_pcu_if *pcu_prim = (struct gsm_pcu_if *) msgb_data(msg); + struct gsm_pcu_if_container *tx_cont = &pcu_prim->u.container; + + msgb_put(msg, sizeof(*tx_cont) + data_length); + tx_cont->msg_type = container->msg_type; + tx_cont->length = container->length; + if (data_length) + memcpy(tx_cont->data, container->data, data_length); + + return abis_osmo_pcu_sendmsg(bts, msg); +} + + +/* incoming IPA/OSMOEXT/PCU Abis message from BSC */ +static int rx_down_osmo_pcu(struct gsm_bts *bts, struct msgb *msg) +{ + struct gsm_pcu_if *pcu_prim; + if (msgb_l2len(msg) < PCUIF_HDR_SIZE) { + LOGP(DPCU, LOGL_ERROR, "ABIS_OSMO_PCU message too short\n"); + oml_tx_failure_event_rep(&bts->mo, NM_SEVER_MAJOR, OSMO_EVT_MAJ_UKWN_MSG, + "ABIS_OSMO_PCU message too short\n"); + msgb_free(msg); + return -EIO; + } + pcu_prim = msgb_l2(msg); + LOGP(DPCU, LOGL_INFO, "Rx BSC->BTS%d ABIS_OSMO_PCU msg type %u\n", + pcu_prim->bts_nr, pcu_prim->msg_type); + /* we patch the bts_nr received from BTS with the bts_nr we used to set up in the local PCU */ + pcu_prim->bts_nr = bts->nr; + /* Trim Abis lower layers: */ + msgb_pull_to_l2(msg); + /* we simply forward it to PCUIF: */ + return pcu_sock_send(msg); +} + +/* incoming IPA/OSMO extension Abis message from BSC */ +int down_osmo(struct gsm_bts *bts, struct msgb *msg) +{ + uint8_t *type; + + if (msgb_l2len(msg) < 1) { + oml_tx_failure_event_rep(&bts->mo, NM_SEVER_MAJOR, OSMO_EVT_MAJ_UKWN_MSG, + "OSMO message too short\n"); + msgb_free(msg); + return -EIO; + } + + type = msgb_l2(msg); + msg->l2h = type + 1; + + switch (*type) { + case IPAC_PROTO_EXT_PCU: + return rx_down_osmo_pcu(bts, msg); + default: + oml_tx_failure_event_rep(&bts->mo, NM_SEVER_MAJOR, OSMO_EVT_MAJ_UKWN_MSG, + "OSMO message unknown extension %u\n", *type); + msgb_free(msg); + return -EIO; + } +} diff --git a/src/common/amr.c b/src/common/amr.c index 05d1aaac..47c9dcb5 100644 --- a/src/common/amr.c +++ b/src/common/amr.c @@ -6,6 +6,85 @@ #include <osmo-bts/logging.h> #include <osmo-bts/amr.h> +/* Reasonable defaults for AMR-FR and AMR-HR rate configuration. + * The values are taken from 3GPP TS 51.010-1 (version 13.11.0). + * See 14.2.19.4.1 and 14.2.20.4.1 for AMR-FR and AMR-HR, respectively. + * + * ^ C/I (dB) | FR / HR | + * | | + * | | + * MODE4 | | + * = | ----+---- THR_MX_Up(3) | 20.5 / 18.0 | + * | | | + * | = ----+---- THR_MX_Dn(4) | 18.5 / 16.0 | + * MODE3 | | + * | = ----+---- THR_MX_Up(2) | 14.5 / 14.0 | + * | | | + * = | ----+---- THR_MX_Dn(3) | 12.5 / 12.0 | + * MODE2 | | + * = | ----+---- THR_MX_Up(1) | 8.5 / 10.0 | + * | | | + * | = ----+---- THR_MX_Dn(2) | 6.5 / 8.0 | + * MODE1 | | + * | | + * | | + */ +static const struct gsm48_multi_rate_conf amr_fr_mr_cfg_def = { + .m4_75 = 1, + .m5_90 = 1, + .m7_95 = 1, + .m12_2 = 1, +}; +static const struct amr_mode amr_fr_bts_mode_def[] = { + { + .mode = 0, /* 4.75k */ + .threshold = 13, /* THR_MX_Dn(2): 6.5 dB */ + .hysteresis = 4, /* THR_MX_Up(1): 8.5 dB */ + }, + { + .mode = 2, /* 5.90k */ + .threshold = 25, /* THR_MX_Dn(3): 12.5 dB */ + .hysteresis = 4, /* THR_MX_Up(2): 14.5 dB */ + }, + { + .mode = 5, /* 7.95k */ + .threshold = 37, /* THR_MX_Dn(4): 18.5 dB */ + .hysteresis = 4, /* THR_MX_Up(3): 20.5 dB */ + }, + { + .mode = 7, /* 12.2k */ + /* this is the last mode, so no threshold */ + }, +}; + +static const struct gsm48_multi_rate_conf amr_hr_mr_cfg_def = { + .m4_75 = 1, + .m5_90 = 1, + .m6_70 = 1, + .m7_95 = 1, +}; +static const struct amr_mode amr_hr_bts_mode_def[] = { + { + .mode = 0, /* 4.75k */ + .threshold = 16, /* THR_MX_Dn(2): 8.0 dB */ + .hysteresis = 4, /* THR_MX_Up(1): 10.0 dB */ + }, + { + .mode = 2, /* 5.90k */ + .threshold = 24, /* THR_MX_Dn(3): 12.0 dB */ + .hysteresis = 4, /* THR_MX_Up(2): 14.0 dB */ + }, + { + .mode = 3, /* 6.70k */ + .threshold = 32, /* THR_MX_Dn(4): 16.0 dB */ + .hysteresis = 4, /* THR_MX_Up(3): 18.0 dB */ + }, + { + .mode = 5, /* 7.95k */ + /* this is the last mode, so no threshold */ + }, +}; + void amr_log_mr_conf(int ss, int logl, const char *pfx, struct amr_multirate_conf *amr_mrc) { @@ -16,9 +95,9 @@ void amr_log_mr_conf(int ss, int logl, const char *pfx, for (i = 0; i < amr_mrc->num_modes; i++) LOGPC(ss, logl, ", mode[%u] = %u/%u/%u", - i, amr_mrc->bts_mode[i].mode, - amr_mrc->bts_mode[i].threshold, - amr_mrc->bts_mode[i].hysteresis); + i, amr_mrc->mode[i].mode, + amr_mrc->mode[i].threshold, + amr_mrc->mode[i].hysteresis); LOGPC(ss, logl, "\n"); } @@ -27,7 +106,7 @@ static inline int get_amr_mode_idx(const struct amr_multirate_conf *amr_mrc, { unsigned int i; for (i = 0; i < amr_mrc->num_modes; i++) { - if (amr_mrc->bts_mode[i].mode == cmi) + if (amr_mrc->mode[i].mode == cmi) return i; } return -EINVAL; @@ -78,13 +157,16 @@ void amr_set_mode_pref(uint8_t *data, const struct amr_multirate_conf *amr_mrc, int amr_parse_mr_conf(struct amr_multirate_conf *amr_mrc, const uint8_t *mr_conf, unsigned int len) { - uint8_t mr_version = mr_conf[0] >> 5; uint8_t num_codecs = 0; int i, j = 0; - if (mr_version != 1) { - LOGP(DRSL, LOGL_ERROR, "AMR Multirate Version %u unknown\n", - mr_version); + if (len < 2) { + LOGP(DRSL, LOGL_ERROR, "AMR Multirate IE is too short (%u)\n", len); + goto ret_einval; + } + + if ((mr_conf[0] >> 5) != 1) { + LOGP(DRSL, LOGL_ERROR, "AMR Multirate Version %u unknown\n", (mr_conf[0] >> 5)); goto ret_einval; } @@ -114,23 +196,26 @@ int amr_parse_mr_conf(struct amr_multirate_conf *amr_mrc, for (i = 0; i < 8; i++) { if (mr_conf[1] & (1 << i)) { - amr_mrc->bts_mode[j++].mode = i; + amr_mrc->mode[j++].mode = i; } } + /* skip the first two octets of the IE */ + mr_conf += 2; + if (num_codecs >= 2) { - amr_mrc->bts_mode[0].threshold = mr_conf[1] & 0x3F; - amr_mrc->bts_mode[0].hysteresis = mr_conf[2] >> 4; + amr_mrc->mode[0].threshold = mr_conf[0] & 0x3F; + amr_mrc->mode[0].hysteresis = mr_conf[1] >> 4; } if (num_codecs >= 3) { - amr_mrc->bts_mode[1].threshold = - ((mr_conf[2] & 0xF) << 2) | (mr_conf[3] >> 6); - amr_mrc->bts_mode[1].hysteresis = (mr_conf[3] >> 2) & 0xF; + amr_mrc->mode[1].threshold = + ((mr_conf[1] & 0xF) << 2) | (mr_conf[2] >> 6); + amr_mrc->mode[1].hysteresis = (mr_conf[2] >> 2) & 0xF; } if (num_codecs >= 4) { - amr_mrc->bts_mode[2].threshold = - ((mr_conf[3] & 0x3) << 4) | (mr_conf[4] >> 4); - amr_mrc->bts_mode[2].hysteresis = mr_conf[4] & 0xF; + amr_mrc->mode[2].threshold = + ((mr_conf[2] & 0x3) << 4) | (mr_conf[3] >> 4); + amr_mrc->mode[2].hysteresis = mr_conf[3] & 0xF; } return num_codecs; @@ -168,3 +253,26 @@ unsigned int amr_get_initial_mode(struct gsm_lchan *lchan) } } } + +void amr_init_mr_conf_def(struct gsm_lchan *lchan) +{ + const struct gsm48_multi_rate_conf *mr_cfg; + const struct amr_mode *bts_mode; + unsigned int num_modes; + + if (lchan->type == GSM_LCHAN_TCH_F) { + num_modes = ARRAY_SIZE(amr_fr_bts_mode_def); + bts_mode = &amr_fr_bts_mode_def[0]; + mr_cfg = &amr_fr_mr_cfg_def; + } else { + num_modes = ARRAY_SIZE(amr_hr_bts_mode_def); + bts_mode = &amr_hr_bts_mode_def[0]; + mr_cfg = &amr_hr_mr_cfg_def; + } + + memcpy(lchan->tch.amr_mr.gsm48_ie, mr_cfg, + sizeof(lchan->tch.amr_mr.gsm48_ie)); + memcpy(&lchan->tch.amr_mr.mode[0], &bts_mode[0], + sizeof(lchan->tch.amr_mr.mode)); + lchan->tch.amr_mr.num_modes = num_modes; +} diff --git a/src/common/asci.c b/src/common/asci.c new file mode 100644 index 00000000..4342f17c --- /dev/null +++ b/src/common/asci.c @@ -0,0 +1,211 @@ +/* ASCI (VGCS/VBS) related common code */ + +/* (C) 2023 by Harald Welte <laforge@osmocom.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 <stdint.h> +#include <errno.h> + +#include <osmocom/gsm/protocol/gsm_04_08.h> +#include <osmocom/gsm/rsl.h> + +#include <osmo-bts/bts.h> +#include <osmo-bts/bts_model.h> +#include <osmo-bts/rsl.h> +#include <osmo-bts/logging.h> +#include <osmo-bts/l1sap.h> +#include <osmo-bts/asci.h> + +static int tx_vgcs_ul_grant(struct gsm_lchan *lchan) +{ + struct gsm0408_vgcs_ul_grant ul_grant; + struct gsm_time gt; + struct msgb *msg; + + gsm_fn2gsmtime(>, lchan->asci.fn); + + /* build the RR VGCS UPLINK GRANT message as per TS 44.018 Section 9.1.49 */ + ul_grant = (struct gsm0408_vgcs_ul_grant) { + .hdr = { + .proto_discr = GSM48_PDISC_RR, + .msg_type = GSM48_MT_RR_VGCS_UPL_GRANT, + }, + .req_ref = { + .ra = lchan->asci.ref, + .t1 = gt.t1, + .t2 = gt.t2, + .t3_low = gt.t3 & 7, + .t3_high = gt.t3 >> 3, + }, + .ta = lchan->ta_ctrl.current, + }; + + /* Wrap it in a RSL UNITDATA REQUEST */ + msg = rsl_rll_simple(RSL_MT_UNIT_DATA_REQ, gsm_lchan2chan_nr(lchan), 0x00, 0); + msg->l3h = msg->tail; /* emulate rsl_rx_rll() behaviour */ + msgb_tl16v_put(msg, RSL_IE_L3_INFO, sizeof(ul_grant), (uint8_t *) &ul_grant); + + /* send it towards MS, just like a RSL message from the BSC */ + return lapdm_rslms_recvmsg(msg, &lchan->lapdm_ch); +} + +/* timer call-back for T3115 (VGCS UPLINK GRANT re-transmit) */ +static void vgcs_t3115_cb(void *data) +{ + struct gsm_lchan *lchan = data; + struct gsm_bts *bts = lchan->ts->trx->bts; + + LOGPLCHAN(lchan, DASCI, LOGL_INFO, "T3115 timeout (%d resends left)\n", + bts->ny2 - lchan->asci.vgcs_ul_grant_count); + + if (lchan->state != LCHAN_S_ACTIVE) { + LOGPLCHAN(lchan, DASCI, LOGL_NOTICE, "is not active. It is in state %s. Ignoring\n", + gsm_lchans_name(lchan->state)); + return; + } + + if (lchan->asci.vgcs_ul_grant_count >= bts->ny2) { + lchan->asci.vgcs_ul_grant_count = 0; + LOGPLCHAN(lchan, DASCI, LOGL_NOTICE, "NY2 reached, sending CONNection FAILure to BSC.\n"); + rsl_tx_conn_fail(lchan, RSL_ERR_TALKER_ACC_FAIL); + lchan->asci.talker_active = VGCS_TALKER_NONE; + return; + } + + tx_vgcs_ul_grant(lchan); + lchan->asci.vgcs_ul_grant_count++; + osmo_timer_schedule(&lchan->asci.t3115, 0, bts->t3115_ms * 1000); +} + +/* Received random access on dedicated channel. */ +void vgcs_rach(struct gsm_lchan *lchan, uint8_t ra, uint8_t acc_delay, uint32_t fn) +{ + LOGPLCHAN(lchan, DASCI, LOGL_NOTICE, "VGCS RACH on dedicated channel type %s received with " + "TA=%u, ref=%u\n", gsm_lchant_name(lchan->type), acc_delay, ra); + + if (ra == 0x25) { /* See TS 44.018 Table 9.1.45.1 */ + /* Listener Detection (TS 48.058 Section 4.14) */ + if (!lchan->asci.listener_detected) { + rsl_tx_listener_det(lchan, &acc_delay); + lchan->asci.listener_detected = true; + } + } else { + /* Talker Detection (TS 48.058 Section 4.13) */ + struct gsm_bts *bts = lchan->ts->trx->bts; + + /* Talker detection on group channels only */ + if (!rsl_chan_rt_is_vgcs(lchan->rsl_chan_rt)) + return; + + if (lchan->asci.talker_active != VGCS_TALKER_NONE) { + LOGPLCHAN(lchan, DASCI, LOGL_DEBUG, "Ignoring RACH, there is an active talker already.\n"); + return; + } + + /* Set timing advance, power level and activate SACCH */ + lchan->ta_ctrl.current = acc_delay; + lchan->ms_power_ctrl.current = lchan->ms_power_ctrl.max; + lchan->want_dl_sacch_active = true; + + /* Stop RACH detection, wait for valid frame */ + lchan->asci.talker_active = VGCS_TALKER_WAIT_FRAME; + if (l1sap_uplink_access(lchan, false) != 0) { + LOGPLCHAN(lchan, DASCI, LOGL_ERROR, "Failed to deactivate uplink access after TALKER DET.\n"); + rsl_tx_conn_fail(lchan, RSL_ERR_EQUIPMENT_FAIL); + lchan->asci.talker_active = VGCS_TALKER_NONE; + return; + } + + lchan->asci.ref = ra; + lchan->asci.fn = fn; + + /* Send TALKER DETECT via RSL to BSC */ + rsl_tx_talker_det(lchan, &acc_delay); + + /* Send VGCS UPLINK GRANT */ + lchan->asci.vgcs_ul_grant_count = 1; + tx_vgcs_ul_grant(lchan); + + /* Start T3115 */ + LOGPLCHAN(lchan, DASCI, LOGL_DEBUG, "Starting T3115 with %u ms\n", bts->t3115_ms); + lchan->asci.t3115.cb = vgcs_t3115_cb; + lchan->asci.t3115.data = lchan; + osmo_timer_schedule(&lchan->asci.t3115, 0, bts->t3115_ms * 1000); + } +} + +/* Received channel activation. */ +void vgcs_lchan_activate(struct gsm_lchan *lchan) +{ + LOGPLCHAN(lchan, DASCI, LOGL_INFO, "Channel is activated.\n"); + if (l1sap_uplink_access(lchan, true) != 0) { + LOGPLCHAN(lchan, DASCI, LOGL_ERROR, "Failed to activate uplink access after channel activation.\n"); + rsl_tx_conn_fail(lchan, RSL_ERR_EQUIPMENT_FAIL); + } +} + +/* Received channel reactivation. (for assignment) */ +void vgcs_lchan_react(struct gsm_lchan *lchan) +{ + LOGPLCHAN(lchan, DASCI, LOGL_INFO, "Channel is activated for assignment.\n"); + lchan->asci.talker_active = VGCS_TALKER_WAIT_FRAME; + if (l1sap_uplink_access(lchan, false) != 0) { + LOGPLCHAN(lchan, DASCI, LOGL_ERROR, "Failed to deactivate uplink access for assignment.\n"); + rsl_tx_conn_fail(lchan, RSL_ERR_EQUIPMENT_FAIL); + } + radio_link_timeout_reset(lchan); +} + +/* Received first valid data frame on dedicated channel. */ +void vgcs_talker_frame(struct gsm_lchan *lchan) +{ + LOGPLCHAN(lchan, DASCI, LOGL_INFO, "First valid frame detected, talker now active.\n"); + osmo_timer_del(&lchan->asci.t3115); + lchan->asci.talker_active = VGCS_TALKER_ACTIVE; + radio_link_timeout_reset(lchan); +} + +/* Release VGCS Talker state. */ +void vgcs_talker_reset(struct gsm_lchan *lchan, bool ul_access) +{ + if (lchan->asci.talker_active == VGCS_TALKER_NONE) + return; + + LOGPLCHAN(lchan, DASCI, LOGL_INFO, "Uplink released, no talker.\n"); + + /* Stop T3115 */ + osmo_timer_del(&lchan->asci.t3115); + + /* Talker released. */ + lchan->asci.talker_active = VGCS_TALKER_NONE; + if (ul_access) { + if (l1sap_uplink_access(lchan, true) != 0) { + LOGPLCHAN(lchan, DASCI, LOGL_ERROR, + "Failed to activate uplink access after uplink became free.\n"); + rsl_tx_conn_fail(lchan, RSL_ERR_EQUIPMENT_FAIL); + } + } +} + +/* Release VGCS Listener state. */ +void vgcs_listener_reset(struct gsm_lchan *lchan) +{ + lchan->asci.listener_detected = false; +} diff --git a/src/common/bts.c b/src/common/bts.c index 73631ae6..633e0d13 100644 --- a/src/common/bts.c +++ b/src/common/bts.c @@ -13,7 +13,7 @@ * 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. + * 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/>. @@ -32,6 +32,7 @@ #include <osmocom/core/timer.h> #include <osmocom/core/msgb.h> #include <osmocom/core/talloc.h> +#include <osmocom/core/tdef.h> #include <osmocom/core/stats.h> #include <osmocom/core/rate_ctr.h> #include <osmocom/gsm/protocol/gsm_12_21.h> @@ -43,24 +44,27 @@ #include <osmo-bts/abis.h> #include <osmo-bts/bts.h> #include <osmo-bts/bts_model.h> +#include <osmo-bts/bts_sm.h> #include <osmo-bts/dtx_dl_amr_fsm.h> #include <osmo-bts/pcuif_proto.h> +#include <osmo-bts/pcu_if.h> #include <osmo-bts/rsl.h> #include <osmo-bts/oml.h> #include <osmo-bts/signal.h> #include <osmo-bts/dtx_dl_amr_fsm.h> #include <osmo-bts/cbch.h> +#include <osmo-bts/bts_shutdown_fsm.h> +#include <osmo-bts/nm_common_fsm.h> +#include <osmo-bts/power_control.h> +#include <osmo-bts/osmux.h> +#include <osmo-bts/notification.h> +#define MAX_TA_DEF 63 /* default max Timing Advance value */ #define MIN_QUAL_RACH 50 /* minimum link quality (in centiBels) for Access Bursts */ #define MIN_QUAL_NORM -5 /* minimum link quality (in centiBels) for Normal Bursts */ static void bts_update_agch_max_queue_length(struct gsm_bts *bts); -struct gsm_network bts_gsmnet = { - .bts_list = { &bts_gsmnet.bts_list, &bts_gsmnet.bts_list }, - .num_bts = 0, -}; - void *tall_bts_ctx; /* Table 3.1 TS 04.08: Values of parameter S */ @@ -86,17 +90,29 @@ static int bts_signal_cbfn(unsigned int subsys, unsigned int signal, static const struct rate_ctr_desc bts_ctr_desc[] = { [BTS_CTR_PAGING_RCVD] = {"paging:rcvd", "Received paging requests (Abis)"}, [BTS_CTR_PAGING_DROP] = {"paging:drop", "Dropped paging requests (Abis)"}, + [BTS_CTR_PAGING_DROP_PS] = {"paging:drop-ps", "Dropped paging requests (PS/PCU)"}, + [BTS_CTR_PAGING_CONG] = {"paging:cong", "Paging congestion detected (Abis)"}, [BTS_CTR_PAGING_SENT] = {"paging:sent", "Sent paging requests (Um)"}, [BTS_CTR_RACH_RCVD] = {"rach:rcvd", "Received RACH requests (Um)"}, [BTS_CTR_RACH_DROP] = {"rach:drop", "Dropped RACH requests (Um)"}, [BTS_CTR_RACH_HO] = {"rach:handover", "Received RACH requests (Handover)"}, + [BTS_CTR_RACH_VGCS] = {"rach:vgcs", "Received RACH requests (VGCS)"}, [BTS_CTR_RACH_CS] = {"rach:cs", "Received RACH requests (CS/Abis)"}, [BTS_CTR_RACH_PS] = {"rach:ps", "Received RACH requests (PS/PCU)"}, [BTS_CTR_AGCH_RCVD] = {"agch:rcvd", "Received AGCH requests (Abis)"}, [BTS_CTR_AGCH_SENT] = {"agch:sent", "Sent AGCH requests (Abis)"}, [BTS_CTR_AGCH_DELETED] = {"agch:delete", "Sent AGCH DELETE IND (Abis)"}, + + [BTS_CTR_RTP_RX_TOTAL] = {"rtp:rx:total", "Total number of received RTP packets"}, + [BTS_CTR_RTP_RX_MARKER] = {"rtp:rx:marker", "Number of received RTP packets with marker bit set"}, + [BTS_CTR_RTP_RX_DROP_PREEN] = {"rtp:rx:drop:preen", "Total number of received RTP packets dropped during preening"}, + [BTS_CTR_RTP_RX_DROP_LOOPBACK] = {"rtp:rx:drop:loopback", "Total number of received RTP packets dropped during loopback"}, + [BTS_CTR_RTP_RX_DROP_OVERFLOW] = {"rtp:rx:drop:overflow", "Total number of received RTP packets dropped during DL queue overflow"}, + [BTS_CTR_RTP_RX_DROP_V110_DEC] = {"rtp:rx:drop:v110_dec", "Total number of received RTP packets dropped during V.110 decode"}, + [BTS_CTR_RTP_TX_TOTAL] = {"rtp:tx:total", "Total number of transmitted RTP packets"}, + [BTS_CTR_RTP_TX_MARKER] = {"rtp:tx:marker", "Number of transmitted RTP packets with marker bit set"}, }; static const struct rate_ctr_group_desc bts_ctrg_desc = { "bts", @@ -122,6 +138,178 @@ static const struct rate_ctr_group_desc cbch_ctrg_desc = { cbch_ctr_desc }; +struct osmo_tdef bts_T_defs[] = { + /* T-1: FIXME: Ideally should be dynamically calculated per trx at + * shutdown start based on params below, and highest trx value taken: + * + VTY's power-ramp step-interval. + * + Amount of steps needed (taking into account how many dB each step moves). + * + Extra time to get response back for each step. + * For now we simply give 5 mins, which should be enough for any + * acceptable setup, while still ensuring will timeout at some point if + * something fails in the ramp down procedure. + */ + { .T=-1, .default_val=300, .desc="Time after which osmo-bts exits if regular ramp down during shut down process does not finish (s)" }, + { .T=-2, .default_val=3, .desc="Time after which osmo-bts exits if requesting transceivers to stop during shut down process does not finish (s)" }, + {} +}; + +struct osmo_tdef abis_T_defs[] = { + { .T=-15, .default_val=0, .unit=OSMO_TDEF_MS, .desc="Time to wait between Channel Activation and dispatching a cached early Immediate Assignment" }, + {} +}; + +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, +}; + +const struct value_string osmo_bts_variant_names[_NUM_BTS_VARIANT + 1] = { + { BTS_UNKNOWN, "unknown" }, + { BTS_OSMO_LITECELL15, "osmo-bts-lc15" }, + { BTS_OSMO_OC2G, "osmo-bts-oc2g" }, + { BTS_OSMO_OCTPHY, "osmo-bts-octphy" }, + { BTS_OSMO_SYSMO, "osmo-bts-sysmo" }, + { BTS_OSMO_TRX, "osmo-bts-trx" }, + { BTS_OSMO_VIRTUAL, "osmo-bts-virtual" }, + { BTS_OSMO_OMLDUMMY, "osmo-bts-omldummy" }, + { 0, NULL } +}; + +const char *btsvariant2str(enum gsm_bts_type_variant v) +{ + return get_value_string(osmo_bts_variant_names, v); +} + +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 } +}; + +const char *btsatttr2str(enum bts_attribute v) +{ + return get_value_string(bts_attribute_names, v); +} + +const struct value_string bts_impl_flag_desc[] = { + { BTS_INTERNAL_FLAG_MS_PWR_CTRL_DSP, "DSP/HW based MS Power Control Loop" }, + { BTS_INTERNAL_FLAG_MEAS_PAYLOAD_COMB, "Measurement and Payload data combined" }, + { BTS_INTERNAL_FLAG_NM_RCHANNEL_DEPENDS_RCARRIER, "OML RadioChannel MO depends on RadioCarrier MO" }, + { BTS_INTERNAL_FLAG_INTERF_MEAS, "Uplink interference measurements" }, + { 0, NULL } +}; + +/* Ensure that all BTS_INTERNAL_FLAG_* entries are present in bts_impl_flag_desc[] */ +osmo_static_assert(ARRAY_SIZE(bts_impl_flag_desc) == _BTS_INTERNAL_FLAG_NUM + 1, _bts_impl_flag_desc); + +static int gsm_bts_talloc_destructor(struct gsm_bts *bts) +{ + if (bts->mo.fi) { + osmo_fsm_inst_free(bts->mo.fi); + bts->mo.fi = NULL; + } + if (bts->shutdown_fi) { + osmo_fsm_inst_free(bts->shutdown_fi); + bts->shutdown_fi = NULL; + } + + bts_osmux_release(bts); + + llist_del(&bts->list); + g_bts_sm->num_bts--; + return 0; +} + +struct gsm_bts *gsm_bts_alloc(struct gsm_bts_sm *bts_sm, uint8_t bts_num) +{ + struct gsm_bts *bts = talloc_zero(bts_sm, struct gsm_bts); + + if (!bts) + return NULL; + + talloc_set_destructor(bts, gsm_bts_talloc_destructor); + + /* add to list of BTSs */ + llist_add_tail(&bts->list, &bts_sm->bts_list); + g_bts_sm->num_bts++; + + bts->site_mgr = bts_sm; + bts->nr = bts_num; + bts->num_trx = 0; + INIT_LLIST_HEAD(&bts->trx_list); + bts->ms_max_power = 15; /* dBm */ + + bts->T_defs = bts_T_defs; + osmo_tdefs_reset(bts->T_defs); + osmo_tdefs_reset(abis_T_defs); + bts->shutdown_fi = osmo_fsm_inst_alloc(&bts_shutdown_fsm, bts, bts, + LOGL_INFO, NULL); + osmo_fsm_inst_update_id_f(bts->shutdown_fi, "bts%d", bts->nr); + + /* NM BTS */ + bts->mo.fi = osmo_fsm_inst_alloc(&nm_bts_fsm, bts, bts, + LOGL_INFO, NULL); + osmo_fsm_inst_update_id_f(bts->mo.fi, "bts%d", bts->nr); + gsm_mo_init(&bts->mo, bts, NM_OC_BTS, bts->nr, 0xff, 0xff); + + /* NM GPRS CELL */ + bts->gprs.cell.mo.fi = osmo_fsm_inst_alloc(&nm_gprs_cell_fsm, bts, &bts->gprs.cell, + LOGL_INFO, NULL); + osmo_fsm_inst_update_id_f(bts->gprs.cell.mo.fi, "gprs_cell%d-0", bts->nr); + gsm_mo_init(&bts->gprs.cell.mo, bts, NM_OC_GPRS_CELL, bts->nr, 0, 0xff); + memcpy(&bts->gprs.cell.rlc_cfg, &rlc_cfg_default, sizeof(bts->gprs.cell.rlc_cfg)); + memcpy(&bts->gprs.cell.timer, bts_cell_timer_default, sizeof(bts->gprs.cell.timer)); + + /* create our primary TRX. It will be initialized during bts_init() */ + bts->c0 = gsm_bts_trx_alloc(bts); + if (!bts->c0) { + talloc_free(bts); + return NULL; + } + bts->c0->ts[0].pchan = GSM_PCHAN_CCCH_SDCCH4; + + bts->features = bitvec_alloc(MAX_BTS_FEATURES / 8, bts); + OSMO_ASSERT(bts->features != NULL); + + return bts; +} + +struct gsm_bts *gsm_bts_num(const struct gsm_bts_sm *bts_sm, int num) +{ + struct gsm_bts *bts; + + if (num >= bts_sm->num_bts) + return NULL; + + llist_for_each_entry(bts, &bts_sm->bts_list, list) { + if (bts->nr == num) + return bts; + } + + return NULL; +} + /* Initialize the BTS data structures, called before config * file reading */ int bts_init(struct gsm_bts *bts) @@ -130,19 +318,14 @@ int bts_init(struct gsm_bts *bts) static int initialized = 0; void *tall_rtp_ctx; - /* add to list of BTSs */ - llist_add_tail(&bts->list, &bts_gsmnet.bts_list); - bts->band = GSM_BAND_1800; INIT_LLIST_HEAD(&bts->agch_queue.queue); bts->agch_queue.length = 0; bts->ctrs = rate_ctr_group_alloc(bts, &bts_ctrg_desc, bts->nr); - if (!bts->ctrs) { - llist_del(&bts->list); + if (!bts->ctrs) return -1; - } /* enable management with default levels, * raise threshold to GSM_BTS_AGCH_QUEUE_THRESH_LEVEL_DISABLE to @@ -154,56 +337,73 @@ int bts_init(struct gsm_bts *bts) /* configurable via VTY */ bts->paging_state = paging_init(bts, 200, 0); - bts->ul_power_target = -75; /* dBm default */ bts->rtp_jitter_adaptive = false; bts->rtp_port_range_start = 16384; bts->rtp_port_range_end = 17407; bts->rtp_port_range_next = bts->rtp_port_range_start; + bts->rtp_ip_dscp = -1; + bts->rtp_priority = -1; + bts->emit_hr_rfc5993 = true; + + /* Default (fall-back) MS/BS Power control parameters */ + power_ctrl_params_def_reset(&bts->bs_dpc_params, true); + power_ctrl_params_def_reset(&bts->ms_dpc_params, false); /* configurable via OML */ + bts->bsic = 0xff; /* invalid value, guarded by bsc_configured=false */ + bts->bsic_configured = false; bts->load.ccch.load_ind_period = 112; - load_timer_start(bts); bts->rtp_jitter_buf_ms = 100; - bts->max_ta = 63; + bts->max_ta = MAX_TA_DEF; bts->ny1 = 4; + bts->ny2 = 4; bts->t3105_ms = 300; + bts->t3115_ms = 300; bts->min_qual_rach = MIN_QUAL_RACH; bts->min_qual_norm = MIN_QUAL_NORM; bts->max_ber10k_rach = 1707; /* 7 of 41 bits is Eb/N0 of 0 dB = 0.1707 */ bts->pcu.sock_path = talloc_strdup(bts, PCU_SOCK_DEFAULT); - for (i = 0; i < ARRAY_SIZE(bts->t200_ms); i++) - bts->t200_ms[i] = oml_default_t200_ms[i]; + bts->pcu.sock_wqueue_len_max = BTS_PCU_SOCK_WQUEUE_LEN_DEFAULT; + for (i = 0; i < ARRAY_SIZE(bts->t200_fn); i++) + bts->t200_fn[i] = oml_default_t200_fn[i]; /* default RADIO_LINK_TIMEOUT */ - bts->radio_link_timeout = 32; - - /* Start with the site manager */ - oml_mo_state_init(&bts->site_mgr.mo, NM_OPSTATE_ENABLED, NM_AVSTATE_OK); + bts->radio_link_timeout.oml = 32; + bts->radio_link_timeout.current = bts->radio_link_timeout.oml; - /* set BTS to dependency */ - oml_mo_state_init(&bts->mo, -1, NM_AVSTATE_DEPENDENCY); - oml_mo_state_init(&bts->gprs.nse.mo, -1, NM_AVSTATE_DEPENDENCY); - oml_mo_state_init(&bts->gprs.cell.mo, -1, NM_AVSTATE_DEPENDENCY); - oml_mo_state_init(&bts->gprs.nsvc[0].mo, -1, NM_AVSTATE_DEPENDENCY); - oml_mo_state_init(&bts->gprs.nsvc[1].mo, NM_OPSTATE_DISABLED, NM_AVSTATE_OFF_LINE); + /* Start with the BTS */ + oml_mo_state_init(&bts->mo, NM_OPSTATE_DISABLED, NM_AVSTATE_NOT_INSTALLED); + oml_mo_state_init(&bts->gprs.cell.mo, NM_OPSTATE_DISABLED, NM_AVSTATE_NOT_INSTALLED); /* allocate a talloc pool for ORTP to ensure it doesn't have to go back * to the libc malloc all the time */ tall_rtp_ctx = talloc_pool(tall_bts_ctx, 262144); osmo_rtp_init(tall_rtp_ctx); - /* features implemented in 'common', available for all models */ - gsm_bts_set_feature(bts, BTS_FEAT_ETWS_PN); + /* Osmux */ + rc = bts_osmux_init(bts); + if (rc < 0) + return rc; + + /* features implemented in 'common', available for all models, + * order alphabetically */ + osmo_bts_set_feature(bts->features, BTS_FEAT_ABIS_OSMO_PCU); + osmo_bts_set_feature(bts->features, BTS_FEAT_CCN); + osmo_bts_set_feature(bts->features, BTS_FEAT_DYN_TS_SDCCH8); + osmo_bts_set_feature(bts->features, BTS_FEAT_ETWS_PN); + osmo_bts_set_feature(bts->features, BTS_FEAT_IPV6_NSVC); + osmo_bts_set_feature(bts->features, BTS_FEAT_PAGING_COORDINATION); + osmo_bts_set_feature(bts->features, BTS_FEAT_TWTS001); + + /* Maximum TA supported by the PHY (can be overridden by PHY specific code) */ + bts->support.max_ta = MAX_TA_DEF; rc = bts_model_init(bts); - if (rc < 0) { - llist_del(&bts->list); + if (rc < 0) return rc; - } /* TRX0 was allocated early during gsm_bts_alloc, not later through VTY */ - bts_trx_init(bts->c0); - bts_gsmnet.num_bts++; + bts_model_trx_init(bts->c0); if (!initialized) { osmo_signal_register_handler(SS_GLOBAL, bts_signal_cbfn, NULL); @@ -220,7 +420,10 @@ int bts_init(struct gsm_bts *bts) bts->smscb_queue_tgt_len = 2; bts->smscb_queue_hyst = 2; - INIT_LLIST_HEAD(&bts->oml_queue); + bts->asci.pos_nch = -ENOTSUP; + INIT_LLIST_HEAD(&bts->asci.notifications); + + INIT_LLIST_HEAD(&bts->bsc_oml_hosts); /* register DTX DL FSM */ rc = osmo_fsm_register(&dtx_dl_amr_fsm); @@ -234,206 +437,20 @@ int bts_init(struct gsm_bts *bts) return rc; } -/* Initialize the TRX data structures, called before config - * file reading */ -int bts_trx_init(struct gsm_bts_trx *trx) -{ - /* initialize bts data structure */ - struct trx_power_params *tpp = &trx->power_params; - int rc, i; - - for (i = 0; i < ARRAY_SIZE(trx->ts); i++) { - struct gsm_bts_trx_ts *ts = &trx->ts[i]; - int k; - - for (k = 0; k < ARRAY_SIZE(ts->lchan); k++) { - struct gsm_lchan *lchan = &ts->lchan[k]; - INIT_LLIST_HEAD(&lchan->dl_tch_queue); - } - } - /* Default values for the power adjustments */ - tpp->ramp.max_initial_pout_mdBm = to_mdB(0); - tpp->ramp.step_size_mdB = to_mdB(2); - tpp->ramp.step_interval_sec = 1; - - rc = bts_model_trx_init(trx); - if (rc < 0) { - llist_del(&trx->list); - return rc; - } - return 0; -} - -static void shutdown_timer_cb(void *data) -{ - fprintf(stderr, "Shutdown timer expired\n"); - exit(42); -} - -static struct osmo_timer_list shutdown_timer = { - .cb = &shutdown_timer_cb, -}; - -void bts_shutdown(struct gsm_bts *bts, const char *reason) -{ - struct gsm_bts_trx *trx; - - if (osmo_timer_pending(&shutdown_timer)) { - LOGP(DOML, LOGL_NOTICE, - "BTS is already being shutdown.\n"); - return; - } - - LOGP(DOML, LOGL_NOTICE, "Shutting down BTS %u, Reason %s\n", - bts->nr, reason); - - llist_for_each_entry_reverse(trx, &bts->trx_list, list) { - bts_model_trx_deact_rf(trx); - bts_model_trx_close(trx); - } - - /* shedule a timer to make sure select loop logic can run again - * to dispatch any pending primitives */ - osmo_timer_schedule(&shutdown_timer, 3, 0); -} - /* main link is established, send status report */ int bts_link_estab(struct gsm_bts *bts) { - int i, j; - - LOGP(DSUM, LOGL_INFO, "Main link established, sending Status'.\n"); - - /* BTS and SITE MGR are EANBLED, BTS is DEPENDENCY */ - oml_tx_state_changed(&bts->site_mgr.mo); - oml_tx_state_changed(&bts->mo); + LOGP(DOML, LOGL_INFO, "Main link established, sending NM Status\n"); - /* those should all be in DEPENDENCY */ - oml_tx_state_changed(&bts->gprs.nse.mo); - oml_tx_state_changed(&bts->gprs.cell.mo); - oml_tx_state_changed(&bts->gprs.nsvc[0].mo); - oml_tx_state_changed(&bts->gprs.nsvc[1].mo); - - /* All other objects start off-line until the BTS Model code says otherwise */ - for (i = 0; i < bts->num_trx; i++) { - struct gsm_bts_trx *trx = gsm_bts_trx_num(bts, i); - - oml_tx_state_changed(&trx->mo); - oml_tx_state_changed(&trx->bb_transc.mo); - - for (j = 0; j < ARRAY_SIZE(trx->ts); j++) { - struct gsm_bts_trx_ts *ts = &trx->ts[j]; - - oml_tx_state_changed(&ts->mo); - } - } + /* Signal OML UP to BTS SITE MGR. It will automatically SW_ACT repoort + * and become Disabled-Offline, then dispatch same event to its children + * objects. + */ + osmo_fsm_inst_dispatch(bts->site_mgr->mo.fi, NM_EV_OML_UP, NULL); return bts_model_oml_estab(bts); } -/* RSL link is established, send status report */ -int trx_link_estab(struct gsm_bts_trx *trx) -{ - struct e1inp_sign_link *link = trx->rsl_link; - uint8_t radio_state = link ? NM_OPSTATE_ENABLED : NM_OPSTATE_DISABLED; - int rc; - - LOGP(DSUM, LOGL_INFO, "RSL link (TRX %02x) state changed to %s, sending Status'.\n", - trx->nr, link ? "up" : "down"); - - oml_mo_state_chg(&trx->mo, radio_state, NM_AVSTATE_OK); - - if (link) - rc = rsl_tx_rf_res(trx); - else - rc = bts_model_trx_deact_rf(trx); - if (rc < 0) { - oml_tx_failure_event_rep(&trx->bb_transc.mo, NM_SEVER_MAJOR, OSMO_EVT_MAJ_RSL_FAIL, - link ? - "Failed to establish RSL link (%d)" : - "Failed to deactivate RF (%d)", rc); - } - - return 0; -} - -/* set the availability of the TRX (used by PHY driver) */ -int trx_set_available(struct gsm_bts_trx *trx, int avail) -{ - int tn; - - LOGP(DSUM, LOGL_INFO, "TRX(%d): Setting available = %d\n", - trx->nr, avail); - if (avail) { - int op_state = trx->rsl_link ? NM_OPSTATE_ENABLED : NM_OPSTATE_DISABLED; - oml_mo_state_chg(&trx->mo, op_state, NM_AVSTATE_OK); - oml_mo_state_chg(&trx->bb_transc.mo, -1, NM_AVSTATE_OK); - for (tn = 0; tn < ARRAY_SIZE(trx->ts); tn++) - oml_mo_state_chg(&trx->ts[tn].mo, op_state, NM_AVSTATE_OK); - } else { - oml_mo_state_chg(&trx->mo, NM_OPSTATE_DISABLED, NM_AVSTATE_NOT_INSTALLED); - oml_mo_state_chg(&trx->bb_transc.mo, -1, NM_AVSTATE_NOT_INSTALLED); - - for (tn = 0; tn < ARRAY_SIZE(trx->ts); tn++) - oml_mo_state_chg(&trx->ts[tn].mo, NM_OPSTATE_DISABLED, NM_AVSTATE_NOT_INSTALLED); - } - return 0; -} - -/* prepare the per-SAPI T200 arrays for a given lchan */ -static int t200_by_lchan(int *t200_ms_dcch, int *t200_ms_acch, struct gsm_lchan *lchan) -{ - struct gsm_bts *bts = lchan->ts->trx->bts; - - /* we have to compensate for the "RTS advance" due to the asynchronous interface between - * the BTS (LAPDm) and the PHY/L1 (OsmoTRX or DSP in case of osmo-bts-{sysmo,lc15,oc2g,octphy} */ - int32_t fn_advance = bts_get_avg_fn_advance(bts); - int32_t fn_advance_us = fn_advance * 4615; - int fn_advance_ms = fn_advance_us / 1000; - - t200_ms_acch[DL_SAPI0] = bts->t200_ms[T200_SACCH_SDCCH] + fn_advance_ms; - t200_ms_acch[DL_SAPI3] = bts->t200_ms[T200_SACCH_SDCCH] + fn_advance_ms; - - switch (lchan->type) { - case GSM_LCHAN_SDCCH: - t200_ms_dcch[DL_SAPI0] = bts->t200_ms[T200_SDCCH] + fn_advance_ms; - t200_ms_dcch[DL_SAPI3] = bts->t200_ms[T200_SDCCH_SAPI3] + fn_advance_ms; - break; - case GSM_LCHAN_TCH_F: - t200_ms_dcch[DL_SAPI0] = bts->t200_ms[T200_FACCH_F] + fn_advance_ms; - t200_ms_dcch[DL_SAPI3] = bts->t200_ms[T200_FACCH_F] + fn_advance_ms; - break; - case GSM_LCHAN_TCH_H: - t200_ms_dcch[DL_SAPI0] = bts->t200_ms[T200_FACCH_H] + fn_advance_ms; - t200_ms_dcch[DL_SAPI3] = bts->t200_ms[T200_FACCH_H] + fn_advance_ms; - break; - default: - /* Channels such as CCCH don't use lapdm DL, and hence no T200 is needed */ - return -1; - } - return 0; -} - -int lchan_init_lapdm(struct gsm_lchan *lchan) -{ - struct lapdm_channel *lc = &lchan->lapdm_ch; - int t200_ms_dcch[_NR_DL_SAPI], t200_ms_acch[_NR_DL_SAPI]; - - if (t200_by_lchan(t200_ms_dcch, t200_ms_acch, lchan) == 0) { - LOGPLCHAN(lchan, DLLAPD, LOGL_DEBUG, - "Setting T200 D0=%u, D3=%u, S0=%u, S3=%u (all in ms)\n", - t200_ms_dcch[DL_SAPI0], t200_ms_dcch[DL_SAPI3], - t200_ms_acch[DL_SAPI0], t200_ms_acch[DL_SAPI3]); - lapdm_channel_init2(lc, LAPDM_MODE_BTS, t200_ms_dcch, t200_ms_acch, lchan->type); - lapdm_channel_set_flags(lc, LAPDM_ENT_F_POLLING_ONLY); - lapdm_channel_set_l1(lc, NULL, lchan); - } - /* We still need to set Rx callback to receive RACH requests: */ - lapdm_channel_set_l3(lc, lapdm_rll_tx_cb, lchan); - - return 0; -} - #define CCCH_RACH_RATIO_COMBINED256 (256*1/9) #define CCCH_RACH_RATIO_SEPARATE256 (256*10/55) @@ -591,7 +608,7 @@ static int try_merge_imm_ass_rej(struct gsm48_imm_ass_rej *old_rej, return 0; /* GSM 08.58, 5.7 - * -> The BTS may combine serveral IMM.ASS.REJ messages + * -> The BTS may combine several IMM.ASS.REJ messages * -> Identical request refs in one message may be squeezed * * GSM 04.08, 9.1.20.2 @@ -628,7 +645,7 @@ int bts_agch_enqueue(struct gsm_bts *bts, struct msgb *msg) struct gsm48_imm_ass_rej *imm_ass_cmd = msgb_l3(msg); if (bts->agch_queue.length > hard_limit) { - LOGP(DSUM, LOGL_ERROR, + LOGP(DRR, LOGL_ERROR, "AGCH: too many messages in queue, " "refusing message type %s, length = %d/%d\n", gsm48_rr_msg_name(((struct gsm48_imm_ass *)msgb_l3(msg))->msg_type), @@ -656,7 +673,7 @@ int bts_agch_enqueue(struct gsm_bts *bts, struct msgb *msg) return 0; } -struct msgb *bts_agch_dequeue(struct gsm_bts *bts) +static struct msgb *bts_agch_dequeue(struct gsm_bts *bts) { struct msgb *msg = msgb_dequeue(&bts->agch_queue.queue); if (!msg) @@ -669,7 +686,7 @@ struct msgb *bts_agch_dequeue(struct gsm_bts *bts) /* * Remove lower prio messages if the queue has grown too long. * - * \return 0 iff the number of messages in the queue would fit into the AGCH + * \return 0 if the number of messages in the queue would fit into the AGCH * reserved part of the CCCH. */ static void compact_agch_queue(struct gsm_bts *bts) @@ -722,12 +739,12 @@ static void compact_agch_queue(struct gsm_bts *bts) return; } -int bts_ccch_copy_msg(struct gsm_bts *bts, uint8_t *out_buf, struct gsm_time *gt, - int is_ag_res) +int bts_ccch_copy_msg(struct gsm_bts *bts, uint8_t *out_buf, struct gsm_time *gt, enum ccch_msgt ccch) { struct msgb *msg = NULL; int rc = 0; int is_empty = 1; + const struct bts_agch_msg_cb *msg_cb; /* Do queue house keeping. * This needs to be done every time a CCCH message is requested, since @@ -736,26 +753,39 @@ int bts_ccch_copy_msg(struct gsm_bts *bts, uint8_t *out_buf, struct gsm_time *gt */ compact_agch_queue(bts); - /* Check for paging messages first if this is PCH */ - if (!is_ag_res) - rc = paging_gen_msg(bts->paging_state, out_buf, gt, &is_empty); - - /* Check whether the block may be overwritten */ - if (!is_empty) - return rc; - - msg = bts_agch_dequeue(bts); - if (!msg) + switch (ccch) { + case CCCH_MSGT_NCH: + /* Send NCH message, it has priority over AGCH and does not overlap with PCH. */ + rc = bts_asci_notify_nch_gen_msg(bts, out_buf); return rc; + case CCCH_MSGT_PCH: + /* Check whether the block may be overwritten by AGCH. */ + rc = paging_gen_msg(bts->paging_state, out_buf, gt, &is_empty); + if (!is_empty) + return rc; + /* fall-through */ + case CCCH_MSGT_AGCH: + /* If fallen here and the AGCH queue is empty, return empty PCH message. */ + msg = bts_agch_dequeue(bts); + if (!msg) + return rc; + /* Continue to return AGCH message. */ + break; + } rate_ctr_inc2(bts->ctrs, BTS_CTR_AGCH_SENT); + /* Confirm sending of the AGCH message towards the PCU */ + msg_cb = (struct bts_agch_msg_cb *) msg->cb; + if (msg_cb->confirm) + pcu_tx_data_cnf(msg_cb->msg_id, PCU_IF_SAPI_AGCH_2); + /* Copy AGCH message */ memcpy(out_buf, msgb_l3(msg), msgb_l3len(msg)); rc = msgb_l3len(msg); msgb_free(msg); - if (is_ag_res) + if (ccch == CCCH_MSGT_AGCH) bts->agch_queue.agch_msgs++; else bts->agch_queue.pch_msgs++; @@ -778,69 +808,206 @@ int bts_supports_cipher(struct gsm_bts *bts, int rsl_cipher) return sup > 0; } -int trx_ms_pwr_ctrl_is_osmo(struct gsm_bts_trx *trx) -{ - return trx->ms_power_control == 1; -} - struct gsm_time *get_time(struct gsm_bts *bts) { return &bts->gsm_time; } -int bts_supports_cm(struct gsm_bts *bts, enum gsm_phys_chan_config pchan, - enum gsm48_chan_mode cm) +bool bts_supports_cm_speech(const struct gsm_bts *bts, + const struct rsl_ie_chan_mode *cm) { - enum gsm_bts_features feature = _NUM_BTS_FEAT; - - /* We assume that signalling support is mandatory, - * there is no BTS_FEAT_* definition to check that. */ - if (cm == GSM48_CMODE_SIGN) - return 1; + enum osmo_bts_features feature = _NUM_BTS_FEAT; + + /* Stage 1: check support for the requested channel type */ + switch (cm->chan_rt) { + case RSL_CMOD_CRT_TCH_GROUP_Bm: + case RSL_CMOD_CRT_TCH_GROUP_Lm: + if (!osmo_bts_has_feature(bts->features, BTS_FEAT_VGCS)) + return false; + break; + case RSL_CMOD_CRT_TCH_BCAST_Bm: + case RSL_CMOD_CRT_TCH_BCAST_Lm: + if (!osmo_bts_has_feature(bts->features, BTS_FEAT_VBS)) + return false; + break; + case RSL_CMOD_CRT_OSMO_TCH_VAMOS_Bm: + case RSL_CMOD_CRT_OSMO_TCH_VAMOS_Lm: + if (!osmo_bts_has_feature(bts->features, BTS_FEAT_VAMOS)) + return false; + break; + } - /* Before the requested pchan/cm combination can be checked, we need to - * convert it to a feature identifier we can check */ - switch (pchan) { - case GSM_PCHAN_TCH_F: - switch(cm) { - case GSM48_CMODE_SPEECH_V1: + /* Stage 2: check support for the requested codec */ + switch (cm->chan_rt) { + case RSL_CMOD_CRT_OSMO_TCH_VAMOS_Bm: + case RSL_CMOD_CRT_TCH_GROUP_Bm: + case RSL_CMOD_CRT_TCH_BCAST_Bm: + case RSL_CMOD_CRT_TCH_Bm: + switch (cm->chan_rate) { + case RSL_CMOD_SP_GSM1: feature = BTS_FEAT_SPEECH_F_V1; break; - case GSM48_CMODE_SPEECH_EFR: + case RSL_CMOD_SP_GSM2: feature = BTS_FEAT_SPEECH_F_EFR; break; - case GSM48_CMODE_SPEECH_AMR: + case RSL_CMOD_SP_GSM3: feature = BTS_FEAT_SPEECH_F_AMR; break; default: /* Invalid speech codec type => Not supported! */ - return 0; + return false; } break; - case GSM_PCHAN_TCH_H: - switch(cm) { - case GSM48_CMODE_SPEECH_V1: + case RSL_CMOD_CRT_OSMO_TCH_VAMOS_Lm: + case RSL_CMOD_CRT_TCH_GROUP_Lm: + case RSL_CMOD_CRT_TCH_BCAST_Lm: + case RSL_CMOD_CRT_TCH_Lm: + switch (cm->chan_rate) { + case RSL_CMOD_SP_GSM1: feature = BTS_FEAT_SPEECH_H_V1; break; - case GSM48_CMODE_SPEECH_AMR: + case RSL_CMOD_SP_GSM3: feature = BTS_FEAT_SPEECH_H_AMR; break; default: /* Invalid speech codec type => Not supported! */ - return 0; + return false; } break; default: - LOGP(DRSL, LOGL_ERROR, "BTS %u: unhandled pchan %s when checking mode %s\n", - bts->nr, gsm_pchan_name(pchan), gsm48_chan_mode_name(cm)); - return 0; + LOGP(DRSL, LOGL_ERROR, + "Unhandled RSL channel type=0x%02x/rate=0x%02x\n", + cm->chan_rt, cm->chan_rate); + return false; } /* Check if the feature is supported by this BTS */ - if (gsm_bts_has_feature(bts, feature)) - return 1; + if (osmo_bts_has_feature(bts->features, feature)) + return true; + + return false; +} + +static bool bts_supports_cm_data(const struct gsm_bts *bts, + const struct rsl_ie_chan_mode *cm) +{ + switch (bts->variant) { + case BTS_OSMO_TRX: + switch (cm->chan_rate) { + /* TODO: RSL_CMOD_CSD_NT_14k5 */ + /* TODO: RSL_CMOD_CSD_T_14k4 */ + case RSL_CMOD_CSD_NT_12k0: + case RSL_CMOD_CSD_T_9k6: + if (cm->chan_rt != RSL_CMOD_CRT_TCH_Bm) + return false; /* invalid */ + /* fall-through */ + case RSL_CMOD_CSD_NT_6k0: + case RSL_CMOD_CSD_T_4k8: + case RSL_CMOD_CSD_T_2k4: + case RSL_CMOD_CSD_T_1k2: + case RSL_CMOD_CSD_T_600: + case RSL_CMOD_CSD_T_1200_75: + return true; + default: + return false; + } + default: + return 0; + } +} + +bool bts_supports_cm(const struct gsm_bts *bts, + const struct rsl_ie_chan_mode *cm) +{ + switch (cm->spd_ind) { + case RSL_CMOD_SPD_SIGN: + /* We assume that signalling support is mandatory, + * there is no BTS_FEAT_* definition to check that. */ + return true; + case RSL_CMOD_SPD_SPEECH: + return bts_supports_cm_speech(bts, cm); + case RSL_CMOD_SPD_DATA: + return bts_supports_cm_data(bts, cm); + default: + return false; + } +} + +/* 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_bts_trx *trx = bts->c0; + + /* According to 3GPP TS 45.002, table 3, CBCH can be allocated + * either on C0/TS0 (CCCH+SDCCH4) or on C0..n/TS0..3 (SDCCH/8). */ + if (trx->ts[0].pchan == GSM_PCHAN_CCCH_SDCCH4_CBCH) + return &trx->ts[0].lchan[2]; /* C0/TS0 */ + + llist_for_each_entry(trx, &bts->trx_list, list) { /* C0..n */ + unsigned int tn; + for (tn = 0; tn <= 3; tn++) { /* TS0..3 */ + struct gsm_bts_trx_ts *ts = &trx->ts[tn]; + if (ts->pchan == GSM_PCHAN_SDCCH8_SACCH8C_CBCH) + return &ts->lchan[2]; + } + } + + return NULL; +} + +/* BCCH carrier power reduction (see 3GPP TS 45.008, section 7.1) */ +int bts_set_c0_pwr_red(struct gsm_bts *bts, const uint8_t red) +{ + struct gsm_bts_trx *c0 = bts->c0; + unsigned int tn; + + if (!osmo_bts_has_feature(bts->features, BTS_FEAT_BCCH_POWER_RED)) { + LOGPTRX(c0, DRSL, LOGL_ERROR, "BCCH carrier power reduction " + "is not supported by this BTS model\n"); + return -ENOTSUP; + } + + if (red > 6 || red % 2 != 0) { + LOGPTRX(c0, DRSL, LOGL_ERROR, "BCCH carrier power reduction " + "value (%u dB) is incorrect or out of range\n", red); + return -EINVAL; + } + + LOGPTRX(c0, DRSL, LOGL_NOTICE, "BCCH carrier power reduction: " + "%u dB (%s)\n", red, red ? "enabled" : "disabled"); + + /* Timeslot 0 is always transmitting BCCH/CCCH */ + c0->ts[0].c0_power_red_db = 0; + + for (tn = 1; tn < ARRAY_SIZE(c0->ts); tn++) { + struct gsm_bts_trx_ts *ts = &c0->ts[tn]; + struct gsm_bts_trx_ts *prev = ts - 1; + + switch (ts_pchan(ts)) { + /* Not allowed on CCCH/BCCH */ + case GSM_PCHAN_CCCH: + /* Preceeding timeslot shall not exceed 2 dB */ + if (prev->c0_power_red_db > 0) + prev->c0_power_red_db = 2; + /* fall-through */ + /* Not recommended on SDCCH/8 */ + case GSM_PCHAN_SDCCH8_SACCH8C: + case GSM_PCHAN_SDCCH8_SACCH8C_CBCH: + ts->c0_power_red_db = 0; + break; + default: + ts->c0_power_red_db = red; + break; + } + } + + /* Timeslot 7 is always preceding BCCH/CCCH */ + if (c0->ts[7].c0_power_red_db > 0) + c0->ts[7].c0_power_red_db = 2; + + bts->c0_power_red_db = red; return 0; } diff --git a/src/common/bts_ctrl_commands.c b/src/common/bts_ctrl_commands.c index 0d318900..5f6857cf 100644 --- a/src/common/bts_ctrl_commands.c +++ b/src/common/bts_ctrl_commands.c @@ -12,7 +12,7 @@ * 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. + * 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/>. @@ -57,7 +57,7 @@ static int set_therm_att(struct ctrl_cmd *cmd, void *data) tpp->thermal_attenuation_mdB = val; - power_ramp_start(trx, tpp->p_total_cur_mdBm, 0); + power_ramp_start(trx, tpp->p_total_cur_mdBm, 0, NULL); return get_therm_att(cmd, data); } @@ -84,12 +84,45 @@ static int set_oml_alert(struct ctrl_cmd *cmd, void *data) return CTRL_CMD_REPLY; } +static int verify_max_ber10k_rach(struct ctrl_cmd *cmd, const char *value, void *_data) +{ + int max_ber10k_rach = atoi(cmd->value); + + if (max_ber10k_rach < 0 || max_ber10k_rach > 10000) { + cmd->reply = "Value is out of range"; + return 1; + } + + return 0; +} + +static int get_max_ber10k_rach(struct ctrl_cmd *cmd, void *data) +{ + cmd->reply = talloc_asprintf(cmd, "%u", g_bts->max_ber10k_rach); + if (!cmd->reply) { + cmd->reply = "OOM"; + return CTRL_CMD_ERROR; + } + + return CTRL_CMD_REPLY; +} + +static int set_max_ber10k_rach(struct ctrl_cmd *cmd, void *data) +{ + g_bts->max_ber10k_rach = atoi(cmd->value); + cmd->reply = "OK"; + return CTRL_CMD_REPLY; +} + +CTRL_CMD_DEFINE(max_ber10k_rach, "max-ber10k-rach"); + int bts_ctrl_cmds_install(struct gsm_bts *bts) { int rc = 0; rc |= ctrl_cmd_install(CTRL_NODE_TRX, &cmd_therm_att); rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_oml_alert); + rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_max_ber10k_rach); g_bts = bts; return rc; diff --git a/src/common/bts_ctrl_lookup.c b/src/common/bts_ctrl_lookup.c index f0157e9a..2a5ff522 100644 --- a/src/common/bts_ctrl_lookup.c +++ b/src/common/bts_ctrl_lookup.c @@ -12,7 +12,7 @@ * 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. + * 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/>. @@ -28,6 +28,7 @@ #include <osmo-bts/logging.h> #include <osmo-bts/gsm_data.h> #include <osmo-bts/control_if.h> +#include <osmo-bts/bts_trx.h> extern vector ctrl_node_vec; @@ -87,14 +88,12 @@ err_index: return -ERANGE; } -struct ctrl_handle *bts_controlif_setup(struct gsm_bts *bts, - const char *bind_addr, uint16_t port) +struct ctrl_handle *bts_controlif_setup(struct gsm_bts *bts, uint16_t port) { struct ctrl_handle *hdl; int rc = 0; - hdl = ctrl_interface_setup_dynip(bts, bind_addr, port, - bts_ctrl_node_lookup); + hdl = ctrl_interface_setup(bts, port, bts_ctrl_node_lookup); if (!hdl) return NULL; diff --git a/src/common/bts_shutdown_fsm.c b/src/common/bts_shutdown_fsm.c new file mode 100644 index 00000000..13a0e1d0 --- /dev/null +++ b/src/common/bts_shutdown_fsm.c @@ -0,0 +1,283 @@ +/* BTS shutdown FSM */ + +/* (C) 2020 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> + * Author: Pau Espin Pedrol <pespin@sysmocom.de> + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU 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/tdef.h> +#include <osmocom/gsm/protocol/gsm_12_21.h> + +#include <osmo-bts/bts_shutdown_fsm.h> +#include <osmo-bts/logging.h> +#include <osmo-bts/gsm_data.h> +#include <osmo-bts/bts_model.h> +#include <osmo-bts/bts.h> +#include <osmo-bts/bts_sm.h> +#include <osmo-bts/nm_common_fsm.h> + +#define X(s) (1 << (s)) + +#define BTS_SHUTDOWN_POWER_RAMP_TGT -10 + +static const struct osmo_tdef_state_timeout bts_shutdown_fsm_timeouts[32] = { + [BTS_SHUTDOWN_ST_WAIT_RAMP_DOWN_COMPL] = { .T = -1 }, + [BTS_SHUTDOWN_ST_WAIT_TRX_CLOSED] = { .T = -2 }, +}; + +#define bts_shutdown_fsm_state_chg(fi, NEXT_STATE) \ + osmo_tdef_fsm_inst_state_chg(fi, NEXT_STATE, bts_shutdown_fsm_timeouts, ((struct gsm_bts *)fi->priv)->T_defs, -1) + +static unsigned int count_trx_operational(struct gsm_bts *bts) { + unsigned int count = 0; + struct gsm_bts_trx *trx; + llist_for_each_entry(trx, &bts->trx_list, list) { + if (trx->mo.nm_state.operational == NM_OPSTATE_ENABLED) + count++; + } + return count; +} + +static void st_none(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_bts *bts = (struct gsm_bts *)fi->priv; + unsigned int count; + switch(event) { + case BTS_SHUTDOWN_EV_START: + /* Firt announce to NM objects that we are starting a shutdown procedure: */ + osmo_fsm_inst_dispatch(bts->site_mgr->mo.fi, NM_EV_SHUTDOWN_START, NULL); + + count = count_trx_operational(bts); + if (count) { + bts_shutdown_fsm_state_chg(fi, BTS_SHUTDOWN_ST_WAIT_RAMP_DOWN_COMPL); + } else { + /* we can skip ramp down since no TRX is running anyway. + * Let's jump into WAIT_TRX_CLOSED to make sure we + * tell lower layer to close all TRX in case there's some + * open() WIP */ + LOGPFSML(fi, LOGL_INFO, "No TRX is operational, skipping power ramp down\n"); + bts_shutdown_fsm_state_chg(fi, BTS_SHUTDOWN_ST_WAIT_TRX_CLOSED); + } + break; + } +} + +static void ramp_down_compl_cb(struct gsm_bts_trx *trx) { + osmo_fsm_inst_dispatch(trx->bts->shutdown_fi, BTS_SHUTDOWN_EV_TRX_RAMP_COMPL, trx); +} + +static void st_wait_ramp_down_compl_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct gsm_bts *bts = (struct gsm_bts *)fi->priv; + struct gsm_bts_trx *trx; + + llist_for_each_entry(trx, &bts->trx_list, list) { + if (trx->mo.nm_state.operational != NM_OPSTATE_ENABLED) + continue; + if (bts->shutdown_fi_skip_power_ramp) + power_ramp_force(trx, to_mdB(BTS_SHUTDOWN_POWER_RAMP_TGT), 1, ramp_down_compl_cb); + else + power_ramp_start(trx, to_mdB(BTS_SHUTDOWN_POWER_RAMP_TGT), 1, ramp_down_compl_cb); + } +} + +static void st_wait_ramp_down_compl(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_bts *bts = (struct gsm_bts *)fi->priv; + struct gsm_bts_trx *src_trx; + unsigned int remaining = 0; + struct gsm_bts_trx *trx; + + switch(event) { + case BTS_SHUTDOWN_EV_TRX_RAMP_COMPL: + src_trx = (struct gsm_bts_trx *)data; + + llist_for_each_entry(trx, &bts->trx_list, list) { + if (trx->mo.nm_state.operational == NM_OPSTATE_ENABLED && + trx->power_params.p_total_cur_mdBm > BTS_SHUTDOWN_POWER_RAMP_TGT) + remaining++; + } + + LOGPFSML(fi, LOGL_INFO, "%s Ramping down complete, %u TRX remaining\n", + gsm_trx_name(src_trx), remaining); + if (remaining == 0) { + /* Make sure we end up any remaining ongoing power ramp + * down under target shutdown tx power level, then + * finally transit to next state: + */ + llist_for_each_entry(trx, &bts->trx_list, list) + power_ramp_abort(trx); + bts_shutdown_fsm_state_chg(fi, BTS_SHUTDOWN_ST_WAIT_TRX_CLOSED); + } + break; + } +} + +static void st_wait_trx_closed_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct gsm_bts *bts = (struct gsm_bts *)fi->priv; + struct gsm_bts_trx *trx; + llist_for_each_entry_reverse(trx, &bts->trx_list, list) { + bts_model_trx_deact_rf(trx); + } + llist_for_each_entry_reverse(trx, &bts->trx_list, list) { + bts_model_trx_close(trx); + } + /* Now wait until all TRX are closed asynchronously, we'll get feedback through events... */ +} + +static void st_wait_trx_closed(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_bts *bts = (struct gsm_bts *)fi->priv; + struct gsm_bts_trx *src_trx; + unsigned int remaining; + + switch(event) { + case BTS_SHUTDOWN_EV_TRX_CLOSED: + src_trx = (struct gsm_bts_trx *)data; + remaining = count_trx_operational(bts); + + LOGPFSML(fi, LOGL_INFO, "%s TRX closed, %u TRX remaining\n", + gsm_trx_name(src_trx), remaining); + if (remaining == 0) + bts_shutdown_fsm_state_chg(fi, BTS_SHUTDOWN_ST_EXIT); + break; + } +} + +static void st_exit_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct gsm_bts *bts = (struct gsm_bts *)fi->priv; + + osmo_fsm_inst_dispatch(bts->site_mgr->mo.fi, NM_EV_SHUTDOWN_FINISH, NULL); + + if (bts->shutdown_fi_exit_proc) { + LOGPFSML(fi, LOGL_NOTICE, "Shutdown process completed successfully, exiting process\n"); + exit(0); + } + bts_shutdown_fsm_state_chg(fi, BTS_SHUTDOWN_ST_NONE); +} + +static struct osmo_fsm_state bts_shutdown_fsm_states[] = { + [BTS_SHUTDOWN_ST_NONE] = { + .in_event_mask = + X(BTS_SHUTDOWN_EV_START), + .out_state_mask = + X(BTS_SHUTDOWN_ST_WAIT_RAMP_DOWN_COMPL) | + X(BTS_SHUTDOWN_ST_WAIT_TRX_CLOSED), + .name = "NONE", + .action = st_none, + }, + [BTS_SHUTDOWN_ST_WAIT_RAMP_DOWN_COMPL] = { + .in_event_mask = + X(BTS_SHUTDOWN_EV_TRX_RAMP_COMPL), + .out_state_mask = + X(BTS_SHUTDOWN_ST_WAIT_TRX_CLOSED), + .name = "WAIT_RAMP_DOWN_COMPL", + .onenter = st_wait_ramp_down_compl_on_enter, + .action = st_wait_ramp_down_compl, + }, + [BTS_SHUTDOWN_ST_WAIT_TRX_CLOSED] = { + .in_event_mask = + X(BTS_SHUTDOWN_EV_TRX_CLOSED), + .out_state_mask = + X(BTS_SHUTDOWN_ST_EXIT), + .name = "WAIT_TRX_CLOSED", + .onenter = st_wait_trx_closed_on_enter, + .action = st_wait_trx_closed, + }, + [BTS_SHUTDOWN_ST_EXIT] = { + .name = "EXIT", + .out_state_mask = + X(BTS_SHUTDOWN_ST_NONE), + .onenter = st_exit_on_enter, + } +}; + +const struct value_string bts_shutdown_fsm_event_names[] = { + OSMO_VALUE_STRING(BTS_SHUTDOWN_EV_START), + OSMO_VALUE_STRING(BTS_SHUTDOWN_EV_TRX_RAMP_COMPL), + OSMO_VALUE_STRING(BTS_SHUTDOWN_EV_TRX_CLOSED), + { 0, NULL } +}; + +int bts_shutdown_fsm_timer_cb(struct osmo_fsm_inst *fi) +{ + switch (fi->state) { + case BTS_SHUTDOWN_ST_WAIT_RAMP_DOWN_COMPL: + LOGPFSML(fi, LOGL_ERROR, "Timer expired waiting for ramp down complete\n"); + bts_shutdown_fsm_state_chg(fi, BTS_SHUTDOWN_ST_WAIT_TRX_CLOSED); + break; + case BTS_SHUTDOWN_ST_WAIT_TRX_CLOSED: + LOGPFSML(fi, LOGL_ERROR, "Timer expired waiting for TRX close\n"); + bts_shutdown_fsm_state_chg(fi, BTS_SHUTDOWN_ST_EXIT); + break; + default: + OSMO_ASSERT(false); + } + return 0; +} + +struct osmo_fsm bts_shutdown_fsm = { + .name = "BTS_SHUTDOWN", + .states = bts_shutdown_fsm_states, + .num_states = ARRAY_SIZE(bts_shutdown_fsm_states), + .event_names = bts_shutdown_fsm_event_names, + .log_subsys = DOML, + .timer_cb = bts_shutdown_fsm_timer_cb, +}; + +static __attribute__((constructor)) void bts_shutdown_fsm_init(void) +{ + OSMO_ASSERT(osmo_fsm_register(&bts_shutdown_fsm) == 0); +} + +bool bts_shutdown_in_progress(const struct gsm_bts *bts) +{ + const struct osmo_fsm_inst *fi = bts->shutdown_fi; + return fi->state != BTS_SHUTDOWN_ST_NONE; +} + +void bts_shutdown_ext(struct gsm_bts *bts, const char *reason, bool exit_proc, bool skip_power_ramp) +{ + struct osmo_fsm_inst *fi = bts->shutdown_fi; + if (bts_shutdown_in_progress(bts)) { + LOGPFSML(fi, LOGL_NOTICE, "BTS is already being shutdown.\n"); + if (exit_proc) + bts->shutdown_fi_exit_proc = true; + return; + } + bts->shutdown_fi_exit_proc = exit_proc; + bts->shutdown_fi_skip_power_ramp = skip_power_ramp; + LOGPFSML(fi, LOGL_NOTICE, "Shutting down BTS, exit %u, reason: %s\n", + exit_proc, reason); + osmo_fsm_inst_dispatch(fi, BTS_SHUTDOWN_EV_START, NULL); +} + +void bts_shutdown(struct gsm_bts *bts, const char *reason) +{ + bts_shutdown_ext(bts, reason, true, true); +} + +void bts_model_trx_close_cb(struct gsm_bts_trx *trx, int rc) +{ + struct osmo_fsm_inst *fi = trx->bts->shutdown_fi; + LOGPFSML(fi, LOGL_DEBUG, "%s Received TRX close cb rc=%d\n", gsm_trx_name(trx), rc); + osmo_fsm_inst_dispatch(fi, BTS_SHUTDOWN_EV_TRX_CLOSED, trx); +} diff --git a/src/common/bts_sm.c b/src/common/bts_sm.c new file mode 100644 index 00000000..6e889b71 --- /dev/null +++ b/src/common/bts_sm.c @@ -0,0 +1,95 @@ +/* BTS support code common to all supported BTS models */ + +/* (C) 2023 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/talloc.h> +#include <osmocom/core/linuxlist.h> +#include <osmocom/core/fsm.h> + +#include <osmo-bts/bts_sm.h> +#include <osmo-bts/bts.h> +#include <osmo-bts/nm_common_fsm.h> + +struct gsm_bts_sm *g_bts_sm; + +static const uint8_t nse_timer_default[] = { 3, 3, 3, 3, 30, 3, 10 }; + +struct gsm_bts *gsm_gprs_nse_get_bts(const struct gsm_gprs_nse *nse) +{ + return gsm_bts_num(g_bts_sm, nse->mo.obj_inst.bts_nr); +} + +static int gsm_bts_sm_talloc_destructor(struct gsm_bts_sm *bts_sm) +{ + struct gsm_bts *bts; + + while ((bts = llist_first_entry_or_null(&bts_sm->bts_list, struct gsm_bts, list))) + talloc_free(bts); + + if (bts_sm->mo.fi) { + osmo_fsm_inst_free(bts_sm->mo.fi); + bts_sm->mo.fi = NULL; + } + + return 0; +} + +struct gsm_bts_sm *gsm_bts_sm_alloc(void *talloc_ctx) +{ + struct gsm_bts_sm *bts_sm = talloc_zero(talloc_ctx, struct gsm_bts_sm); + struct gsm_gprs_nse *nse = &bts_sm->gprs.nse; + unsigned int i; + + if (!bts_sm) + return NULL; + + talloc_set_destructor(bts_sm, gsm_bts_sm_talloc_destructor); + + INIT_LLIST_HEAD(&bts_sm->bts_list); + + /* NM SITE_MGR */ + bts_sm->mo.fi = osmo_fsm_inst_alloc(&nm_bts_sm_fsm, bts_sm, bts_sm, + LOGL_INFO, "bts_sm"); + gsm_mo_init(&bts_sm->mo, NULL, NM_OC_SITE_MANAGER, + 0xff, 0xff, 0xff); + oml_mo_state_init(&bts_sm->mo, NM_OPSTATE_DISABLED, NM_AVSTATE_NOT_INSTALLED); + + /* NM GPRS NSE */ + nse->mo.fi = osmo_fsm_inst_alloc(&nm_gprs_nse_fsm, bts_sm, nse, + LOGL_INFO, "gprs_nse0"); + gsm_mo_init(&nse->mo, NULL, NM_OC_GPRS_NSE, 0, 0xff, 0xff); + oml_mo_state_init(&nse->mo, NM_OPSTATE_DISABLED, NM_AVSTATE_NOT_INSTALLED); + memcpy(&nse->timer, nse_timer_default, sizeof(nse->timer)); + + /* NM GPRS NSVCs */ + for (i = 0; i < ARRAY_SIZE(nse->nsvc); i++) { + struct gsm_gprs_nsvc *nsvc = &nse->nsvc[i]; + nsvc->nse = nse; + nsvc->id = i; + nsvc->mo.fi = osmo_fsm_inst_alloc(&nm_gprs_nsvc_fsm, bts_sm, nsvc, + LOGL_INFO, NULL); + osmo_fsm_inst_update_id_f(nsvc->mo.fi, "gprs_nsvc%d-%d", + nse->mo.obj_inst.bts_nr, i); + gsm_mo_init(&nsvc->mo, NULL, NM_OC_GPRS_NSVC, nse->mo.obj_inst.bts_nr, i, 0xff); + oml_mo_state_init(&nsvc->mo, NM_OPSTATE_DISABLED, NM_AVSTATE_NOT_INSTALLED); + } + + return bts_sm; +} diff --git a/src/common/bts_trx.c b/src/common/bts_trx.c new file mode 100644 index 00000000..251b6735 --- /dev/null +++ b/src/common/bts_trx.c @@ -0,0 +1,256 @@ +/* (C) 2008-2010 by Harald Welte <laforge@gnumonks.org> + * (C) 2020-2021 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/fsm.h> + +#include <osmocom/gsm/abis_nm.h> + +#include <osmo-bts/logging.h> +#include <osmo-bts/gsm_data.h> +#include <osmo-bts/bts_trx.h> +#include <osmo-bts/bts.h> +#include <osmo-bts/bts_model.h> +#include <osmo-bts/rsl.h> +#include <osmo-bts/phy_link.h> +#include <osmo-bts/nm_common_fsm.h> + +static int gsm_bts_trx_talloc_destructor(struct gsm_bts_trx *trx) +{ + unsigned int i; + + if (trx->bb_transc.mo.fi) { + osmo_fsm_inst_free(trx->bb_transc.mo.fi); + trx->bb_transc.mo.fi = NULL; + } + if (trx->mo.fi) { + osmo_fsm_inst_free(trx->mo.fi); + trx->mo.fi = NULL; + } + for (i = 0; i < TRX_NR_TS; i++) { + struct gsm_bts_trx_ts *ts = &trx->ts[i]; + if (ts->mo.fi) { + osmo_fsm_inst_free(ts->mo.fi); + ts->mo.fi = NULL; + } + } + return 0; +} + +/* Initialize all logical channels of the given timeslot */ +static void gsm_bts_trx_ts_init_lchan(struct gsm_bts_trx_ts *ts) +{ + unsigned int ln; + + for (ln = 0; ln < ARRAY_SIZE(ts->lchan); ln++) { + struct gsm_lchan *lchan = &ts->lchan[ln]; + gsm_lchan_init(lchan, ts, ln); + } +} + +/* Initialize primary timeslots of the given transceiver */ +static void gsm_bts_trx_init_ts(struct gsm_bts_trx *trx) +{ + unsigned int tn; + + for (tn = 0; tn < ARRAY_SIZE(trx->ts); tn++) { + struct gsm_bts_trx_ts *ts = &trx->ts[tn]; + + ts->trx = trx; + ts->nr = tn; + + ts->tsc_oml_configured = false; + ts->tsc_rsl_configured = false; + ts->tsc = ts->tsc_oml = ts->tsc_rsl = 0xff; + + ts->mo.fi = osmo_fsm_inst_alloc(&nm_chan_fsm, trx, ts, + LOGL_INFO, NULL); + osmo_fsm_inst_update_id_f(ts->mo.fi, "%s-ts%u", + trx->bb_transc.mo.fi->id, ts->nr); + gsm_mo_init(&ts->mo, trx->bts, NM_OC_CHANNEL, + trx->bts->nr, trx->nr, ts->nr); + oml_mo_state_init(&ts->mo, NM_OPSTATE_DISABLED, NM_AVSTATE_NOT_INSTALLED); + + gsm_bts_trx_ts_init_lchan(ts); + } +} + +/* Initialize shadow timeslots of the given transceiver */ +void gsm_bts_trx_init_shadow_ts(struct gsm_bts_trx *trx) +{ + unsigned int tn; + + for (tn = 0; tn < ARRAY_SIZE(trx->ts); tn++) { + struct gsm_bts_trx_ts *ts; + + ts = talloc_zero(trx, struct gsm_bts_trx_ts); + OSMO_ASSERT(ts != NULL); + + ts->trx = trx; + ts->nr = tn; + + ts->tsc_oml_configured = false; + ts->tsc_rsl_configured = false; + ts->tsc = ts->tsc_oml = ts->tsc_rsl = 0xff; + + /* Link both primary and shadow */ + trx->ts[tn].vamos.peer = ts; + ts->vamos.peer = &trx->ts[tn]; + ts->vamos.is_shadow = true; + + /* Shadow timeslot uses the primary's NM FSM */ + OSMO_ASSERT(trx->ts[tn].mo.fi != NULL); + ts->mo.fi = trx->ts[tn].mo.fi; + + gsm_bts_trx_ts_init_lchan(ts); + } +} + +void gsm_bts_trx_free_shadow_ts(struct gsm_bts_trx *trx) +{ + unsigned int tn; + unsigned int ln; + + for (tn = 0; tn < ARRAY_SIZE(trx->ts); tn++) { + struct gsm_bts_trx_ts *shadow_ts = trx->ts[tn].vamos.peer; + if (!shadow_ts) + continue; + + /* free lchan related mem allocated on the trx object: */ + for (ln = 0; ln < ARRAY_SIZE(shadow_ts->lchan); ln++) { + struct gsm_lchan *lchan = &shadow_ts->lchan[ln]; + TALLOC_FREE(lchan->name); + } + + talloc_free(shadow_ts); + trx->ts[tn].vamos.peer = 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); + + if (!trx) + return NULL; + + talloc_set_destructor(trx, gsm_bts_trx_talloc_destructor); + + trx->bts = bts; + trx->nr = bts->num_trx++; + + trx->mo.fi = osmo_fsm_inst_alloc(&nm_rcarrier_fsm, trx, trx, + LOGL_INFO, NULL); + osmo_fsm_inst_update_id_f(trx->mo.fi, "bts%d-trx%d", bts->nr, trx->nr); + gsm_mo_init(&trx->mo, bts, NM_OC_RADIO_CARRIER, bts->nr, trx->nr, 0xff); + oml_mo_state_init(&trx->mo, NM_OPSTATE_DISABLED, NM_AVSTATE_NOT_INSTALLED); + + trx->bb_transc.mo.fi = osmo_fsm_inst_alloc(&nm_bb_transc_fsm, trx, &trx->bb_transc, + LOGL_INFO, NULL); + osmo_fsm_inst_update_id_f(trx->bb_transc.mo.fi, "bts%d-trx%d", bts->nr, trx->nr); + gsm_mo_init(&trx->bb_transc.mo, bts, NM_OC_BASEB_TRANSC, bts->nr, trx->nr, 0xff); + oml_mo_state_init(&trx->bb_transc.mo, NM_OPSTATE_DISABLED, NM_AVSTATE_NOT_INSTALLED); + + gsm_bts_trx_init_ts(trx); + + if (trx->nr != 0) + trx->nominal_power = bts->c0->nominal_power; + + /* Default values for the power adjustments */ + trx->power_params.ramp.max_initial_pout_mdBm = to_mdB(0); + trx->power_params.ramp.step_size_mdB = to_mdB(2); + trx->power_params.ramp.step_interval_sec = 1; + + /* Default (fall-back) Dynamic Power Control parameters */ + trx->bs_dpc_params = &bts->bs_dpc_params; + trx->ms_dpc_params = &bts->ms_dpc_params; + + /* IF BTS model doesn't DSP/HW support MS Power Control Loop, enable osmo algo by default: */ + if (!bts_internal_flag_get(trx->bts, BTS_INTERNAL_FLAG_MS_PWR_CTRL_DSP)) + trx->ms_pwr_ctl_soft = true; + + llist_add_tail(&trx->list, &bts->trx_list); + + return trx; +} + +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; +} + +const char *gsm_trx_unit_id(struct gsm_bts_trx *trx) +{ + static char buf[23]; + + snprintf(buf, sizeof(buf), "%u/%u/%u", trx->bts->ip_access.site_id, + trx->bts->ip_access.bts_id, trx->nr); + return buf; +} + +/* RSL link is established, send status report */ +int trx_link_estab(struct gsm_bts_trx *trx) +{ + int rc; + + LOGPTRX(trx, DRSL, LOGL_INFO, "RSL link up\n"); + + osmo_fsm_inst_dispatch(trx->mo.fi, NM_EV_RSL_UP, NULL); + osmo_fsm_inst_dispatch(trx->bb_transc.mo.fi, NM_EV_RSL_UP, NULL); + + if (trx->mo.nm_state.operational == NM_OPSTATE_ENABLED || + trx->bb_transc.mo.nm_state.operational == NM_OPSTATE_ENABLED) { + rc = rsl_tx_rf_res(trx); + if (rc < 0) + oml_tx_failure_event_rep(&trx->bb_transc.mo, NM_SEVER_MAJOR, OSMO_EVT_MAJ_RSL_FAIL, + "Failed to establish RSL link (%d)", rc); + } + if (trx == trx->bts->c0) + load_timer_start(trx->bts); + + return 0; +} + + +bool trx_ms_pwr_ctrl_is_osmo(const struct gsm_bts_trx *trx) +{ + return trx->ms_pwr_ctl_soft; +} diff --git a/src/common/cbch.c b/src/common/cbch.c index 7ed11c2f..363eb672 100644 --- a/src/common/cbch.c +++ b/src/common/cbch.c @@ -12,7 +12,7 @@ * 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. + * 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/>. @@ -38,7 +38,7 @@ struct smscb_msg { uint8_t num_segs; /* total number of segments */ }; -/* determine if current queue length differes more than permitted hysteresis from target +/* determine if current queue length differs more than permitted hysteresis from target * queue length. If it does, send CBCH LOAD IND */ static void check_and_send_cbch_load(struct gsm_bts *bts, struct bts_smscb_state *bts_ss) { @@ -139,12 +139,8 @@ static int get_smscb_block(struct bts_smscb_state *bts_ss, uint8_t *out, uint8_t block_type->seq_nr = block_nr; /* determine if this is the last block */ - if (block_nr + 1 == msg->num_segs) - block_type->lb = 1; - else - block_type->lb = 0; - - if (block_nr == 4) { + block_type->lb = (block_nr + 1 == msg->num_segs); + if (block_type->lb) { if (msg != bts_ss->default_msg) { DEBUGPGT(DLSMS, g_time, "%s: deleting fully-transmitted message %p\n", chan_name, msg); @@ -198,7 +194,7 @@ int bts_process_smscb_cmd(struct gsm_bts *bts, struct rsl_ie_cb_cmd_type cmd_typ return -EINVAL; } - scm = talloc_zero_size(bts, sizeof(*scm)); + scm = talloc_zero(bts, struct smscb_msg); if (!scm) return -1; @@ -233,10 +229,10 @@ int bts_process_smscb_cmd(struct gsm_bts *bts, struct rsl_ie_cb_cmd_type cmd_typ rate_ctr_inc2(bts_ss->ctrs, CBCH_CTR_RCVD_QUEUED); break; case RSL_CB_CMD_TYPE_DEFAULT: - /* old default msg will be free'd in get_smscb_block() if it is currently in transit - * and we set a new default_msg here */ + /* clear the cur_msg pointer if it is the old default message */ if (bts_ss->cur_msg && bts_ss->cur_msg == bts_ss->default_msg) - talloc_free(bts_ss->cur_msg); + bts_ss->cur_msg = NULL; + talloc_free(bts_ss->default_msg); if (cmd_type.def_bcast == RSL_CB_CMD_DEFBCAST_NORMAL) /* def_bcast == 0: normal message */ bts_ss->default_msg = scm; @@ -322,3 +318,25 @@ int bts_cbch_get(struct gsm_bts *bts, uint8_t *outbuf, struct gsm_time *g_time) return rc; } + +static void bts_smscb_state_reset(struct bts_smscb_state *bts_ss) +{ + struct smscb_msg *scm, *tmp; + llist_for_each_entry_safe(scm, tmp, &bts_ss->queue, list) { + llist_del(&scm->list); + talloc_free(scm); + } + bts_ss->queue_len = 0; + rate_ctr_group_reset(bts_ss->ctrs); + /* avoid double-free of default_msg in case cur_msg == default_msg */ + if (bts_ss->cur_msg && bts_ss->cur_msg != bts_ss->default_msg) + talloc_free(bts_ss->cur_msg); + bts_ss->cur_msg = NULL; + TALLOC_FREE(bts_ss->default_msg); +} + +void bts_cbch_reset(struct gsm_bts *bts) +{ + bts_smscb_state_reset(&bts->smscb_basic); + bts_smscb_state_reset(&bts->smscb_extended); +} diff --git a/src/common/csd_v110.c b/src/common/csd_v110.c new file mode 100644 index 00000000..d6c00b03 --- /dev/null +++ b/src/common/csd_v110.c @@ -0,0 +1,188 @@ +/* + * (C) 2023 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> + * Author: Vadim Yanitskiy <vyanitskiy@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 <stdint.h> +#include <errno.h> + +#include <osmocom/core/bits.h> +#include <osmocom/core/utils.h> +#include <osmocom/gsm/gsm44021.h> +#include <osmocom/gsm/gsm_utils.h> +#include <osmocom/gsm/protocol/gsm_04_08.h> +#include <osmocom/isdn/v110.h> + +#include <osmo-bts/csd_v110.h> +#include <osmo-bts/lchan.h> + +/* key is enum gsm48_chan_mode, so assuming a value in range 0..255 */ +const struct csd_v110_lchan_desc csd_v110_lchan_desc[256] = { +#if 0 + [GSM48_CMODE_DATA_14k5] = { + /* TCH/F14.4: 290 bits every 20 ms (14.5 kbit/s) */ + .fr = { .num_blocks = 1, .num_bits = 290 }, + }, +#endif + [GSM48_CMODE_DATA_12k0] = { + /* TCH/F9.6: 4 * 60 bits every 20 ms (12.0 kbit/s) */ + .fr = { .num_blocks = 4, .num_bits = 60 }, + }, + [GSM48_CMODE_DATA_6k0] = { + /* TCH/F4.8: 2 * 60 bits every 20 ms (6.0 kbit/s) */ + .fr = { .num_blocks = 2, .num_bits = 60 }, + /* TCH/H4.8: 4 * 60 bits every 40 ms (6.0 kbit/s) */ + .hr = { .num_blocks = 4, .num_bits = 60 }, + }, + [GSM48_CMODE_DATA_3k6] = { + /* TCH/F2.4: 2 * 36 bits every 20 ms (3.6 kbit/s) */ + .fr = { .num_blocks = 2, .num_bits = 36 }, + /* TCH/H2.4: 4 * 36 bits every 40 ms (3.6 kbit/s) */ + .hr = { .num_blocks = 4, .num_bits = 36 }, + }, +}; + +/* 3GPP TS 44.021, Figure 4: Coding of data rates (E1/E2/E3 bits) */ +static const uint8_t e1e2e3_map[_LCHAN_CSD_M_NUM][3] = { + [LCHAN_CSD_M_T_600] = { 1, 0, 0 }, + [LCHAN_CSD_M_T_1200] = { 0, 1, 0 }, + [LCHAN_CSD_M_T_2400] = { 1, 1, 0 }, + [LCHAN_CSD_M_T_4800] = { 0, 1, 1 }, + [LCHAN_CSD_M_T_9600] = { 0, 1, 1 }, +#if 0 + [LCHAN_CSD_M_T_19200] = { 0, 1, 1 }, + [LCHAN_CSD_M_T_38400] = { 0, 1, 1 }, + [LCHAN_CSD_M_T_14400] = { 1, 0, 1 }, + [LCHAN_CSD_M_T_28800] = { 1, 0, 1 }, +#endif +}; + +int csd_v110_rtp_encode(const struct gsm_lchan *lchan, uint8_t *rtp, + const uint8_t *data, size_t data_len) +{ + const struct csd_v110_frame_desc *desc; + ubit_t ra_bits[80 * 4]; + + OSMO_ASSERT(lchan->tch_mode < ARRAY_SIZE(csd_v110_lchan_desc)); + if (lchan->type == GSM_LCHAN_TCH_F) + desc = &csd_v110_lchan_desc[lchan->tch_mode].fr; + else + desc = &csd_v110_lchan_desc[lchan->tch_mode].hr; + if (OSMO_UNLIKELY(desc->num_blocks == 0)) + return -ENOTSUP; + + /* handle empty/incomplete Uplink frames gracefully */ + if (OSMO_UNLIKELY(data_len < (desc->num_blocks * desc->num_bits))) { + /* encode N idle frames as per 3GPP TS 44.021, section 8.1.6 */ + memset(&ra_bits[0], 0x01, sizeof(ra_bits)); + for (unsigned int i = 0; i < desc->num_blocks; i++) + memset(&ra_bits[i * 80], 0x00, 8); /* alignment pattern */ + goto ra1_ra2; + } + + /* RA1'/RA1: convert from radio rate to an intermediate data rate */ + for (unsigned int i = 0; i < desc->num_blocks; i++) { + struct osmo_v110_decoded_frame df; + + /* convert a V.110 36-/60-bit frame to a V.110 80-bit frame */ + if (desc->num_bits == 60) + osmo_csd_12k_6k_decode_frame(&df, &data[i * 60], 60); + else /* desc->num_bits == 36 */ + osmo_csd_3k6_decode_frame(&df, &data[i * 36], 36); + + /* E1 .. E3 must set by out-of-band knowledge */ + if (lchan->csd_mode == LCHAN_CSD_M_NT) { + /* non-transparent: as per 3GPP TS 48.020, Table 7 */ + df.e_bits[0] = 0; /* E1: as per 15.1.2, shall be set to 0 (for BSS-MSC) */ + df.e_bits[1] = (i >> 1) & 0x01; /* E2: 0 for Q1/Q2, 1 for Q3/Q4 */ + df.e_bits[2] = (i >> 0) & 0x01; /* E3: 0 for Q1/Q3, 1 for Q2/Q4 */ + } else { + /* transparent: as per 3GPP TS 44.021, Figure 4 */ + df.e_bits[0] = e1e2e3_map[lchan->csd_mode][0]; /* E1 */ + df.e_bits[1] = e1e2e3_map[lchan->csd_mode][1]; /* E2 */ + df.e_bits[2] = e1e2e3_map[lchan->csd_mode][2]; /* E3 */ + } + + osmo_v110_encode_frame(&ra_bits[i * 80], 80, &df); + } + +ra1_ra2: + /* RA1/RA2: convert from an intermediate rate to 64 kbit/s */ + if (desc->num_blocks == 4) { + /* 4 * 80 bits (16 kbit/s) => 2 bits per octet */ + for (unsigned int i = 0, j = 0; i < RFC4040_RTP_PLEN; i++) { + rtp[i] = (0xff >> 2); + rtp[i] |= (ra_bits[j++] << 7); + rtp[i] |= (ra_bits[j++] << 6); + } + } else { + /* 2 * 80 bits (8 kbit/s) => 1 bit per octet */ + for (unsigned int i = 0; i < RFC4040_RTP_PLEN; i++) { + rtp[i] = (0xff >> 1); + rtp[i] |= (ra_bits[i] << 7); + } + } + + return RFC4040_RTP_PLEN; +} + +int csd_v110_rtp_decode(const struct gsm_lchan *lchan, uint8_t *data, + const uint8_t *rtp, size_t rtp_len) +{ + const struct csd_v110_frame_desc *desc; + ubit_t ra_bits[80 * 4]; + + OSMO_ASSERT(lchan->tch_mode < ARRAY_SIZE(csd_v110_lchan_desc)); + if (lchan->type == GSM_LCHAN_TCH_F) + desc = &csd_v110_lchan_desc[lchan->tch_mode].fr; + else + desc = &csd_v110_lchan_desc[lchan->tch_mode].hr; + if (OSMO_UNLIKELY(desc->num_blocks == 0)) + return -ENOTSUP; + + if (OSMO_UNLIKELY(rtp_len != RFC4040_RTP_PLEN)) + return -EINVAL; + + /* RA1/RA2: convert from 64 kbit/s to an intermediate rate */ + if (desc->num_blocks == 4) { + /* 4 * 80 bits (16 kbit/s) => 2 bits per octet */ + for (unsigned int i = 0, j = 0; i < RFC4040_RTP_PLEN; i++) { + ra_bits[j++] = (rtp[i] >> 7); + ra_bits[j++] = (rtp[i] >> 6) & 0x01; + } + } else { + /* 2 * 80 bits (8 kbit/s) => 1 bit per octet */ + for (unsigned int i = 0; i < RFC4040_RTP_PLEN; i++) + ra_bits[i] = (rtp[i] >> 7); + } + + /* RA1'/RA1: convert from an intermediate rate to radio rate */ + for (unsigned int i = 0; i < desc->num_blocks; i++) { + struct osmo_v110_decoded_frame df; + + /* convert a V.110 80-bit frame to a V.110 36-/60-bit frame */ + osmo_v110_decode_frame(&df, &ra_bits[i * 80], 80); + if (desc->num_bits == 60) + osmo_csd_12k_6k_encode_frame(&data[i * 60], 60, &df); + else /* desc->num_bits == 36 */ + osmo_csd_3k6_encode_frame(&data[i * 36], 36, &df); + } + + return desc->num_blocks * desc->num_bits; +} diff --git a/src/common/dtx_dl_amr_fsm.c b/src/common/dtx_dl_amr_fsm.c index 38e22c95..bbe1b0ad 100644 --- a/src/common/dtx_dl_amr_fsm.c +++ b/src/common/dtx_dl_amr_fsm.c @@ -12,7 +12,7 @@ * 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. + * 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/common/gsm_data.c b/src/common/gsm_data.c new file mode 100644 index 00000000..dad5587b --- /dev/null +++ b/src/common/gsm_data.c @@ -0,0 +1,348 @@ +/* (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 <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <ctype.h> + +#include <netinet/in.h> + +#include <osmocom/core/linuxlist.h> +#include <osmocom/core/talloc.h> +#include <osmocom/core/statistics.h> +#include <osmocom/core/fsm.h> + +#include <osmocom/gsm/gsm_utils.h> +#include <osmocom/gsm/abis_nm.h> +#include <osmocom/codec/ecu.h> + +#include <osmo-bts/gsm_data.h> +#include <osmo-bts/bts.h> +#include <osmo-bts/bts_trx.h> +#include <osmo-bts/logging.h> + +struct osmo_tdef_group bts_tdef_groups[] = { + { .name = "bts", .tdefs = bts_T_defs, .desc = "BTS process timers" }, + { .name = "abis", .tdefs = abis_T_defs, .desc = "Abis (RSL) related timers" }, + {} +}; + +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_OSMO_DYN, "TCH/F_TCH/H_SDCCH8_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_OSMO_DYN, "Dynamic TCH/F or TCH/H or SDCCH/8 or GPRS PDCH" }, + { 0, NULL } +}; + +const char *gsm_pchan_name(enum gsm_phys_chan_config c) +{ + return get_value_string(gsm_pchant_names, c); +} + +/* 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 char ts2str[255]; + +char *gsm_ts_name(const struct gsm_bts_trx_ts *ts) +{ + snprintf(ts2str, sizeof(ts2str), + "(" GSM_TS_NAME_FMT ")", + GSM_TS_NAME_ARGS(ts)); + + 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_OSMO_DYN: + if (ts->dyn.pchan_is == ts->dyn.pchan_want) + snprintf(ts2str, sizeof(ts2str), + "(" GSM_TS_NAME_FMT ",pchan=%s as %s)", + GSM_TS_NAME_ARGS(ts), + gsm_pchan_name(ts->pchan), + gsm_pchan_name(ts->dyn.pchan_is)); + else + snprintf(ts2str, sizeof(ts2str), + "(" GSM_TS_NAME_FMT ",pchan=%s switching %s -> %s)", + GSM_TS_NAME_ARGS(ts), + 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), + "(" GSM_TS_NAME_FMT ",pchan=%s as %s)", + GSM_TS_NAME_ARGS(ts), + gsm_pchan_name(ts->pchan), + (ts->flags & TS_F_PDCH_ACTIVE)? "PDCH" + : "TCH/F"); + else + snprintf(ts2str, sizeof(ts2str), + "(" GSM_TS_NAME_FMT ",pchan=%s switching %s -> %s)", + GSM_TS_NAME_ARGS(ts), + 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), "(" GSM_TS_NAME_FMT ",pchan=%s)", + GSM_TS_NAME_ARGS(ts), gsm_pchan_name(ts->pchan)); + break; + } + + return ts2str; +} + +/* 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; + + switch (cbits) { + case ABIS_RSL_CHAN_NR_CBITS_OSMO_VAMOS_Bm_ACCHs: + if (ts->vamos.peer == NULL) + return NULL; + ts = ts->vamos.peer; + /* fall-through */ + case ABIS_RSL_CHAN_NR_CBITS_Bm_ACCHs: + 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_OSMO_DYN) + ok = false; + break; + case ABIS_RSL_CHAN_NR_CBITS_OSMO_VAMOS_Lm_ACCHs(0): + case ABIS_RSL_CHAN_NR_CBITS_OSMO_VAMOS_Lm_ACCHs(1): + if (ts->vamos.peer == NULL) + return NULL; + ts = ts->vamos.peer; + /* fall-through */ + case ABIS_RSL_CHAN_NR_CBITS_Lm_ACCHs(0): + case ABIS_RSL_CHAN_NR_CBITS_Lm_ACCHs(1): + lch_idx = cbits & 0x1; /* TCH/H */ + if (ts->pchan != GSM_PCHAN_TCH_H && + ts->pchan != GSM_PCHAN_OSMO_DYN) + ok = false; + break; + case ABIS_RSL_CHAN_NR_CBITS_SDCCH4_ACCH(0): + case ABIS_RSL_CHAN_NR_CBITS_SDCCH4_ACCH(1): + case ABIS_RSL_CHAN_NR_CBITS_SDCCH4_ACCH(2): + case ABIS_RSL_CHAN_NR_CBITS_SDCCH4_ACCH(3): + lch_idx = cbits & 0x3; /* SDCCH/4 */ + if (ts->pchan != GSM_PCHAN_CCCH_SDCCH4 && + ts->pchan != GSM_PCHAN_CCCH_SDCCH4_CBCH) + ok = false; + break; + case ABIS_RSL_CHAN_NR_CBITS_SDCCH8_ACCH(0): + case ABIS_RSL_CHAN_NR_CBITS_SDCCH8_ACCH(1): + case ABIS_RSL_CHAN_NR_CBITS_SDCCH8_ACCH(2): + case ABIS_RSL_CHAN_NR_CBITS_SDCCH8_ACCH(3): + case ABIS_RSL_CHAN_NR_CBITS_SDCCH8_ACCH(4): + case ABIS_RSL_CHAN_NR_CBITS_SDCCH8_ACCH(5): + case ABIS_RSL_CHAN_NR_CBITS_SDCCH8_ACCH(6): + case ABIS_RSL_CHAN_NR_CBITS_SDCCH8_ACCH(7): + lch_idx = cbits & 0x7; /* SDCCH/8 */ + if (ts->pchan != GSM_PCHAN_SDCCH8_SACCH8C && + ts->pchan != GSM_PCHAN_SDCCH8_SACCH8C_CBCH && + ts->pchan != GSM_PCHAN_OSMO_DYN) + ok = false; + break; + case ABIS_RSL_CHAN_NR_CBITS_BCCH: + case ABIS_RSL_CHAN_NR_CBITS_RACH: + case ABIS_RSL_CHAN_NR_CBITS_PCH_AGCH: + 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 !!! */ + break; + case ABIS_RSL_CHAN_NR_CBITS_OSMO_PDCH: + lch_idx = 0; + if (ts->pchan != GSM_PCHAN_OSMO_DYN) + ok = false; + break; + default: + 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_OSMO_DYN 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(const struct gsm_bts_trx_ts *ts) +{ + switch (ts->pchan) { + case GSM_PCHAN_OSMO_DYN: + 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(const 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(const struct gsm_bts_trx_ts *ts) +{ + return pchan_is_tch(ts_pchan(ts)); +} + +bool ts_is_pdch(const struct gsm_bts_trx_ts *ts) +{ + switch (ts->pchan) { + case GSM_PCHAN_PDCH: + return true; + case GSM_PCHAN_TCH_F_PDCH: + return (ts->flags & TS_F_PDCH_ACTIVE) + && !(ts->flags & TS_F_PDCH_PENDING_MASK); + case GSM_PCHAN_OSMO_DYN: + return ts->dyn.pchan_is == GSM_PCHAN_PDCH + && ts->dyn.pchan_want == ts->dyn.pchan_is; + default: + return false; + } +} + +/* Apply ts->tsc based on what was configured coming from different sources. + * Priorities (preferred first, overrides ones afterward): + * 1- RSL OSMO_TSC IE + * 2- OML SetChannelAttr TSC IE + * 3- OML SetBtsAttr BSIC IE + */ +void gsm_ts_apply_configured_tsc(struct gsm_bts_trx_ts *ts) +{ + if (ts->tsc_rsl_configured) { + ts->tsc = ts->tsc_rsl; + return; + } + if (ts->tsc_oml_configured) { + ts->tsc = ts->tsc_oml; + return; + } + if (ts->trx->bts->bsic_configured) { + ts->tsc = BTS_TSC(ts->trx->bts); + return; + } + ts->tsc = 0xff; /* invalid value */ +} + +void gsm_ts_release(struct gsm_bts_trx_ts *ts) +{ + unsigned int ln; + + for (ln = 0; ln < ARRAY_SIZE(ts->lchan); ln++) { + struct gsm_lchan *lchan = &ts->lchan[ln]; + gsm_lchan_release(lchan, LCHAN_REL_ACT_OML); + } + ts->pchan = GSM_PCHAN_NONE; + /* Make sure pchan_is is reset, since PCU act_req to release it will be + * ignored as the lchan will already be released. */ + ts->dyn.pchan_is = ts->dyn.pchan_want = GSM_PCHAN_NONE; + + ts->tsc_oml_configured = false; + ts->tsc_rsl_configured = false; + ts->tsc = ts->tsc_oml = ts->tsc_rsl = 0xff; +} diff --git a/src/common/gsm_data_shared.c b/src/common/gsm_data_shared.c deleted file mode 100644 index f0f5ae20..00000000 --- a/src/common/gsm_data_shared.c +++ /dev/null @@ -1,831 +0,0 @@ -/* (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 <stdlib.h> -#include <stdio.h> -#include <string.h> -#include <errno.h> -#include <ctype.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/codec/ecu.h> - -#include <osmo-bts/gsm_data.h> - -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; - mo->nm_state.administrative = NM_STATE_LOCKED; -} - -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_OC2G, "osmo-bts-oc2g" }, - { BTS_OSMO_OCTPHY, "osmo-bts-octphy" }, - { BTS_OSMO_SYSMO, "osmo-bts-sysmo" }, - { BTS_OSMO_TRX, "omso-bts-trx" }, - { BTS_OSMO_VIRTUAL, "omso-bts-virtual" }, - { BTS_OSMO_OMLDUMMY, "omso-bts-omldummy" }, - { 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 gsm_bts_features_descs[] = { - { BTS_FEAT_HSCSD, "HSCSD" }, - { BTS_FEAT_GPRS, "GPRS" }, - { BTS_FEAT_EGPRS, "EGPRS" }, - { BTS_FEAT_ECSD, "ECSD" }, - { BTS_FEAT_HOPPING, "Frequency Hopping" }, - { BTS_FEAT_MULTI_TSC, "Multi-TSC" }, - { BTS_FEAT_OML_ALERTS, "OML Alerts" }, - { BTS_FEAT_AGCH_PCH_PROP, "AGCH/PCH proportional allocation" }, - { BTS_FEAT_CBCH, "CBCH" }, - { BTS_FEAT_SPEECH_F_V1, "Fullrate speech V1" }, - { BTS_FEAT_SPEECH_H_V1, "Halfrate speech V1" }, - { BTS_FEAT_SPEECH_F_EFR, "Fullrate speech EFR" }, - { BTS_FEAT_SPEECH_F_AMR, "Fullrate speech AMR" }, - { BTS_FEAT_SPEECH_H_AMR, "Halfrate speech AMR" }, - { BTS_FEAT_ETWS_PN, "ETWS Primary Notification on PCH" }, - { 0, NULL } -}; - -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++; - - 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); - INIT_LLIST_HEAD(&lchan->sapi_cmds); - } - } - - 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, -}; - -struct gsm_bts *gsm_bts_alloc(void *ctx, uint8_t bts_num) -{ - struct gsm_bts *bts = talloc_zero(ctx, struct gsm_bts); - int i; - - if (!bts) - return NULL; - - bts->nr = bts_num; - bts->num_trx = 0; - INIT_LLIST_HEAD(&bts->trx_list); - 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)); - - /* create our primary TRX. It will be initialized during bts_init() */ - bts->c0 = gsm_bts_trx_alloc(bts); - if (!bts->c0) { - 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->features.data = &bts->_features_data[0]; - bts->features.data_len = sizeof(bts->_features_data); - - /* si handling */ - bts->bcch_change_mark = 1; - - return bts; -} - -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 */ -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_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 */ -static uint8_t gsm_pchan2chan_nr(enum gsm_phys_chan_config pchan, - uint8_t ts_nr, uint8_t lchan_nr) -{ - uint8_t cbits, chan_nr; - - OSMO_ASSERT(pchan != GSM_PCHAN_TCH_F_TCH_H_PDCH); - OSMO_ASSERT(pchan != GSM_PCHAN_TCH_F_PDCH); - - switch (pchan) { - case GSM_PCHAN_TCH_F: - 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) - cbits = 0x10; /* BCCH */ - 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; - case GSM_PCHAN_CCCH: - default: - /* OSMO_ASSERT(lchan_nr == 0); - * FIXME: On octphy and litecell, we hit above assertion (see - * Max's comment at https://gerrit.osmocom.org/589 ); disabled - * for BTS until this is clarified; remove the #ifdef when it - * is fixed. Tracked in OS#2906. - */ -#pragma message "fix caller that passes 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) -{ - switch (lchan->ts->pchan) { - case GSM_PCHAN_TCH_F_TCH_H_PDCH: - /* Return chan_nr reflecting the current TS pchan, either a standard TCH kind, or the - * nonstandard value reflecting PDCH for Osmocom style dyn TS. */ - return gsm_lchan_as_pchan2chan_nr(lchan, - lchan->ts->dyn.pchan_is); - case GSM_PCHAN_TCH_F_PDCH: - /* For ip.access style dyn TS, we always want to use the chan_nr as if it was TCH/F. - * We're using custom PDCH ACT and DEACT messages that use the usual chan_nr values. */ - return gsm_lchan_as_pchan2chan_nr(lchan, GSM_PCHAN_TCH_F); - default: - 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) - 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) - 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)); -} - -const char *gsm_trx_unit_id(struct gsm_bts_trx *trx) -{ - static char buf[23]; - - snprintf(buf, sizeof(buf), "%u/%u/%u", trx->bts->ip_access.site_id, - trx->bts->ip_access.bts_id, trx->nr); - return buf; -} - -const struct value_string lchan_ciph_state_names[] = { - { LCHAN_CIPH_NONE, "NONE" }, - { LCHAN_CIPH_RX_REQ, "RX_REQ" }, - { LCHAN_CIPH_RX_CONF, "RX_CONF" }, - { LCHAN_CIPH_RXTX_REQ, "RXTX_REQ" }, - { LCHAN_CIPH_RX_CONF_TX_REQ, "RX_CONF_TX_REQ" }, - { LCHAN_CIPH_RXTX_CONF, "RXTX_CONF" }, - { 0, NULL } -}; - -/* determine the ECU codec constant for the codec used by given lchan */ -int lchan2ecu_codec(const struct gsm_lchan *lchan) -{ - struct gsm_bts_trx_ts *ts = lchan->ts; - - switch (lchan->tch_mode) { - case GSM48_CMODE_SPEECH_V1: - if (ts_pchan(ts) == GSM_PCHAN_TCH_H) - return OSMO_ECU_CODEC_HR; - else - return OSMO_ECU_CODEC_FR; - break; - case GSM48_CMODE_SPEECH_EFR: - return OSMO_ECU_CODEC_EFR; - case GSM48_CMODE_SPEECH_AMR: - return OSMO_ECU_CODEC_AMR; - default: - return -1; - } -} diff --git a/src/common/handover.c b/src/common/handover.c index 63a98324..4b2e6bec 100644 --- a/src/common/handover.c +++ b/src/common/handover.c @@ -14,7 +14,7 @@ * 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. + * 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/>. @@ -51,7 +51,7 @@ static int ho_tx_phys_info(struct gsm_lchan *lchan) gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); gh->proto_discr = GSM48_PDISC_RR; gh->msg_type = GSM48_MT_RR_HANDO_INFO; - msgb_put_u8(msg, lchan->rqd_ta); + msgb_put_u8(msg, lchan->ta_ctrl.current); rsl_rll_push_l3(msg, RSL_MT_UNIT_DATA_REQ, gsm_lchan2chan_nr(lchan), 0x00, 0); @@ -111,7 +111,8 @@ void handover_rach(struct gsm_lchan *lchan, uint8_t ra, uint8_t acc_delay) "TA=%u, ref=%u\n", gsm_lchant_name(lchan->type), acc_delay, ra); /* Set timing advance */ - lchan->rqd_ta = acc_delay; + lchan->ta_ctrl.current = acc_delay; + lchan->want_dl_sacch_active = true; /* Stop handover detection, wait for valid frame */ lchan->ho.active = HANDOVER_WAIT_FRAME; @@ -122,7 +123,7 @@ void handover_rach(struct gsm_lchan *lchan, uint8_t ra, uint8_t acc_delay) } /* Send HANDover DETect to BSC */ - rsl_tx_hando_det(lchan, &lchan->rqd_ta); + rsl_tx_hando_det(lchan, &lchan->ta_ctrl.current); /* Send PHYS INFO */ lchan->ho.phys_info_count = 1; @@ -135,7 +136,7 @@ void handover_rach(struct gsm_lchan *lchan, uint8_t ra, uint8_t acc_delay) osmo_timer_schedule(&lchan->ho.t3105, 0, bts->t3105_ms * 1000); } -/* received frist valid data frame on dedicated channel */ +/* received first valid data frame on dedicated channel */ void handover_frame(struct gsm_lchan *lchan) { LOGPLCHAN(lchan, DHO, LOGL_INFO, "First valid frame detected\n"); diff --git a/src/common/l1sap.c b/src/common/l1sap.c index f07e79ca..5a900f80 100644 --- a/src/common/l1sap.c +++ b/src/common/l1sap.c @@ -13,7 +13,7 @@ * 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. + * 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/>. @@ -33,10 +33,14 @@ #include <osmocom/gsm/l1sap.h> #include <osmocom/gsm/gsm_utils.h> #include <osmocom/gsm/rsl.h> +#include <osmocom/gsm/rlp.h> +#include <osmocom/gsm/rtp_extensions.h> #include <osmocom/core/gsmtap.h> #include <osmocom/core/gsmtap_util.h> #include <osmocom/core/utils.h> +#include <osmocom/codec/codec.h> + #include <osmocom/trau/osmo_ortp.h> #include <osmo-bts/logging.h> @@ -51,41 +55,17 @@ #include <osmo-bts/abis.h> #include <osmo-bts/bts_model.h> #include <osmo-bts/handover.h> -#include <osmo-bts/power_control.h> #include <osmo-bts/msg_utils.h> +#include <osmo-bts/rtp_input_preen.h> #include <osmo-bts/pcuif_proto.h> #include <osmo-bts/cbch.h> - - -#define CB_FCCH -1 -#define CB_SCH -2 -#define CB_BCCH -3 -#define CB_IDLE -4 - -/* according to TS 05.02 Clause 7 Table 3 of 9 an Figure 8a */ -static const int ccch_block_table[51] = { - CB_FCCH, CB_SCH,/* 0..1 */ - CB_BCCH, CB_BCCH, CB_BCCH, CB_BCCH, /* 2..5: BCCH */ - 0, 0, 0, 0, /* 6..9: B0 */ - CB_FCCH, CB_SCH,/* 10..11 */ - 1, 1, 1, 1, /* 12..15: B1 */ - 2, 2, 2, 2, /* 16..19: B2 */ - CB_FCCH, CB_SCH,/* 20..21 */ - 3, 3, 3, 3, /* 22..25: B3 */ - 4, 4, 4, 4, /* 26..29: B4 */ - CB_FCCH, CB_SCH,/* 30..31 */ - 5, 5, 5, 5, /* 32..35: B5 */ - 6, 6, 6, 6, /* 36..39: B6 */ - CB_FCCH, CB_SCH,/* 40..41 */ - 7, 7, 7, 7, /* 42..45: B7 */ - 8, 8, 8, 8, /* 46..49: B8 */ - -4 /* 50: Idle */ -}; +#include <osmo-bts/asci.h> +#include <osmo-bts/csd_v110.h> /* determine the CCCH block number based on the frame number */ unsigned int l1sap_fn2ccch_block(uint32_t fn) { - int rc = ccch_block_table[fn%51]; + int rc = gsm0502_fn2ccch_block(fn); /* if FN is negative, we were called for something that's not CCCH! */ OSMO_ASSERT(rc >= 0); return rc; @@ -94,18 +74,25 @@ unsigned int l1sap_fn2ccch_block(uint32_t fn) struct gsm_lchan *get_lchan_by_chan_nr(struct gsm_bts_trx *trx, unsigned int chan_nr) { + struct gsm_bts_trx_ts *ts; unsigned int tn, ss; tn = L1SAP_CHAN2TS(chan_nr); - OSMO_ASSERT(tn < ARRAY_SIZE(trx->ts)); + ts = &trx->ts[tn]; + + if (L1SAP_IS_CHAN_VAMOS(chan_nr)) { + if (ts->vamos.peer == NULL) + return NULL; + ts = ts->vamos.peer; + } if (L1SAP_IS_CHAN_CBCH(chan_nr)) ss = 2; /* CBCH is always on sub-slot 2 */ else ss = l1sap_chan2ss(chan_nr); - OSMO_ASSERT(ss < ARRAY_SIZE(trx->ts[tn].lchan)); + OSMO_ASSERT(ss < ARRAY_SIZE(ts->lchan)); - return &trx->ts[tn].lchan[ss]; + return &ts->lchan[ss]; } static struct gsm_lchan * @@ -131,7 +118,8 @@ static uint32_t fn_ms_adj(uint32_t fn, const struct gsm_lchan *lchan) /* 12/13 frames usable for audio in TCH, 160 samples per RTP packet, 1 RTP packet per 4 frames */ - samples_passed = (fn - lchan->tch.last_fn) * 12 * 160 / (13 * 4); + const uint32_t num_fn = GSM_TDMA_FN_SUB(fn, lchan->tch.last_fn); + samples_passed = num_fn * 12 * 160 / (13 * 4); /* round number of samples to the nearest multiple of GSM_RTP_DURATION */ r = samples_passed + GSM_RTP_DURATION / 2; @@ -145,27 +133,13 @@ static uint32_t fn_ms_adj(uint32_t fn, const struct gsm_lchan *lchan) return GSM_RTP_DURATION; } -/*! limit number of queue entries to %u; drops any surplus messages */ -static void queue_limit_to(const char *prefix, struct llist_head *queue, unsigned int limit) -{ - unsigned int count = llist_count(queue); - - if (count > limit) - LOGP(DL1P, LOGL_NOTICE, "%s: freeing %d queued frames\n", prefix, count-limit); - while (count > limit) { - struct msgb *tmp = msgb_dequeue(queue); - msgb_free(tmp); - count--; - } -} - /* allocate a msgb containing a osmo_phsap_prim + optional l2 data - * in order to wrap femtobts header arround l2 data, there must be enough space + * in order to wrap femtobts header around l2 data, there must be enough space * in front and behind data pointer */ struct msgb *l1sap_msgb_alloc(unsigned int l2_len) { - int headroom = 128; - int size = headroom + sizeof(struct osmo_phsap_prim) + l2_len; + const int headroom = L1SAP_MSGB_HEADROOM; + const int size = headroom + sizeof(struct osmo_phsap_prim) + l2_len; struct msgb *msg = msgb_alloc_headroom(size, headroom, "l1sap_prim"); if (!msg) @@ -176,9 +150,15 @@ struct msgb *l1sap_msgb_alloc(unsigned int l2_len) return msg; } +/* Enclose rmsg into an osmo_phsap primitive and hand it over to the higher + * layers. The phsap primitive also contains measurement information. The + * parameters rssi, ta_offs and is_sub are only needed when the measurement + * information is passed along with the TCH data. When separate measurement + * indications are used, those last three parameters may be set to zero. */ int add_l1sap_header(struct gsm_bts_trx *trx, struct msgb *rmsg, struct gsm_lchan *lchan, uint8_t chan_nr, uint32_t fn, - uint16_t ber10k, int16_t lqual_cb) + uint16_t ber10k, int16_t lqual_cb, int8_t rssi, + int16_t ta_offs, uint8_t is_sub) { struct osmo_phsap_prim *l1sap; @@ -194,6 +174,10 @@ int add_l1sap_header(struct gsm_bts_trx *trx, struct msgb *rmsg, l1sap->u.tch.ber10k = ber10k; l1sap->u.tch.lqual_cb = lqual_cb; + l1sap->u.tch.rssi = rssi; + l1sap->u.tch.ta_offs_256bits = ta_offs; + l1sap->u.tch.is_sub = is_sub; + return l1sap_up(trx, l1sap); } @@ -257,37 +241,96 @@ int bts_check_for_ciph_cmd(struct msgb *msg, struct gsm_lchan *lchan, return check_for_ciph_cmd(msg, lchan, chan_nr); } -struct gsmtap_inst *gsmtap = NULL; -uint32_t gsmtap_sapi_mask = 0; -uint8_t gsmtap_sapi_acch = 0; - -const struct value_string gsmtap_sapi_names[] = { - { GSMTAP_CHANNEL_BCCH, "BCCH" }, - { GSMTAP_CHANNEL_CCCH, "CCCH" }, - { GSMTAP_CHANNEL_RACH, "RACH" }, - { GSMTAP_CHANNEL_AGCH, "AGCH" }, - { GSMTAP_CHANNEL_PCH, "PCH" }, - { GSMTAP_CHANNEL_SDCCH, "SDCCH" }, - { GSMTAP_CHANNEL_TCH_F, "TCH/F" }, - { GSMTAP_CHANNEL_TCH_H, "TCH/H" }, - { GSMTAP_CHANNEL_PACCH, "PACCH" }, - { GSMTAP_CHANNEL_PDCH, "PDTCH" }, - { GSMTAP_CHANNEL_PTCCH, "PTCCH" }, - { GSMTAP_CHANNEL_CBCH51,"CBCH" }, - { GSMTAP_CHANNEL_ACCH, "SACCH" }, +uint16_t l1sap_log_ctx_sapi; + +const struct value_string l1sap_common_sapi_names[] = { + { L1SAP_COMMON_SAPI_UNKNOWN, "UNKNOWN" }, + /* alphabetic order */ + { L1SAP_COMMON_SAPI_AGCH, "AGCH" }, + { L1SAP_COMMON_SAPI_BCCH, "BCCH" }, + { L1SAP_COMMON_SAPI_CBCH, "CBCH" }, + { L1SAP_COMMON_SAPI_FACCH_F, "FACCH/F" }, + { L1SAP_COMMON_SAPI_FACCH_H, "FACCH/H" }, + { L1SAP_COMMON_SAPI_FCCH, "FCCH" }, + { L1SAP_COMMON_SAPI_IDLE, "IDLE" }, + { L1SAP_COMMON_SAPI_NCH, "NCH" }, + { L1SAP_COMMON_SAPI_PACCH, "PACCH" }, + { L1SAP_COMMON_SAPI_PAGCH, "PAGCH" }, + { L1SAP_COMMON_SAPI_PBCCH, "PBCCH" }, + { L1SAP_COMMON_SAPI_PCH, "PCH" }, + { L1SAP_COMMON_SAPI_PDTCH, "PDTCH" }, + { L1SAP_COMMON_SAPI_PNCH, "PNCH" }, + { L1SAP_COMMON_SAPI_PPCH, "PPCH" }, + { L1SAP_COMMON_SAPI_PRACH, "PRACH" }, + { L1SAP_COMMON_SAPI_PTCCH, "PTCCH" }, + { L1SAP_COMMON_SAPI_RACH, "RACH" }, + { L1SAP_COMMON_SAPI_SACCH, "SACCH" }, + { L1SAP_COMMON_SAPI_SCH, "SCH" }, + { L1SAP_COMMON_SAPI_SDCCH, "SDCCH" }, + { L1SAP_COMMON_SAPI_TCH_F, "TCH/F" }, + { L1SAP_COMMON_SAPI_TCH_H, "TCH/H" }, { 0, NULL } }; +static enum l1sap_common_sapi get_common_sapi_ph_data(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap) +{ + uint8_t link_id = l1sap->u.data.link_id; + uint8_t chan_nr = l1sap->u.data.chan_nr; + uint32_t u32Fn = l1sap->u.data.fn; + + if (L1SAP_IS_CHAN_TCHF(chan_nr)) + return L1SAP_COMMON_SAPI_TCH_F; + + if (L1SAP_IS_CHAN_TCHH(chan_nr)) + return L1SAP_COMMON_SAPI_TCH_H; + + if (L1SAP_IS_CHAN_SDCCH4(chan_nr) || L1SAP_IS_CHAN_SDCCH8(chan_nr)) + return L1SAP_COMMON_SAPI_SDCCH; + + if (L1SAP_IS_CHAN_BCCH(chan_nr)) + return L1SAP_COMMON_SAPI_BCCH; + + if (L1SAP_IS_CHAN_AGCH_PCH(chan_nr)) + /* The sapi depends on DSP configuration, not on the actual SYSTEM INFORMATION 3. */ + return ((l1sap_fn2ccch_block(u32Fn) >= num_agch(trx, "PH-DATA-REQ")) + ? L1SAP_COMMON_SAPI_PCH + : L1SAP_COMMON_SAPI_AGCH); + + if (L1SAP_IS_CHAN_CBCH(chan_nr)) + return L1SAP_COMMON_SAPI_CBCH; + + if (L1SAP_IS_LINK_SACCH(link_id)) + return L1SAP_COMMON_SAPI_SACCH; + + return L1SAP_COMMON_SAPI_UNKNOWN; +} + +static enum l1sap_common_sapi get_common_sapi_by_trx_prim(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap) +{ + /* Only downlink prims are relevant */ + switch (OSMO_PRIM_HDR(&l1sap->oph)) { + case OSMO_PRIM(PRIM_PH_DATA, PRIM_OP_REQUEST): + if (ts_is_pdch(&trx->ts[L1SAP_CHAN2TS(l1sap->u.data.chan_nr)])) + return ((L1SAP_IS_PTCCH(l1sap->u.data.fn)) + ? L1SAP_COMMON_SAPI_PTCCH + : L1SAP_COMMON_SAPI_PDTCH); + return get_common_sapi_ph_data(trx, l1sap); + default: + return L1SAP_COMMON_SAPI_UNKNOWN; + } +} + /* send primitive as gsmtap */ -static int gsmtap_ph_data(struct osmo_phsap_prim *l1sap, uint8_t *chan_type, - uint8_t *ss, uint32_t fn, uint8_t **data, unsigned int *len, +static int gsmtap_ph_data(const struct osmo_phsap_prim *l1sap, + uint8_t *chan_type, uint8_t *ss, uint32_t fn, + uint8_t **data, unsigned int *len, uint8_t num_agch) { struct msgb *msg = l1sap->oph.msg; uint8_t chan_nr, link_id; *data = msgb_l2(msg); - *len = msgb_l2len(msg); + *len = msgb_l2(msg) ? msgb_l2len(msg) : 0; chan_nr = l1sap->u.data.chan_nr; link_id = l1sap->u.data.link_id; @@ -322,47 +365,63 @@ static int gsmtap_ph_data(struct osmo_phsap_prim *l1sap, uint8_t *chan_type, return 0; } -static int gsmtap_pdch(struct osmo_phsap_prim *l1sap, uint8_t *chan_type, - uint8_t *ss, uint32_t fn, uint8_t **data, unsigned int *len) +static int gsmtap_pdch(const struct osmo_phsap_prim *l1sap, + uint8_t *chan_type, uint8_t *ss, uint32_t fn, + uint8_t **data, unsigned int *len) { struct msgb *msg = l1sap->oph.msg; *data = msgb_l2(msg); - *len = msgb_l2len(msg); + *len = msgb_l2(msg) ? msgb_l2len(msg) : 0; if (L1SAP_IS_PTCCH(fn)) { *chan_type = GSMTAP_CHANNEL_PTCCH; *ss = L1SAP_FN2PTCCHBLOCK(fn); - if (l1sap->oph.primitive == PRIM_OP_INDICATION) { - OSMO_ASSERT(len > 0); - if ((*data[0]) == 7) - return -EINVAL; - (*data)++; - (*len)--; - } - } else - *chan_type = GSMTAP_CHANNEL_PACCH; + } else { + /* TODO: distinguish PACCH */ + *chan_type = GSMTAP_CHANNEL_PDTCH; + } return 0; } -static int gsmtap_ph_rach(struct osmo_phsap_prim *l1sap, uint8_t *chan_type, - uint8_t *tn, uint8_t *ss, uint32_t *fn, uint8_t **data, unsigned int *len) +static int gsmtap_ph_rach(const struct osmo_phsap_prim *l1sap, uint8_t *chan_type, + uint8_t *tn, uint8_t *ss, uint32_t *fn, + uint8_t **data, unsigned int *len) { - uint8_t chan_nr; + uint8_t chan_nr = l1sap->u.rach_ind.chan_nr; + static uint8_t ra_buf[2]; *chan_type = GSMTAP_CHANNEL_RACH; *fn = l1sap->u.rach_ind.fn; - *tn = L1SAP_CHAN2TS(l1sap->u.rach_ind.chan_nr); - chan_nr = l1sap->u.rach_ind.chan_nr; + *tn = L1SAP_CHAN2TS(chan_nr); + if (L1SAP_IS_CHAN_TCHH(chan_nr)) *ss = L1SAP_CHAN2SS_TCHH(chan_nr); else if (L1SAP_IS_CHAN_SDCCH4(chan_nr)) *ss = L1SAP_CHAN2SS_SDCCH4(chan_nr); else if (L1SAP_IS_CHAN_SDCCH8(chan_nr)) *ss = L1SAP_CHAN2SS_SDCCH8(chan_nr); - *data = (uint8_t *)&l1sap->u.rach_ind.ra; - *len = 1; + else if (L1SAP_IS_CHAN_PDCH(chan_nr)) { + if (L1SAP_IS_PTCCH(*fn)) { + /* TODO: calculate sub-slot from frame-number */ + *chan_type = GSMTAP_CHANNEL_PTCCH; + } else { + *chan_type = GSMTAP_CHANNEL_PDTCH; + } + } + + if (l1sap->u.rach_ind.is_11bit) { + /* Pack as described in 3GPP TS 44.004, figure 7.4a.b */ + ra_buf[0] = (uint8_t) (l1sap->u.rach_ind.ra >> 3); + ra_buf[1] = (uint8_t) (l1sap->u.rach_ind.ra & 0x07); + *len = sizeof(ra_buf); + *data = ra_buf; + } else { + ra_buf[0] = (uint8_t) (l1sap->u.rach_ind.ra & 0xff); + *len = sizeof(ra_buf[0]); + *data = ra_buf; + } return 0; } @@ -375,8 +434,14 @@ static const uint8_t paging_fill[GSM_MACBLOCK_LEN] = { static bool is_fill_frame(uint8_t chan_type, const uint8_t *data, unsigned int len) { + if (len != GSM_MACBLOCK_LEN) + return false; + switch (chan_type) { case GSMTAP_CHANNEL_AGCH: + case GSMTAP_CHANNEL_SDCCH: + case GSMTAP_CHANNEL_TCH_F: + case GSMTAP_CHANNEL_TCH_H: if (!memcmp(data, fill_frame, GSM_MACBLOCK_LEN)) return true; break; @@ -384,6 +449,7 @@ static bool is_fill_frame(uint8_t chan_type, const uint8_t *data, unsigned int l if (!memcmp(data, paging_fill, GSM_MACBLOCK_LEN)) return true; break; + /* FIXME: implement the same for GSMTAP_CHANNEL_PDTCH from/to PCU */ /* don't use 'default' case here as the above only conditionally return true */ } return false; @@ -396,9 +462,11 @@ static int to_gsmtap(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap) uint8_t chan_type = 0, tn = 0, ss = 0; uint32_t fn; uint16_t uplink = GSMTAP_ARFCN_F_UPLINK; + int8_t signal_dbm; int rc; - if (!gsmtap) + struct gsmtap_inst *inst = trx->bts->gsmtap.inst; + if (!inst) return 0; switch (OSMO_PRIM_HDR(&l1sap->oph)) { @@ -414,10 +482,12 @@ static int to_gsmtap(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap) else rc = gsmtap_ph_data(l1sap, &chan_type, &ss, fn, &data, &len, num_agch(trx, "GSMTAP")); + signal_dbm = l1sap->u.data.rssi; break; case OSMO_PRIM(PRIM_PH_RACH, PRIM_OP_INDICATION): rc = gsmtap_ph_rach(l1sap, &chan_type, &tn, &ss, &fn, &data, &len); + signal_dbm = l1sap->u.rach_ind.rssi; break; default: rc = -ENOTSUP; @@ -429,10 +499,10 @@ static int to_gsmtap(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap) if (len == 0) return 0; if ((chan_type & GSMTAP_CHANNEL_ACCH)) { - if (!gsmtap_sapi_acch) + if (!trx->bts->gsmtap.sapi_acch) return 0; } else { - if (!((1 << (chan_type & 31)) & gsmtap_sapi_mask)) + if (!((1 << (chan_type & 31)) & trx->bts->gsmtap.sapi_mask)) return 0; } @@ -441,8 +511,8 @@ static int to_gsmtap(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap) if (is_fill_frame(chan_type, data, len)) return 0; - gsmtap_send(gsmtap, trx->arfcn | uplink, tn, chan_type, ss, fn, 0, 0, - data, len); + gsmtap_send(inst, trx->arfcn | uplink, tn, chan_type, ss, fn, + signal_dbm, 0 /* TODO: SNR */, data, len); return 0; } @@ -487,22 +557,73 @@ static unsigned int calc_exprd_rach_frames(struct gsm_bts *bts, uint32_t fn) return rach_frames_expired; } +static void l1sap_interf_meas_calc_avg(struct gsm_bts_trx *trx) +{ + unsigned int tn, ln; + + for (tn = 0; tn < ARRAY_SIZE(trx->ts); tn++) { + struct gsm_bts_trx_ts *ts = &trx->ts[tn]; + + if (ts->mo.nm_state.operational != NM_OPSTATE_ENABLED) + continue; + if (ts->mo.nm_state.availability != NM_AVSTATE_OK) + continue; + + for (ln = 0; ln < ARRAY_SIZE(ts->lchan); ln++) { + struct gsm_lchan *lchan = &ts->lchan[ln]; + + lchan->meas.interf_meas_avg_dbm = 0; + lchan->meas.interf_band = 0; + + /* There must be at least one sample */ + if (lchan->meas.interf_meas_num == 0) + continue; + + /* Average all collected samples */ + gsm_lchan_interf_meas_calc_avg(lchan); + } + } +} + +static void l1sap_interf_meas_report(struct gsm_bts *bts) +{ + const uint32_t period = bts->interference.intave * 104; + struct gsm_bts_trx *trx; + + if (bts->interference.intave == 0) + return; + if (bts->gsm_time.fn % period != 0) + return; + + llist_for_each_entry(trx, &bts->trx_list, list) { + if (trx->mo.nm_state.operational != NM_OPSTATE_ENABLED || + trx->bb_transc.mo.nm_state.operational != NM_OPSTATE_ENABLED) + continue; + /* Calculate the average of all received samples */ + l1sap_interf_meas_calc_avg(trx); + /* Report to the BSC over the A-bis/RSL */ + rsl_tx_rf_res(trx); + /* Report to the PCU over the PCUIF */ + pcu_tx_interf_ind(trx, bts->gsm_time.fn); + } +} + /* time information received from bts model */ static int l1sap_info_time_ind(struct gsm_bts *bts, struct osmo_phsap_prim *l1sap, struct info_time_ind_param *info_time_ind) { - int frames_expired; - int i; + unsigned int frames_expired; + unsigned int i; DEBUGPFN(DL1P, info_time_ind->fn, "Rx MPH_INFO time ind\n"); /* Calculate and check frame difference */ - frames_expired = info_time_ind->fn - bts->gsm_time.fn; + frames_expired = GSM_TDMA_FN_SUB(info_time_ind->fn, bts->gsm_time.fn); if (frames_expired > 1) { if (bts->gsm_time.fn) LOGPFN(DL1P, LOGL_ERROR, info_time_ind->fn, - "Invalid condition detected: Frame difference is %"PRIu32"-%"PRIu32"=%d > 1!\n", + "Invalid condition detected: Frame difference is %"PRIu32"-%"PRIu32"=%u > 1!\n", info_time_ind->fn, bts->gsm_time.fn, frames_expired); } @@ -515,10 +636,14 @@ static int l1sap_info_time_ind(struct gsm_bts *bts, /* increment number of RACH slots that have passed by since the * last time indication */ for (i = 0; i < frames_expired; i++) { - uint32_t fn = (info_time_ind->fn + GSM_HYPERFRAME - i) % GSM_HYPERFRAME; + uint32_t fn = GSM_TDMA_FN_SUB(info_time_ind->fn, i); bts->load.rach.total += calc_exprd_rach_frames(bts, fn); } + /* Report interference levels to the BSC */ + if (bts_internal_flag_get(bts, BTS_INTERNAL_FLAG_INTERF_MEAS)) + l1sap_interf_meas_report(bts); + return 0; } @@ -542,49 +667,93 @@ static inline void set_ms_to_data(struct gsm_lchan *lchan, int16_t data, bool se } } +bool trx_sched_is_sacch_fn(const struct gsm_bts_trx_ts *ts, uint32_t fn, bool uplink); + /* measurement information received from bts model */ -static int l1sap_info_meas_ind(struct gsm_bts_trx *trx, - struct osmo_phsap_prim *l1sap, - struct info_meas_ind_param *info_meas_ind) +static void process_l1sap_meas_data(struct gsm_lchan *lchan, + const struct osmo_phsap_prim *l1sap, + enum osmo_ph_prim ind_type) { struct bts_ul_meas ulm; - struct gsm_lchan *lchan; - - lchan = get_active_lchan_by_chan_nr(trx, info_meas_ind->chan_nr); - if (!lchan) { - LOGPFN(DL1P, LOGL_ERROR, info_meas_ind->fn, - "No lchan for MPH INFO MEAS IND (chan_nr=%s)\n", rsl_chan_nr_str(info_meas_ind->chan_nr)); - return 0; - } + const struct info_meas_ind_param *info_meas_ind; + const struct ph_data_param *ph_data_ind; + const struct ph_tch_param *ph_tch_ind; + uint32_t fn; + const char *ind_name; - DEBUGPFN(DL1P, info_meas_ind->fn, - "%s MPH_INFO meas ind, ta_offs_256bits=%d, ber10k=%d, inv_rssi=%u\n", - gsm_lchan_name(lchan), info_meas_ind->ta_offs_256bits, - info_meas_ind->ber10k, info_meas_ind->inv_rssi); + /* Do not process measurement reports from non-active VGCS calls. */ + if (rsl_chan_rt_is_asci(lchan->rsl_chan_rt) && lchan->asci.talker_active != VGCS_TALKER_ACTIVE) + return; - /* in the GPRS case we are not interested in measurement - * processing. The PCU will take care of it */ - if (lchan->type == GSM_LCHAN_PDTCH) - return 0; + switch (ind_type) { + case PRIM_MPH_INFO: + /* (legacy way, see also OS#2977) */ + info_meas_ind = &l1sap->u.info.u.meas_ind; + fn = info_meas_ind->fn; + ind_name = "MPH INFO"; + ulm = (struct bts_ul_meas) { + .ta_offs_256bits = info_meas_ind->ta_offs_256bits, + .inv_rssi = info_meas_ind->inv_rssi, + .ber10k = info_meas_ind->ber10k, + .ci_cb = info_meas_ind->c_i_cb, + }; + /* additionally treat SACCH frames (match by TDMA FN) as SUB frames */ + if (info_meas_ind->is_sub || trx_sched_is_sacch_fn(lchan->ts, fn, true)) + ulm.is_sub = 1; + break; + case PRIM_TCH: + ph_tch_ind = &l1sap->u.tch; + if (ph_tch_ind->rssi == 0) + return; + fn = ph_tch_ind->fn; + ind_name = "TCH"; + ulm = (struct bts_ul_meas) { + .ta_offs_256bits = ph_tch_ind->ta_offs_256bits, + .inv_rssi = abs(ph_tch_ind->rssi), + .ber10k = ph_tch_ind->ber10k, + .ci_cb = ph_tch_ind->lqual_cb, + .is_sub = ph_tch_ind->is_sub, + }; + /* PRIM_TCH always carries DCCH, not SACCH */ + break; + case PRIM_PH_DATA: + ph_data_ind = &l1sap->u.data; + if (ph_data_ind->rssi == 0) + return; + fn = ph_data_ind->fn; + ind_name = "DATA"; + ulm = (struct bts_ul_meas) { + .ta_offs_256bits = ph_data_ind->ta_offs_256bits, + .inv_rssi = abs(ph_data_ind->rssi), + .ber10k = ph_data_ind->ber10k, + .ci_cb = ph_data_ind->lqual_cb, + }; + /* additionally treat SACCH frames (match by RSL link ID) as SUB frames */ + if (ph_data_ind->is_sub || L1SAP_IS_LINK_SACCH(ph_data_ind->link_id)) + ulm.is_sub = 1; + break; + default: + OSMO_ASSERT(false); + } - memset(&ulm, 0, sizeof(ulm)); - ulm.ta_offs_256bits = info_meas_ind->ta_offs_256bits; - ulm.ber10k = info_meas_ind->ber10k; - ulm.inv_rssi = info_meas_ind->inv_rssi; - ulm.is_sub = info_meas_ind->is_sub; + LOGPLCFN(lchan, fn, DL1P, LOGL_DEBUG, + "%s meas ind, ta_offs_256bits=%d, ber10k=%d, inv_rssi=%u, C/I=%d cB\n", ind_name, + ulm.ta_offs_256bits, ulm.ber10k, ulm.inv_rssi, ulm.ci_cb); /* we assume that symbol period is 1 bit: */ - set_ms_to_data(lchan, info_meas_ind->ta_offs_256bits / 256, true); + set_ms_to_data(lchan, ulm.ta_offs_256bits / 256, true); - lchan_meas_process_measurement(lchan, &ulm, info_meas_ind->fn); + lchan_meas_process_measurement(lchan, &ulm, fn); - return 0; + return; } -/* any L1 MPH_INFO indication prim recevied from bts model */ +/* any L1 MPH_INFO indication prim received from bts model */ static int l1sap_mph_info_ind(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap, struct mph_info_param *info) { + const struct info_meas_ind_param *meas_ind; + struct gsm_lchan *lchan; int rc = 0; switch (info->type) { @@ -599,7 +768,22 @@ static int l1sap_mph_info_ind(struct gsm_bts_trx *trx, &info->u.time_ind); break; case PRIM_INFO_MEAS: - rc = l1sap_info_meas_ind(trx, l1sap, &info->u.meas_ind); + /* We should never get an INFO_IND with PRIM_INFO_MEAS + * when BTS_INTERNAL_FLAG_MEAS_PAYLOAD_COMB is set */ + if (bts_internal_flag_get(trx->bts, BTS_INTERNAL_FLAG_MEAS_PAYLOAD_COMB)) + OSMO_ASSERT(false); + + meas_ind = &l1sap->u.info.u.meas_ind; + + lchan = get_active_lchan_by_chan_nr(trx, meas_ind->chan_nr); + if (!lchan) { + LOGPFN(DL1P, LOGL_ERROR, meas_ind->fn, + "No lchan for chan_nr=%s\n", + rsl_chan_nr_str(meas_ind->chan_nr)); + return 0; + } + + process_l1sap_meas_data(lchan, l1sap, PRIM_MPH_INFO); break; default: LOGP(DL1P, LOGL_NOTICE, "unknown MPH_INFO ind type %d\n", @@ -616,6 +800,12 @@ static int l1sap_info_act_cnf(struct gsm_bts_trx *trx, struct info_act_cnf_param *info_act_cnf) { struct gsm_lchan *lchan = get_lchan_by_chan_nr(trx, info_act_cnf->chan_nr); + if (lchan == NULL) { + LOGPTRX(trx, DL1C, LOGL_ERROR, "get_lchan_by_chan_nr(chan_nr=%s) " + "yields NULL for PRIM_INFO_ACTIVATE.conf\n", + rsl_chan_nr_str(info_act_cnf->chan_nr)); + return -ENODEV; + } LOGPLCHAN(lchan, DL1C, LOGL_INFO, "activate confirm chan_nr=%s trx=%d\n", rsl_chan_nr_str(info_act_cnf->chan_nr), trx->nr); @@ -639,6 +829,12 @@ static int l1sap_info_rel_cnf(struct gsm_bts_trx *trx, struct info_act_cnf_param *info_act_cnf) { struct gsm_lchan *lchan = get_lchan_by_chan_nr(trx, info_act_cnf->chan_nr); + if (lchan == NULL) { + LOGPTRX(trx, DL1C, LOGL_ERROR, "get_lchan_by_chan_nr(chan_nr=%s) " + "yields NULL for PRIM_INFO_ACTIVATE.conf\n", + rsl_chan_nr_str(info_act_cnf->chan_nr)); + return -ENODEV; + } LOGPLCHAN(lchan, DL1C, LOGL_INFO, "deactivate confirm chan_nr=%s trx=%d\n", rsl_chan_nr_str(info_act_cnf->chan_nr), trx->nr); @@ -655,7 +851,7 @@ static int l1sap_info_rel_cnf(struct gsm_bts_trx *trx, return 0; } -/* any L1 MPH_INFO confirm prim recevied from bts model */ +/* any L1 MPH_INFO confirm prim received from bts model */ static int l1sap_mph_info_cnf(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap, struct mph_info_param *info) { @@ -692,16 +888,15 @@ static int lchan_pdtch_ph_rts_ind_loop(struct gsm_lchan *lchan, uint8_t *p; /* de-queue response message (loopback) */ - loop_msg = msgb_dequeue(&lchan->dl_tch_queue); + loop_msg = msgb_dequeue_count(&lchan->dl_tch_queue, &lchan->dl_tch_queue_len); if (!loop_msg) { - LOGPGT(DL1P, LOGL_NOTICE, tm, "%s: no looped PDTCH message, sending empty\n", - gsm_lchan_name(lchan)); + LOGPLCGT(lchan, tm, DL1P, LOGL_NOTICE, "no looped PDTCH message, sending empty\n"); /* empty downlink message */ p = msgb_put(msg, GSM_MACBLOCK_LEN); memset(p, 0, GSM_MACBLOCK_LEN); } else { - LOGPGT(DL1P, LOGL_NOTICE, tm, "%s: looped PDTCH message of %u bytes\n", - gsm_lchan_name(lchan), msgb_l2len(loop_msg)); + LOGPLCGT(lchan, tm, DL1P, LOGL_NOTICE, "looped PDTCH message of %u bytes\n", + msgb_l2len(loop_msg)); /* copy over data from queued response message */ p = msgb_put(msg, msgb_l2len(loop_msg)); memcpy(p, msgb_l2(loop_msg), msgb_l2len(loop_msg)); @@ -710,17 +905,34 @@ static int lchan_pdtch_ph_rts_ind_loop(struct gsm_lchan *lchan, return 0; } -/* Check if given CCCH frame number is for a PCH or for an AGCH (this function is +/* Check if given CCCH frame number is for a NCH, PCH or for an AGCH (this function is * only used internally, it is public to call it from unit-tests) */ -int is_ccch_for_agch(struct gsm_bts_trx *trx, uint32_t fn) { +enum ccch_msgt get_ccch_msgt(struct gsm_bts_trx *trx, uint32_t fn) +{ + uint8_t block, first_block, num_blocks; + int rc; + + block = l1sap_fn2ccch_block(fn); + + /* If there is an NCH, check if the block number matches. It has priority over PCH/AGCH. */ + if (trx->bts->asci.pos_nch >= 0) { + rc = osmo_gsm48_si1ro_nch_pos_decode(trx->bts->asci.pos_nch, &num_blocks, &first_block); + if (rc >= 0 && block >= first_block && block < first_block + num_blocks) + return CCCH_MSGT_NCH; + } + /* Note: The number of available access grant channels is set by the * parameter BS_AG_BLKS_RES via system information type 3. This SI is - * transfered to osmo-bts via RSL */ - return l1sap_fn2ccch_block(fn) < num_agch(trx, "PH-RTS-IND"); + * transferred to osmo-bts via RSL */ + if (l1sap_fn2ccch_block(fn) < num_agch(trx, "PH-RTS-IND")) + return CCCH_MSGT_AGCH; + + return CCCH_MSGT_PCH; } + /* return the measured average of frame numbers that the RTS clock is running in advance */ -int32_t bts_get_avg_fn_advance(struct gsm_bts *bts) +int32_t bts_get_avg_fn_advance(const struct gsm_bts *bts) { if (bts->fn_stats.avg_count == 0) return 0; @@ -729,7 +941,7 @@ int32_t bts_get_avg_fn_advance(struct gsm_bts *bts) static void l1sap_update_fnstats(struct gsm_bts *bts, uint32_t rts_fn) { - int32_t delta = (rts_fn + GSM_HYPERFRAME - bts->gsm_time.fn) % GSM_HYPERFRAME; + int32_t delta = GSM_TDMA_FN_SUB(rts_fn, bts->gsm_time.fn); if (delta < bts->fn_stats.min) bts->fn_stats.min = delta; @@ -746,6 +958,100 @@ static void l1sap_update_fnstats(struct gsm_bts *bts, uint32_t rts_fn) } } +/* Common dequeueing function */ +static inline struct msgb *lapdm_phsap_dequeue_msg(struct lapdm_entity *le, uint32_t fn) +{ + struct osmo_phsap_prim pp; + if (lapdm_phsap_dequeue_prim_fn(le, &pp, fn) < 0) + return NULL; + return pp.oph.msg; +} + +/* Special dequeueing function with FACCH repetition (3GPP TS 44.006, section 10) */ +static inline struct msgb *lapdm_phsap_dequeue_msg_facch(struct gsm_lchan *lchan, struct lapdm_entity *le, uint32_t fn) +{ + struct osmo_phsap_prim pp; + struct msgb *msg; + + /* Note: The repeated version of the FACCH block must be scheduled 8 or 9 bursts after the original + * transmission. see 3GPP TS 44.006, section 10.2 for a more detailed explaination. */ + if (lchan->rep_acch.dl_facch[0].msg && GSM_TDMA_FN_SUB(fn, lchan->rep_acch.dl_facch[0].fn) >= 8) { + /* Re-use stored FACCH message buffer from SLOT #0 for repetition. */ + msg = lchan->rep_acch.dl_facch[0].msg; + lchan->rep_acch.dl_facch[0].msg = NULL; + } else if (lchan->rep_acch.dl_facch[1].msg && GSM_TDMA_FN_SUB(fn, lchan->rep_acch.dl_facch[1].fn) >= 8) { + /* Re-use stored FACCH message buffer from SLOT #1 for repetition. */ + msg = lchan->rep_acch.dl_facch[1].msg; + lchan->rep_acch.dl_facch[1].msg = NULL; + } else { + /* Fetch new FACCH from queue ... */ + if (lapdm_phsap_dequeue_prim_fn(le, &pp, fn) < 0) + return NULL; + msg = pp.oph.msg; + + /* Check if the LAPDm frame is a command frame, + * see also: 3GPP TS 04.06 section 3.2 and 3.3.2. + * If the MS explicitly indicated that repeated ACCH is + * supported, than all FACCH frames may be repeated + * see also: 3GPP TS 44.006, section 10.3). */ + if (!(lchan->rep_acch_cap.dl_facch_all || msg->data[0] & 0x02)) + return msg; + + /* ... and store the message buffer for repetition. */ + if (lchan->rep_acch.dl_facch[0].msg == NULL) { + lchan->rep_acch.dl_facch[0].msg = msgb_copy(msg, "rep_facch_0"); + lchan->rep_acch.dl_facch[0].fn = fn; + } else if (lchan->rep_acch.dl_facch[1].msg == NULL) { + lchan->rep_acch.dl_facch[1].msg = msgb_copy(msg, "rep_facch_1"); + lchan->rep_acch.dl_facch[1].fn = fn; + } else { + /* By definition 3GPP TS 05.02 does not allow more than two (for TCH/H only one) FACCH blocks + * to be transmitted simultaniously. */ + OSMO_ASSERT(false); + } + } + + return msg; +} + +/* Special dequeueing function with SACCH repetition (3GPP TS 44.006, section 11) */ +static inline struct msgb *lapdm_phsap_dequeue_msg_sacch(struct gsm_lchan *lchan, struct lapdm_entity *le, uint32_t fn) +{ + struct osmo_phsap_prim pp; + struct msgb *msg; + uint8_t sapi; + + /* Note: When the MS disables SACCH repetition, we still must collect + * possible candidates in order to have one ready in case the MS enables + * SACCH repetition. */ + + if (lchan->rep_acch.dl_sacch_msg) { + if (lchan->meas.l1_info.srr_sro == 0) { + /* Toss previous repetition candidate */ + msgb_free(lchan->rep_acch.dl_sacch_msg); + lchan->rep_acch.dl_sacch_msg = NULL; + } else { + /* Use previous repetition candidate */ + msg = lchan->rep_acch.dl_sacch_msg; + lchan->rep_acch.dl_sacch_msg = NULL; + return msg; + } + } + + /* Fetch new repetition candidate from queue */ + if (lapdm_phsap_dequeue_prim_fn(le, &pp, fn) < 0) + return NULL; + msg = pp.oph.msg; + sapi = (msg->data[0] >> 2) & 0x07; + + /* Only LAPDm frames for SAPI 0 may become a repetition + * candidate. */ + if (sapi == 0) + lchan->rep_acch.dl_sacch_msg = msgb_copy(msg, "rep_sacch"); + + return msg; +} + /* PH-RTS-IND prim received from bts model */ static int l1sap_ph_rts_ind(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap, struct ph_data_param *rts_ind) @@ -756,12 +1062,12 @@ static int l1sap_ph_rts_ind(struct gsm_bts_trx *trx, uint8_t chan_nr, link_id; uint8_t tn; uint32_t fn; - uint8_t *p, *si; + uint8_t *p = NULL; + uint8_t *si; struct lapdm_entity *le; - struct osmo_phsap_prim pp; + struct msgb *pp_msg; bool dtxd_facch = false; int rc; - int is_ag_res; chan_nr = rts_ind->chan_nr; link_id = rts_ind->link_id; @@ -793,10 +1099,10 @@ static int l1sap_ph_rts_ind(struct gsm_bts_trx *trx, } else { /* forward RTS.ind to PCU */ if (L1SAP_IS_PTCCH(rts_ind->fn)) { - pcu_tx_rts_req(&trx->ts[tn], 1, fn, 1 /* ARFCN */, + pcu_tx_rts_req(&trx->ts[tn], 1, fn, trx->arfcn, L1SAP_FN2PTCCHBLOCK(fn)); } else { - pcu_tx_rts_req(&trx->ts[tn], 0, fn, 0 /* ARFCN */, + pcu_tx_rts_req(&trx->ts[tn], 0, fn, trx->arfcn, L1SAP_FN2MACBLOCK(fn)); } /* return early, PCU takes care of rest */ @@ -820,19 +1126,45 @@ static int l1sap_ph_rts_ind(struct gsm_bts_trx *trx, rsl_chan_nr_str(chan_nr)); return 0; } + if (lchan->pending_rel_ind_msg) { + LOGPLCGT(lchan, &g_time, DRSL, LOGL_INFO, "Forward RLL RELease INDication to the BSC\n"); + abis_bts_rsl_sendmsg(lchan->pending_rel_ind_msg); + lchan->pending_rel_ind_msg = NULL; + } if (L1SAP_IS_LINK_SACCH(link_id)) { p = msgb_put(msg, GSM_MACBLOCK_LEN); /* L1-header, if not set/modified by layer 1 */ p[0] = lchan->ms_power_ctrl.current; - p[1] = lchan->rqd_ta; + if (lchan->rep_acch.ul_sacch_active) + p[0] |= 0x40; /* See also: 3GPP TS 44.004, section 7.1 */ + p[1] = lchan->ta_ctrl.current; le = &lchan->lapdm_ch.lapdm_acch; + if (lchan->rep_acch_cap.dl_sacch) { + /* Check if MS requests SACCH repetition and update state accordingly */ + if (lchan->meas.l1_info.srr_sro) { + if (lchan->rep_acch.dl_sacch_active == false) + LOGPLCHAN(lchan, DL1P, LOGL_DEBUG, "DL-SACCH repetition: inactive => active\n"); + lchan->rep_acch.dl_sacch_active = true; + } else { + if (lchan->rep_acch.dl_sacch_active == true) + LOGPLCHAN(lchan, DL1P, LOGL_DEBUG, "DL-SACCH repetition: active => inactive\n"); + lchan->rep_acch.dl_sacch_active = false; + } + pp_msg = lapdm_phsap_dequeue_msg_sacch(lchan, le, fn); + } else { + pp_msg = lapdm_phsap_dequeue_msg(le, fn); + } } else { if (lchan->ts->trx->bts->dtxd) dtxd_facch = true; le = &lchan->lapdm_ch.lapdm_dcch; + if (lchan->rep_acch.dl_facch_active && lchan->rsl_cmode != RSL_CMOD_SPD_SIGN) + pp_msg = lapdm_phsap_dequeue_msg_facch(lchan, le, fn); + else + pp_msg = lapdm_phsap_dequeue_msg(le, fn); + lchan->tch.dtx_fr_hr_efr.dl_facch_stealing = (pp_msg != NULL); } - rc = lapdm_phsap_dequeue_prim(le, &pp); - if (rc < 0) { + if (!pp_msg) { if (L1SAP_IS_LINK_SACCH(link_id)) { /* No SACCH data from LAPDM pending, send SACCH filling */ uint8_t *si = lchan_sacch_get(lchan); @@ -841,6 +1173,10 @@ static int l1sap_ph_rts_ind(struct gsm_bts_trx *trx, memcpy(p + 2, si, GSM_MACBLOCK_LEN - 2); } else memcpy(p + 2, fill_frame, GSM_MACBLOCK_LEN - 2); + } else if (vgcs_is_uplink_free(lchan)) { + /* If UPLINK FREE message is stored, send it with every DCCH frame. */ + p = msgb_put(msg, GSM_MACBLOCK_LEN); + vgcs_uplink_free_get(lchan, p); } else if (L1SAP_IS_CHAN_SDCCH4(chan_nr) || L1SAP_IS_CHAN_SDCCH8(chan_nr) || (lchan->rsl_cmode == RSL_CMOD_SPD_SIGN && !lchan->ts->trx->bts->dtxd)) { /* @@ -857,21 +1193,28 @@ static int l1sap_ph_rts_ind(struct gsm_bts_trx *trx, } else { /* The +2 is empty space where the DSP inserts the L1 hdr */ if (L1SAP_IS_LINK_SACCH(link_id)) - memcpy(p + 2, pp.oph.msg->data + 2, GSM_MACBLOCK_LEN - 2); + memcpy(p + 2, pp_msg->data + 2, GSM_MACBLOCK_LEN - 2); else { p = msgb_put(msg, GSM_MACBLOCK_LEN); - memcpy(p, pp.oph.msg->data, GSM_MACBLOCK_LEN); + memcpy(p, pp_msg->data, GSM_MACBLOCK_LEN); /* check if it is a RR CIPH MODE CMD. if yes, enable RX ciphering */ - check_for_ciph_cmd(pp.oph.msg, lchan, chan_nr); + check_for_ciph_cmd(pp_msg, lchan, chan_nr); if (dtxd_facch) dtx_dispatch(lchan, E_FACCH); + if (rsl_chan_rt_is_vgcs(lchan->rsl_chan_rt)) { + /* Check for UPLINK FREE message and store. */ + if (pp_msg->data[0] == GSM48_MT_RR_SH_UL_FREE << 2) + vgcs_uplink_free_set(lchan, pp_msg->data); + /* Keep UPLINK FREE message when sending short header messages. */ + else if ((pp_msg->data[0] & 0x03) != 0x00) + vgcs_uplink_free_reset(lchan); + } } - msgb_free(pp.oph.msg); + msgb_free(pp_msg); } } else if (L1SAP_IS_CHAN_AGCH_PCH(chan_nr)) { p = msgb_put(msg, GSM_MACBLOCK_LEN); - is_ag_res = is_ccch_for_agch(trx, fn); - rc = bts_ccch_copy_msg(trx->bts, p, &g_time, is_ag_res); + rc = bts_ccch_copy_msg(trx->bts, p, &g_time, get_ccch_msgt(trx, fn)); if (rc <= 0) memcpy(p, fill_frame, GSM_MACBLOCK_LEN); } @@ -884,48 +1227,270 @@ static int l1sap_ph_rts_ind(struct gsm_bts_trx *trx, return 1; } -static bool rtppayload_is_octet_aligned(const uint8_t *rtp_pl, uint8_t payload_len) -{ - /* - * Logic: If 1st bit padding is not zero, packet is either: - * - bandwidth-efficient AMR payload. - * - malformed packet. - * However, Bandwidth-efficient AMR 4,75 frame last in payload(F=0, FT=0) - * with 4th,5ht,6th AMR payload to 0 matches padding==0. - * Furthermore, both AMR 4,75 bw-efficient and octet alignment are 14 bytes long (AMR 4,75 encodes 95b): - * bw-efficient: 95b, + 4b hdr + 6b ToC = 105b, + padding = 112b = 14B. - * octet-aligned: 1B hdr + 1B ToC + 95b = 111b, + padding = 112b = 14B. - * We cannot use other fields to match since they are inside the AMR - * payload bits which are unknown. - * As a result, this function may return false positive (true) for some AMR - * 4,75 AMR frames, but given the length, CMR and FT read is the same as a - * consequence, the damage in here is harmless other than being unable to - * decode the audio at the other side. - */ - #define AMR_PADDING1(rtp_pl) (rtp_pl[0] & 0x0f) - #define AMR_PADDING2(rtp_pl) (rtp_pl[1] & 0x03) +/* The following static functions are helpers for l1sap_tch_rts_ind(), + * used only for FR/HR/EFR speech modes. For these speech TCH modes, + * if our incoming RTP stream includes SID frames, we need to apply + * the following transformations to the downlink frame stream we actually + * transmit: + * + * - We need to cache the last received SID and retransmit it in + * SACCH-aligned frame positions, or if the SACCH-aligned frame + * position was stolen by FACCH, then right after that FACCH. + * + * - That cached SID needs to be aged and expired, in accord with + * TS 28.062 section C.3.2.1.1 paragraph 5 - the paragraph concerning + * expiration of cached downlink SID. + * + * - In all other frame positions, extraneous SID frames need to be + * dropped - we send an empty payload to the BTS model, causing it + * to transmit an induced BFI condition on the air (fake DTXd), + * or perhaps real DTXd (actually turning off Tx) in the future. + */ - if(payload_len < 2 || AMR_PADDING1(rtp_pl) || AMR_PADDING2(rtp_pl)) +/*! \brief Check if the given FN of TCH-RTS-IND corresponds to a mandatory + * SID position for non-AMR codecs that follow SACCH alignment. + * \param[in] lchan Logical channel on which we check scheduling + * \param[in] fn Frame Number for which we check scheduling + * \returns true if this FN is a mandatory SID position, false otherwise + */ +static inline bool fr_hr_efr_sid_position(struct gsm_lchan *lchan, uint32_t fn) +{ + /* See GSM 05.08 section 8.3 for frame numbers - but we are + * specifically looking for FNs corresponding to the beginning + * of the complete SID frame to be transmitted, rather than all FNs + * where we have to put out a non-suppressed burst. */ + switch (lchan->type) { + case GSM_LCHAN_TCH_F: + return fn % 104 == 52; + case GSM_LCHAN_TCH_H: + switch (lchan->nr) { + case 0: + return fn % 104 == 0 || fn % 104 == 52; + case 1: + return fn % 104 == 14 || fn % 104 == 66; + default: + return false; + } + default: return false; + } +} +/*! \brief This helper function implements DTXd input processing for FR/HR/EFR: + * we got an RTP input frame, now we need to check if it is SID or not, + * and update our DL SID reshaper state accordingly. + * \param[in] lchan Logical channel structure of the TCH we work with + * \param[in] resp_msg The input frame from RTP + * \param[out] sid_result Output flag indicating if the received frame is SID + * \returns true if the frame in resp_msg is good, false otherwise + */ +static bool fr_hr_efr_dtxd_input(struct gsm_lchan *lchan, struct msgb *resp_msg, + bool *sid_result) +{ + enum osmo_gsm631_sid_class sidc; + bool is_sid; + + switch (lchan->tch_mode) { + case GSM48_CMODE_SPEECH_V1: + if (lchan->type == GSM_LCHAN_TCH_F) { + sidc = osmo_fr_sid_classify(msgb_l2(resp_msg)); + switch (sidc) { + case OSMO_GSM631_SID_CLASS_SPEECH: + is_sid = false; + break; + case OSMO_GSM631_SID_CLASS_INVALID: + /* TS 28.062 section C.3.2.1.1: invalid SIDs + * from call leg A UL are treated like BFIs. + * Drop this frame and act as if we got nothing + * at all from RTP for this FN. */ + return false; + case OSMO_GSM631_SID_CLASS_VALID: + is_sid = true; + /* The SID code word may have a one bit error - + * rejuvenate it. */ + osmo_fr_sid_reset(msgb_l2(resp_msg)); + break; + default: + /* SID classification per GSM 06.31 section + * 6.1.1 has only 3 possible outcomes. */ + OSMO_ASSERT(0); + } + } else { + /* The same kind of classification as we do in + * osmo_{fr,efr}_sid_classify() is impossible for HR: + * the equivalent ternary SID classification per + * GSM 06.41 can only be done in the UL-handling BTS, + * directly coupled to the GSM 05.03 channel decoder, + * and cannot be reconstructed downstream from frame + * payload bits. The only kind of SID we can detect + * here is the perfect, error-free kind. However, + * if we received RFC 5993 payload and the sender + * told us it is valid SID, honor that indication + * and rejuvenate the SID codeword. */ + if (rtpmsg_is_rfc5993_sid(resp_msg)) { + is_sid = true; + osmo_hr_sid_reset(msgb_l2(resp_msg)); + } else { + is_sid = osmo_hr_check_sid(msgb_l2(resp_msg), + msgb_l2len(resp_msg)); + } + } + break; + case GSM48_CMODE_SPEECH_EFR: + /* same logic as for FRv1 */ + sidc = osmo_efr_sid_classify(msgb_l2(resp_msg)); + switch (sidc) { + case OSMO_GSM631_SID_CLASS_SPEECH: + is_sid = false; + break; + case OSMO_GSM631_SID_CLASS_INVALID: + return false; + case OSMO_GSM631_SID_CLASS_VALID: + is_sid = true; + osmo_efr_sid_reset(msgb_l2(resp_msg)); + break; + default: + OSMO_ASSERT(0); + } + break; + default: + /* This static function should never be called except for + * V1 and EFR speech modes. */ + OSMO_ASSERT(0); + } + *sid_result = is_sid; + lchan->tch.dtx_fr_hr_efr.last_rtp_input_was_sid = is_sid; + if (is_sid) { + uint8_t copy_len; + + copy_len = OSMO_MIN(msgb_l2len(resp_msg), + ARRAY_SIZE(lchan->tch.dtx_fr_hr_efr.last_sid)); + memcpy(lchan->tch.dtx_fr_hr_efr.last_sid, + msgb_l2(resp_msg), copy_len); + lchan->tch.dtx_fr_hr_efr.last_sid_len = copy_len; + lchan->tch.dtx_fr_hr_efr.last_sid_age = 0; + } else { + /* We got a speech frame, not SID - therefore, the state flag + * of "we already transmitted a SID" needs to be cleared, + * so that the very first SID that follows this talkspurt + * will get transmitted right away, without waiting for + * the next mandatory SID position. */ + lchan->tch.dtx_fr_hr_efr.dl_sid_transmitted = false; + } return true; } -static bool rtppayload_is_valid(struct gsm_lchan *lchan, struct msgb *resp_msg) +/*! \brief This helper function implements DTXd output processing for FR/HR/EFR: + * here we update our state to deal with cached SID aging, mandatory-Tx + * frame positions and FACCH stealing, and we make the desired output + * transformations of either regurgitating a cached SID or vice-versa, + * dropping a SID we received from RTP. + * \param[in] lchan Logical channel structure of the TCH we work with + * \param[in] fn Frame Number for which we are preparing DL + * \param[in] current_input_is_sid Self-explanatory + * \param[in] resp_msg_p Pointer to l1sap_tch_rts_ind() internal variable + * \param[in] resp_l1sap_p ditto + * \param[in] empty_l1sap_p ditto + * + * This function gets called with pointers to l1sap_tch_rts_ind() internal + * variables and cannot be properly understood on its own, without + * understanding the parent function first. This situation is unfortunate, + * but it was the only way to factor the present logic out of + * l1sap_tch_rts_ind() main body. + */ +static void fr_hr_efr_dtxd_output(struct gsm_lchan *lchan, uint32_t fn, + bool current_input_is_sid, + struct msgb **resp_msg_p, + struct osmo_phsap_prim **resp_l1sap_p, + struct osmo_phsap_prim *empty_l1sap_p) { - /* Avoid sending bw-efficient AMR to lower layers, most bts models - * don't support it. */ - if(lchan->tch_mode == GSM48_CMODE_SPEECH_AMR && - !rtppayload_is_octet_aligned(resp_msg->data, resp_msg->len)) { - LOGPLCHAN(lchan, DL1P, LOGL_NOTICE, - "RTP->L1: Dropping unexpected AMR encoding (bw-efficient?) %s\n", - osmo_hexdump(resp_msg->data, resp_msg->len)); - return false; + struct msgb *resp_msg = *resp_msg_p; + bool sid_in_hand = current_input_is_sid; + + /* Are we at a mandatory-Tx frame position? If so, clear the state flag + * of "we already transmitted a SID in this window" - as of right now, + * a SID has _not_ been transmitted in the present window yet, and if + * we are in a DTX pause, we do need to transmit a SID update as soon + * as we are able to, FACCH permitting. */ + if (fr_hr_efr_sid_position(lchan, fn)) + lchan->tch.dtx_fr_hr_efr.dl_sid_transmitted = false; + + /* The next stanza implements logic that was originally meant to reside + * in the TFO block in TRAUs: if the source feeding us RTP is in a DTXu + * pause (resp_msg == NULL, meaning we got nothing from RTP) *and* the + * most recent RTP input we did get was a SID, and that SID hasn't + * expired, then we need to replicate that SID. */ + if (!resp_msg && lchan->tch.dtx_fr_hr_efr.last_rtp_input_was_sid && + lchan->tch.dtx_fr_hr_efr.last_sid_age < 47) { + /* Whatever we do further below, any time another 20 ms window + * has passed since the last SID was received in RTP, we have + * to age that cached SID. */ + lchan->tch.dtx_fr_hr_efr.last_sid_age++; + + /* The following conditional checking dl_sid_transmitted flag + * is peculiar to our sans-E1 architecture. In the original + * T1/E1 Abis architecture the TFO-enabled TRAU would repeat + * the cached SID from call leg A into *every* 20 ms frame in + * its Abis output, and then the BTS would select which ones + * it should transmit per the rules of GSM 06.31/06.81 section + * 5.1.2. But in our architecture it would be silly and + * wasteful to allocate and fill an msgb for this cached SID + * only to toss it later in the same function - hence the + * logic of section 5.1.2 is absorbed into the decision right + * here to proceed with cached SID regurgitation or not, + * in the form of the following conditional. */ + if (!lchan->tch.dtx_fr_hr_efr.dl_sid_transmitted) + resp_msg = l1sap_msgb_alloc(lchan->tch.dtx_fr_hr_efr.last_sid_len); + if (resp_msg) { + resp_msg->l2h = msgb_put(resp_msg, + lchan->tch.dtx_fr_hr_efr.last_sid_len); + memcpy(resp_msg->l2h, lchan->tch.dtx_fr_hr_efr.last_sid, + lchan->tch.dtx_fr_hr_efr.last_sid_len); + *resp_msg_p = resp_msg; + *resp_l1sap_p = msgb_l1sap_prim(resp_msg); + sid_in_hand = true; + } + } else if (resp_msg && sid_in_hand) { + /* This "else if" leg implements the logic of section 5.1.2 + * for cases when a SID is already present in the RTP input, + * as indicated by (resp_msg != NULL) and sid_in_hand being + * true. Because we are in the "else" clause of the big "if" + * above, this path executes only when the SID has come from + * RTP in _this_ frame, rather than regurgitated from cache. + * But be it fresh or cached, the rules of section 5.1.2 still + * apply, and if we've already transmitted a SID in the current + * window, then we need to suppress further SIDs and send + * an empty payload to the BTS model, causing the latter + * to transmit an induced BFI condition on the air. This + * strange-seeming behavior is needed so that the spec-compliant + * Rx DTX handler in the MS will produce the expected output, + * same as if the GSM network were the old-fashioned kind with + * Abis TRAUs and TFO. */ + if (lchan->tch.dtx_fr_hr_efr.dl_sid_transmitted) { + msgb_free(resp_msg); + resp_msg = NULL; + *resp_msg_p = NULL; + *resp_l1sap_p = empty_l1sap_p; + } } - return true; + + /* The following conditional answers this question: are we actually + * transmitting a SID frame on the air right now, at this frame number? + * If we are certain the BTS model is going to transmit this SID, + * we set the state flag so we won't be transmitting any more SIDs + * until we either hit the next mandatory-Tx position or get another + * little talkspurt followed by new SID. The check for FACCH stealing + * is included because if the BTS model is going to transmit FACCH in + * the current FN, then we are not actually transmitting SID right now, + * and we still need to transmit a SID ASAP, as soon as the TCH becomes + * becomes free from FACCH activity. GSM 06.31/06.81 section 5.1.2 + * does mention that if the mandatory-Tx frame position is taken up + * by FACCH, then we need to send SID in the following frame. */ + if (resp_msg && sid_in_hand && !lchan->tch.dtx_fr_hr_efr.dl_facch_stealing) + lchan->tch.dtx_fr_hr_efr.dl_sid_transmitted = true; } -/* TCH-RTS-IND prim recevied from bts model */ +/* TCH-RTS-IND prim received from bts model */ static int l1sap_tch_rts_ind(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap, struct ph_tch_param *rts_ind) { @@ -935,19 +1500,19 @@ static int l1sap_tch_rts_ind(struct gsm_bts_trx *trx, struct gsm_lchan *lchan; uint8_t chan_nr, marker = 0; uint32_t fn; - int rc; + bool is_fr_hr_efr_sid = false; chan_nr = rts_ind->chan_nr; fn = rts_ind->fn; gsm_fn2gsmtime(&g_time, fn); - DEBUGPGT(DL1P, &g_time, "Rx TCH-RTS.ind chan_nr=%s\n", rsl_chan_nr_str(chan_nr)); - lchan = get_active_lchan_by_chan_nr(trx, chan_nr); if (!lchan) { LOGPGT(DL1P, LOGL_ERROR, &g_time, "No lchan for PH-RTS.ind (chan_nr=%s)\n", rsl_chan_nr_str(chan_nr)); return 0; + } else { + LOGPLCGT(lchan, &g_time, DL1P, LOGL_DEBUG, "Rx TCH-RTS.ind\n"); } if (!lchan->loopback && lchan->abis_ip.rtp_socket) { @@ -961,13 +1526,9 @@ static int l1sap_tch_rts_ind(struct gsm_bts_trx *trx, lchan->abis_ip.rtp_socket->rx_user_ts += GSM_RTP_DURATION; } /* get a msgb from the dl_tx_queue */ - resp_msg = msgb_dequeue(&lchan->dl_tch_queue); + resp_msg = msgb_dequeue_count(&lchan->dl_tch_queue, &lchan->dl_tch_queue_len); if (!resp_msg) { - DEBUGPGT(DL1P, &g_time, "%s DL TCH Tx queue underrun\n", gsm_lchan_name(lchan)); - resp_l1sap = &empty_l1sap; - } else if(!rtppayload_is_valid(lchan, resp_msg)) { - msgb_free(resp_msg); - resp_msg = NULL; + LOGPLCGT(lchan, &g_time, DL1P, LOGL_DEBUG, "DL TCH Tx queue underrun\n"); resp_l1sap = &empty_l1sap; } else { /* Obtain RTP header Marker bit from control buffer */ @@ -977,16 +1538,27 @@ static int l1sap_tch_rts_ind(struct gsm_bts_trx *trx, msgb_push(resp_msg, sizeof(*resp_l1sap)); resp_msg->l1h = resp_msg->data; resp_l1sap = msgb_l1sap_prim(resp_msg); + + /* FR/HR/EFR SID or non-SID input handling */ + if (lchan->tch_mode == GSM48_CMODE_SPEECH_V1 || + lchan->tch_mode == GSM48_CMODE_SPEECH_EFR) { + bool drop; + + drop = !fr_hr_efr_dtxd_input(lchan, resp_msg, + &is_fr_hr_efr_sid); + if (OSMO_UNLIKELY(drop)) { + msgb_free(resp_msg); + resp_msg = NULL; + resp_l1sap = &empty_l1sap; + } + } } - /* check for pending REL_IND */ - if (lchan->pending_rel_ind_msg) { - LOGPGT(DRSL, LOGL_INFO, &g_time, "%s Forward REL_IND to L3\n", gsm_lchan_name(lchan)); - /* Forward it to L3 */ - rc = abis_bts_rsl_sendmsg(lchan->pending_rel_ind_msg); - lchan->pending_rel_ind_msg = NULL; - if (rc < 0) - return rc; + /* FR/HR/EFR DTXd output stage */ + if (lchan->tch_mode == GSM48_CMODE_SPEECH_V1 || + lchan->tch_mode == GSM48_CMODE_SPEECH_EFR) { + fr_hr_efr_dtxd_output(lchan, fn, is_fr_hr_efr_sid, &resp_msg, + &resp_l1sap, &empty_l1sap); } memset(resp_l1sap, 0, sizeof(*resp_l1sap)); @@ -996,48 +1568,67 @@ static int l1sap_tch_rts_ind(struct gsm_bts_trx *trx, resp_l1sap->u.tch.fn = fn; resp_l1sap->u.tch.marker = marker; - DEBUGPGT(DL1P, &g_time, "Tx TCH.req chan_nr=%s\n", rsl_chan_nr_str(chan_nr)); + LOGPLCGT(lchan, &g_time, DL1P, LOGL_DEBUG, "Tx TCH.req\n"); l1sap_down(trx, resp_l1sap); return 0; } +/* Reset link timeout to current value. */ +void radio_link_timeout_reset(struct gsm_lchan *lchan) +{ + struct gsm_bts *bts = lchan->ts->trx->bts; + + lchan->s = bts->radio_link_timeout.current; +} + /* process radio link timeout counter S. Follows TS 05.08 Section 5.2 * "MS Procedure" as the "BSS Procedure [...] shall be determined by the * network operator." */ -static void radio_link_timeout(struct gsm_lchan *lchan, int bad_frame) +static void radio_link_timeout(struct gsm_lchan *lchan, bool bad_frame) { struct gsm_bts *bts = lchan->ts->trx->bts; + /* Bypass radio link timeout on VGCS/VBS channels: There is no + * uplink SACCH on these when talker is not active. */ + if (rsl_chan_rt_is_asci(lchan->rsl_chan_rt) && lchan->asci.talker_active != VGCS_TALKER_ACTIVE) + return; + /* Bypass radio link timeout if set to -1 */ - if (bts->radio_link_timeout < 0) + if (bts->radio_link_timeout.current < 0) return; /* if link loss criterion already reached */ if (lchan->s == 0) { - DEBUGP(DMEAS, "%s radio link counter S already 0.\n", - gsm_lchan_name(lchan)); + LOGPLCHAN(lchan, DMEAS, LOGL_DEBUG, + "radio link timeout counter S is already 0\n"); return; } if (bad_frame) { - /* count down radio link counter S */ - lchan->s--; - DEBUGP(DMEAS, "%s counting down radio link counter S=%d\n", - gsm_lchan_name(lchan), lchan->s); - if (lchan->s == 0) + LOGPLCHAN(lchan, DMEAS, LOGL_DEBUG, + "decreasing radio link timeout counter S=%d -> %d\n", + lchan->s, lchan->s - 1); + lchan->s--; /* count down radio link counter S */ + if (lchan->s == 0) { + LOGPLCHAN(lchan, DMEAS, LOGL_NOTICE, + "radio link timeout counter S reached zero, " + "dropping connection\n"); rsl_tx_conn_fail(lchan, RSL_ERR_RADIO_LINK_FAIL); + } return; } - if (lchan->s < bts->radio_link_timeout) { + if (lchan->s < bts->radio_link_timeout.current) { /* count up radio link counter S */ - lchan->s += 2; - if (lchan->s > bts->radio_link_timeout) - lchan->s = bts->radio_link_timeout; - DEBUGP(DMEAS, "%s counting up radio link counter S=%d\n", - gsm_lchan_name(lchan), lchan->s); + int s = lchan->s + 2; + if (s > bts->radio_link_timeout.current) + s = bts->radio_link_timeout.current; + LOGPLCHAN(lchan, DMEAS, LOGL_DEBUG, + "increasing radio link timeout counter S=%d -> %d\n", + lchan->s, s); + lchan->s = s; } } @@ -1074,6 +1665,59 @@ int bts_check_for_first_ciphrd(struct gsm_lchan *lchan, return check_for_first_ciphrd(lchan, data, len); } +/* Decide if repeated UL-SACCH should be applied or not. If the BER level, of + * the received SACCH blocks rises above a certain threshold UL-SACCH + * repetition is enabled */ +static void repeated_ul_sacch_active_decision(struct gsm_lchan *lchan, + uint16_t ber10k) +{ + uint16_t upper = 0; + uint16_t lower = 0; + bool prev_repeated_ul_sacch_active = lchan->rep_acch.ul_sacch_active; + + /* This is an optimization so that we exit as quickly as possible if + * there are no uplink SACCH repetition capabilities present. + * However If the repeated UL-SACCH capabilities vanish for whatever + * reason, we must be sure that UL-SACCH repetition is disabled. */ + if (!lchan->rep_acch_cap.ul_sacch) { + lchan->rep_acch.ul_sacch_active = false; + goto out; + } + + /* Threshold disabled (repetition is always on) */ + if (lchan->rep_acch_cap.rxqual == 0) { + lchan->rep_acch.ul_sacch_active = true; + goto out; + } + + /* convert from RXQUAL value to ber10k value. + * see also GSM 05.08, section 8.2.4 (first table, without frame */ + static const uint16_t ber10k_by_rxqual_upper[] = + { 0, 20, 40, 80, 160, 320, 640, 1280 }; + static const uint16_t ber10k_by_rxqual_lower[] = + { 0, 0, 0, 20, 40, 80, 160, 320 }; + /* Note: The values in the upper vector are taken from the left side + * of the table in GSM 05.08, section 8.2.4. The lower vector is just + * the upper vector shifted by 2. */ + + upper = ber10k_by_rxqual_upper[lchan->rep_acch_cap.rxqual]; + lower = ber10k_by_rxqual_lower[lchan->rep_acch_cap.rxqual]; + + /* If upper/rxqual == 0, then repeated UL-SACCH is always on */ + if (ber10k >= upper) + lchan->rep_acch.ul_sacch_active = true; + else if (ber10k <= lower) + lchan->rep_acch.ul_sacch_active = false; + +out: + if (lchan->rep_acch.ul_sacch_active == prev_repeated_ul_sacch_active) + return; + if (lchan->rep_acch.ul_sacch_active) + LOGPLCHAN(lchan, DL1P, LOGL_DEBUG, "UL-SACCH repetition: inactive => active\n"); + else + LOGPLCHAN(lchan, DL1P, LOGL_DEBUG, "UL-SACCH repetition: active => inactive\n"); +} + /* DATA received from bts model */ static int l1sap_ph_data_ind(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap, struct ph_data_param *data_ind) @@ -1087,10 +1731,8 @@ static int l1sap_ph_data_ind(struct gsm_bts_trx *trx, uint8_t chan_nr, link_id; uint8_t tn; uint32_t fn; - int8_t rssi; enum osmo_ph_pres_info_type pr_info = data_ind->pdch_presence_info; - rssi = data_ind->rssi; chan_nr = data_ind->chan_nr; link_id = data_ind->link_id; fn = data_ind->fn; @@ -1105,36 +1747,33 @@ static int l1sap_ph_data_ind(struct gsm_bts_trx *trx, lchan = get_lchan_by_chan_nr(trx, chan_nr); if (!lchan) LOGPGT(DL1P, LOGL_ERROR, &g_time, "No lchan for chan_nr=%s\n", rsl_chan_nr_str(chan_nr)); - if (lchan && lchan->loopback && !L1SAP_IS_PTCCH(fn)) { + if (lchan && lchan->loopback) { /* we are in loopback mode (for BER testing) * mode and need to enqeue the frame to be * returned in downlink */ - queue_limit_to(gsm_lchan_name(lchan), &lchan->dl_tch_queue, 1); - msgb_enqueue(&lchan->dl_tch_queue, msg); + lchan_dl_tch_queue_enqueue(lchan, msg, 1); /* Return 1 to signal that we're still using msg * and it should not be freed */ return 1; } - /* don't send bad frames to PCU */ - if (len == 0) - return -EINVAL; + /* There can be no DATA.ind on PTCCH/U (rather RACH.ind instead), but some + * BTS models with buggy implementation may still be sending them to us. */ if (L1SAP_IS_PTCCH(fn)) { - pcu_tx_data_ind(&trx->ts[tn], PCU_IF_SAPI_PTCCH, fn, - 0 /* ARFCN */, L1SAP_FN2PTCCHBLOCK(fn), - data, len, rssi, data_ind->ber10k, - data_ind->ta_offs_256bits/64, - data_ind->lqual_cb); - } else { - /* drop incomplete UL block */ - if (pr_info != PRES_INFO_BOTH) - return 0; - /* PDTCH / PACCH frame handling */ - pcu_tx_data_ind(&trx->ts[tn], PCU_IF_SAPI_PDTCH, fn, 0 /* ARFCN */, - L1SAP_FN2MACBLOCK(fn), data, len, rssi, data_ind->ber10k, - data_ind->ta_offs_256bits/64, data_ind->lqual_cb); + LOGPGT(DL1P, LOGL_NOTICE, &g_time, "There can be no DATA.ind on PTCCH/U. " + "This is probably a bug of the BTS model you're using, please fix!\n"); + return -EINVAL; } + + /* Drop all data from incomplete UL block */ + if (pr_info != PRES_INFO_BOTH) + len = 0; + + /* PDTCH / PACCH frame handling */ + pcu_tx_data_ind(&trx->ts[tn], PCU_IF_SAPI_PDTCH, fn, trx->arfcn, + L1SAP_FN2MACBLOCK(fn), data, len, data_ind->rssi, data_ind->ber10k, + data_ind->ta_offs_256bits/64, data_ind->lqual_cb); return 0; } @@ -1144,10 +1783,37 @@ static int l1sap_ph_data_ind(struct gsm_bts_trx *trx, return 0; } + /* The ph_data_param contained in the l1sap primitive may contain + * measurement data. If this data is present, forward it for + * processing */ + if (bts_internal_flag_get(trx->bts, BTS_INTERNAL_FLAG_MEAS_PAYLOAD_COMB)) + process_l1sap_meas_data(lchan, l1sap, PRIM_PH_DATA); + + if (L1SAP_IS_LINK_SACCH(link_id)) { + repeated_ul_sacch_active_decision(lchan, data_ind->ber10k); + + /* Radio Link Timeout counter */ + if (len == 0) { + LOGPLCGT(lchan, &g_time, DL1P, LOGL_INFO, "Lost SACCH block\n"); + radio_link_timeout(lchan, true); + } else { + radio_link_timeout(lchan, false); + } + + /* Trigger the measurement reporting/processing logic */ + lchan_meas_handle_sacch(lchan, msg); + } + + if (L1SAP_IS_LINK_SACCH(link_id)) + le = &lchan->lapdm_ch.lapdm_acch; + else + le = &lchan->lapdm_ch.lapdm_dcch; + /* bad frame */ if (len == 0) { - if (L1SAP_IS_LINK_SACCH(link_id)) - radio_link_timeout(lchan, 1); + /* Notify current receive FN to lapdm. */ + lapdm_t200_fn(le, fn); + return -EINVAL; } @@ -1155,39 +1821,264 @@ static int l1sap_ph_data_ind(struct gsm_bts_trx *trx, if (lchan->ho.active == HANDOVER_WAIT_FRAME) handover_frame(lchan); - if (L1SAP_IS_LINK_SACCH(link_id)) { - radio_link_timeout(lchan, 0); - le = &lchan->lapdm_ch.lapdm_acch; - /* save the SACCH L1 header in the lchan struct for RSL MEAS RES */ - if (len < 2) { - LOGPGT(DL1P, LOGL_NOTICE, &g_time, "SACCH with size %u<2 !?!\n", len); - return -EINVAL; - } - /* Some brilliant engineer decided that the ordering of - * fields on the Um interface is different from the - * order of fields in RLS. See TS 04.04 (Chapter 7.2) - * vs. TS 08.58 (Chapter 9.3.10). */ - lchan->meas.l1_info[0] = data[0] << 3; - lchan->meas.l1_info[0] |= ((data[0] >> 5) & 1) << 2; - lchan->meas.l1_info[1] = data[1]; - lchan->meas.flags |= LC_UL_M_F_L1_VALID; - - lchan_ms_pwr_ctrl(lchan, data[0] & 0x1f, data_ind->rssi); - } else - le = &lchan->lapdm_ch.lapdm_dcch; + if (rsl_chan_rt_is_asci(lchan->rsl_chan_rt)) { + /* report first valid received frame to VGCS talker process */ + if (lchan->asci.talker_active == VGCS_TALKER_WAIT_FRAME) + vgcs_talker_frame(lchan); + /* Do not forward any message that is received on the uplink to LAPD while + * the uplink is not active. If the MS did not recognize (fast enough) that + * the uplink is free, it may continue to transmit LAPD messages. A + * response by LAPD to these messages is not desired and not required. If + * LAPD would respond, it would cause stopping transmission of UPLINK FREE + * messages. No MS could access the uplink anymore. + */ + if (lchan->asci.talker_active != VGCS_TALKER_ACTIVE) + return 0; + } if (check_for_first_ciphrd(lchan, data, len)) l1sap_tx_ciph_req(lchan->ts->trx, chan_nr, 1, 0); /* SDCCH, SACCH and FACCH all go to LAPDm */ - msgb_pull(msg, (msg->l2h - msg->data)); - msg->l1h = NULL; + msgb_pull_to_l2(msg); lapdm_phsap_up(&l1sap->oph, le); + /* Notify current receive FN to lapdm. */ + lapdm_t200_fn(le, fn); + /* don't free, because we forwarded data */ return 1; } +/* process one MAC block of unpacked bits of a non-transparent CSD channel */ +static void gsmtap_csd_rlp_process(struct gsm_lchan *lchan, bool is_uplink, + const struct ph_tch_param *tch_ind, + const uint8_t *data, unsigned int data_len) +{ + struct gsm_bts_trx *trx = lchan->ts->trx; + struct gsmtap_inst *inst = trx->bts->gsmtap.inst; + struct osmo_rlp_frame_decoded rlpf; + pbit_t *rlp_buf; + uint16_t arfcn; + int byte_len; + + if (!inst || !trx->bts->gsmtap.rlp) + return; + + if (lchan->csd_mode != LCHAN_CSD_M_NT) + return; + + if (is_uplink) + rlp_buf = lchan->tch.csd.rlp_buf_ul; + else + rlp_buf = lchan->tch.csd.rlp_buf_dl; + + /* TCH/F 9.6: 4x60bit block => 240bit RLP frame + * TCH/F 4.8: 2x 2x60bit blocks starting at B0/B2/B4 => 240bit RLP frame + * TCH/H 4.8: 4x60bit block => 240bit RLP frame + * TCH/F 2.4: 2x36bit blocks => transparent only + * TCH/H 2.4: 4x36bit blocks => transparent only + * TCH/F 14.4: 2x 290 bit block (starting with M1=0) => 576-bit RLP frame + */ + + if (lchan->type == GSM_LCHAN_TCH_F && lchan->tch_mode == GSM48_CMODE_DATA_6k0) { + /* in this mode we have 120bit MAC blocks; two of them need to be concatenated + * to render a 240-bit RLP frame. The fist block is present in B0/B2/B4. + * The E7 bit is used to indicate the Frame MF0a */ + OSMO_ASSERT(data_len == 120); + ubit_t e7 = data[4*7+3]; + if (e7 == 0) { + osmo_ubit2pbit_ext(rlp_buf, 0, data, 0, data_len, 1); + return; + } else { + osmo_ubit2pbit_ext(rlp_buf, 120, data, 0, data_len, 1); + byte_len = 240/8; + } + } else if (lchan->type == GSM_LCHAN_TCH_F && lchan->tch_mode == GSM48_CMODE_DATA_14k5) { + /* in this mode we have 290bit MAC blocks containing M1, M2 and 288 data bits; + * two of them need to be concatenated to render a + * 576-bit RLP frame. The start of a RLP frame is + * denoted by a block with M1-bit set to 0. */ + OSMO_ASSERT(data_len == 290); + ubit_t m1 = data[0]; + if (m1 == 0) { + osmo_ubit2pbit_ext(rlp_buf, 0, data, 2, data_len, 1); + return; + } else { + osmo_ubit2pbit_ext(rlp_buf, 288, data, 2, data_len, 1); + byte_len = 576/8; + } + } else { + byte_len = osmo_ubit2pbit_ext(rlp_buf, 0, data, 0, data_len, 1); + } + + if (trx->bts->gsmtap.rlp_skip_null) { + int rc = osmo_rlp_decode(&rlpf, 0, rlp_buf, byte_len); + if (rc == 0 && rlpf.ftype == OSMO_RLP_FT_U && rlpf.u_ftype == OSMO_RLP_U_FT_NULL) + return; + } + + arfcn = trx->arfcn; + if (is_uplink) + arfcn |= GSMTAP_ARFCN_F_UPLINK; + + gsmtap_send_ex(inst, GSMTAP_TYPE_GSM_RLP, arfcn, lchan->ts->nr, + lchan->type == GSM_LCHAN_TCH_H ? GSMTAP_CHANNEL_VOICE_H : GSMTAP_CHANNEL_VOICE_F, + lchan->nr, tch_ind->fn, tch_ind->rssi, 0, rlp_buf, byte_len); + +} + +static void send_ul_rtp_packet_data(struct gsm_lchan *lchan, const struct ph_tch_param *tch_ind, + const uint8_t *data, uint16_t data_len) +{ + struct gsm_bts *bts = lchan->ts->trx->bts; + uint8_t rtp_pl[RFC4040_RTP_PLEN]; + int rc; + + gsmtap_csd_rlp_process(lchan, true, tch_ind, data, data_len); + + rc = csd_v110_rtp_encode(lchan, &rtp_pl[0], data, data_len); + if (rc < 0) + return; + + rate_ctr_inc2(bts->ctrs, BTS_CTR_RTP_TX_TOTAL); + if (lchan->rtp_tx_marker) + rate_ctr_inc2(bts->ctrs, BTS_CTR_RTP_TX_MARKER); + + osmo_rtp_send_frame_ext(lchan->abis_ip.rtp_socket, + &rtp_pl[0], sizeof(rtp_pl), + fn_ms_adj(tch_ind->fn, lchan), + lchan->rtp_tx_marker); + /* Only clear the marker bit once we have sent a RTP packet with it */ + lchan->rtp_tx_marker = false; +} + +/* a helper function for the logic in l1sap_tch_ind() */ +static void send_ul_rtp_packet_speech(struct gsm_lchan *lchan, uint32_t fn, + const uint8_t *rtp_pl, uint16_t rtp_pl_len) +{ + struct gsm_bts *bts = lchan->ts->trx->bts; + + if (lchan->abis_ip.osmux.use) { + lchan_osmux_send_frame(lchan, rtp_pl, rtp_pl_len, + fn_ms_adj(fn, lchan), lchan->rtp_tx_marker); + } else if (lchan->abis_ip.rtp_socket) { + osmo_rtp_send_frame_ext(lchan->abis_ip.rtp_socket, + rtp_pl, rtp_pl_len, fn_ms_adj(fn, lchan), lchan->rtp_tx_marker); + rate_ctr_inc2(bts->ctrs, BTS_CTR_RTP_TX_TOTAL); + if (lchan->rtp_tx_marker) + rate_ctr_inc2(bts->ctrs, BTS_CTR_RTP_TX_MARKER); + } + /* Only clear the marker bit once we have sent a RTP packet with it */ + lchan->rtp_tx_marker = false; +} + +/* a helper function for emitting HR1 UL in RFC 5993 format */ +static void send_rtp_rfc5993(struct gsm_lchan *lchan, uint32_t fn, + struct msgb *msg) +{ + uint8_t toc; + + OSMO_ASSERT(msg->len == GSM_HR_BYTES); + /* FIXME: implement proper SID classification per GSM 06.41 section + * 6.1.1; see OS#6036. Until then, detect error-free SID frames + * using our existing osmo_hr_check_sid() function. */ + if (osmo_hr_check_sid(msg->data, msg->len)) + toc = 0x20; + else + toc = 0x00; + msgb_push_u8(msg, toc); + send_ul_rtp_packet_speech(lchan, fn, msg->data, msg->len); +} + +/* a helper function for emitting FR/EFR UL in TW-TS-001 format */ +static void send_rtp_twts001(struct gsm_lchan *lchan, uint32_t fn, + struct msgb *msg, bool good_frame) +{ + uint8_t teh; + bool send_frame; + + if (msg->len == GSM_FR_BYTES || msg->len == GSM_EFR_BYTES) { + if (good_frame) + teh = 0xE0; + else + teh = 0xE2; + send_frame = true; + } else { + teh = 0xE6; + send_frame = false; + } + /* always set DTXd and TAF bits */ + if (lchan->ts->trx->bts->dtxd) + teh |= 0x08; + if (fn % 104 == 52) + teh |= 0x01; + if (send_frame) { + msgb_push_u8(msg, teh); + send_ul_rtp_packet_speech(lchan, fn, msg->data, msg->len); + } else { + send_ul_rtp_packet_speech(lchan, fn, &teh, 1); + } +} + +/* A helper function for l1sap_tch_ind(): handling BFI + * + * Please note that the msgb passed to this function is used only when + * the CN asked the BSS to emit extended RTP formats (currently TW-TS-001, + * later TW-TS-002 as well) that can indicate BFI along with deemed-bad + * frame data bits, just like GSM 08.60 and 08.61 TRAU-UL frames. + */ +static void tch_ul_bfi_handler(struct gsm_lchan *lchan, + const struct gsm_time *g_time, struct msgb *msg) +{ + uint32_t fn = g_time->fn; + uint8_t ecu_out[GSM_FR_BYTES]; + int rc; + + /* Are we on TCH/FS or TCH/EFS, configured to emit TW-TS-001 extended + * RTP format? If so, emit BFI per that spec. The placement of + * this check before the ECU is intentional: the modes of TW-TS-001 + * UL output (closely replicating the classic GSM architecture in which + * a BTS never applies an ECU to its UL output) and internal UL ECU + * are mutually exclusive. */ + if ((lchan->abis_ip.rtp_extensions & OSMO_RTP_EXT_TWTS001) && + lchan->type == GSM_LCHAN_TCH_F && + (lchan->tch_mode == GSM48_CMODE_SPEECH_V1 || + lchan->tch_mode == GSM48_CMODE_SPEECH_EFR)) { + send_rtp_twts001(lchan, fn, msg, false); + return; + } + + /* Are we applying an ECU to this uplink, and are we in a state + * (not DTX pause) where we emit ECU output? */ + if (lchan->ecu_state && !osmo_ecu_is_dtx_pause(lchan->ecu_state)) { + rc = osmo_ecu_frame_out(lchan->ecu_state, ecu_out); + /* did it actually give us some output? */ + if (rc > 0) { + /* yes, send it out in RTP */ + send_ul_rtp_packet_speech(lchan, fn, ecu_out, rc); + return; + } + } + + /* Are we in rtp continuous-streaming special mode? If so, send out + * a BFI packet as zero-length RTP payload. */ + if (lchan->ts->trx->bts->rtp_nogaps_mode) { + send_ul_rtp_packet_speech(lchan, fn, NULL, 0); + return; + } + + /* Most classic form of BFI handling: generate an intentional gap + * in the outgoing RTP stream. */ + LOGPLCGT(lchan, g_time, DRTP, LOGL_DEBUG, + "Skipping RTP frame with lost payload\n"); + if (lchan->abis_ip.osmux.use) + lchan_osmux_skipped_frame(lchan, fn_ms_adj(fn, lchan)); + else if (lchan->abis_ip.rtp_socket) + osmo_rtp_skipped_frame(lchan->abis_ip.rtp_socket, fn_ms_adj(fn, lchan)); + lchan->rtp_tx_marker = true; +} + /* TCH received from bts model */ static int l1sap_tch_ind(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap, struct ph_tch_param *tch_ind) @@ -1204,42 +2095,78 @@ static int l1sap_tch_ind(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap, gsm_fn2gsmtime(&g_time, fn); - LOGPGT(DL1P, LOGL_INFO, &g_time, "Rx TCH.ind chan_nr=%s\n", rsl_chan_nr_str(chan_nr)); - lchan = get_active_lchan_by_chan_nr(trx, chan_nr); if (!lchan) { LOGPGT(DL1P, LOGL_ERROR, &g_time, "No lchan for TCH.ind (chan_nr=%s)\n", rsl_chan_nr_str(chan_nr)); return 0; + } else { + LOGPLCGT(lchan, &g_time, DL1P, LOGL_DEBUG, "Rx TCH.ind\n"); } - msgb_pull(msg, sizeof(*l1sap)); + /* Notify current receive FN to lapdm. + * TCH frames may be indicated before FACCH frames are indicated. To prevent T200 timeout before FACCH is + * received, subtract one frame number, so that timeout is processed next time after FACCH is received. + */ + lapdm_t200_fn(&lchan->lapdm_ch.lapdm_dcch, GSM_TDMA_FN_SUB(fn, 1)); + + /* The ph_tch_param contained in the l1sap primitive may contain + * measurement data. If this data is present, forward it for + * processing */ + if (bts_internal_flag_get(trx->bts, BTS_INTERNAL_FLAG_MEAS_PAYLOAD_COMB)) + process_l1sap_meas_data(lchan, l1sap, PRIM_TCH); + + msgb_pull_to_l2(msg); /* Low level layers always call us when TCH content is expected, even if * the content is not available due to decoding issues. Content not * available is expected as empty payload. We also check if quality is * good enough. */ if (msg->len && tch_ind->lqual_cb >= bts->min_qual_norm) { + /* feed the good frame to the ECU, if we are applying one */ + if (lchan->ecu_state) + osmo_ecu_frame_in(lchan->ecu_state, false, msg->data, msg->len); /* hand msg to RTP code for transmission */ - if (lchan->abis_ip.rtp_socket) - osmo_rtp_send_frame_ext(lchan->abis_ip.rtp_socket, - msg->data, msg->len, fn_ms_adj(fn, lchan), lchan->rtp_tx_marker); + switch (lchan->rsl_cmode) { + case RSL_CMOD_SPD_SPEECH: + /* support different RTP output formats per codec */ + if (lchan->type == GSM_LCHAN_TCH_F && + (lchan->tch_mode == GSM48_CMODE_SPEECH_V1 || + lchan->tch_mode == GSM48_CMODE_SPEECH_EFR)) { + /* FR and EFR codecs */ + if (lchan->abis_ip.rtp_extensions & OSMO_RTP_EXT_TWTS001) + send_rtp_twts001(lchan, fn, msg, true); + else + send_ul_rtp_packet_speech(lchan, fn, msg->data, msg->len); + } else if (lchan->type == GSM_LCHAN_TCH_H && + lchan->tch_mode == GSM48_CMODE_SPEECH_V1) { + /* HR codec: TS 101 318 or RFC 5993, + * will also support TW-TS-002 in the future. */ + if (bts->emit_hr_rfc5993) + send_rtp_rfc5993(lchan, fn, msg); + else + send_ul_rtp_packet_speech(lchan, fn, msg->data, msg->len); + } else { + /* generic case, no RTP alterations */ + send_ul_rtp_packet_speech(lchan, fn, msg->data, msg->len); + } + break; + case RSL_CMOD_SPD_DATA: + send_ul_rtp_packet_data(lchan, tch_ind, msg->data, msg->len); + break; + case RSL_CMOD_SPD_SIGN: + return 0; /* drop stale TCH.ind */ + default: /* shall not happen */ + OSMO_ASSERT(0); + } /* if loopback is enabled, also queue received RTP data */ if (lchan->loopback) { - /* make sure the queue doesn't get too long */ - queue_limit_to(gsm_lchan_name(lchan), &lchan->dl_tch_queue, 1); - /* add new frame to queue */ - msgb_enqueue(&lchan->dl_tch_queue, msg); + /* add new frame to queue, make sure the queue doesn't get too long */ + lchan_dl_tch_queue_enqueue(lchan, msg, 1); /* Return 1 to signal that we're still using msg and it should not be freed */ return 1; } - /* Only clear the marker bit once we have sent a RTP packet with it */ - lchan->rtp_tx_marker = false; } else { - DEBUGPGT(DRTP, &g_time, "Skipping RTP frame with lost payload (chan_nr=0x%02x)\n", - chan_nr); - if (lchan->abis_ip.rtp_socket) - osmo_rtp_skipped_frame(lchan->abis_ip.rtp_socket, fn_ms_adj(fn, lchan)); - lchan->rtp_tx_marker = true; + tch_ul_bfi_handler(lchan, &g_time, msg); } lchan->tch.last_fn = fn; @@ -1248,14 +2175,15 @@ static int l1sap_tch_ind(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap, #define RACH_MIN_TOA256 -2 * 256 -static bool rach_pass_filter(struct ph_rach_ind_param *rach_ind, struct gsm_bts *bts) +static bool rach_pass_filter(struct ph_rach_ind_param *rach_ind, struct gsm_bts *bts, + const char *chan_name) { int16_t toa256 = rach_ind->acc_delay_256bits; /* Check for RACH exceeding BER threshold (ghost RACH) */ if (rach_ind->ber10k > bts->max_ber10k_rach) { - LOGPFN(DL1C, LOGL_INFO, rach_ind->fn, "Ignoring RACH request: " - "BER10k(%u) > BER10k_MAX(%u)\n", + LOGPFN(DL1C, LOGL_DEBUG, rach_ind->fn, "Ignoring an Access Burst on %s: " + "BER10k(%u) > BER10k_MAX(%u)\n", chan_name, rach_ind->ber10k, bts->max_ber10k_rach); return false; } @@ -1266,16 +2194,16 @@ static bool rach_pass_filter(struct ph_rach_ind_param *rach_ind, struct gsm_bts * according to maximal allowed Timing Advance value. */ if (toa256 < RACH_MIN_TOA256 || toa256 > bts->max_ta * 256) { - LOGPFN(DL1C, LOGL_INFO, rach_ind->fn, "Ignoring RACH request: " - "ToA(%d) exceeds the allowed range (%d..%d)\n", + LOGPFN(DL1C, LOGL_DEBUG, rach_ind->fn, "Ignoring an Access Burst on %s: " + "ToA(%d) exceeds the allowed range (%d..%d)\n", chan_name, toa256, RACH_MIN_TOA256, bts->max_ta * 256); return false; } /* Link quality defined by C/I (Carrier-to-Interference ratio) */ if (rach_ind->lqual_cb < bts->min_qual_rach) { - LOGPFN(DL1C, LOGL_INFO, rach_ind->fn, "Ignoring RACH request: " - "link quality (%d) below the minimum (%d)\n", + LOGPFN(DL1C, LOGL_DEBUG, rach_ind->fn, "Ignoring an Access Burst on %s: " + "link quality (%d) below the minimum (%d)\n", chan_name, rach_ind->lqual_cb, bts->min_qual_rach); return false; } @@ -1283,23 +2211,58 @@ static bool rach_pass_filter(struct ph_rach_ind_param *rach_ind, struct gsm_bts return true; } -/* Special case where handover RACH is detected */ -static int l1sap_handover_rach(struct gsm_bts_trx *trx, - struct osmo_phsap_prim *l1sap, struct ph_rach_ind_param *rach_ind) +/* Special case where RACH on DCCH uplink is detected */ +static int l1sap_dcch_rach(struct gsm_bts_trx *trx, struct ph_rach_ind_param *rach_ind) { + struct gsm_lchan *lchan; + /* Filter out noise / interference / ghosts */ - if (!rach_pass_filter(rach_ind, trx->bts)) { + if (!rach_pass_filter(rach_ind, trx->bts, "DCCH")) { rate_ctr_inc2(trx->bts->ctrs, BTS_CTR_RACH_DROP); return 0; } - handover_rach(get_lchan_by_chan_nr(trx, rach_ind->chan_nr), - rach_ind->ra, rach_ind->acc_delay); + lchan = get_lchan_by_chan_nr(trx, rach_ind->chan_nr); + /* Differentiate + dispatch hand-over and VGCS RACH */ + if (rsl_chan_rt_is_asci(lchan->rsl_chan_rt)) { + rate_ctr_inc2(trx->bts->ctrs, BTS_CTR_RACH_VGCS); + vgcs_rach(lchan, rach_ind->ra, rach_ind->acc_delay, rach_ind->fn); + } else { + rate_ctr_inc2(trx->bts->ctrs, BTS_CTR_RACH_HO); + handover_rach(lchan, rach_ind->ra, rach_ind->acc_delay); + } /* must return 0, so in case of msg at l1sap, it will be freed */ return 0; } +/* Special case for Access Bursts on PDTCH/U or PTCCH/U */ +static int l1sap_pdch_rach(struct gsm_bts_trx *trx, struct ph_rach_ind_param *rach_ind) +{ + /* Filter out noise / interference / ghosts */ + if (!rach_pass_filter(rach_ind, trx->bts, "PDCH")) + return -EAGAIN; + + /* PTCCH/U (Packet Timing Advance Control Channel) */ + if (L1SAP_IS_PTCCH(rach_ind->fn)) { + LOGPFN(DL1P, LOGL_DEBUG, rach_ind->fn, + /* TODO: calculate and print Timing Advance Index */ + "Access Burst for continuous Timing Advance control (toa256=%d)\n", + rach_ind->acc_delay_256bits); + + /* QTA: Timing Advance in units of 1/4 of a symbol */ + pcu_tx_rach_ind(trx->bts->nr, trx->nr, rach_ind->chan_nr & 0x07, + rach_ind->acc_delay_256bits >> 6, + rach_ind->ra, rach_ind->fn, rach_ind->is_11bit, + rach_ind->burst_type, PCU_IF_SAPI_PTCCH); + return 0; + } else { /* The MS may acknowledge DL data by 4 consequent Access Bursts */ + LOGPFN(DL1P, LOGL_NOTICE, rach_ind->fn, + "Access Bursts on PDTCH/U are not (yet) supported\n"); + return -ENOTSUP; + } +} + /* RACH received from bts model */ static int l1sap_ph_rach_ind(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap, struct ph_rach_ind_param *rach_ind) @@ -1309,10 +2272,16 @@ static int l1sap_ph_rach_ind(struct gsm_bts_trx *trx, DEBUGPFN(DL1P, rach_ind->fn, "Rx PH-RA.ind\n"); - /* check for handover access burst on dedicated channels */ - if (!L1SAP_IS_CHAN_RACH(rach_ind->chan_nr)) { - rate_ctr_inc2(trx->bts->ctrs, BTS_CTR_RACH_HO); - return l1sap_handover_rach(trx, l1sap, rach_ind); + /* Check the origin of an Access Burst */ + switch (rach_ind->chan_nr & 0xf8) { + case RSL_CHAN_RACH: + /* CS or PS RACH, to be handled in this function */ + break; + case RSL_CHAN_OSMO_PDCH: + /* TODO: do we need to count Access Bursts on PDCH? */ + return l1sap_pdch_rach(trx, rach_ind); + default: + return l1sap_dcch_rach(trx, rach_ind); } rate_ctr_inc2(trx->bts->ctrs, BTS_CTR_RACH_RCVD); @@ -1322,7 +2291,7 @@ static int l1sap_ph_rach_ind(struct gsm_bts_trx *trx, bts->load.rach.busy++; /* Filter out noise / interference / ghosts */ - if (!rach_pass_filter(rach_ind, bts)) { + if (!rach_pass_filter(rach_ind, bts, "CCCH")) { rate_ctr_inc2(trx->bts->ctrs, BTS_CTR_RACH_DROP); return 0; } @@ -1344,9 +2313,11 @@ static int l1sap_ph_rach_ind(struct gsm_bts_trx *trx, LOGPFN(DL1P, LOGL_INFO, rach_ind->fn, "RACH for packet access (toa=%d, ra=%d)\n", rach_ind->acc_delay, rach_ind->ra); - pcu_tx_rach_ind(bts, rach_ind->acc_delay << 2, - rach_ind->ra, rach_ind->fn, - rach_ind->is_11bit, rach_ind->burst_type); + /* QTA: Timing Advance in units of 1/4 of a symbol */ + pcu_tx_rach_ind(bts->nr, trx->nr, rach_ind->chan_nr & 0x07, + rach_ind->acc_delay_256bits >> 6, + rach_ind->ra, rach_ind->fn, rach_ind->is_11bit, + rach_ind->burst_type, PCU_IF_SAPI_RACH); return 0; } @@ -1412,6 +2383,9 @@ int l1sap_up(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap) /* any L1 prim sent to bts model */ static int l1sap_down(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap) { + l1sap_log_ctx_sapi = get_common_sapi_by_trx_prim(trx, l1sap); + log_set_context(LOG_CTX_L1_SAPI, &l1sap_log_ctx_sapi); + if (OSMO_PRIM_HDR(&l1sap->oph) == OSMO_PRIM(PRIM_PH_DATA, PRIM_OP_REQUEST)) to_gsmtap(trx, l1sap); @@ -1440,8 +2414,12 @@ int l1sap_pdch_req(struct gsm_bts_trx_ts *ts, int is_ptcch, uint32_t fn, l1sap->u.data.chan_nr = RSL_CHAN_OSMO_PDCH | ts->nr; l1sap->u.data.link_id = 0x00; l1sap->u.data.fn = fn; - msg->l2h = msgb_put(msg, len); - memcpy(msg->l2h, data, len); + if (len) { + msg->l2h = msgb_put(msg, len); + memcpy(msg->l2h, data, len); + } else { + msg->l2h = NULL; /* Idle block */ + } return l1sap_down(ts->trx, l1sap); } @@ -1452,19 +2430,59 @@ void l1sap_rtp_rx_cb(struct osmo_rtp_socket *rs, const uint8_t *rtp_pl, uint32_t timestamp, bool marker) { struct gsm_lchan *lchan = rs->priv; + struct gsm_bts *bts = lchan->ts->trx->bts; struct msgb *msg; - struct osmo_phsap_prim *l1sap; + bool rfc5993_sid = false; + + rate_ctr_inc2(bts->ctrs, BTS_CTR_RTP_RX_TOTAL); + if (marker) + rate_ctr_inc2(bts->ctrs, BTS_CTR_RTP_RX_MARKER); /* if we're in loopback mode, we don't accept frames from the * RTP socket anymore */ - if (lchan->loopback) + if (lchan->loopback) { + rate_ctr_inc2(bts->ctrs, BTS_CTR_RTP_RX_DROP_LOOPBACK); return; + } - msg = l1sap_msgb_alloc(rtp_pl_len); + /* initial preen */ + switch (rtp_payload_input_preen(lchan, rtp_pl, rtp_pl_len, &rfc5993_sid)) { + case PL_DECISION_DROP: + rate_ctr_inc2(bts->ctrs, BTS_CTR_RTP_RX_DROP_PREEN); + return; + case PL_DECISION_ACCEPT: + break; + case PL_DECISION_STRIP_HDR_OCTET: + rtp_pl++; + rtp_pl_len--; + break; + default: + OSMO_ASSERT(0); + } + + msg = l1sap_msgb_alloc(512); if (!msg) return; - memcpy(msgb_put(msg, rtp_pl_len), rtp_pl, rtp_pl_len); - msgb_pull(msg, sizeof(*l1sap)); + + if (lchan->rsl_cmode == RSL_CMOD_SPD_DATA) { + int rc = csd_v110_rtp_decode(lchan, msg->tail, + rtp_pl, rtp_pl_len); + if (rc > 0) { + /* 'fake' tch_ind containing all-zero so gsmtap code can be shared + * between UL and DL */ + static const struct ph_tch_param fake_tch_ind = {}; + gsmtap_csd_rlp_process(lchan, false, &fake_tch_ind, msg->tail, rc); + msgb_put(msg, rc); + } else { + rate_ctr_inc2(bts->ctrs, BTS_CTR_RTP_RX_DROP_V110_DEC); + msgb_free(msg); + return; + } + } else { + memcpy(msgb_put(msg, rtp_pl_len), rtp_pl, rtp_pl_len); + } + + msgb_pull(msg, sizeof(struct osmo_phsap_prim)); /* Store RTP header Marker bit in control buffer */ rtpmsg_marker_bit(msg) = marker; @@ -1472,11 +2490,11 @@ void l1sap_rtp_rx_cb(struct osmo_rtp_socket *rs, const uint8_t *rtp_pl, rtpmsg_seq(msg) = seq_number; /* Store RTP header Timestamp in control buffer */ rtpmsg_ts(msg) = timestamp; + /* Store RFC 5993 SID flag likewise */ + rtpmsg_is_rfc5993_sid(msg) = rfc5993_sid; /* make sure the queue doesn't get too long */ - queue_limit_to(gsm_lchan_name(lchan), &lchan->dl_tch_queue, 1); - - msgb_enqueue(&lchan->dl_tch_queue, msg); + lchan_dl_tch_queue_enqueue(lchan, msg, 1); } static int l1sap_chan_act_dact_modify(struct gsm_bts_trx *trx, uint8_t chan_nr, @@ -1494,53 +2512,50 @@ static int l1sap_chan_act_dact_modify(struct gsm_bts_trx *trx, uint8_t chan_nr, return l1sap_down(trx, &l1sap); } -int l1sap_chan_act(struct gsm_bts_trx *trx, uint8_t chan_nr, struct tlv_parsed *tp) +int l1sap_chan_act(struct gsm_bts_trx *trx, uint8_t chan_nr) { struct gsm_lchan *lchan = get_lchan_by_chan_nr(trx, chan_nr); - struct gsm48_chan_desc *cd; int rc; - LOGPLCHAN(lchan, DL1C, LOGL_INFO, "activating channel chan_nr=%s trx=%d\n", - rsl_chan_nr_str(chan_nr), trx->nr); - - /* osmo-pcu calls this without a valid 'tp' parameter, so we - * need to make sure ew don't crash here */ - if (tp && TLVP_PRESENT(tp, GSM48_IE_CHANDESC_2) && - TLVP_LEN(tp, GSM48_IE_CHANDESC_2) >= sizeof(*cd)) { - cd = (struct gsm48_chan_desc *) - TLVP_VAL(tp, GSM48_IE_CHANDESC_2); - - /* our L1 only supports one global TSC for all channels - * one one TRX, so we need to make sure not to activate - * channels with a different TSC!! */ - if (cd->h0.tsc != (lchan->ts->trx->bts->bsic & 7)) { - LOGPLCHAN(lchan, DL1C, LOGL_ERROR, "lchan TSC %u != BSIC-TSC %u\n", - cd->h0.tsc, lchan->ts->trx->bts->bsic & 7); - return -RSL_ERR_SERV_OPT_UNIMPL; - } + if (lchan->state == LCHAN_S_ACTIVE) { + LOGPLCHAN(lchan, DL1C, LOGL_ERROR, "Trying to activate already active channel %s\n", + rsl_chan_nr_str(chan_nr)); + return -1; } - lchan->sacch_deact = 0; - lchan->s = lchan->ts->trx->bts->radio_link_timeout; + LOGPLCHAN(lchan, DL1C, LOGL_INFO, "Activating channel %s\n", rsl_chan_nr_str(chan_nr)); + + radio_link_timeout_reset(lchan); rc = l1sap_chan_act_dact_modify(trx, chan_nr, PRIM_INFO_ACTIVATE, 0); if (rc) return -RSL_ERR_EQUIPMENT_FAIL; + /* Is it TCH? If it is, attempt to allocate an Error Concealment Unit + * instance, if available, unless it is disabled by vty config. */ + if (lchan_is_tch(lchan) && trx->bts->use_ul_ecu) + lchan->ecu_state = osmo_ecu_init(trx, lchan2ecu_codec(lchan)); + else + lchan->ecu_state = NULL; + /* Init DTX DL FSM if necessary */ - if (trx->bts->dtxd && lchan->type != GSM_LCHAN_SDCCH) { - char name[32]; - snprintf(name, sizeof(name), "bts%u-trx%u-ts%u-ss%u", lchan->ts->trx->bts->nr, - lchan->ts->trx->nr, lchan->ts->nr, lchan->nr); + if (trx->bts->dtxd && lchan_is_tch(lchan)) { lchan->tch.dtx.dl_amr_fsm = osmo_fsm_inst_alloc(&dtx_dl_amr_fsm, tall_bts_ctx, lchan, LOGL_DEBUG, - name); + NULL); if (!lchan->tch.dtx.dl_amr_fsm) { l1sap_chan_act_dact_modify(trx, chan_nr, PRIM_INFO_DEACTIVATE, 0); return -RSL_ERR_EQUIPMENT_FAIL; } + + rc = osmo_fsm_inst_update_id_f(lchan->tch.dtx.dl_amr_fsm, + "bts%u-trx%u-ts%u-ss%u%s", + trx->bts->nr, trx->nr, + lchan->ts->nr, lchan->nr, + lchan->ts->vamos.is_shadow ? "-shadow" : ""); + OSMO_ASSERT(rc == 0); } return 0; } @@ -1548,14 +2563,31 @@ int l1sap_chan_act(struct gsm_bts_trx *trx, uint8_t chan_nr, struct tlv_parsed * int l1sap_chan_rel(struct gsm_bts_trx *trx, uint8_t chan_nr) { struct gsm_lchan *lchan = get_lchan_by_chan_nr(trx, chan_nr); - LOGPLCHAN(lchan, DL1C, LOGL_INFO, "deactivating channel chan_nr=%s trx=%d\n", - rsl_chan_nr_str(chan_nr), trx->nr); + + if (lchan->state == LCHAN_S_NONE) { + LOGPLCHAN(lchan, DL1C, LOGL_ERROR, "Trying to deactivate already deactivated channel %s\n", + rsl_chan_nr_str(chan_nr)); + return -1; + } + + LOGPLCHAN(lchan, DL1C, LOGL_INFO, "Deactivating channel %s\n", + rsl_chan_nr_str(chan_nr)); if (lchan->tch.dtx.dl_amr_fsm) { osmo_fsm_inst_free(lchan->tch.dtx.dl_amr_fsm); lchan->tch.dtx.dl_amr_fsm = NULL; } + /* clear ECU state (if any) */ + if (lchan->ecu_state) { + osmo_ecu_destroy(lchan->ecu_state); + lchan->ecu_state = NULL; + } + + /* reset CSD RLP buffers to avoid any user plane data leaking from + * one previous lchan into a later one */ + memset(&lchan->tch.csd, 0, sizeof(lchan->tch.csd)); + return l1sap_chan_act_dact_modify(trx, chan_nr, PRIM_INFO_DEACTIVATE, 0); } @@ -1564,10 +2596,8 @@ int l1sap_chan_deact_sacch(struct gsm_bts_trx *trx, uint8_t chan_nr) { struct gsm_lchan *lchan = get_lchan_by_chan_nr(trx, chan_nr); - LOGPLCHAN(lchan, DL1C, LOGL_INFO, "deactivating sacch chan_nr=%s trx=%d\n", - rsl_chan_nr_str(chan_nr), trx->nr); - - lchan->sacch_deact = 1; + LOGPLCHAN(lchan, DL1C, LOGL_INFO, "Deactivating SACCH on channel %s\n", + rsl_chan_nr_str(chan_nr)); return l1sap_chan_act_dact_modify(trx, chan_nr, PRIM_INFO_DEACTIVATE, 1); @@ -1575,8 +2605,46 @@ int l1sap_chan_deact_sacch(struct gsm_bts_trx *trx, uint8_t chan_nr) int l1sap_chan_modify(struct gsm_bts_trx *trx, uint8_t chan_nr) { - LOGP(DL1C, LOGL_INFO, "modifying channel chan_nr=%s trx=%d\n", - rsl_chan_nr_str(chan_nr), trx->nr); + struct gsm_lchan *lchan = get_lchan_by_chan_nr(trx, chan_nr); + + LOGPLCHAN(lchan, DL1C, LOGL_INFO, "Modifying channel %s\n", + rsl_chan_nr_str(chan_nr)); + + /* Is it TCH? If it is and we are applying internal uplink ECUs, + * the new channel mode calls for a different ECU. Any changes + * in vty config (enabling or disabling this ECU application) + * will also take effect upon channel modification. */ + if (lchan_is_tch(lchan)) { + if (lchan->ecu_state) + osmo_ecu_destroy(lchan->ecu_state); + if (trx->bts->use_ul_ecu) + lchan->ecu_state = osmo_ecu_init(trx, lchan2ecu_codec(lchan)); + else + lchan->ecu_state = NULL; + } return l1sap_chan_act_dact_modify(trx, chan_nr, PRIM_INFO_MODIFY, 0); } + +int l1sap_uplink_access(struct gsm_lchan *lchan, bool active) +{ + uint8_t chan_nr = gsm_lchan2chan_nr(lchan); + struct osmo_phsap_prim l1sap; + + if (lchan->state != LCHAN_S_ACTIVE && !rsl_chan_rt_is_asci(lchan->rsl_chan_rt)) { + LOGPLCHAN(lchan, DL1C, LOGL_ERROR, "Channel %s is not an active ASCI type channel.\n", + rsl_chan_nr_str(chan_nr)); + return -EINVAL; + } + + LOGPLCHAN(lchan, DL1C, LOGL_INFO, "%s uplink access detection on channel %s\n", + (active) ? "Activating" : "Deactivating", rsl_chan_nr_str(chan_nr)); + + + memset(&l1sap, 0, sizeof(l1sap)); + osmo_prim_init(&l1sap.oph, SAP_GSM_PH, PRIM_MPH_INFO, PRIM_OP_REQUEST, NULL); + l1sap.u.info.type = (active) ? PRIM_INFO_ACT_UL_ACC : PRIM_INFO_DEACT_UL_ACC; + l1sap.u.info.u.ulacc_req.chan_nr = chan_nr; + + return l1sap_down(lchan->ts->trx, &l1sap); +} diff --git a/src/common/lchan.c b/src/common/lchan.c index 9e98166d..5b41a158 100644 --- a/src/common/lchan.c +++ b/src/common/lchan.c @@ -12,38 +12,654 @@ * 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. + * 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 "btsconfig.h" /* for PACKAGE_VERSION */ + #include <osmocom/core/logging.h> + +#include <osmocom/trau/osmo_ortp.h> + #include <osmo-bts/logging.h> -#include <osmo-bts/gsm_data.h> +#include <osmo-bts/lchan.h> +#include <osmo-bts/bts.h> +#include <osmo-bts/rsl.h> +#include <osmo-bts/pcu_if.h> +#include <osmo-bts/handover.h> +#include <osmo-bts/l1sap.h> +#include <osmo-bts/bts_model.h> +#include <osmo-bts/asci.h> +#include <errno.h> + +static const struct value_string lchan_s_names[] = { + { LCHAN_S_NONE, "NONE" }, + { LCHAN_S_ACT_REQ, "ACTIVATION REQUESTED" }, + { LCHAN_S_ACTIVE, "ACTIVE" }, + { LCHAN_S_REL_REQ, "RELEASE REQUESTED" }, + { LCHAN_S_REL_ERR, "RELEASE DUE ERROR" }, + { LCHAN_S_BROKEN, "BROKEN UNUSABLE" }, + { 0, NULL } +}; + +const struct value_string lchan_ciph_state_names[] = { + { LCHAN_CIPH_NONE, "NONE" }, + { LCHAN_CIPH_RX_REQ, "RX_REQ" }, + { LCHAN_CIPH_RX_CONF, "RX_CONF" }, + { LCHAN_CIPH_RXTX_REQ, "RXTX_REQ" }, + { LCHAN_CIPH_RX_CONF_TX_REQ, "RX_CONF_TX_REQ" }, + { LCHAN_CIPH_RXTX_CONF, "RXTX_CONF" }, + { 0, NULL } +}; + +/* prepare the per-SAPI T200 arrays for a given lchan */ +static int t200_by_lchan(uint32_t *t200_fn_dcch, uint32_t *t200_fn_acch, struct gsm_lchan *lchan) +{ + struct gsm_bts *bts = lchan->ts->trx->bts; + + switch (lchan->type) { + case GSM_LCHAN_SDCCH: + t200_fn_dcch[DL_SAPI0] = bts->t200_fn[T200_SDCCH]; + t200_fn_dcch[DL_SAPI3] = bts->t200_fn[T200_SDCCH_SAPI3]; + t200_fn_acch[DL_SAPI0] = bts->t200_fn[T200_SACCH_SDCCH]; + t200_fn_acch[DL_SAPI3] = bts->t200_fn[T200_SACCH_SDCCH]; + break; + case GSM_LCHAN_TCH_F: + t200_fn_dcch[DL_SAPI0] = bts->t200_fn[T200_FACCH_F]; + t200_fn_dcch[DL_SAPI3] = bts->t200_fn[T200_FACCH_F]; + t200_fn_acch[DL_SAPI0] = bts->t200_fn[T200_SACCH_TCH_SAPI0]; + t200_fn_acch[DL_SAPI3] = bts->t200_fn[T200_SACCH_TCH_SAPI3]; + break; + case GSM_LCHAN_TCH_H: + t200_fn_dcch[DL_SAPI0] = bts->t200_fn[T200_FACCH_H]; + t200_fn_dcch[DL_SAPI3] = bts->t200_fn[T200_FACCH_H]; + t200_fn_acch[DL_SAPI0] = bts->t200_fn[T200_SACCH_TCH_SAPI0]; + t200_fn_acch[DL_SAPI3] = bts->t200_fn[T200_SACCH_TCH_SAPI3]; + break; + default: + /* Channels such as CCCH don't use lapdm DL, and hence no T200 is needed */ + return -1; + } + + /* Add time of two extra messages frames. */ + if (lchan->rep_acch_cap.dl_facch_all && lchan_is_tch(lchan)) { + t200_fn_acch[DL_SAPI0] += 104 * 2; + t200_fn_acch[DL_SAPI3] += 104 * 2; + } + + return 0; +} + +static void early_rr_ia_delay_cb(void *data) +{ + struct gsm_lchan *lchan = data; + struct gsm_bts *bts = lchan->ts->trx->bts; + + if (!lchan->early_rr_ia) { + /* The IA message has disappeared since the timer was started. */ + return; + } + + if (lchan->state != LCHAN_S_ACTIVE) { + /* Release has happened since the timer was started. */ + msgb_free(lchan->early_rr_ia); + lchan->early_rr_ia = NULL; + return; + } + + /* Activation is done, send the RR IA now. Put RR IA msg into the AGCH queue of the BTS. */ + if (bts_agch_enqueue(bts, lchan->early_rr_ia) < 0) { + /* if there is no space in the queue: send DELETE IND */ + rsl_tx_delete_ind(bts, lchan->early_rr_ia->data, lchan->early_rr_ia->len); + rate_ctr_inc2(bts->ctrs, BTS_CTR_AGCH_DELETED); + msgb_free(lchan->early_rr_ia); + } + lchan->early_rr_ia = NULL; +} + +void gsm_lchan_init(struct gsm_lchan *lchan, struct gsm_bts_trx_ts *ts, unsigned int lchan_nr) +{ + lchan->ts = ts; + lchan->nr = lchan_nr; + lchan->type = GSM_LCHAN_NONE; + gsm_lchan_name_update(lchan); + + osmo_timer_setup(&lchan->early_rr_ia_delay, early_rr_ia_delay_cb, lchan); + + INIT_LLIST_HEAD(&lchan->sapi_cmds); + INIT_LLIST_HEAD(&lchan->dl_tch_queue); + lchan->dl_tch_queue_len = 0; +} + +void gsm_lchan_name_update(struct gsm_lchan *lchan) +{ + const struct gsm_bts_trx_ts *ts = lchan->ts; + const struct gsm_bts_trx *trx = ts->trx; + char *name; + + name = talloc_asprintf(trx, "(" GSM_TS_NAME_FMT ",ss=%u)", + GSM_TS_NAME_ARGS(ts), lchan->nr); + if (lchan->name != NULL) + talloc_free(lchan->name); + lchan->name = name; +} + +int lchan_init_lapdm(struct gsm_lchan *lchan) +{ + struct lapdm_channel *lc = &lchan->lapdm_ch; + uint32_t t200_fn_dcch[_NR_DL_SAPI], t200_fn_acch[_NR_DL_SAPI]; + + if (t200_by_lchan(t200_fn_dcch, t200_fn_acch, lchan) == 0) { + LOGPLCHAN(lchan, DLLAPD, LOGL_DEBUG, + "Setting T200 D0=%u, D3=%u, S0=%u, S3=%u (all in frames)\n", + t200_fn_dcch[DL_SAPI0], t200_fn_dcch[DL_SAPI3], + t200_fn_acch[DL_SAPI0], t200_fn_acch[DL_SAPI3]); + lapdm_channel_init3(lc, LAPDM_MODE_BTS, NULL, NULL, lchan->type, gsm_lchan_name(lchan)); + lapdm_channel_set_flags(lc, LAPDM_ENT_F_POLLING_ONLY | LAPDM_ENT_F_RTS); + lapdm_channel_set_l1(lc, NULL, lchan); + lapdm_channel_set_t200_fn(lc, t200_fn_dcch, t200_fn_acch); + } + /* We still need to set Rx callback to receive RACH requests: */ + lapdm_channel_set_l3(lc, lapdm_rll_tx_cb, lchan); + + return 0; +} + +static int dyn_ts_pdch_release(struct gsm_lchan *lchan) +{ + struct gsm_bts_trx_ts *ts = lchan->ts; + + if (ts->dyn.pchan_is != ts->dyn.pchan_want) { + LOGP(DRSL, LOGL_ERROR, "%s: PDCH release requested but already" + " in switchover\n", gsm_ts_and_pchan_name(ts)); + return -EINVAL; + } + + /* + * Indicate PDCH Disconnect in dyn_pdch.want, let pcu_tx_info_ind() + * pick it up and wait for PCU to disable the channel. + */ + ts->dyn.pchan_want = GSM_PCHAN_NONE; + + if (!pcu_connected()) { + /* PCU not connected yet. Just record the new type and done, + * the PCU will pick it up once connected. */ + ts->dyn.pchan_is = GSM_PCHAN_NONE; + return 1; + } + + return pcu_tx_info_ind(); +} + +void gsm_lchan_release(struct gsm_lchan *lchan, enum lchan_rel_act_kind rel_kind) +{ + int rc; + + if (lchan->abis_ip.rtp_socket) { + rsl_tx_ipac_dlcx_ind(lchan, RSL_ERR_NORMAL_UNSPEC); + osmo_rtp_socket_log_stats(lchan->abis_ip.rtp_socket, DRTP, LOGL_INFO, + "Closing RTP socket on Channel Release "); + lchan_rtp_socket_free(lchan); + } else if (lchan->abis_ip.osmux.use) { + lchan_osmux_release(lchan); + } + /* reset all Abis related config: */ + memset(&lchan->abis_ip, 0, sizeof(lchan->abis_ip)); + + /* FIXME: right now we allow creating the rtp_socket even if chan is not + * activated... Once we check for that, we can move this check at the + * start of the function */ + if (lchan->state == LCHAN_S_NONE) + return; + + /* release handover, listener and talker states */ + handover_reset(lchan); + vgcs_talker_reset(lchan, false); + vgcs_listener_reset(lchan); + vgcs_uplink_free_reset(lchan); + + lchan->rel_act_kind = rel_kind; + + /* Dynamic channel in PDCH mode is released via PCU */ + if (lchan->ts->pchan == GSM_PCHAN_OSMO_DYN + && lchan->ts->dyn.pchan_is == GSM_PCHAN_PDCH) { + rc = dyn_ts_pdch_release(lchan); + if (rc == 1) { + /* If the PCU is not connected, continue to rel ack right away. */ + lchan->rel_act_kind = LCHAN_REL_ACT_PCU; + rsl_tx_rf_rel_ack(lchan); + return; + } + /* Waiting for PDCH release */ + return; + } + + l1sap_chan_rel(lchan->ts->trx, gsm_lchan2chan_nr(lchan)); +} + +int lchan_deactivate(struct gsm_lchan *lchan) +{ + OSMO_ASSERT(lchan); + + lchan->ciph_state = 0; + return bts_model_lchan_deactivate(lchan); +} + +const char *gsm_lchans_name(enum gsm_lchan_state s) +{ + return get_value_string(lchan_s_names, s); +} + +/* obtain the next to-be transmitted dowlink SACCH frame (L2 hdr + L3); returns pointer to lchan->si buffer */ +uint8_t *lchan_sacch_get(struct gsm_lchan *lchan) +{ + uint32_t tmp, i; + + for (i = 0; i < _MAX_SYSINFO_TYPE; i++) { + tmp = (lchan->si.last + 1 + i) % _MAX_SYSINFO_TYPE; + if (!(lchan->si.valid & (1 << tmp))) + continue; + lchan->si.last = tmp; + return GSM_LCHAN_SI(lchan, tmp); + } + LOGPLCHAN(lchan, DL1P, LOGL_NOTICE, "SACCH no SI available\n"); + return NULL; +} void lchan_set_state(struct gsm_lchan *lchan, enum gsm_lchan_state state) { - DEBUGP(DL1C, "%s state %s -> %s\n", - gsm_lchan_name(lchan), - gsm_lchans_name(lchan->state), - gsm_lchans_name(state)); + if (lchan->state == state) + return; + LOGPLCHAN(lchan, DL1C, LOGL_INFO, "state %s -> %s\n", + gsm_lchans_name(lchan->state), gsm_lchans_name(state)); lchan->state = state; + + switch (lchan->state) { + case LCHAN_S_ACT_REQ: + /* Early Immediate Assignment: Activation is requested, keep the + * early IA until active. This allows the BSC to send the IA + * even before a dynamic timeslot is done switching to a + * different pchan kind (experimental). */ + break; + case LCHAN_S_ACTIVE: + lchan_init_lapdm(lchan); + if (lchan->early_rr_ia) { + /* Early Immediate Assignment: Activation is done, send + * the RR IA now. Delay a bit more to give Um time to + * let the lchan light up for the MS */ + osmo_timer_del(&lchan->early_rr_ia_delay); + osmo_timer_schedule(&lchan->early_rr_ia_delay, 0, + osmo_tdef_get(abis_T_defs, -15, OSMO_TDEF_US, -1)); + } + break; + case LCHAN_S_NONE: + lapdm_channel_exit(&lchan->lapdm_ch); + /* Also ensure that there are no leftovers from repeated FACCH or + * repeated SACCH that might cause memory leakage. */ + msgb_free(lchan->rep_acch.dl_facch[0].msg); + msgb_free(lchan->rep_acch.dl_facch[1].msg); + lchan->rep_acch.dl_facch[0].msg = NULL; + lchan->rep_acch.dl_facch[1].msg = NULL; + msgb_free(lchan->rep_acch.dl_sacch_msg); + lchan->rep_acch.dl_sacch_msg = NULL; + /* free() pending messages */ + msgb_free(lchan->pending_rel_ind_msg); + lchan->pending_rel_ind_msg = NULL; + msgb_free(lchan->pending_chan_activ); + lchan->pending_chan_activ = NULL; + /* fall through */ + default: + if (lchan->early_rr_ia) { + /* Early Immediate Assignment: Transition to any other + * state means whatever IA the BSC has sent shall now + * not be relevant anymore. */ + osmo_timer_del(&lchan->early_rr_ia_delay); + msgb_free(lchan->early_rr_ia); + lchan->early_rr_ia = NULL; + } + break; + } } -bool ts_is_pdch(const struct gsm_bts_trx_ts *ts) +/* See 3GPP TS 44.018 Table 10.5.2.5.1 "Channel Description information element" */ +static uint8_t gsm_pchan2chan_nr(enum gsm_phys_chan_config pchan, + uint8_t ts_nr, uint8_t lchan_nr) { - switch (ts->pchan) { + uint8_t cbits, chan_nr; + + OSMO_ASSERT(pchan != GSM_PCHAN_OSMO_DYN); + OSMO_ASSERT(pchan != GSM_PCHAN_TCH_F_PDCH); + + switch (pchan) { + case GSM_PCHAN_TCH_F: + OSMO_ASSERT(lchan_nr == 0); + cbits = ABIS_RSL_CHAN_NR_CBITS_Bm_ACCHs; + break; case GSM_PCHAN_PDCH: - return true; + OSMO_ASSERT(lchan_nr == 0); + cbits = ABIS_RSL_CHAN_NR_CBITS_OSMO_PDCH; + break; + case GSM_PCHAN_TCH_H: + OSMO_ASSERT(lchan_nr < 2); + cbits = ABIS_RSL_CHAN_NR_CBITS_Lm_ACCHs(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) + cbits = ABIS_RSL_CHAN_NR_CBITS_BCCH; + else { + OSMO_ASSERT(lchan_nr < 4); + cbits = ABIS_RSL_CHAN_NR_CBITS_SDCCH4_ACCH(lchan_nr); + } + break; + case GSM_PCHAN_SDCCH8_SACCH8C: + case GSM_PCHAN_SDCCH8_SACCH8C_CBCH: + OSMO_ASSERT(lchan_nr < 8); + cbits = ABIS_RSL_CHAN_NR_CBITS_SDCCH8_ACCH(lchan_nr); + break; + case GSM_PCHAN_CCCH: + cbits = ABIS_RSL_CHAN_NR_CBITS_BCCH; + break; + case GSM_PCHAN_NONE: + LOGP(DRSL, LOGL_ERROR, "ts=%u,ss=%u Physical channel %s not expected!\n", + ts_nr, lchan_nr, gsm_pchan_name(pchan)); + cbits = 0x00; /* Reserved */ + break; + default: + LOGP(DRSL, LOGL_ERROR, "ts=%u,ss=%u Physical channel %s (0x%02x) not expected!\n", + ts_nr, lchan_nr, gsm_pchan_name(pchan), (int)pchan); + OSMO_ASSERT(0); + break; + } + + chan_nr = (cbits << 3) | (ts_nr & 0x7); + + return chan_nr; +} + +static uint8_t _gsm_lchan2chan_nr(const struct gsm_lchan *lchan, bool rsl) +{ + uint8_t chan_nr; + + switch (lchan->ts->pchan) { + case GSM_PCHAN_OSMO_DYN: + /* Return chan_nr reflecting the current TS pchan, either a standard TCH kind, or the + * nonstandard value reflecting PDCH for Osmocom style dyn TS. */ + chan_nr = gsm_lchan_as_pchan2chan_nr(lchan, lchan->ts->dyn.pchan_is); + break; case GSM_PCHAN_TCH_F_PDCH: - return (ts->flags & TS_F_PDCH_ACTIVE) - && !(ts->flags & TS_F_PDCH_PENDING_MASK); - case GSM_PCHAN_TCH_F_TCH_H_PDCH: - return ts->dyn.pchan_is == GSM_PCHAN_PDCH - && ts->dyn.pchan_want == ts->dyn.pchan_is; + /* For ip.access style dyn TS, on RSL we want to use the chan_nr as if it was TCH/F. + * We're using custom PDCH ACT and DEACT messages that use the usual chan_nr values. */ + if (rsl) + chan_nr = gsm_lchan_as_pchan2chan_nr(lchan, GSM_PCHAN_TCH_F); + else if (~lchan->ts->flags & TS_F_PDCH_ACTIVE) + chan_nr = gsm_lchan_as_pchan2chan_nr(lchan, GSM_PCHAN_TCH_F); + else + chan_nr = gsm_lchan_as_pchan2chan_nr(lchan, GSM_PCHAN_PDCH); + break; + default: + chan_nr = gsm_pchan2chan_nr(lchan->ts->pchan, lchan->ts->nr, lchan->nr); + break; + } + + /* VAMOS: if this lchan belongs to a shadow timeslot, we must reflect + * this in the channel number. Convert it to Osmocom specific value. */ + if (lchan->ts->vamos.is_shadow) + chan_nr |= RSL_CHAN_OSMO_VAMOS_MASK; + + return chan_nr; +} + +uint8_t gsm_lchan2chan_nr(const struct gsm_lchan *lchan) +{ + return _gsm_lchan2chan_nr(lchan, false); +} + +uint8_t gsm_lchan2chan_nr_rsl(const struct gsm_lchan *lchan) +{ + return _gsm_lchan2chan_nr(lchan, true); +} + +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_OSMO_DYN + && 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); +} + +/* Called by the model specific code every 104 TDMA frames (SACCH period) */ +void gsm_lchan_interf_meas_push(struct gsm_lchan *lchan, int dbm) +{ + const uint8_t meas_num = lchan->meas.interf_meas_num; + + if (meas_num >= ARRAY_SIZE(lchan->meas.interf_meas_dbm)) { + LOGPLCHAN(lchan, DL1C, LOGL_ERROR, "Not enough room " + "to store interference report (%ddBm)\n", dbm); + return; + } + + lchan->meas.interf_meas_dbm[meas_num] = dbm; + lchan->meas.interf_meas_num++; +} + +/* Called by the higher layers every Intave * 104 TDMA frames */ +void gsm_lchan_interf_meas_calc_avg(struct gsm_lchan *lchan) +{ + const uint8_t meas_num = lchan->meas.interf_meas_num; + const struct gsm_bts *bts = lchan->ts->trx->bts; + int b, meas_avg, meas_sum = 0; + + /* There must be at least one sample */ + OSMO_ASSERT(meas_num > 0); + + /* Calculate the sum of all collected samples (in -x dBm) */ + while (lchan->meas.interf_meas_num) { + uint8_t i = --lchan->meas.interf_meas_num; + meas_sum += lchan->meas.interf_meas_dbm[i]; + } + + /* Calculate the average of all collected samples */ + meas_avg = meas_sum / (int) meas_num; + + /* 3GPP TS 48.008 defines 5 interference bands, and 6 interference level + * boundaries (0, X1, ... X5). It's not clear how to handle values + * exceeding the outer boundaries (0 or X5), because bands 0 and 6 do + * not exist (sigh). Let's map such values to closest bands 1 and 5. */ + if (bts->interference.boundary[0] < bts->interference.boundary[5]) { + /* Ascending order (band=1 indicates lowest interference) */ + for (b = 1; b < ARRAY_SIZE(bts->interference.boundary) - 1; b++) { + if (meas_avg < bts->interference.boundary[b]) + break; /* Current 'b' is the band value */ + } + } else { + /* Descending order (band=1 indicates highest interference) */ + for (b = 1; b < ARRAY_SIZE(bts->interference.boundary) - 1; b++) { + if (meas_avg >= bts->interference.boundary[b]) + break; /* Current 'b' is the band value */ + } + } + + LOGPLCHAN(lchan, DL1C, LOGL_DEBUG, + "Interference AVG: %ddBm (band %d, samples %u)\n", + meas_avg, b, meas_num); + + lchan->meas.interf_meas_avg_dbm = meas_avg; + lchan->meas.interf_band = b; +} + +/* determine the ECU codec constant for the codec used by given lchan */ +int lchan2ecu_codec(const struct gsm_lchan *lchan) +{ + const struct gsm_bts_trx_ts *ts = lchan->ts; + + switch (lchan->tch_mode) { + case GSM48_CMODE_SPEECH_V1: + if (ts_pchan(ts) == GSM_PCHAN_TCH_H) + return OSMO_ECU_CODEC_HR; + else + return OSMO_ECU_CODEC_FR; + break; + case GSM48_CMODE_SPEECH_EFR: + return OSMO_ECU_CODEC_EFR; + case GSM48_CMODE_SPEECH_AMR: + return OSMO_ECU_CODEC_AMR; default: - return false; + return -1; + } +} + +static int bind_rtp(struct gsm_bts *bts, struct osmo_rtp_socket *rs, const char *ip) +{ + int rc; + unsigned int i; + unsigned int tries; + + tries = (bts->rtp_port_range_end - bts->rtp_port_range_start) / 2; + for (i = 0; i < tries; i++) { + + if (bts->rtp_port_range_next >= bts->rtp_port_range_end) + bts->rtp_port_range_next = bts->rtp_port_range_start; + + rc = osmo_rtp_socket_bind(rs, ip, bts->rtp_port_range_next); + + bts->rtp_port_range_next += 2; + + if (rc != 0) + continue; + + if (bts->rtp_ip_dscp != -1) { + if (osmo_rtp_socket_set_dscp(rs, bts->rtp_ip_dscp)) + LOGP(DRSL, LOGL_ERROR, "failed to set DSCP=%d: %s\n", + bts->rtp_ip_dscp, strerror(errno)); + } + if (bts->rtp_priority != -1) { + if (osmo_rtp_socket_set_priority(rs, bts->rtp_priority)) + LOGP(DRSL, LOGL_ERROR, "failed to set socket priority %d: %s\n", + bts->rtp_priority, strerror(errno)); + } + return 0; + } + + return -1; +} + +int lchan_rtp_socket_create(struct gsm_lchan *lchan, const char *bind_ip) +{ + struct gsm_bts *bts = lchan->ts->trx->bts; + char cname[256+4]; + int rc; + + if (lchan->abis_ip.rtp_socket) { + LOGPLCHAN(lchan, DRSL, LOGL_ERROR, "Rx RSL IPAC CRCX, " + "but we already have socket!\n"); + return -EALREADY; + } + + /* FIXME: select default value depending on speech_mode */ + //if (!payload_type) + lchan->tch.last_fn = LCHAN_FN_DUMMY; + lchan->abis_ip.rtp_socket = osmo_rtp_socket_create(lchan->ts->trx, + OSMO_RTP_F_POLL); + + if (!lchan->abis_ip.rtp_socket) { + LOGPLCHAN(lchan, DRTP, LOGL_ERROR, "IPAC Failed to create RTP/RTCP sockets\n"); + oml_tx_failure_event_rep(&lchan->ts->trx->mo, + NM_SEVER_MINOR, OSMO_EVT_CRIT_RTP_TOUT, + "%s IPAC Failed to create RTP/RTCP sockets", + gsm_lchan_name(lchan)); + return -ENOTCONN; + } + + rc = osmo_rtp_socket_set_param(lchan->abis_ip.rtp_socket, + bts->rtp_jitter_adaptive ? + OSMO_RTP_P_JIT_ADAP : + OSMO_RTP_P_JITBUF, + bts->rtp_jitter_buf_ms); + if (rc < 0) + LOGPLCHAN(lchan, DRTP, LOGL_ERROR, + "IPAC Failed to set RTP socket parameters: %s\n", strerror(-rc)); + else + LOGPLCHAN(lchan, DRTP, LOGL_INFO, "IPAC set RTP socket parameters: %d\n", rc); + + lchan->abis_ip.rtp_socket->priv = lchan; + lchan->abis_ip.rtp_socket->rx_cb = &l1sap_rtp_rx_cb; + + rc = bind_rtp(bts, lchan->abis_ip.rtp_socket, bind_ip); + if (rc < 0) { + LOGPLCHAN(lchan, DRTP, LOGL_ERROR, "IPAC Failed to bind RTP/RTCP sockets\n"); + oml_tx_failure_event_rep(&lchan->ts->trx->mo, + NM_SEVER_MINOR, OSMO_EVT_CRIT_RTP_TOUT, + "%s IPAC Failed to bind RTP/RTCP sockets", + gsm_lchan_name(lchan)); + lchan_rtp_socket_free(lchan); + return -EBADFD; + } + + /* Ensure RTCP SDES contains some useful information */ + snprintf(cname, sizeof(cname), "bts@%s", bind_ip); + osmo_rtp_set_source_desc(lchan->abis_ip.rtp_socket, cname, + gsm_lchan_name(lchan), NULL, NULL, + gsm_trx_unit_id(lchan->ts->trx), + "OsmoBTS-" PACKAGE_VERSION, NULL); + /* FIXME: multiplex connection, BSC proxy */ + return 0; +} + + +int lchan_rtp_socket_connect(struct gsm_lchan *lchan, const struct in_addr *ia, uint16_t connect_port) +{ + int bound_port = 0; + int rc; + + rc = osmo_rtp_socket_connect(lchan->abis_ip.rtp_socket, + inet_ntoa(*ia), connect_port); + if (rc < 0) { + LOGPLCHAN(lchan, DRTP, LOGL_ERROR, "Failed to connect RTP/RTCP sockets\n"); + return -ECONNREFUSED; + } + /* save IP address and port number */ + lchan->abis_ip.connect_ip = ntohl(ia->s_addr); + lchan->abis_ip.connect_port = connect_port; + + rc = osmo_rtp_get_bound_ip_port(lchan->abis_ip.rtp_socket, + &lchan->abis_ip.bound_ip, + &bound_port); + if (rc < 0) + LOGPLCHAN(lchan, DRTP, LOGL_ERROR, "IPAC cannot obtain locally bound IP/port: %d\n", rc); + lchan->abis_ip.bound_port = bound_port; + return 0; +} + +void lchan_rtp_socket_free(struct gsm_lchan *lchan) +{ + osmo_rtp_socket_free(lchan->abis_ip.rtp_socket); + lchan->abis_ip.rtp_socket = NULL; + msgb_queue_free(&lchan->dl_tch_queue); + lchan->dl_tch_queue_len = 0; +} + +/*! limit number of queue entries to %u; drops any surplus messages */ +void lchan_dl_tch_queue_enqueue(struct gsm_lchan *lchan, struct msgb *msg, unsigned int limit) +{ + if (lchan->dl_tch_queue_len > limit) { + unsigned int excess = lchan->dl_tch_queue_len - limit; + LOGPLCHAN(lchan, DL1P, LOGL_NOTICE, "freeing %d queued frames\n", excess); + rate_ctr_add2(lchan->ts->trx->bts->ctrs, BTS_CTR_RTP_RX_DROP_OVERFLOW, excess); + } + while (lchan->dl_tch_queue_len > limit) { + struct msgb *tmp = msgb_dequeue_count(&lchan->dl_tch_queue, &lchan->dl_tch_queue_len); + msgb_free(tmp); } + msgb_enqueue_count(&lchan->dl_tch_queue, msg, &lchan->dl_tch_queue_len); } diff --git a/src/common/load_indication.c b/src/common/load_indication.c index fa4745b1..c0d6efb1 100644 --- a/src/common/load_indication.c +++ b/src/common/load_indication.c @@ -12,7 +12,7 @@ * 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. + * 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/>. @@ -27,6 +27,7 @@ #include <osmo-bts/gsm_data.h> #include <osmo-bts/rsl.h> #include <osmo-bts/paging.h> +#include <osmo-bts/bts.h> static void reset_load_counters(struct gsm_bts *bts) { @@ -40,6 +41,10 @@ static void load_timer_cb(void *data) struct gsm_bts *bts = data; unsigned int pch_percent, rach_percent; + /* It makes no sense to send Load Indication if CCCH is still disabled...*/ + if (bts->c0->ts[0].mo.nm_state.operational != NM_OPSTATE_ENABLED) + goto retry_later; + /* compute percentages */ if (bts->load.ccch.pch_total == 0) pch_percent = 0; @@ -52,7 +57,7 @@ static void load_timer_cb(void *data) uint16_t buffer_space = paging_buffer_space(bts->paging_state); rsl_tx_ccch_load_ind_pch(bts, buffer_space); } else { - /* This is an extenstion of TS 08.58. We don't only + /* This is an extension of TS 08.58. We don't only * send load indications if the load is above threshold, * but we also explicitly indicate that we are below * threshold by using the magic value 0xffff */ @@ -72,6 +77,7 @@ static void load_timer_cb(void *data) bts->load.rach.access); } +retry_later: reset_load_counters(bts); /* re-schedule the timer */ @@ -93,3 +99,8 @@ void load_timer_stop(struct gsm_bts *bts) { osmo_timer_del(&bts->load.ccch.timer); } + +bool load_timer_is_running(const struct gsm_bts *bts) +{ + return osmo_timer_pending(&bts->load.ccch.timer); +} diff --git a/src/common/logging.c b/src/common/logging.c index 3315a019..15c126a2 100644 --- a/src/common/logging.c +++ b/src/common/logging.c @@ -13,7 +13,7 @@ * 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. + * 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/>. @@ -35,13 +35,13 @@ static struct log_info_cat bts_log_info_cat[] = { .name = "DRSL", .description = "A-bis Radio Siganlling Link (RSL)", .color = "\033[1;35m", - .enabled = 1, .loglevel = LOGL_INFO, + .enabled = 1, .loglevel = LOGL_NOTICE, }, [DOML] = { .name = "DOML", .description = "A-bis Network Management / O&M (NM/OML)", .color = "\033[1;36m", - .enabled = 1, .loglevel = LOGL_INFO, + .enabled = 1, .loglevel = LOGL_NOTICE, }, [DRLL] = { .name = "DRLL", @@ -64,34 +64,23 @@ static struct log_info_cat bts_log_info_cat[] = { .name = "DPAG", .description = "Paging Subsystem", .color = "\033[1;38m", - .enabled = 1, .loglevel = LOGL_INFO, + .enabled = 1, .loglevel = LOGL_NOTICE, }, [DL1C] = { .name = "DL1C", .description = "Layer 1 Control (MPH)", - .loglevel = LOGL_INFO, + .loglevel = LOGL_NOTICE, .enabled = 1, }, [DL1P] = { .name = "DL1P", .description = "Layer 1 Primitives (PH)", - .loglevel = LOGL_INFO, + .loglevel = LOGL_NOTICE, .enabled = 0, }, [DDSP] = { .name = "DDSP", .description = "DSP Trace Messages", - .loglevel = LOGL_DEBUG, - .enabled = 1, - }, - [DABIS] = { - .name = "DABIS", - .description = "A-bis Intput Subsystem", - .enabled = 1, .loglevel = LOGL_NOTICE, - }, - [DRTP] = { - .name = "DRTP", - .description = "Realtime Transfer Protocol", .loglevel = LOGL_NOTICE, .enabled = 1, }, @@ -116,35 +105,50 @@ static struct log_info_cat bts_log_info_cat[] = { [DLOOP] = { .name = "DLOOP", .description = "Control loops", - .color = "\033[0;34m", + .color = "\033[0;94m", .enabled = 1, .loglevel = LOGL_NOTICE, }, -#if 0 - [DNS] = { - .name = "DNS", - .description = "GPRS Network Service (NS)", - .enabled = 1, .loglevel = LOGL_INFO, - }, - [DBSSGP] = { - .name = "DBSSGP", - .description = "GPRS BSS Gateway Protocol (BSSGP)", - .enabled = 1, .loglevel = LOGL_DEBUG, - }, - [DLLC] = { - .name = "DLLC", - .description = "GPRS Logical Link Control Protocol (LLC)", - .enabled = 1, .loglevel = LOGL_DEBUG, - }, -#endif - [DSUM] = { - .name = "DSUM", - .description = "DSUM", + [DABIS] = { + .name = "DABIS", + .description = "A-bis Intput Subsystem", + .enabled = 1, .loglevel = LOGL_NOTICE, + }, + [DRTP] = { + .name = "DRTP", + .description = "Realtime Transfer Protocol", + .loglevel = LOGL_NOTICE, + .enabled = 1, + }, + [DOSMUX] = { + .name = "DOSMUX", + .description = "Osmux (Osmocom RTP multiplexing)", + .loglevel = LOGL_NOTICE, + .enabled = 1, + }, + [DASCI] = { + .name = "DASCI", + .description = "ASCI (Advanced Speech Call Items: VGCS/VBS)", .loglevel = LOGL_NOTICE, .enabled = 1, }, + }; +static int osmo_bts_filter_fn(const struct log_context *ctx, struct log_target *tgt) +{ + uint8_t *sapi = ctx->ctx[LOG_CTX_L1_SAPI]; + uint16_t *sapi_mask = tgt->filter_data[LOG_FLT_L1_SAPI]; + + if ((tgt->filter_map & (1 << LOG_FLT_L1_SAPI)) != 0 + && sapi_mask && sapi + && (*sapi_mask & (1 << *sapi)) != 0) + return 1; + + return 0; +} + const struct log_info bts_log_info = { + .filter_fn = osmo_bts_filter_fn, .cat = bts_log_info_cat, .num_cat = ARRAY_SIZE(bts_log_info_cat), }; diff --git a/src/common/main.c b/src/common/main.c index 6d8088ca..e57885a6 100644 --- a/src/common/main.c +++ b/src/common/main.c @@ -12,7 +12,7 @@ * 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. + * 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/>. @@ -38,6 +38,9 @@ #include <osmocom/core/application.h> #include <osmocom/vty/telnet_interface.h> #include <osmocom/vty/logging.h> +#include <osmocom/vty/stats.h> +#include <osmocom/vty/misc.h> +#include <osmocom/vty/cpu_sched_vty.h> #include <osmocom/core/gsmtap_util.h> #include <osmocom/core/gsmtap.h> @@ -46,6 +49,7 @@ #include <osmo-bts/logging.h> #include <osmo-bts/abis.h> #include <osmo-bts/bts.h> +#include <osmo-bts/bts_sm.h> #include <osmo-bts/vty.h> #include <osmo-bts/l1sap.h> #include <osmo-bts/bts_model.h> @@ -56,30 +60,62 @@ #include <osmocom/ctrl/control_vty.h> #include <osmo-bts/oml.h> -int quit = 0; +static int quit = 0; static const char *config_file = "osmo-bts.cfg"; static int daemonize = 0; static int rt_prio = -1; static char *gsmtap_ip = 0; extern int g_vty_port_num; +static bool vty_test_mode = false; static void print_help() { printf( "Some useful options:\n" - " -h --help this text\n" - " -d --debug MASK Enable debugging (e.g. -d DRSL:DOML:DLAPDM)\n" - " -D --daemonize For the process into a background daemon\n" - " -c --config-file Specify the filename of the config file\n" - " -s --disable-color Don't use colors in stderr log output\n" - " -T --timestamp Prefix every log line with a timestamp\n" - " -V --version Print version information and exit\n" - " -e --log-level Set a global log-level\n" - " -r --realtime PRIO Use SCHED_RR with the specified priority\n" - " -i --gsmtap-ip The destination IP used for GSMTAP.\n" + " -h --help this text\n" + " -d --debug MASK Enable debugging (e.g. -d DRSL:DOML:DLAPDM)\n" + " -D --daemonize For the process into a background daemon\n" + " -c --config-file Specify the filename of the config file\n" + " -s --disable-color Don't use colors in stderr log output\n" + " -T --timestamp Prefix every log line with a timestamp\n" + " -V --version Print version information and exit\n" + " -e --log-level Set a global log-level\n" + "\nVTY reference generation:\n" + " --vty-ref-mode MODE VTY reference generation mode (e.g. 'expert').\n" + " --vty-ref-xml Generate the VTY reference XML output and exit.\n" + "\nRegression testing:\n" + " --vty-test VTY test mode. Do not connect to BSC, do not exit.\n" ); bts_model_print_help(); } +static void handle_long_options(const char *prog_name, const int long_option) +{ + static int vty_ref_mode = VTY_REF_GEN_MODE_DEFAULT; + + switch (long_option) { + case 1: + vty_ref_mode = get_string_value(vty_ref_gen_mode_names, optarg); + if (vty_ref_mode < 0) { + fprintf(stderr, "%s: Unknown VTY reference generation " + "mode '%s'\n", prog_name, optarg); + exit(2); + } + break; + case 2: + fprintf(stderr, "Generating the VTY reference in mode '%s' (%s)\n", + get_value_string(vty_ref_gen_mode_names, vty_ref_mode), + get_value_string(vty_ref_gen_mode_desc, vty_ref_mode)); + vty_dump_xml_ref_mode(stdout, (enum vty_ref_gen_mode) vty_ref_mode); + exit(0); + case 3: + vty_test_mode = true; + break; + default: + fprintf(stderr, "%s: error parsing cmdline options\n", prog_name); + exit(2); + } +} + /* FIXME: finally get some option parsing code into libosmocore */ static void handle_options(int argc, char **argv) { @@ -94,6 +130,7 @@ static void handle_options(int argc, char **argv) while (1) { int option_idx = 0, c; + static int long_option = 0; static const struct option long_options[] = { /* FIXME: all those are generic Osmocom app options */ { "help", 0, 0, 'h' }, @@ -108,6 +145,9 @@ static void handle_options(int argc, char **argv) { "gsmtap-ip", 1, 0, 'i' }, { "trx-num", 1, 0, 't' }, { "realtime", 1, 0, 'r' }, + { "vty-ref-mode", 1, &long_option, 1 }, + { "vty-ref-xml", 0, &long_option, 2 }, + { "vty-test", 0, &long_option, 3 }, { 0, 0, 0, 0 } }; @@ -121,6 +161,9 @@ static void handle_options(int argc, char **argv) print_help(); exit(0); break; + case 0: + handle_long_options(argv[0], long_option); + break; case 's': log_set_use_color(osmo_stderr_target, 0); break; @@ -145,13 +188,17 @@ static void handle_options(int argc, char **argv) break; case 'r': rt_prio = atoi(optarg); + fprintf(stderr, "Command line argument '-r' is deprecated, use VTY " + "cpu-sched node setting 'policy rr %d' instead.\n", rt_prio); break; case 'i': gsmtap_ip = optarg; + fprintf(stderr, "Command line argument '-i' is deprecated, use VTY " + "parameter 'gsmtap-remote-host %s' instead.\n", gsmtap_ip); break; case 't': - fprintf(stderr, "Parameter -t is deprecated and does nothing, " - "TRX num is calculated from VTY\n"); + fprintf(stderr, "Command line argument '-t' is deprecated and does nothing, " + "TRX number is calculated from the VTY automatically.\n"); break; case '?': case 1: @@ -173,24 +220,35 @@ static void handle_options(int argc, char **argv) } } -static struct gsm_bts *bts; +/* FIXME: remove this once we add multi-BTS support */ +struct gsm_bts *g_bts = NULL; -static void signal_handler(int signal) +static void signal_handler(int signum) { - fprintf(stderr, "signal %u received\n", signal); + fprintf(stderr, "signal %u received\n", signum); - switch (signal) { + switch (signum) { case SIGINT: case SIGTERM: if (!quit) { - oml_tx_failure_event_rep(&bts->mo, + oml_tx_failure_event_rep(&g_bts->mo, NM_SEVER_CRITICAL, OSMO_EVT_CRIT_PROC_STOP, "BTS: SIGINT received -> shutdown"); - bts_shutdown(bts, "SIGINT"); + bts_shutdown_ext(g_bts, "SIGINT", true, false); } quit++; break; case SIGABRT: + /* in case of abort, we want to obtain a talloc report and + * then run default SIGABRT handler, who will generate coredump + * and abort the process. abort() should do this for us after we + * return, but program wouldn't exit if an external SIGABRT is + * received. + */ + talloc_report_full(tall_bts_ctx, stderr); + signal(SIGABRT, SIG_DFL); + raise(SIGABRT); + break; case SIGUSR1: case SIGUSR2: talloc_report_full(tall_bts_ctx, stderr); @@ -200,33 +258,11 @@ static void signal_handler(int signal) } } -static int write_pid_file(char *procname) -{ - FILE *outf; - char tmp[PATH_MAX+1]; - - snprintf(tmp, sizeof(tmp)-1, "/var/run/%s.pid", procname); - tmp[PATH_MAX-1] = '\0'; - - outf = fopen(tmp, "w"); - if (!outf) - return -1; - - fprintf(outf, "%d\n", getpid()); - - fclose(outf); - - return 0; -} - int bts_main(int argc, char **argv) { struct gsm_bts_trx *trx; - struct e1inp_line *line; int rc; - printf("((*))\n |\n / \\ OsmoBTS\n"); - /* Track the use of talloc NULL memory contexts */ talloc_enable_null_tracking(); @@ -238,18 +274,36 @@ int bts_main(int argc, char **argv) osmo_stats_init(tall_bts_ctx); vty_init(&bts_vty_info); ctrl_vty_init(tall_bts_ctx); + osmo_cpu_sched_vty_init(tall_bts_ctx); rate_ctr_init(tall_bts_ctx); + logging_vty_add_cmds(); + osmo_talloc_vty_add_cmds(); + osmo_stats_vty_add_cmds(); + osmo_fsm_vty_add_cmds(); + + bts_vty_init(tall_bts_ctx); + e1inp_vty_init(); + + logging_vty_add_deprecated_subsys(tall_bts_ctx, "sum"); + handle_options(argc, argv); - bts = gsm_bts_alloc(tall_bts_ctx, 0); - if (!bts) { - fprintf(stderr, "Failed to create BTS structure\n"); + fprintf(stderr, "((*))\n |\n / \\ OsmoBTS\n"); + if (vty_test_mode) + fprintf(stderr, "--- VTY test mode: not connecting to BSC, not exiting ---\n"); + + g_bts_sm = gsm_bts_sm_alloc(tall_bts_ctx); + if (!g_bts_sm) { + fprintf(stderr, "Failed to create BTS Site Manager structure\n"); exit(1); } - e1inp_vty_init(); - bts_vty_init(bts); + g_bts = gsm_bts_alloc(g_bts_sm, 0); + if (!g_bts) { + fprintf(stderr, "Failed to create BTS structure\n"); + exit(1); + } /* enable realtime priority for us */ if (rt_prio != -1) { @@ -265,21 +319,12 @@ int bts_main(int argc, char **argv) } } - if (gsmtap_ip) { - gsmtap = gsmtap_source_init(gsmtap_ip, GSMTAP_UDP_PORT, 1); - if (!gsmtap) { - fprintf(stderr, "Failed during gsmtap_init()\n"); - exit(1); - } - gsmtap_source_add_sink(gsmtap); - } - - if (bts_init(bts) < 0) { + if (bts_init(g_bts) < 0) { fprintf(stderr, "unable to open bts\n"); exit(1); } - abis_init(bts); + abis_init(g_bts); rc = vty_read_config_file(config_file, NULL); if (rc < 0) { @@ -293,48 +338,78 @@ int bts_main(int argc, char **argv) exit(1); } - llist_for_each_entry(trx, &bts->trx_list, list) { - if (!trx->role_bts.l1h) { + llist_for_each_entry(trx, &g_bts->trx_list, list) { + if (!trx->pinst) { fprintf(stderr, "TRX %u has no associated PHY instance\n", trx->nr); exit(1); } } - write_pid_file("osmo-bts"); + /* Accept a GSMTAP host from VTY config, but a commandline option overrides that. */ + if (gsmtap_ip != NULL) { + if (g_bts->gsmtap.remote_host != NULL) { + LOGP(DLGLOBAL, LOGL_NOTICE, + "Command line argument '-i %s' overrides " + "'gsmtap-remote-host %s' from the config file\n", + gsmtap_ip, g_bts->gsmtap.remote_host); + talloc_free(g_bts->gsmtap.remote_host); + } + g_bts->gsmtap.remote_host = talloc_strdup(g_bts, gsmtap_ip); + } - bts_controlif_setup(bts, ctrl_vty_get_bind_addr(), OSMO_CTRL_PORT_BTS); + /* TODO: move this to gsm_bts_alloc() */ + if (g_bts->gsmtap.remote_host != NULL) { + LOGP(DLGLOBAL, LOGL_NOTICE, + "Setting up GSMTAP Um forwarding '%s->'%s:%u'\n", + g_bts->gsmtap.local_host, g_bts->gsmtap.remote_host, GSMTAP_UDP_PORT); + g_bts->gsmtap.inst = gsmtap_source_init2(g_bts->gsmtap.local_host, 0, + g_bts->gsmtap.remote_host, GSMTAP_UDP_PORT, 1); + if (g_bts->gsmtap.inst == NULL) { + fprintf(stderr, "Failed during gsmtap_source_init2()\n"); + exit(1); + } + gsmtap_source_add_sink(g_bts->gsmtap.inst); + } - rc = telnet_init_dynif(tall_bts_ctx, NULL, vty_get_bind_addr(), - g_vty_port_num); + bts_controlif_setup(g_bts, OSMO_CTRL_PORT_BTS); + + rc = telnet_init_default(tall_bts_ctx, NULL, g_vty_port_num); if (rc < 0) { fprintf(stderr, "Error initializing telnet\n"); exit(1); } - if (pcu_sock_init(bts->pcu.sock_path)) { + if (pcu_sock_init(g_bts->pcu.sock_path, g_bts->pcu.sock_wqueue_len_max)) { fprintf(stderr, "PCU L1 socket failed\n"); exit(1); } signal(SIGINT, &signal_handler); signal(SIGTERM, &signal_handler); - //signal(SIGABRT, &signal_handler); + signal(SIGABRT, &signal_handler); signal(SIGUSR1, &signal_handler); signal(SIGUSR2, &signal_handler); osmo_init_ignore_signals(); - if (!bts->bsc_oml_host) { - fprintf(stderr, "Cannot start BTS without knowing BSC OML IP\n"); + if (bts_osmux_open(g_bts) < 0) { + fprintf(stderr, "Osmux setup failed\n"); exit(1); } - line = abis_open(bts, bts->bsc_oml_host, "sysmoBTS"); - if (!line) { - fprintf(stderr, "unable to connect to BSC\n"); - exit(2); + if (vty_test_mode) { + /* Just select-loop without connecting to the BSC, don't exit. This allows running tests on the VTY + * telnet port. */ + while (!quit) { + log_reset_context(); + osmo_select_main(0); + } + return EXIT_SUCCESS; } + if (abis_open(g_bts, "osmo-bts") != 0) + exit(1); + rc = phy_links_open(); if (rc < 0) { fprintf(stderr, "unable to open PHY link(s)\n"); diff --git a/src/common/measurement.c b/src/common/measurement.c index c2001dae..19bff714 100644 --- a/src/common/measurement.c +++ b/src/common/measurement.c @@ -2,19 +2,40 @@ #include <stdint.h> #include <errno.h> -#include <osmocom/gsm/gsm_utils.h> #include <osmocom/core/utils.h> +#include <osmocom/core/endian.h> + +#include <osmocom/gsm/gsm_utils.h> +#include <osmocom/gsm/protocol/gsm_44_004.h> #include <osmo-bts/gsm_data.h> #include <osmo-bts/logging.h> #include <osmo-bts/measurement.h> #include <osmo-bts/scheduler.h> #include <osmo-bts/rsl.h> +#include <osmo-bts/power_control.h> +#include <osmo-bts/ta_control.h> + +/* Active TDMA frame subset for TCH/H in DTX mode (see 3GPP TS 45.008 Section 8.3). + * This mapping is used to determine if a L2 block starting at the given TDMA FN + * belongs to the SUB set and thus shall always be transmitted in DTX mode. */ +static const uint8_t ts45008_dtx_tchh_speech_fn_map[104] = { + /* TCH/H(0): 0, 2, 4, 6, 52, 54, 56, 58 */ + [0] = 1, /* block { 0, 2, 4, 6} */ + [52] = 1, /* block {52, 54, 56, 58} */ + /* TCH/H(1): 14, 16, 18, 20, 66, 68, 70, 72 */ + [14] = 1, /* block {14, 16, 18, 20} */ + [66] = 1, /* block {66, 68, 70, 72} */ +}; -/* Tables as per TS 45.008 Section 8.3 */ -static const uint8_t ts45008_83_tch_f[] = { 52, 53, 54, 55, 56, 57, 58, 59 }; -static const uint8_t ts45008_83_tch_hs0[] = { 0, 2, 4, 6, 52, 54, 56, 58 }; -static const uint8_t ts45008_83_tch_hs1[] = { 14, 16, 18, 20, 66, 68, 70, 72 }; +static const uint8_t ts45008_dtx_tchh_data_fn_map[104] = { + /* UL TCH/H(0): 52, 54, 56, 58, 60, 62, 65, 67, 69, 71 */ + [52] = 1, /* block {52, 54, 56, 58, 60, 62} */ + [60] = 1, /* block {60, 62, 65, 67, 69, 71} */ + /* UL TCH/H(1): 70, 72, 74, 76, 79, 81, 83, 85, 87, 89 */ + [70] = 1, /* block {70, 72, 74, 76, 79, 81} */ + [79] = 1, /* block {79, 81, 83, 85, 87, 89} */ +}; /* In cases where we less measurements than we expect we must assume that we * just did not receive the block because it was lost due to bad channel @@ -23,88 +44,74 @@ static const uint8_t ts45008_83_tch_hs1[] = { 14, 16, 18, 20, 66, 68, 70, 72 }; * the missing measurements */ #define MEASUREMENT_DUMMY_BER 10000 /* 100% BER */ #define MEASUREMENT_DUMMY_IRSSI 109 /* noise floor in -dBm */ -static const struct bts_ul_meas measurement_dummy = (struct bts_ul_meas) { +static const struct bts_ul_meas measurement_dummy = { .ber10k = MEASUREMENT_DUMMY_BER, .ta_offs_256bits = 0, - .c_i = 0, + .ci_cb = 0, .is_sub = 0, .inv_rssi = MEASUREMENT_DUMMY_IRSSI }; -/* find out if an array contains a given key as element */ -#define ARRAY_CONTAINS(arr, val) array_contains(arr, ARRAY_SIZE(arr), val) -static bool array_contains(const uint8_t *arr, unsigned int len, uint8_t val) { - int i; - for (i = 0; i < len; i++) { - if (arr[i] == val) - return true; - } - return false; -} - /* Decide if a given frame number is part of the "-SUB" measurements (true) or not (false) * (this function is only used internally, it is public to call it from unit-tests) */ -bool ts45008_83_is_sub(struct gsm_lchan *lchan, uint32_t fn, bool is_amr_sid_update) +bool ts45008_83_is_sub(struct gsm_lchan *lchan, uint32_t fn) { uint32_t fn104 = fn % 104; /* See TS 45.008 Sections 8.3 and 8.4 for a detailed descriptions of the rules - * implemented here. We only implement the logic for Voice, not CSD */ + * implemented here. We implement the logic for both speech and data (CSD). */ + + /* AMR is special, SID frames may be scheduled dynamically at any time */ + if (lchan->tch_mode == GSM48_CMODE_SPEECH_AMR) + return false; switch (lchan->type) { case GSM_LCHAN_TCH_F: switch (lchan->tch_mode) { - case GSM48_CMODE_SIGN: + case GSM48_CMODE_SIGN: /* TCH/F sign: DTX *is* permitted */ case GSM48_CMODE_SPEECH_V1: + case GSM48_CMODE_SPEECH_V1_VAMOS: case GSM48_CMODE_SPEECH_EFR: - if (trx_sched_is_sacch_fn(lchan->ts, fn, true)) - return true; - if (ARRAY_CONTAINS(ts45008_83_tch_f, fn104)) - return true; - break; - case GSM48_CMODE_SPEECH_AMR: - if (trx_sched_is_sacch_fn(lchan->ts, fn, true)) - return true; - if (is_amr_sid_update) + case GSM48_CMODE_SPEECH_V2_VAMOS: + /* Active TDMA frame subset for TCH/F: 52, 53, 54, 55, 56, 57, 58, 59. + * There is only one *complete* block in this subset starting at FN=52. + * Incomplete blocks {... 52, 53, 54, 55} and {56, 57, 58, 59 ...} + * contain only 50% of the useful bits (partial SID) and thus ~50% BER. */ + if (fn104 == 52) return true; break; + case GSM48_CMODE_DATA_12k0: /* TCH/F9.6 */ + case GSM48_CMODE_DATA_6k0: /* TCH/F4.8 */ + /* FIXME: The RXQUAL_SUB (not RXLEV!) report shall include measurements on + * the TDMA frames given in the table of subclause 8.3 only if L2 fill frames + * have been received as FACCH/F frames at the corresponding frame positions. */ default: - LOGPFN(DMEAS, LOGL_ERROR, fn, "%s: Unsupported lchan->tch_mode %u\n", - gsm_lchan_name(lchan), lchan->tch_mode); + if (lchan->rsl_cmode == RSL_CMOD_SPD_DATA) + return fn104 == 52; + LOGPLCFN(lchan, fn, DMEAS, LOGL_ERROR, "Unsupported lchan->tch_mode %u\n", lchan->tch_mode); break; } break; case GSM_LCHAN_TCH_H: switch (lchan->tch_mode) { case GSM48_CMODE_SPEECH_V1: - if (trx_sched_is_sacch_fn(lchan->ts, fn, true)) - return true; - switch (lchan->nr) { - case 0: - if (ARRAY_CONTAINS(ts45008_83_tch_hs0, fn104)) - return true; - break; - case 1: - if (ARRAY_CONTAINS(ts45008_83_tch_hs1, fn104)) - return true; - break; - default: - OSMO_ASSERT(0); - } - break; - case GSM48_CMODE_SPEECH_AMR: - if (trx_sched_is_sacch_fn(lchan->ts, fn, true)) - return true; - if (is_amr_sid_update) + case GSM48_CMODE_SPEECH_V1_VAMOS: + if (ts45008_dtx_tchh_speech_fn_map[fn104]) return true; break; case GSM48_CMODE_SIGN: /* No DTX allowed; SUB=FULL, therefore measurements at all frame numbers are * SUB */ return true; + case GSM48_CMODE_DATA_6k0: /* TCH/H4.8 */ + case GSM48_CMODE_DATA_3k6: /* TCH/H2.4 */ + /* FIXME: The RXQUAL_SUB (not RXLEV!) report shall include measurements on + * the TDMA frames given in the table of subclause 8.3 only if L2 fill frames + * have been received as FACCH/H frames at the corresponding frame positions. */ default: - LOGPFN(DMEAS, LOGL_ERROR, fn, "%s: Unsupported lchan->tch_mode %u\n", - gsm_lchan_name(lchan), lchan->tch_mode); + if (lchan->rsl_cmode == RSL_CMOD_SPD_DATA) + return ts45008_dtx_tchh_data_fn_map[fn104] == 1; + LOGPLCFN(lchan, fn, DMEAS, LOGL_ERROR, "Unsupported lchan->tch_mode %u\n", lchan->tch_mode); break; } break; @@ -250,11 +257,6 @@ int is_meas_complete(struct gsm_lchan *lchan, uint32_t fn) int rc = 0; enum gsm_phys_chan_config pchan = ts_pchan(lchan->ts); - if (lchan->ts->nr >= 8) - return -EINVAL; - if (pchan >= _GSM_PCHAN_MAX) - return -EINVAL; - switch (pchan) { case GSM_PCHAN_TCH_F: fn_mod = translate_tch_meas_rep_fn104(fn % 104); @@ -288,9 +290,8 @@ int is_meas_complete(struct gsm_lchan *lchan, uint32_t fn) } if (rc == 1) { - DEBUGP(DMEAS, - "%s meas period end fn:%u, fn_mod:%i, status:%d, pchan:%s\n", - gsm_lchan_name(lchan), fn, fn_mod, rc, gsm_pchan_name(pchan)); + LOGPLCFN(lchan, fn, DMEAS, LOGL_DEBUG, "meas period end fn_mod:%d, status:%d, pchan:%s\n", fn_mod, + rc, gsm_pchan_name(pchan)); } return rc; @@ -305,50 +306,51 @@ static uint8_t modulus_by_lchan(struct gsm_lchan *lchan) case GSM_PCHAN_TCH_F: case GSM_PCHAN_TCH_H: return 104; - break; case GSM_PCHAN_SDCCH8_SACCH8C: case GSM_PCHAN_SDCCH8_SACCH8C_CBCH: case GSM_PCHAN_CCCH_SDCCH4: case GSM_PCHAN_CCCH_SDCCH4_CBCH: return 102; - break; default: /* Invalid */ return 1; - break; } } /* receive a L1 uplink measurement from L1 (this function is only used * internally, it is public to call it from unit-tests) */ -int lchan_new_ul_meas(struct gsm_lchan *lchan, struct bts_ul_meas *ulm, uint32_t fn) +int lchan_new_ul_meas(struct gsm_lchan *lchan, + const struct bts_ul_meas *ulm, + uint32_t fn) { uint32_t fn_mod = fn % modulus_by_lchan(lchan); + struct bts_ul_meas *dest; if (lchan->state != LCHAN_S_ACTIVE) { - LOGPFN(DMEAS, LOGL_NOTICE, fn, - "%s measurement during state: %s, num_ul_meas=%d, fn_mod=%u\n", - gsm_lchan_name(lchan), gsm_lchans_name(lchan->state), - lchan->meas.num_ul_meas, fn_mod); + LOGPLCFN(lchan, fn, DMEAS, LOGL_NOTICE, + "measurement during state: %s, num_ul_meas=%d, fn_mod=%u\n", + gsm_lchans_name(lchan->state), lchan->meas.num_ul_meas, fn_mod); } if (lchan->meas.num_ul_meas >= ARRAY_SIZE(lchan->meas.uplink)) { - LOGPFN(DMEAS, LOGL_NOTICE, fn, - "%s no space for uplink measurement, num_ul_meas=%d, fn_mod=%u\n", - gsm_lchan_name(lchan), lchan->meas.num_ul_meas, fn_mod); + LOGPLCFN(lchan, fn, DMEAS, LOGL_NOTICE, + "no space for uplink measurement, num_ul_meas=%d, fn_mod=%u\n", lchan->meas.num_ul_meas, + fn_mod); return -ENOSPC; } + dest = &lchan->meas.uplink[lchan->meas.num_ul_meas++]; + memcpy(dest, ulm, sizeof(*ulm)); + /* We expect the lower layers to mark AMR SID_UPDATE frames already as such. - * In this function, we only deal with the comon logic as per the TS 45.008 tables */ + * In this function, we only deal with the common logic as per the TS 45.008 tables */ if (!ulm->is_sub) - ulm->is_sub = ts45008_83_is_sub(lchan, fn, false); + dest->is_sub = ts45008_83_is_sub(lchan, fn); - DEBUGPFN(DMEAS, fn, "%s adding measurement (is_sub=%u), num_ul_meas=%d, fn_mod=%u\n", - gsm_lchan_name(lchan), ulm->is_sub, lchan->meas.num_ul_meas, fn_mod); - - memcpy(&lchan->meas.uplink[lchan->meas.num_ul_meas++], ulm, - sizeof(*ulm)); + LOGPLCFN(lchan, fn, DMEAS, LOGL_DEBUG, + "adding a %s measurement (ber10k=%u, ta_offs=%d, ci_cB=%d, rssi=-%u), num_ul_meas=%d, fn_mod=%u\n", + dest->is_sub ? "SUB" : "FULL", ulm->ber10k, ulm->ta_offs_256bits, ulm->ci_cb, ulm->inv_rssi, + lchan->meas.num_ul_meas, fn_mod); lchan->meas.last_fn = fn; @@ -398,11 +400,16 @@ static unsigned int lchan_meas_num_expected(const struct gsm_lchan *lchan) switch (pchan) { case GSM_PCHAN_TCH_F: - /* 24 for TCH + 1 for SACCH */ + /* 24 blocks for TCH + 1 for SACCH */ return 25; case GSM_PCHAN_TCH_H: - /* 24 half-blocks for TCH + 1 for SACCH */ - return 25; + if (lchan->tch_mode == GSM48_CMODE_SIGN) { + /* 12 blocks for TCH + 1 for SACCH */ + return 13; + } else { + /* 24 blocks for TCH + 1 for SACCH */ + return 25; + } case GSM_PCHAN_SDCCH8_SACCH8C: case GSM_PCHAN_SDCCH8_SACCH8C_CBCH: /* 2 for SDCCH + 1 for SACCH */ @@ -417,23 +424,51 @@ static unsigned int lchan_meas_num_expected(const struct gsm_lchan *lchan) } /* In DTX a subset of blocks must always be transmitted - * See also: GSM 05.08, chapter 8.3 Aspects of discontinuous transmission (DTX) */ -static unsigned int lchan_meas_sub_num_expected(const struct gsm_lchan *lchan) + * See also: GSM 05.08, chapter 8.3 Aspects of discontinuous transmission (DTX) + * Return value N: (N < 0) -- at least N SUB frames expected; + * (N > 0) -- exactly N SUB frames expected; + * (N == 0) - unknown channel type/mode? */ +static int lchan_meas_sub_num_expected(const struct gsm_lchan *lchan) { enum gsm_phys_chan_config pchan = ts_pchan(lchan->ts); - /* AMR is using a more elaborated model with a dymanic amount of - * DTX blocks so this function is not applicable to determine the - * amount of expected SUB Measurements when AMR is used */ - OSMO_ASSERT(lchan->tch_mode != GSM48_CMODE_SPEECH_AMR) - switch (pchan) { case GSM_PCHAN_TCH_F: - /* 1 block SDCCH, 2 blocks TCH */ - return 3; + if (lchan->rsl_cmode == RSL_CMOD_SPD_DATA) + return 1 + 1; /* 1 x SACCH + 1 x FACCH */ + /* else: signalling or speech */ + switch (lchan->tch_mode) { + case GSM48_CMODE_SIGN: /* TCH/F sign: DTX *is* permitted */ + case GSM48_CMODE_SPEECH_V1: /* TCH/FS */ + case GSM48_CMODE_SPEECH_V1_VAMOS: + case GSM48_CMODE_SPEECH_EFR: /* TCH/EFS */ + case GSM48_CMODE_SPEECH_V2_VAMOS: + return 1 + 1; /* 1 x SACCH + 1 x TCH */ + case GSM48_CMODE_SPEECH_AMR: /* TCH/AFS */ + case GSM48_CMODE_SPEECH_V3_VAMOS: + case GSM48_CMODE_SPEECH_V4: /* O-TCH/WFS */ + case GSM48_CMODE_SPEECH_V5: /* TCH/WFS */ + case GSM48_CMODE_SPEECH_V5_VAMOS: + default: + return -1; /* at least 1 x SACCH + M x TCH (variable) */ + } case GSM_PCHAN_TCH_H: - /* 1 block SDCCH, 4 half-blocks TCH */ - return 5; + if (lchan->rsl_cmode == RSL_CMOD_SPD_DATA) + return 1 + 2; /* 1 x SACCH + 2 x FACCH */ + /* else: signalling or speech */ + switch (lchan->tch_mode) { + case GSM48_CMODE_SIGN: /* TCH/H sign: DTX *is not* permitted */ + return 1 + 12; /* 1 x SACCH + 12 x TCH */ + case GSM48_CMODE_SPEECH_V1: + case GSM48_CMODE_SPEECH_V1_VAMOS: + return 1 + 2; /* 1 x SACCH + 2 x TCH */ + case GSM48_CMODE_SPEECH_AMR: /* TCH/AHS */ + case GSM48_CMODE_SPEECH_V3_VAMOS: + case GSM48_CMODE_SPEECH_V4: /* O-TCH/WHS */ + case GSM48_CMODE_SPEECH_V6: /* O-TCH/AHS */ + default: + return -1; /* at least 1 x SACCH + M x TCH (variable) */ + } case GSM_PCHAN_SDCCH8_SACCH8C: case GSM_PCHAN_SDCCH8_SACCH8C_CBCH: /* no DTX here, all blocks must be present! */ @@ -471,7 +506,7 @@ static void lchan_meas_compute_extended(struct gsm_lchan *lchan) /* each measurement is an int32_t, so the squared difference value must fit in 32bits */ /* the sum of the squared values (each up to 32bit) can very easily exceed 32 bits */ - u_int64_t sq_diff_sum = 0; + uint64_t sq_diff_sum = 0; /* In case we do not have any measurement values collected there is no * computation possible. We just skip the whole computation here, the @@ -542,13 +577,15 @@ int lchan_meas_check_compute(struct gsm_lchan *lchan, uint32_t fn) struct gsm_meas_rep_unidir *mru; uint32_t ber_full_sum = 0; uint32_t irssi_full_sum = 0; + int32_t ci_full_sum = 0; uint32_t ber_sub_sum = 0; uint32_t irssi_sub_sum = 0; + int32_t ci_sub_sum = 0; int32_t ta256b_sum = 0; unsigned int num_meas_sub = 0; unsigned int num_meas_sub_actual = 0; unsigned int num_meas_sub_subst = 0; - unsigned int num_meas_sub_expect; + int num_meas_sub_expect; unsigned int num_ul_meas; unsigned int num_ul_meas_actual = 0; unsigned int num_ul_meas_subst = 0; @@ -560,33 +597,25 @@ int lchan_meas_check_compute(struct gsm_lchan *lchan, uint32_t fn) if (!is_meas_complete(lchan, fn)) return 0; - LOGP(DMEAS, LOGL_DEBUG, "%s Calculating measurement results for physical channel:%s\n", - gsm_lchan_name(lchan), gsm_pchan_name(ts_pchan(lchan->ts))); + LOGPLCHAN(lchan, DMEAS, LOGL_DEBUG, "Calculating measurement results " + "for physical channel: %s\n", gsm_pchan_name(ts_pchan(lchan->ts))); /* Note: Some phys will send no measurement indication at all * when a block is lost. Also in DTX mode blocks are left out * intentionally to save energy. It is not necessarly an error * when we get less measurements as we expect. */ num_ul_meas_expect = lchan_meas_num_expected(lchan); - - if (lchan->tch_mode != GSM48_CMODE_SPEECH_AMR) - num_meas_sub_expect = lchan_meas_sub_num_expected(lchan); - else { - /* FIXME: the amount of SUB Measurements is a dynamic parameter - * in AMR and can not be determined by using a lookup table. - * See also: OS#2978 */ - num_meas_sub_expect = 0; - } + num_meas_sub_expect = lchan_meas_sub_num_expected(lchan); if (lchan->meas.num_ul_meas > num_ul_meas_expect) num_ul_meas_excess = lchan->meas.num_ul_meas - num_ul_meas_expect; num_ul_meas = num_ul_meas_expect; - LOGP(DMEAS, LOGL_DEBUG, "%s received %u UL measurements, expected %u\n", gsm_lchan_name(lchan), - lchan->meas.num_ul_meas, num_ul_meas_expect); + LOGPLCHAN(lchan, DMEAS, LOGL_DEBUG, "Received %u UL measurements, expected %u\n", + lchan->meas.num_ul_meas, num_ul_meas_expect); if (num_ul_meas_excess) - LOGP(DMEAS, LOGL_DEBUG, "%s received %u excess UL measurements\n", gsm_lchan_name(lchan), - num_ul_meas_excess); + LOGPLCHAN(lchan, DMEAS, LOGL_DEBUG, "Received %u excess UL measurements\n", + num_ul_meas_excess); /* Measurement computation step 1: add up */ for (i = 0; i < num_ul_meas; i++) { @@ -610,18 +639,24 @@ int lchan_meas_check_compute(struct gsm_lchan *lchan, uint32_t fn) m = &lchan->meas.uplink[i + num_ul_meas_excess]; if (m->is_sub) { irssi_sub_sum += m->inv_rssi; + ci_sub_sum += m->ci_cb; num_meas_sub_actual++; is_sub = true; } irssi_full_sum += m->inv_rssi; ta256b_sum += m->ta_offs_256bits; + ci_full_sum += m->ci_cb; num_ul_meas_actual++; } else { m = &measurement_dummy; - if (num_ul_meas_expect - i <= num_meas_sub_expect - num_meas_sub) { - num_meas_sub_subst++; - is_sub = true; + + /* only if we know the exact number of SUB measurements */ + if (num_meas_sub_expect >= 0) { + if (num_meas_sub < num_meas_sub_expect) { + num_meas_sub_subst++; + is_sub = true; + } } num_ul_meas_subst++; @@ -634,50 +669,71 @@ int lchan_meas_check_compute(struct gsm_lchan *lchan, uint32_t fn) } } - LOGP(DMEAS, LOGL_DEBUG, "%s received UL measurements contain %u SUB measurements, expected %u\n", - gsm_lchan_name(lchan), num_meas_sub_actual, num_meas_sub_expect); - LOGP(DMEAS, LOGL_DEBUG, "%s replaced %u measurements with dummy values, from which %u were SUB measurements\n", - gsm_lchan_name(lchan), num_ul_meas_subst, num_meas_sub_subst); - - if (num_meas_sub != num_meas_sub_expect) { - LOGP(DMEAS, LOGL_ERROR, "%s Incorrect number of SUB measurements detected! (%u vs exp %u)\n", - gsm_lchan_name(lchan), num_meas_sub, num_meas_sub_expect); - /* Normally the logic above should make sure that there is - * always the exact amount of SUB measurements taken into - * account. If not then the logic that decides tags the received - * measurements as is_sub works incorrectly. Since the logic - * above only adds missing measurements during the calculation - * it can not remove excess SUB measurements or add missing SUB - * measurements when there is no more room in the interval. */ + LOGPLCHAN(lchan, DMEAS, LOGL_DEBUG, "Replaced %u measurements with dummy values, " + "from which %u were SUB measurements\n", num_ul_meas_subst, num_meas_sub_subst); + + /* Normally the logic above should make sure that there is + * always the exact amount of SUB measurements taken into + * account. If not then the logic that decides tags the received + * measurements as is_sub works incorrectly. Since the logic + * above only adds missing measurements during the calculation + * it can not remove excess SUB measurements or add missing SUB + * measurements when there is no more room in the interval. */ + if (num_meas_sub_expect < 0) { + num_meas_sub_expect = -num_meas_sub_expect; + LOGPLCHAN(lchan, DMEAS, LOGL_DEBUG, + "Received UL measurements contain %u SUB measurements, expected at least %d\n", + num_meas_sub_actual, num_meas_sub_expect); + if (OSMO_UNLIKELY(num_meas_sub < num_meas_sub_expect)) { + LOGPLCHAN(lchan, DMEAS, LOGL_ERROR, + "Incorrect number of SUB measurements detected! " + "(%u vs exp >=%d)\n", num_meas_sub, num_meas_sub_expect); + } + } else { + LOGPLCHAN(lchan, DMEAS, LOGL_DEBUG, + "Received UL measurements contain %u SUB measurements, expected %d\n", + num_meas_sub_actual, num_meas_sub_expect); + if (OSMO_UNLIKELY(num_meas_sub != num_meas_sub_expect)) { + LOGPLCHAN(lchan, DMEAS, LOGL_ERROR, + "Incorrect number of SUB measurements detected! " + "(%u vs exp %d)\n", num_meas_sub, num_meas_sub_expect); + } } /* Measurement computation step 2: divide */ ber_full_sum = ber_full_sum / num_ul_meas; if (!irssi_full_sum) - ber_full_sum = MEASUREMENT_DUMMY_IRSSI; + irssi_full_sum = MEASUREMENT_DUMMY_IRSSI; else irssi_full_sum = irssi_full_sum / num_ul_meas_actual; - if (!num_ul_meas_actual) + if (!num_ul_meas_actual) { ta256b_sum = lchan->meas.ms_toa256; - else - ta256b_sum = ta256b_sum / num_ul_meas_actual; + ci_full_sum = lchan->meas.ul_ci_cb_full; + } else { + ta256b_sum = ta256b_sum / (signed)num_ul_meas_actual; + ci_full_sum = ci_full_sum / (signed)num_ul_meas_actual; + } if (!num_meas_sub) ber_sub_sum = MEASUREMENT_DUMMY_BER; else ber_sub_sum = ber_sub_sum / num_meas_sub; - if (!num_meas_sub_actual) + if (!num_meas_sub_actual) { irssi_sub_sum = MEASUREMENT_DUMMY_IRSSI; - else + ci_sub_sum = lchan->meas.ul_ci_cb_sub; + } else { irssi_sub_sum = irssi_sub_sum / num_meas_sub_actual; + ci_sub_sum = ci_sub_sum / (signed)num_meas_sub_actual; + } - LOGP(DMEAS, LOGL_INFO, "%s Computed TA256(% 4d) BER-FULL(%2u.%02u%%), RSSI-FULL(-%3udBm), " - "BER-SUB(%2u.%02u%%), RSSI-SUB(-%3udBm)\n", gsm_lchan_name(lchan), - ta256b_sum, ber_full_sum / 100, - ber_full_sum % 100, irssi_full_sum, ber_sub_sum / 100, ber_sub_sum % 100, irssi_sub_sum); + LOGPLCHAN(lchan, DMEAS, LOGL_INFO, + "Computed TA256(% 4d), BER-FULL(%2u.%02u%%), RSSI-FULL(-%3udBm), C/I-FULL(% 4d cB), " + "BER-SUB(%2u.%02u%%), RSSI-SUB(-%3udBm), C/I-SUB(% 4d cB)\n", + ta256b_sum, ber_full_sum / 100, ber_full_sum % 100, irssi_full_sum, ci_full_sum, + ber_sub_sum / 100, ber_sub_sum % 100, irssi_sub_sum, ci_sub_sum); /* store results */ mru = &lchan->meas.ul_res; @@ -686,11 +742,15 @@ int lchan_meas_check_compute(struct gsm_lchan *lchan, uint32_t fn) mru->full.rx_qual = ber10k_to_rxqual(ber_full_sum); mru->sub.rx_qual = ber10k_to_rxqual(ber_sub_sum); lchan->meas.ms_toa256 = ta256b_sum; + lchan->meas.ul_ci_cb_full = ci_full_sum; + lchan->meas.ul_ci_cb_sub = ci_sub_sum; - LOGP(DMEAS, LOGL_INFO, "%s UL MEAS RXLEV_FULL(%u), RXLEV_SUB(%u)," - "RXQUAL_FULL(%u), RXQUAL_SUB(%u), num_meas_sub(%u), num_ul_meas(%u) \n", - gsm_lchan_name(lchan), - mru->full.rx_lev, mru->sub.rx_lev, mru->full.rx_qual, mru->sub.rx_qual, num_meas_sub, num_ul_meas_expect); + LOGPLCHAN(lchan, DMEAS, LOGL_INFO, + "UL MEAS RXLEV_FULL(%u), RXLEV_SUB(%u), RXQUAL_FULL(%u), RXQUAL_SUB(%u), " + "num_meas_sub(%u), num_ul_meas(%u)\n", + mru->full.rx_lev, mru->sub.rx_lev, + mru->full.rx_qual, mru->sub.rx_qual, + num_meas_sub, num_ul_meas_expect); lchan->meas.flags |= LC_UL_M_F_RES_VALID; @@ -698,7 +758,7 @@ int lchan_meas_check_compute(struct gsm_lchan *lchan, uint32_t fn) lchan->meas.num_ul_meas = 0; - /* return 1 to indicte that the computation has been done and the next + /* return 1 to indicate that the computation has been done and the next * interval begins. */ return 1; } @@ -707,7 +767,9 @@ int lchan_meas_check_compute(struct gsm_lchan *lchan, uint32_t fn) * l1sap.c every time a measurement indication is received. It collects the * measurement samples and automatically detects the end of the measurement * interval. */ -int lchan_meas_process_measurement(struct gsm_lchan *lchan, struct bts_ul_meas *ulm, uint32_t fn) +int lchan_meas_process_measurement(struct gsm_lchan *lchan, + const struct bts_ul_meas *ulm, + uint32_t fn) { lchan_new_ul_meas(lchan, ulm, fn); return lchan_meas_check_compute(lchan, fn); @@ -721,3 +783,230 @@ void lchan_meas_reset(struct gsm_lchan *lchan) memset(&lchan->meas, 0, sizeof(lchan->meas)); lchan->meas.last_fn = LCHAN_FN_DUMMY; } + +static inline uint8_t ms_to2rsl(const struct gsm_lchan *lchan, uint8_t ta) +{ + return (lchan->ms_t_offs >= 0) ? lchan->ms_t_offs : (lchan->p_offs - ta); +} + +static inline bool ms_to_valid(const struct gsm_lchan *lchan) +{ + return (lchan->ms_t_offs >= 0) || (lchan->p_offs >= 0); +} + +/* Decide if repeated FACCH should be applied or not. If RXQUAL level, that the + * MS reports is high enough, FACCH repetition is not needed. */ +static void repeated_dl_facch_active_decision(struct gsm_lchan *lchan, + const struct gsm48_meas_res *meas_res) +{ + uint8_t upper; + uint8_t lower; + uint8_t rxqual; + bool prev_repeated_dl_facch_active = lchan->rep_acch.dl_facch_active; + + /* This is an optimization so that we exit as quickly as possible if + * there are no FACCH repetition capabilities present. However If the + * repeated FACCH capabilities vanish for whatever reason, we must be + * sure that FACCH repetition is disabled. */ + if (!lchan->rep_acch_cap.dl_facch_cmd + && !lchan->rep_acch_cap.dl_facch_all) { + lchan->rep_acch.dl_facch_active = false; + goto out; + } + + /* Threshold disabled (always on) */ + if (lchan->rep_acch_cap.rxqual == 0) { + lchan->rep_acch.dl_facch_active = true; + goto out; + } + + /* When the MS sets the SRR bit in the UL-SACCH L1 header + * (repeated SACCH requested) then it makes sense to enable + * FACCH repetition too. */ + if (lchan->meas.l1_info.srr_sro) { + lchan->rep_acch.dl_facch_active = true; + goto out; + } + + /* Parse MS measurement results */ + if (meas_res == NULL) + goto out; + if (!gsm48_meas_res_is_valid(meas_res)) + goto out; + + /* If the RXQUAL level at the MS drops under a certain threshold + * we enable FACCH repetition. */ + upper = lchan->rep_acch_cap.rxqual; + if (upper > 2) + lower = lchan->rep_acch_cap.rxqual - 2; + else + lower = 0; + + /* When downlink DTX is applied, use RXQUAL-SUB, otherwise use + * RXQUAL-FULL. */ + if (meas_res->dtx_used) + rxqual = meas_res->rxqual_sub; + else + rxqual = meas_res->rxqual_full; + + if (rxqual >= upper) + lchan->rep_acch.dl_facch_active = true; + else if (rxqual <= lower) + lchan->rep_acch.dl_facch_active = false; + +out: + if (lchan->rep_acch.dl_facch_active == prev_repeated_dl_facch_active) + return; + if (lchan->rep_acch.dl_facch_active) + LOGPLCHAN(lchan, DL1P, LOGL_DEBUG, "DL-FACCH repetition: inactive => active\n"); + else + LOGPLCHAN(lchan, DL1P, LOGL_DEBUG, "DL-FACCH repetition: active => inactive\n"); +} + +static void acch_overpower_active_decision(struct gsm_lchan *lchan, + const struct gsm48_meas_res *meas_res) +{ + const bool old = lchan->top_acch_active; + uint8_t upper, lower, rxqual; + + /* ACCH overpower is not allowed => nothing to do */ + if (lchan->top_acch_cap.overpower_db == 0) + return; + /* RxQual threshold is disabled => overpower is always on */ + if (lchan->top_acch_cap.rxqual == 0) + return; + + /* If DTx is active on Downlink, use the '-SUB' */ + if (meas_res->dtx_used) + rxqual = meas_res->rxqual_sub; + else /* ... otherwise use the '-FULL' */ + rxqual = meas_res->rxqual_full; + + upper = lchan->top_acch_cap.rxqual; + if (upper > 2) + lower = upper - 2; + else + lower = 0; + + if (rxqual >= upper) + lchan->top_acch_active = true; + else if (rxqual <= lower) + lchan->top_acch_active = false; + + if (lchan->top_acch_active != old) { + LOGPLCHAN(lchan, DL1P, LOGL_DEBUG, "Temporary ACCH overpower: %s\n", + lchan->top_acch_active ? "inactive => active" + : "active => inactive"); + } +} + +static bool data_is_rr_meas_rep(const uint8_t *data) +{ + const struct gsm48_hdr *gh = (void *)(data + 5); + const uint8_t *lapdm_hdr = (void *)(data + 2); + + /* LAPDm address field: SAPI=0, C/R=0, EA=1 */ + if (lapdm_hdr[0] != 0x01) + return false; + /* LAPDm control field: U, func=UI */ + if (lapdm_hdr[1] != 0x03) + return false; + /* Protocol discriminator: RR */ + if (gh->proto_discr != GSM48_PDISC_RR) + return false; + + switch (gh->msg_type) { + case GSM48_MT_RR_EXT_MEAS_REP: + case GSM48_MT_RR_MEAS_REP: + return true; + default: + return false; + } +} + +/* Called every time a SACCH block is received from lower layers */ +void lchan_meas_handle_sacch(struct gsm_lchan *lchan, struct msgb *msg) +{ + const struct gsm48_meas_res *mr = NULL; + const struct gsm48_hdr *gh = NULL; + int timing_offset, rc; + bool dtxu_used = true; /* safe default assumption */ + uint8_t ms_pwr; + uint8_t ms_ta; + int8_t ul_rssi; + int16_t ul_ci_cb; + uint8_t *l3; + unsigned int l3_len; + + if (msgb_l2len(msg) == GSM_MACBLOCK_LEN) { + /* Some brilliant engineer decided that the ordering of + * fields on the Um interface is different from the + * order of fields in RSL. See 3GPP TS 44.004 (section 7.2) + * vs. 3GPP TS 48.058 (section 9.3.10). */ + const struct gsm_sacch_l1_hdr *l1h = msgb_l2(msg); + lchan->meas.l1_info.ms_pwr = l1h->ms_pwr; + lchan->meas.l1_info.fpc_epc = l1h->fpc_epc; + lchan->meas.l1_info.srr_sro = l1h->srr_sro; + lchan->meas.l1_info.ta = l1h->ta; + lchan->meas.flags |= LC_UL_M_F_L1_VALID; + + /* Check if this is a Measurement Report */ + if (data_is_rr_meas_rep(msgb_l2(msg))) { + /* Skip both L1 SACCH and LAPDm headers */ + msg->l3h = (void *)(msg->l2h + 2 + 3); + gh = msgb_l3(msg); + } + + ms_pwr = lchan->meas.l1_info.ms_pwr; + ms_ta = lchan->meas.l1_info.ta; + } else { + lchan->meas.flags &= ~LC_UL_M_F_L1_VALID; + ms_pwr = lchan->ms_power_ctrl.current; + ms_ta = lchan->ta_ctrl.current; + } + + timing_offset = ms_to_valid(lchan) ? ms_to2rsl(lchan, ms_ta) : -1; + l3 = msgb_l3(msg); + l3_len = l3 ? msgb_l3len(msg) : 0; + rc = rsl_tx_meas_res(lchan, l3, l3_len, timing_offset); + if (rc == 0) /* Count successful transmissions */ + lchan->meas.res_nr++; + + /* Run control loops now that we have all the information: */ + /* 3GPP TS 45.008 sec 4.2: UL L1 SACCH Header contains TA and + * MS_PWR used "for the last burst of the previous SACCH + * period". Since MS must use the values provided in DL SACCH + * starting at next meas period, the value of the "last burst" + * is actually the value used in the entire meas period. Since + * it contains info about the previous meas period, we want to + * feed the Control Loop with the measurements for the same + * period (the previous one), which is stored in lchan->meas(.ul_res): + */ + if (gh && gh->msg_type == GSM48_MT_RR_MEAS_REP) { + mr = (const struct gsm48_meas_res *)gh->data; + if (gsm48_meas_res_is_valid(mr)) + dtxu_used = mr->dtx_used; + } + + if (dtxu_used) { + ul_rssi = rxlev2dbm(lchan->meas.ul_res.sub.rx_lev); + ul_ci_cb = lchan->meas.ul_ci_cb_sub; + } else { + ul_rssi = rxlev2dbm(lchan->meas.ul_res.full.rx_lev); + ul_ci_cb = lchan->meas.ul_ci_cb_full; + } + lchan_ms_ta_ctrl(lchan, ms_ta, lchan->meas.ms_toa256); + lchan_ms_pwr_ctrl(lchan, ms_pwr, ul_rssi, ul_ci_cb); + if (mr && gsm48_meas_res_is_valid(mr)) { + lchan_bs_pwr_ctrl(lchan, mr); + acch_overpower_active_decision(lchan, mr); + } + + repeated_dl_facch_active_decision(lchan, mr); + + /* Reset state for next iteration */ + lchan->tch.dtx.dl_active = false; + lchan->meas.flags &= ~LC_UL_M_F_OSMO_EXT_VALID; + lchan->ms_t_offs = -1; + lchan->p_offs = -1; +} diff --git a/src/common/msg_utils.c b/src/common/msg_utils.c index 52b05668..04a0c344 100644 --- a/src/common/msg_utils.c +++ b/src/common/msg_utils.c @@ -10,7 +10,7 @@ * 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. + * 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/>. @@ -23,6 +23,7 @@ #include <osmo-bts/oml.h> #include <osmo-bts/amr.h> #include <osmo-bts/rsl.h> +#include <osmo-bts/bts.h> #include <osmocom/gsm/protocol/ipaccess.h> #include <osmocom/gsm/protocol/gsm_12_21.h> @@ -320,17 +321,17 @@ static inline bool dtx_amr_sid_optional(struct gsm_lchan *lchan, uint32_t fn) already: we rely here on the order of RTS arrival from L1 - we expect that PH-DATA.req ALWAYS comes before PH-TCH.req for the same FN */ - if(lchan->type == GSM_LCHAN_TCH_H) { + if (lchan->type == GSM_LCHAN_TCH_H) { if (lchan->tch.dtx.fn != LCHAN_FN_DUMMY && lchan->tch.dtx.fn != LCHAN_FN_WAIT) { /* FACCH interruption is over */ dtx_dispatch(lchan, E_COMPL); return false; - } else if(lchan->tch.dtx.fn == LCHAN_FN_DUMMY) { + } else if (lchan->tch.dtx.fn == LCHAN_FN_DUMMY) { lchan->tch.dtx.fn = LCHAN_FN_WAIT; } else lchan->tch.dtx.fn = fn; - } else if(lchan->type == GSM_LCHAN_TCH_F) { + } else if (lchan->type == GSM_LCHAN_TCH_F) { if (lchan->tch.dtx.fn != LCHAN_FN_DUMMY) { /* FACCH interruption is over */ dtx_dispatch(lchan, E_COMPL); @@ -379,15 +380,19 @@ static inline bool dtx_sched_optional(struct gsm_lchan *lchan, uint32_t fn) static const uint8_t f[] = { 52, 53, 54, 55, 56, 57, 58, 59 }, h0[] = { 0, 2, 4, 6, 52, 54, 56, 58 }, h1[] = { 14, 16, 18, 20, 66, 68, 70, 72 }; - if (lchan->tch_mode == GSM48_CMODE_SPEECH_V1) { + switch (lchan->tch_mode) { + case GSM48_CMODE_SPEECH_V1: if (lchan->type == GSM_LCHAN_TCH_F) return fn_chk(f, fn, ARRAY_SIZE(f)); else return fn_chk(lchan->nr ? h1 : h0, fn, lchan->nr ? ARRAY_SIZE(h1) : ARRAY_SIZE(h0)); + case GSM48_CMODE_SPEECH_EFR: + return fn_chk(f, fn, ARRAY_SIZE(f)); + default: + return false; } - return false; } /*! \brief Check if DTX DL AMR is enabled for a given lchan (it have proper type, @@ -465,10 +470,6 @@ void dtx_int_signal(struct gsm_lchan *lchan) */ uint8_t repeat_last_sid(struct gsm_lchan *lchan, uint8_t *dst, uint32_t fn) { - /* FIXME: add EFR support */ - if (lchan->tch_mode == GSM48_CMODE_SPEECH_EFR) - return 0; - if (lchan->tch_mode != GSM48_CMODE_SPEECH_AMR) { if (dtx_sched_optional(lchan, fn)) return 0; diff --git a/src/common/nm_bb_transc_fsm.c b/src/common/nm_bb_transc_fsm.c new file mode 100644 index 00000000..b173102e --- /dev/null +++ b/src/common/nm_bb_transc_fsm.c @@ -0,0 +1,325 @@ +/* NM Radio Carrier FSM */ + +/* (C) 2020 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> + * Author: Pau Espin Pedrol <pespin@sysmocom.de> + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU 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 <inttypes.h> + +#include <osmocom/core/fsm.h> +#include <osmocom/core/tdef.h> +#include <osmocom/gsm/protocol/gsm_12_21.h> + +#include <osmo-bts/logging.h> +#include <osmo-bts/gsm_data.h> +#include <osmo-bts/bts_model.h> +#include <osmo-bts/bts.h> +#include <osmo-bts/rsl.h> +#include <osmo-bts/nm_common_fsm.h> +#include <osmo-bts/phy_link.h> + +#define X(s) (1 << (s)) + +#define nm_bb_transc_fsm_state_chg(fi, NEXT_STATE) \ + osmo_fsm_inst_state_chg(fi, NEXT_STATE, 0, 0) + +static void ev_dispatch_children(struct gsm_bts_bb_trx *bb_transc, uint32_t event) +{ + struct gsm_bts_trx *trx = gsm_bts_bb_trx_get_trx(bb_transc); + uint8_t tn; + + for (tn = 0; tn < TRX_NR_TS; tn++) { + struct gsm_bts_trx_ts *ts = &trx->ts[tn]; + osmo_fsm_inst_dispatch(ts->mo.fi, event, NULL); + } +} + +////////////////////////// +// FSM STATE ACTIONS +////////////////////////// + +static void st_op_disabled_notinstalled_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct gsm_bts_bb_trx *bb_transc = (struct gsm_bts_bb_trx *)fi->priv; + /* Reset state: */ + TALLOC_FREE(bb_transc->mo.nm_attr); + + bb_transc->mo.setattr_success = false; + bb_transc->mo.opstart_success = false; + oml_mo_state_chg(&bb_transc->mo, NM_OPSTATE_DISABLED, NM_AVSTATE_NOT_INSTALLED, NM_STATE_LOCKED); +} + +static void st_op_disabled_notinstalled(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_bts_bb_trx *bb_transc = (struct gsm_bts_bb_trx *)fi->priv; + + switch (event) { + case NM_EV_SW_ACT: + oml_mo_tx_sw_act_rep(&bb_transc->mo); + nm_bb_transc_fsm_state_chg(fi, NM_BBTRANSC_ST_OP_DISABLED_OFFLINE); + ev_dispatch_children(bb_transc, event); + return; + case NM_EV_OML_UP: + /* Report current state: */ + oml_tx_state_changed(&bb_transc->mo); + ev_dispatch_children(bb_transc, event); + return; + case NM_EV_RSL_UP: + return; + case NM_EV_RSL_DOWN: + return; + case NM_EV_PHYLINK_UP: + return; + case NM_EV_PHYLINK_DOWN: + return; + case NM_EV_DISABLE: + return; + default: + OSMO_ASSERT(0); + } +} + +static void st_op_disabled_offline_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct gsm_bts_bb_trx *bb_transc = (struct gsm_bts_bb_trx *)fi->priv; + struct gsm_bts_trx *trx = gsm_bts_bb_trx_get_trx(bb_transc); + int i; + + bb_transc->mo.setattr_success = false; + bb_transc->mo.opstart_success = false; + oml_mo_state_chg(&bb_transc->mo, NM_OPSTATE_DISABLED, NM_AVSTATE_OFF_LINE, -1); + + if (prev_state == NM_BBTRANSC_ST_OP_ENABLED) { + for (i = 0; i < TRX_NR_TS; i++) { + struct gsm_bts_trx_ts *ts = &trx->ts[i]; + osmo_fsm_inst_dispatch(ts->mo.fi, NM_EV_BBTRANSC_DISABLED, NULL); + } + } +} + +static void st_op_disabled_offline(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_bts_bb_trx *bb_transc = (struct gsm_bts_bb_trx *)fi->priv; + struct gsm_bts_trx *trx = gsm_bts_bb_trx_get_trx(bb_transc); + struct gsm_bts *bts = trx->bts; + struct nm_fsm_ev_setattr_data *setattr_data; + bool phy_state_connected; + bool rsl_link_connected; + int rc; + + switch (event) { + case NM_EV_OML_UP: + /* Report current state: */ + oml_tx_state_changed(&bb_transc->mo); + ev_dispatch_children(bb_transc, event); + return; + case NM_EV_RX_SETATTR: + setattr_data = (struct nm_fsm_ev_setattr_data *)data; + rc = bts_model_apply_oml(trx->bts, setattr_data->msg, + &bb_transc->mo, bb_transc); + bb_transc->mo.setattr_success = rc == 0; + oml_fom_ack_nack_copy_msg(setattr_data->msg, rc); + return; + case NM_EV_RX_OPSTART: +#if 0 + /* Disabled because osmo-bsc doesn't send SetAttr on BB_TRANSC object */ + if (!bb_transc->mo.setattr_success) { + oml_mo_opstart_nack(&bb_transc->mo, NM_NACK_CANT_PERFORM); + return; + } +#endif + /* Connect RSL link: */ + if (bts->variant == BTS_OSMO_OMLDUMMY) { + LOGPFSML(fi, LOGL_NOTICE, "Not connecting RSL in OML-DUMMY!\n"); + } else { + rc = e1inp_ipa_bts_rsl_connect_n(bts->oml_link->ts->line, + bb_transc->rsl.rem_addrstr.ip, + bb_transc->rsl.rem_addrstr.port, trx->nr); + if (rc < 0) { + LOGPFSML(fi, LOGL_NOTICE, "Error connecting IPA RSL: %d\n", rc); + oml_mo_opstart_nack(&bb_transc->mo, NM_NACK_CANT_PERFORM); + return; + } + } + bts_model_opstart(trx->bts, &bb_transc->mo, bb_transc); + return; + case NM_EV_OPSTART_ACK: + bb_transc->mo.opstart_success = true; + oml_mo_opstart_ack(&bb_transc->mo); + break; /* check statechg below */ + case NM_EV_OPSTART_NACK: + bb_transc->mo.opstart_success = false; + oml_mo_opstart_nack(&bb_transc->mo, (int)(intptr_t)data); + return; + case NM_EV_RSL_UP: + break; /* check statechg below */ + case NM_EV_RSL_DOWN: + return; + case NM_EV_PHYLINK_UP: + break; /* check statechg below */ + case NM_EV_PHYLINK_DOWN: + return; + case NM_EV_DISABLE: + return; + default: + OSMO_ASSERT(0); + } + + + if (bts->variant != BTS_OSMO_OMLDUMMY) { /* In OMLDUMMY, phy=NULL */ + struct phy_instance *pinst = trx_phy_instance(trx); + phy_state_connected = phy_link_state_get(pinst->phy_link) == PHY_LINK_CONNECTED; + rsl_link_connected = !!trx->bb_transc.rsl.link; + } else { + phy_state_connected = true; + rsl_link_connected = true; + } + + /* We so far don't expect any SetAttributes for this NM object */ + if (rsl_link_connected && phy_state_connected && + bb_transc->mo.opstart_success) { + nm_bb_transc_fsm_state_chg(fi, NM_BBTRANSC_ST_OP_ENABLED); + } else { + LOGPFSML(fi, LOGL_INFO, "Delay switch to operative state Enabled, wait for:%s%s%s\n", + rsl_link_connected ? "" : " rsl", + phy_state_connected ? "" : " phy", + bb_transc->mo.opstart_success ? "" : " opstart"); + + } +} + +static void st_op_enabled_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct gsm_bts_bb_trx *bb_transc = (struct gsm_bts_bb_trx *)fi->priv; + struct gsm_bts_trx *trx = gsm_bts_bb_trx_get_trx(bb_transc); + uint8_t tn; + + oml_mo_state_chg(&bb_transc->mo, NM_OPSTATE_ENABLED, NM_AVSTATE_OK, -1); + /* Mark Dependency TS as Offline (ready to be Opstarted) */ + for (tn = 0; tn < TRX_NR_TS; tn++) { + struct gsm_bts_trx_ts *ts = &trx->ts[tn]; + osmo_fsm_inst_dispatch(ts->mo.fi, NM_EV_BBTRANSC_ENABLED, NULL); + } +} + +static void st_op_enabled(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + switch (event) { + case NM_EV_RSL_DOWN: + break; + case NM_EV_PHYLINK_DOWN: + break; + case NM_EV_DISABLE: + break; + default: + OSMO_ASSERT(0); + } + + nm_bb_transc_fsm_state_chg(fi, NM_BBTRANSC_ST_OP_DISABLED_OFFLINE); +} + +static void nm_bb_transc_allstate(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_bts_bb_trx *bb_transc = (struct gsm_bts_bb_trx *)fi->priv; + + switch (event) { + case NM_EV_SHUTDOWN_START: + /* Announce we start shutting down */ + oml_mo_state_chg(&bb_transc->mo, -1, -1, NM_STATE_SHUTDOWN); + + /* Propagate event to children: */ + ev_dispatch_children(bb_transc, event); + break; + case NM_EV_SHUTDOWN_FINISH: + /* Propagate event to children: */ + ev_dispatch_children(bb_transc, event); + nm_bb_transc_fsm_state_chg(fi, NM_BBTRANSC_ST_OP_DISABLED_NOTINSTALLED); + break; + default: + OSMO_ASSERT(false); + } +} + +static struct osmo_fsm_state nm_bb_transc_fsm_states[] = { + [NM_BBTRANSC_ST_OP_DISABLED_NOTINSTALLED] = { + .in_event_mask = + X(NM_EV_SW_ACT) | + X(NM_EV_OML_UP) | + X(NM_EV_RSL_UP) | + X(NM_EV_RSL_DOWN) | + X(NM_EV_PHYLINK_UP) | + X(NM_EV_PHYLINK_DOWN) | + X(NM_EV_DISABLE), + .out_state_mask = + X(NM_BBTRANSC_ST_OP_DISABLED_NOTINSTALLED) | + X(NM_BBTRANSC_ST_OP_DISABLED_OFFLINE), + .name = "DISABLED_NOTINSTALLED", + .onenter = st_op_disabled_notinstalled_on_enter, + .action = st_op_disabled_notinstalled, + }, + [NM_BBTRANSC_ST_OP_DISABLED_OFFLINE] = { + .in_event_mask = + X(NM_EV_OML_UP) | + X(NM_EV_RX_SETATTR) | + X(NM_EV_RX_OPSTART) | + X(NM_EV_OPSTART_ACK) | + X(NM_EV_OPSTART_NACK) | + X(NM_EV_RSL_UP) | + X(NM_EV_RSL_DOWN) | + X(NM_EV_PHYLINK_UP) | + X(NM_EV_PHYLINK_DOWN) | + X(NM_EV_DISABLE), + .out_state_mask = + X(NM_BBTRANSC_ST_OP_DISABLED_NOTINSTALLED) | + X(NM_BBTRANSC_ST_OP_ENABLED), + .name = "DISABLED_OFFLINE", + .onenter = st_op_disabled_offline_on_enter, + .action = st_op_disabled_offline, + }, + [NM_BBTRANSC_ST_OP_ENABLED] = { + .in_event_mask = + X(NM_EV_RSL_DOWN) | + X(NM_EV_PHYLINK_DOWN) | + X(NM_EV_DISABLE), + .out_state_mask = + X(NM_BBTRANSC_ST_OP_DISABLED_NOTINSTALLED) | + X(NM_BBTRANSC_ST_OP_DISABLED_OFFLINE), + .name = "ENABLED", + .onenter = st_op_enabled_on_enter, + .action = st_op_enabled, + }, +}; + +struct osmo_fsm nm_bb_transc_fsm = { + .name = "NM_BBTRANSC_OP", + .states = nm_bb_transc_fsm_states, + .num_states = ARRAY_SIZE(nm_bb_transc_fsm_states), + .event_names = nm_fsm_event_names, + .allstate_action = nm_bb_transc_allstate, + .allstate_event_mask = X(NM_EV_SHUTDOWN_START) | + X(NM_EV_SHUTDOWN_FINISH), + .log_subsys = DOML, +}; + +static __attribute__((constructor)) void nm_bb_transc_fsm_init(void) +{ + OSMO_ASSERT(osmo_fsm_register(&nm_bb_transc_fsm) == 0); +} diff --git a/src/common/nm_bts_fsm.c b/src/common/nm_bts_fsm.c new file mode 100644 index 00000000..36aad733 --- /dev/null +++ b/src/common/nm_bts_fsm.c @@ -0,0 +1,230 @@ +/* NM BTS FSM */ + +/* (C) 2020 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> + * Author: Pau Espin Pedrol <pespin@sysmocom.de> + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU 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 <inttypes.h> + +#include <osmocom/core/fsm.h> +#include <osmocom/core/tdef.h> +#include <osmocom/gsm/protocol/gsm_12_21.h> + +#include <osmo-bts/logging.h> +#include <osmo-bts/gsm_data.h> +#include <osmo-bts/bts_model.h> +#include <osmo-bts/bts.h> +#include <osmo-bts/rsl.h> +#include <osmo-bts/nm_common_fsm.h> +#include <osmo-bts/phy_link.h> +#include <osmo-bts/cbch.h> +#include <osmo-bts/notification.h> + +#define X(s) (1 << (s)) + +#define nm_bts_fsm_state_chg(fi, NEXT_STATE) \ + osmo_fsm_inst_state_chg(fi, NEXT_STATE, 0, 0) + +static void ev_dispatch_children(struct gsm_bts *bts, uint32_t event) +{ + struct gsm_bts_trx *trx; + llist_for_each_entry(trx, &bts->trx_list, list) { + osmo_fsm_inst_dispatch(trx->mo.fi, event, NULL); + osmo_fsm_inst_dispatch(trx->bb_transc.mo.fi, event, NULL); + } +} + +////////////////////////// +// FSM STATE ACTIONS +////////////////////////// + +static void st_op_disabled_notinstalled_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct gsm_bts *bts = (struct gsm_bts *)fi->priv; + /* Reset state: */ + bts->si_valid = 0; + bts->bsic_configured = false; + bts->bsic = 0xff; /* invalid value */ + TALLOC_FREE(bts->mo.nm_attr); + bts_cbch_reset(bts); + bts_asci_notification_reset(bts); + if (bts->c0_power_red_db > 0) + bts_set_c0_pwr_red(bts, 0); + + bts->mo.setattr_success = false; + bts->mo.opstart_success = false; + oml_mo_state_chg(&bts->mo, NM_OPSTATE_DISABLED, NM_AVSTATE_NOT_INSTALLED, NM_STATE_LOCKED); +} + +static void st_op_disabled_notinstalled(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_bts *bts = (struct gsm_bts *)fi->priv; + struct gsm_bts_trx *trx; + + switch (event) { + case NM_EV_OML_UP: + /* automatic SW_ACT upon OML link establishment: */ + oml_mo_tx_sw_act_rep(&bts->mo); + + llist_for_each_entry(trx, &bts->trx_list, list) { + if (trx->bts->variant == BTS_OSMO_OMLDUMMY) /* In OMLDUMMY, phy=NULL */ + continue; + /* During startup, phy_links are already opened, but if we are + * re-connecting, phy_link was closed when disconnected from + * previous BSC, so let's re-open it. + */ + struct phy_instance *pinst = trx_phy_instance(trx); + struct phy_link *plink = pinst->phy_link; + if (phy_link_state_get(plink) == PHY_LINK_SHUTDOWN) + bts_model_phy_link_open(plink); + } + + nm_bts_fsm_state_chg(fi, NM_BTS_ST_OP_DISABLED_OFFLINE); + ev_dispatch_children(bts, event); + return; + default: + OSMO_ASSERT(0); + } +} + +static void st_op_disabled_offline_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct gsm_bts *bts = (struct gsm_bts *)fi->priv; + bts->mo.setattr_success = false; + bts->mo.opstart_success = false; + oml_mo_state_chg(&bts->mo, NM_OPSTATE_DISABLED, NM_AVSTATE_OFF_LINE, -1); +} + +static void st_op_disabled_offline(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_bts *bts = (struct gsm_bts *)fi->priv; + struct nm_fsm_ev_setattr_data *setattr_data; + int rc; + + switch (event) { + case NM_EV_RX_SETATTR: + setattr_data = (struct nm_fsm_ev_setattr_data *)data; + rc = bts_model_apply_oml(bts, setattr_data->msg, &bts->mo, bts); + bts->mo.setattr_success = rc == 0; + oml_fom_ack_nack_copy_msg(setattr_data->msg, rc); + break; + case NM_EV_RX_OPSTART: + if (!bts->mo.setattr_success) { + oml_mo_opstart_nack(&bts->mo, NM_NACK_CANT_PERFORM); + return; + } + bts_model_opstart(bts, &bts->mo, bts); + break; + case NM_EV_OPSTART_ACK: + bts->mo.opstart_success = true; + oml_mo_opstart_ack(&bts->mo); + nm_bts_fsm_state_chg(fi, NM_BTS_ST_OP_ENABLED); + break; /* check statechg below */ + case NM_EV_OPSTART_NACK: + bts->mo.opstart_success = false; + oml_mo_opstart_nack(&bts->mo, (int)(intptr_t)data); + return; + default: + OSMO_ASSERT(0); + } +} + +static void st_op_enabled_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct gsm_bts *bts = (struct gsm_bts *)fi->priv; + oml_mo_state_chg(&bts->mo, NM_OPSTATE_ENABLED, NM_AVSTATE_OK, -1); +} + +static void st_op_enabled(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ +} + +static void nm_bts_allstate(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_bts *bts = (struct gsm_bts *)fi->priv; + + switch (event) { + case NM_EV_SHUTDOWN_START: + /* Announce we start shutting down */ + oml_mo_state_chg(&bts->mo, -1, -1, NM_STATE_SHUTDOWN); + + /* Propagate event to children: */ + ev_dispatch_children(bts, event); + break; + case NM_EV_SHUTDOWN_FINISH: + /* Propagate event to children: */ + ev_dispatch_children(bts, event); + nm_bts_fsm_state_chg(fi, NM_BTS_ST_OP_DISABLED_NOTINSTALLED); + break; + default: + OSMO_ASSERT(false); + } +} + +static struct osmo_fsm_state nm_bts_fsm_states[] = { + [NM_BTS_ST_OP_DISABLED_NOTINSTALLED] = { + .in_event_mask = + X(NM_EV_OML_UP), + .out_state_mask = + X(NM_BTS_ST_OP_DISABLED_NOTINSTALLED) | + X(NM_BTS_ST_OP_DISABLED_OFFLINE), + .name = "DISABLED_NOTINSTALLED", + .onenter = st_op_disabled_notinstalled_on_enter, + .action = st_op_disabled_notinstalled, + }, + [NM_BTS_ST_OP_DISABLED_OFFLINE] = { + .in_event_mask = + X(NM_EV_RX_SETATTR) | + X(NM_EV_RX_OPSTART) | + X(NM_EV_OPSTART_ACK) | + X(NM_EV_OPSTART_NACK), + .out_state_mask = + X(NM_BTS_ST_OP_DISABLED_NOTINSTALLED) | + X(NM_BTS_ST_OP_ENABLED), + .name = "DISABLED_OFFLINE", + .onenter = st_op_disabled_offline_on_enter, + .action = st_op_disabled_offline, + }, + [NM_BTS_ST_OP_ENABLED] = { + .in_event_mask = 0, + .out_state_mask = + X(NM_BTS_ST_OP_DISABLED_NOTINSTALLED), + .name = "ENABLED", + .onenter = st_op_enabled_on_enter, + .action = st_op_enabled, + }, +}; + +struct osmo_fsm nm_bts_fsm = { + .name = "NM_BTS_OP", + .states = nm_bts_fsm_states, + .num_states = ARRAY_SIZE(nm_bts_fsm_states), + .event_names = nm_fsm_event_names, + .allstate_action = nm_bts_allstate, + .allstate_event_mask = X(NM_EV_SHUTDOWN_START) | + X(NM_EV_SHUTDOWN_FINISH), + .log_subsys = DOML, +}; + +static __attribute__((constructor)) void nm_bts_fsm_init(void) +{ + OSMO_ASSERT(osmo_fsm_register(&nm_bts_fsm) == 0); +} diff --git a/src/common/nm_bts_sm_fsm.c b/src/common/nm_bts_sm_fsm.c new file mode 100644 index 00000000..9ac0edd9 --- /dev/null +++ b/src/common/nm_bts_sm_fsm.c @@ -0,0 +1,209 @@ +/* NM BTS Site Manager FSM */ + +/* (C) 2020 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> + * Author: Pau Espin Pedrol <pespin@sysmocom.de> + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU 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 <inttypes.h> + +#include <osmocom/core/fsm.h> +#include <osmocom/core/tdef.h> +#include <osmocom/gsm/protocol/gsm_12_21.h> + +#include <osmo-bts/logging.h> +#include <osmo-bts/gsm_data.h> +#include <osmo-bts/bts_model.h> +#include <osmo-bts/bts.h> +#include <osmo-bts/bts_sm.h> +#include <osmo-bts/rsl.h> +#include <osmo-bts/nm_common_fsm.h> +#include <osmo-bts/phy_link.h> + +#define X(s) (1 << (s)) + +#define nm_bts_sm_fsm_state_chg(fi, NEXT_STATE) \ + osmo_fsm_inst_state_chg(fi, NEXT_STATE, 0, 0) + + +static void ev_dispatch_children(struct gsm_bts_sm *site_mgr, uint32_t event) +{ + struct gsm_bts *bts; + osmo_fsm_inst_dispatch(site_mgr->gprs.nse.mo.fi, event, NULL); + llist_for_each_entry(bts, &site_mgr->bts_list, list) { + osmo_fsm_inst_dispatch(bts->mo.fi, event, NULL); + } +} + +////////////////////////// +// FSM STATE ACTIONS +////////////////////////// + +static void st_op_disabled_notinstalled_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct gsm_bts_sm *site_mgr = (struct gsm_bts_sm *)fi->priv; + site_mgr->mo.setattr_success = false; + site_mgr->mo.opstart_success = false; + oml_mo_state_chg(&site_mgr->mo, NM_OPSTATE_DISABLED, NM_AVSTATE_NOT_INSTALLED, NM_STATE_LOCKED); +} + +static void st_op_disabled_notinstalled(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_bts_sm *site_mgr = (struct gsm_bts_sm *)fi->priv; + + switch (event) { + case NM_EV_OML_UP: + /* automatic SW_ACT upon OML link establishment: */ + oml_mo_tx_sw_act_rep(&site_mgr->mo); + nm_bts_sm_fsm_state_chg(fi, NM_BTS_SM_ST_OP_DISABLED_OFFLINE); + ev_dispatch_children(site_mgr, event); + return; + default: + OSMO_ASSERT(0); + } +} + +static void st_op_disabled_offline_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct gsm_bts_sm *site_mgr = (struct gsm_bts_sm *)fi->priv; + site_mgr->mo.setattr_success = false; + site_mgr->mo.opstart_success = false; + oml_mo_state_chg(&site_mgr->mo, NM_OPSTATE_DISABLED, NM_AVSTATE_OFF_LINE, -1); +} + +static void st_op_disabled_offline(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_bts_sm *site_mgr = (struct gsm_bts_sm *)fi->priv; + struct nm_fsm_ev_setattr_data *setattr_data; + int rc; + + switch (event) { + case NM_EV_RX_SETATTR: + setattr_data = (struct nm_fsm_ev_setattr_data *)data; + /* No bts_model_apply_oml() needed yet for site_mgr obj yet: */ + rc = 0; + site_mgr->mo.setattr_success = rc == 0; + oml_fom_ack_nack_copy_msg(setattr_data->msg, rc); + break; + case NM_EV_RX_OPSTART: +#if 0 + /* Disabled because osmo-bsc doesn't send SetAttr on SITE_MGR object */ + if (!site_mgr->mo.setattr_success) { + oml_mo_opstart_nack(&site_mgr->mo, NM_NACK_CANT_PERFORM); + return; + } +#endif + bts_model_opstart(NULL, &site_mgr->mo, site_mgr); + break; + case NM_EV_OPSTART_ACK: + site_mgr->mo.opstart_success = true; + oml_mo_opstart_ack(&site_mgr->mo); + nm_bts_sm_fsm_state_chg(fi, NM_BTS_SM_ST_OP_ENABLED); + break; /* check statechg below */ + case NM_EV_OPSTART_NACK: + site_mgr->mo.opstart_success = false; + oml_mo_opstart_nack(&site_mgr->mo, (int)(intptr_t)data); + return; + default: + OSMO_ASSERT(0); + } +} + +static void st_op_enabled_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct gsm_bts_sm *site_mgr = (struct gsm_bts_sm *)fi->priv; + oml_mo_state_chg(&site_mgr->mo, NM_OPSTATE_ENABLED, NM_AVSTATE_OK, -1); +} + +static void st_op_enabled(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ +} + +static void nm_bts_sm_allstate(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_bts_sm *site_mgr = (struct gsm_bts_sm *)fi->priv; + + switch (event) { + case NM_EV_SHUTDOWN_START: + /* Announce we start shutting down */ + oml_mo_state_chg(&site_mgr->mo, -1, -1, NM_STATE_SHUTDOWN); + + /* Propagate event to children: */ + ev_dispatch_children(site_mgr, event); + break; + case NM_EV_SHUTDOWN_FINISH: + /* Propagate event to children: */ + ev_dispatch_children(site_mgr, event); + nm_bts_sm_fsm_state_chg(fi, NM_BTS_SM_ST_OP_DISABLED_NOTINSTALLED); + break; + default: + OSMO_ASSERT(false); + } +} + +static struct osmo_fsm_state nm_bts_sm_fsm_states[] = { + [NM_BTS_SM_ST_OP_DISABLED_NOTINSTALLED] = { + .in_event_mask = + X(NM_EV_OML_UP), + .out_state_mask = + X(NM_BTS_SM_ST_OP_DISABLED_NOTINSTALLED) | + X(NM_BTS_SM_ST_OP_DISABLED_OFFLINE), + .name = "DISABLED_NOTINSTALLED", + .onenter = st_op_disabled_notinstalled_on_enter, + .action = st_op_disabled_notinstalled, + }, + [NM_BTS_SM_ST_OP_DISABLED_OFFLINE] = { + .in_event_mask = + X(NM_EV_RX_SETATTR) | + X(NM_EV_RX_OPSTART) | + X(NM_EV_OPSTART_ACK) | + X(NM_EV_OPSTART_NACK), + .out_state_mask = + X(NM_BTS_SM_ST_OP_DISABLED_NOTINSTALLED) | + X(NM_BTS_SM_ST_OP_ENABLED), + .name = "DISABLED_OFFLINE", + .onenter = st_op_disabled_offline_on_enter, + .action = st_op_disabled_offline, + }, + [NM_BTS_SM_ST_OP_ENABLED] = { + .in_event_mask = 0, + .out_state_mask = + X(NM_BTS_SM_ST_OP_DISABLED_NOTINSTALLED), + .name = "ENABLED", + .onenter = st_op_enabled_on_enter, + .action = st_op_enabled, + }, +}; + +struct osmo_fsm nm_bts_sm_fsm = { + .name = "NM_BTS_SM_OP", + .states = nm_bts_sm_fsm_states, + .num_states = ARRAY_SIZE(nm_bts_sm_fsm_states), + .event_names = nm_fsm_event_names, + .allstate_action = nm_bts_sm_allstate, + .allstate_event_mask = X(NM_EV_SHUTDOWN_START) | + X(NM_EV_SHUTDOWN_FINISH), + .log_subsys = DOML, +}; + +static __attribute__((constructor)) void nm_bts_sm_fsm_init(void) +{ + OSMO_ASSERT(osmo_fsm_register(&nm_bts_sm_fsm) == 0); +} diff --git a/src/common/nm_channel_fsm.c b/src/common/nm_channel_fsm.c new file mode 100644 index 00000000..d41fcd11 --- /dev/null +++ b/src/common/nm_channel_fsm.c @@ -0,0 +1,317 @@ +/* NM Radio Carrier FSM */ + +/* (C) 2020 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> + * Author: Pau Espin Pedrol <pespin@sysmocom.de> + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU 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 <inttypes.h> + +#include <osmocom/core/fsm.h> +#include <osmocom/core/tdef.h> +#include <osmocom/gsm/protocol/gsm_12_21.h> + +#include <osmo-bts/logging.h> +#include <osmo-bts/gsm_data.h> +#include <osmo-bts/bts_model.h> +#include <osmo-bts/bts.h> +#include <osmo-bts/rsl.h> +#include <osmo-bts/nm_common_fsm.h> + +#define X(s) (1 << (s)) + +#define nm_chan_fsm_state_chg(fi, NEXT_STATE) \ + osmo_fsm_inst_state_chg(fi, NEXT_STATE, 0, 0) + +/* Can the TS be enabled (OPSTARTed)? aka should it stay in "Disabled Dependency" state? */ +static bool ts_can_be_enabled(const struct gsm_bts_trx_ts *ts) +{ + return (ts->trx->bb_transc.mo.nm_state.operational == NM_OPSTATE_ENABLED && + (!bts_internal_flag_get(ts->trx->bts, BTS_INTERNAL_FLAG_NM_RCHANNEL_DEPENDS_RCARRIER) || + ts->trx->mo.nm_state.operational == NM_OPSTATE_ENABLED)); +} + +////////////////////////// +// FSM STATE ACTIONS +////////////////////////// + +static void st_op_disabled_notinstalled_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct gsm_bts_trx_ts *ts = (struct gsm_bts_trx_ts *)fi->priv; + /* Reset state: */ + gsm_ts_release(ts); + if (ts->vamos.peer) + gsm_ts_release(ts->vamos.peer); + TALLOC_FREE(ts->mo.nm_attr); + + ts->mo.setattr_success = false; + ts->mo.opstart_success = false; + oml_mo_state_chg(&ts->mo, NM_OPSTATE_DISABLED, NM_AVSTATE_NOT_INSTALLED, NM_STATE_LOCKED); +} + +static void st_op_disabled_notinstalled(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_bts_trx_ts *ts = (struct gsm_bts_trx_ts *)fi->priv; + + switch (event) { + case NM_EV_OML_UP: + /* Report current state: */ + oml_tx_state_changed(&ts->mo); + return; + case NM_EV_SW_ACT: + oml_mo_tx_sw_act_rep(&ts->mo); + if (ts_can_be_enabled(ts)) + nm_chan_fsm_state_chg(fi, NM_CHAN_ST_OP_DISABLED_OFFLINE); + else + nm_chan_fsm_state_chg(fi, NM_CHAN_ST_OP_DISABLED_DEPENDENCY); + return; + default: + OSMO_ASSERT(0); + } +} + +static void st_op_disabled_dependency_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct gsm_bts_trx_ts *ts = (struct gsm_bts_trx_ts *)fi->priv; + ts->mo.opstart_success = false; + oml_mo_state_chg(&ts->mo, NM_OPSTATE_DISABLED, NM_AVSTATE_DEPENDENCY, -1); +} + +static void st_op_disabled_dependency(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_bts_trx_ts *ts = (struct gsm_bts_trx_ts *)fi->priv; + struct nm_fsm_ev_setattr_data *setattr_data; + int rc; + + switch (event) { + case NM_EV_OML_UP: + /* Report current state: */ + oml_tx_state_changed(&ts->mo); + return; + case NM_EV_RX_SETATTR: + setattr_data = (struct nm_fsm_ev_setattr_data *)data; + rc = bts_model_apply_oml(ts->trx->bts, setattr_data->msg, + &ts->mo, ts); + ts->mo.setattr_success = rc == 0; + oml_fom_ack_nack_copy_msg(setattr_data->msg, rc); + break; + case NM_EV_RX_OPSTART: + LOGPFSML(fi, LOGL_NOTICE, "BSC trying to activate TS while still in avail=dependency. " + "Allowing it to stay backward-compatible with older osmo-bts versions, but BSC is wrong.\n"); + if (!ts->mo.setattr_success) { + oml_mo_opstart_nack(&ts->mo, NM_NACK_CANT_PERFORM); + return; + } + bts_model_opstart(ts->trx->bts, &ts->mo, ts); + break; + case NM_EV_OPSTART_ACK: + ts->mo.opstart_success = true; + oml_mo_opstart_ack(&ts->mo); + nm_chan_fsm_state_chg(fi, NM_CHAN_ST_OP_ENABLED); + return; + case NM_EV_OPSTART_NACK: + ts->mo.opstart_success = false; + oml_mo_opstart_nack(&ts->mo, (int)(intptr_t)data); + return; + case NM_EV_BBTRANSC_ENABLED: + case NM_EV_RCARRIER_ENABLED: + if (ts_can_be_enabled(ts)) + nm_chan_fsm_state_chg(fi, NM_CHAN_ST_OP_DISABLED_OFFLINE); + return; + case NM_EV_BBTRANSC_DISABLED: + case NM_EV_RCARRIER_DISABLED: + /* do nothing, we are simply waiting for (potentially) both to be enabled */ + return; + default: + OSMO_ASSERT(0); + } +} + +static void st_op_disabled_offline_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct gsm_bts_trx_ts *ts = (struct gsm_bts_trx_ts *)fi->priv; + ts->mo.opstart_success = false; + oml_mo_state_chg(&ts->mo, NM_OPSTATE_DISABLED, NM_AVSTATE_OFF_LINE, -1); +} + +static void st_op_disabled_offline(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_bts_trx_ts *ts = (struct gsm_bts_trx_ts *)fi->priv; + struct nm_fsm_ev_setattr_data *setattr_data; + int rc; + + switch (event) { + case NM_EV_OML_UP: + /* Report current state: */ + oml_tx_state_changed(&ts->mo); + return; + case NM_EV_RX_SETATTR: + setattr_data = (struct nm_fsm_ev_setattr_data *)data; + rc = bts_model_apply_oml(ts->trx->bts, setattr_data->msg, + &ts->mo, ts); + ts->mo.setattr_success = rc == 0; + oml_fom_ack_nack_copy_msg(setattr_data->msg, rc); + break; + case NM_EV_RX_OPSTART: + if (!ts->mo.setattr_success) { + oml_mo_opstart_nack(&ts->mo, NM_NACK_CANT_PERFORM); + return; + } + bts_model_opstart(ts->trx->bts, &ts->mo, ts); + break; + case NM_EV_OPSTART_ACK: + ts->mo.opstart_success = true; + oml_mo_opstart_ack(&ts->mo); + nm_chan_fsm_state_chg(fi, NM_CHAN_ST_OP_ENABLED); + return; + case NM_EV_OPSTART_NACK: + ts->mo.opstart_success = false; + oml_mo_opstart_nack(&ts->mo, (int)(intptr_t)data); + return; + case NM_EV_BBTRANSC_DISABLED: + case NM_EV_RCARRIER_DISABLED: + if (!ts_can_be_enabled(ts)) + nm_chan_fsm_state_chg(fi, NM_CHAN_ST_OP_DISABLED_DEPENDENCY); + return; + default: + OSMO_ASSERT(0); + } +} + +static void st_op_enabled_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct gsm_bts_trx_ts *ts = (struct gsm_bts_trx_ts *)fi->priv; + oml_mo_state_chg(&ts->mo, NM_OPSTATE_ENABLED, NM_AVSTATE_OK, -1); +} + +static void st_op_enabled(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_bts_trx_ts *ts = (struct gsm_bts_trx_ts *)fi->priv; + + switch (event) { + case NM_EV_BBTRANSC_DISABLED: + case NM_EV_RCARRIER_DISABLED: + if (!ts_can_be_enabled(ts)) + nm_chan_fsm_state_chg(fi, NM_CHAN_ST_OP_DISABLED_DEPENDENCY); + return; + case NM_EV_DISABLE: + nm_chan_fsm_state_chg(fi, NM_CHAN_ST_OP_DISABLED_OFFLINE); + return; + default: + OSMO_ASSERT(0); + } +} + +static void nm_chan_allstate(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_bts_trx_ts *ts = (struct gsm_bts_trx_ts *)fi->priv; + + switch (event) { + case NM_EV_SHUTDOWN_START: + /* Announce we start shutting down */ + oml_mo_state_chg(&ts->mo, -1, -1, NM_STATE_SHUTDOWN); + break; + case NM_EV_SHUTDOWN_FINISH: + nm_chan_fsm_state_chg(fi, NM_CHAN_ST_OP_DISABLED_NOTINSTALLED); + break; + default: + OSMO_ASSERT(false); + } +} + +static struct osmo_fsm_state nm_chan_fsm_states[] = { + [NM_CHAN_ST_OP_DISABLED_NOTINSTALLED] = { + .in_event_mask = + X(NM_EV_SW_ACT) | + X(NM_EV_OML_UP), + .out_state_mask = + X(NM_CHAN_ST_OP_DISABLED_NOTINSTALLED) | + X(NM_CHAN_ST_OP_DISABLED_OFFLINE) | + X(NM_CHAN_ST_OP_DISABLED_DEPENDENCY), + .name = "DISABLED_NOTINSTALLED", + .onenter = st_op_disabled_notinstalled_on_enter, + .action = st_op_disabled_notinstalled, + }, + [NM_CHAN_ST_OP_DISABLED_DEPENDENCY] = { + .in_event_mask = + X(NM_EV_OML_UP) | + X(NM_EV_RX_SETATTR) | + X(NM_EV_RX_OPSTART) | /* backward compatibility, buggy BSC */ + X(NM_EV_OPSTART_ACK) | /* backward compatibility, buggy BSC */ + X(NM_EV_OPSTART_NACK) | /* backward compatibility, buggy BSC */ + X(NM_EV_BBTRANSC_ENABLED) | + X(NM_EV_RCARRIER_ENABLED) | + X(NM_EV_BBTRANSC_DISABLED) | + X(NM_EV_RCARRIER_DISABLED), + .out_state_mask = + X(NM_CHAN_ST_OP_DISABLED_NOTINSTALLED) | + X(NM_CHAN_ST_OP_DISABLED_OFFLINE) | + X(NM_CHAN_ST_OP_ENABLED), /* backward compatibility, buggy BSC */ + .name = "DISABLED_DEPENDENCY", + .onenter = st_op_disabled_dependency_on_enter, + .action = st_op_disabled_dependency, + }, + [NM_CHAN_ST_OP_DISABLED_OFFLINE] = { + .in_event_mask = + X(NM_EV_OML_UP) | + X(NM_EV_RX_SETATTR) | + X(NM_EV_RX_OPSTART) | + X(NM_EV_OPSTART_ACK) | + X(NM_EV_OPSTART_NACK) | + X(NM_EV_BBTRANSC_DISABLED) | + X(NM_EV_RCARRIER_DISABLED), + .out_state_mask = + X(NM_CHAN_ST_OP_DISABLED_NOTINSTALLED) | + X(NM_CHAN_ST_OP_ENABLED) | + X(NM_CHAN_ST_OP_DISABLED_DEPENDENCY), + .name = "DISABLED_OFFLINE", + .onenter = st_op_disabled_offline_on_enter, + .action = st_op_disabled_offline, + }, + [NM_CHAN_ST_OP_ENABLED] = { + .in_event_mask = + X(NM_EV_BBTRANSC_DISABLED) | + X(NM_EV_RCARRIER_DISABLED) | + X(NM_EV_DISABLE), + .out_state_mask = + X(NM_CHAN_ST_OP_DISABLED_NOTINSTALLED) | + X(NM_CHAN_ST_OP_DISABLED_OFFLINE) | + X(NM_CHAN_ST_OP_DISABLED_DEPENDENCY), + .name = "ENABLED", + .onenter = st_op_enabled_on_enter, + .action = st_op_enabled, + }, +}; + +struct osmo_fsm nm_chan_fsm = { + .name = "NM_CHAN_OP", + .states = nm_chan_fsm_states, + .num_states = ARRAY_SIZE(nm_chan_fsm_states), + .event_names = nm_fsm_event_names, + .allstate_action = nm_chan_allstate, + .allstate_event_mask = X(NM_EV_SHUTDOWN_START) | + X(NM_EV_SHUTDOWN_FINISH), + .log_subsys = DOML, +}; + +static __attribute__((constructor)) void nm_chan_fsm_init(void) +{ + OSMO_ASSERT(osmo_fsm_register(&nm_chan_fsm) == 0); +} diff --git a/src/common/nm_common_fsm.c b/src/common/nm_common_fsm.c new file mode 100644 index 00000000..aa9bda4c --- /dev/null +++ b/src/common/nm_common_fsm.c @@ -0,0 +1,45 @@ +/* NM FSM, common bits */ + +/* (C) 2020 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> + * Author: Pau Espin Pedrol <pespin@sysmocom.de> + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU 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 <osmo-bts/nm_common_fsm.h> + + +const struct value_string nm_fsm_event_names[] = { + { NM_EV_SW_ACT, "SW_ACT" }, + { NM_EV_RX_SETATTR, "RX_SETATTR" }, + { NM_EV_RX_OPSTART, "RX_OPSTART" }, + { NM_EV_OPSTART_ACK, "OPSTART_ACK" }, + { NM_EV_OPSTART_NACK, "OPSTART_NACK" }, + { NM_EV_SHUTDOWN_START, "SHUTDOWN_START" }, + { NM_EV_SHUTDOWN_FINISH, "SHUTDOWN_FINISH" }, + { NM_EV_OML_UP, "OML_UP" }, + { NM_EV_RSL_UP, "RSL_UP" }, + { NM_EV_RSL_DOWN, "RSL_DOWN" }, + { NM_EV_PHYLINK_UP, "PHYLINK_UP" }, + { NM_EV_PHYLINK_DOWN, "PHYLINK_DOWN" }, + { NM_EV_DISABLE, "DISABLE" }, + { NM_EV_BBTRANSC_ENABLED, "BBTRANSC_ENABLED" }, + { NM_EV_BBTRANSC_DISABLED, "BBTRANSC_DISABLED" }, + { NM_EV_RCARRIER_ENABLED, "RCARRIER_ENABLED" }, + { NM_EV_RCARRIER_DISABLED, "RCARRIER_DISABLED" }, + { 0, NULL } +}; diff --git a/src/common/nm_gprs_cell_fsm.c b/src/common/nm_gprs_cell_fsm.c new file mode 100644 index 00000000..95f46e31 --- /dev/null +++ b/src/common/nm_gprs_cell_fsm.c @@ -0,0 +1,260 @@ +/* NM GPRS Cell FSM */ + +/* (C) 2023 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> + * Author: Pau Espin Pedrol <pespin@sysmocom.de> + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU 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 <inttypes.h> + +#include <osmocom/core/fsm.h> +#include <osmocom/core/tdef.h> +#include <osmocom/gsm/protocol/gsm_12_21.h> + +#include <osmo-bts/logging.h> +#include <osmo-bts/gsm_data.h> +#include <osmo-bts/bts_model.h> +#include <osmo-bts/bts.h> +#include <osmo-bts/bts_sm.h> +#include <osmo-bts/rsl.h> +#include <osmo-bts/nm_common_fsm.h> + +#define X(s) (1 << (s)) + +#define nm_gprs_cell_fsm_state_chg(fi, NEXT_STATE) \ + osmo_fsm_inst_state_chg(fi, NEXT_STATE, 0, 0) + +/* Can the GPRS Cell be enabled (OPSTARTed)? aka should it stay in "Disabled Dependency" state? */ +static bool gprs_cell_can_be_enabled(struct gsm_gprs_cell *cell) +{ + struct gsm_bts *bts = gsm_gprs_cell_get_bts(cell); + return bts->site_mgr->gprs.nse.mo.nm_state.operational == NM_OPSTATE_ENABLED; +} + + +////////////////////////// +// FSM STATE ACTIONS +////////////////////////// + +static void st_op_disabled_notinstalled_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct gsm_gprs_cell *cell = (struct gsm_gprs_cell *)fi->priv; + /* Reset state here: */ + + cell->mo.setattr_success = false; + cell->mo.opstart_success = false; + oml_mo_state_chg(&cell->mo, NM_OPSTATE_DISABLED, NM_AVSTATE_NOT_INSTALLED, NM_STATE_LOCKED); +} + +static void st_op_disabled_notinstalled(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_gprs_cell *cell = (struct gsm_gprs_cell *)fi->priv; + + switch (event) { + case NM_EV_OML_UP: + /* automatic SW_ACT upon OML link establishment: */ + oml_mo_tx_sw_act_rep(&cell->mo); + if (gprs_cell_can_be_enabled(cell)) + nm_gprs_cell_fsm_state_chg(fi, NM_GPRS_CELL_ST_OP_DISABLED_OFFLINE); + else + nm_gprs_cell_fsm_state_chg(fi, NM_GPRS_CELL_ST_OP_DISABLED_DEPENDENCY); + return; + default: + OSMO_ASSERT(0); + } +} + +static void st_op_disabled_dependency_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct gsm_gprs_cell *cell = (struct gsm_gprs_cell *)fi->priv; + cell->mo.setattr_success = false; + cell->mo.opstart_success = false; + oml_mo_state_chg(&cell->mo, NM_OPSTATE_DISABLED, NM_AVSTATE_DEPENDENCY, -1); +} + +static void st_op_disabled_dependency(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_gprs_cell *cell = (struct gsm_gprs_cell *)fi->priv; + struct gsm_bts *bts = gsm_gprs_cell_get_bts(cell); + struct nm_fsm_ev_setattr_data *setattr_data; + int rc; + + switch (event) { + case NM_EV_RX_SETATTR: + setattr_data = (struct nm_fsm_ev_setattr_data *)data; + rc = bts_model_apply_oml(bts, setattr_data->msg, + &cell->mo, cell); + cell->mo.setattr_success = rc == 0; + oml_fom_ack_nack_copy_msg(setattr_data->msg, rc); + break; + case NM_EV_RX_OPSTART: + if (!cell->mo.setattr_success) { + oml_mo_opstart_nack(&cell->mo, NM_NACK_CANT_PERFORM); + return; + } + bts_model_opstart(bts, &cell->mo, cell); + break; + case NM_EV_OPSTART_ACK: + cell->mo.opstart_success = true; + oml_mo_opstart_ack(&cell->mo); + nm_gprs_cell_fsm_state_chg(fi, NM_CHAN_ST_OP_ENABLED); + return; + case NM_EV_OPSTART_NACK: + cell->mo.opstart_success = false; + oml_mo_opstart_nack(&cell->mo, (int)(intptr_t)data); + return; + default: + OSMO_ASSERT(0); + } +} + +static void st_op_disabled_offline_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct gsm_gprs_cell *cell = (struct gsm_gprs_cell *)fi->priv; + cell->mo.opstart_success = false; + oml_mo_state_chg(&cell->mo, NM_OPSTATE_DISABLED, NM_AVSTATE_OFF_LINE, -1); +} + +static void st_op_disabled_offline(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_gprs_cell *cell = (struct gsm_gprs_cell *)fi->priv; + struct gsm_bts *bts = gsm_gprs_cell_get_bts(cell); + struct nm_fsm_ev_setattr_data *setattr_data; + int rc; + + switch (event) { + case NM_EV_RX_SETATTR: + setattr_data = (struct nm_fsm_ev_setattr_data *)data; + rc = bts_model_apply_oml(bts, setattr_data->msg, &cell->mo, bts); + cell->mo.setattr_success = rc == 0; + oml_fom_ack_nack_copy_msg(setattr_data->msg, rc); + break; + case NM_EV_RX_OPSTART: + if (!cell->mo.setattr_success) { + oml_mo_opstart_nack(&cell->mo, NM_NACK_CANT_PERFORM); + return; + } + bts_model_opstart(bts, &cell->mo, bts); + break; + case NM_EV_OPSTART_ACK: + cell->mo.opstart_success = true; + oml_mo_opstart_ack(&cell->mo); + nm_gprs_cell_fsm_state_chg(fi, NM_GPRS_CELL_ST_OP_ENABLED); + break; /* check statechg below */ + case NM_EV_OPSTART_NACK: + cell->mo.opstart_success = false; + oml_mo_opstart_nack(&cell->mo, (int)(intptr_t)data); + return; + default: + OSMO_ASSERT(0); + } +} + +static void st_op_enabled_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct gsm_gprs_cell *cell = (struct gsm_gprs_cell *)fi->priv; + oml_mo_state_chg(&cell->mo, NM_OPSTATE_ENABLED, NM_AVSTATE_OK, -1); +} + +static void st_op_enabled(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ +} + +static void nm_gprs_cell_allstate(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_gprs_cell *cell = (struct gsm_gprs_cell *)fi->priv; + + switch (event) { + case NM_EV_SHUTDOWN_START: + /* Announce we start shutting down */ + oml_mo_state_chg(&cell->mo, -1, -1, NM_STATE_SHUTDOWN); + break; + case NM_EV_SHUTDOWN_FINISH: + nm_gprs_cell_fsm_state_chg(fi, NM_GPRS_CELL_ST_OP_DISABLED_NOTINSTALLED); + break; + default: + OSMO_ASSERT(false); + } +} + +static struct osmo_fsm_state nm_gprs_cell_fsm_states[] = { + [NM_GPRS_CELL_ST_OP_DISABLED_NOTINSTALLED] = { + .in_event_mask = + X(NM_EV_OML_UP), + .out_state_mask = + X(NM_GPRS_CELL_ST_OP_DISABLED_NOTINSTALLED) | + X(NM_GPRS_CELL_ST_OP_DISABLED_DEPENDENCY) | + X(NM_GPRS_CELL_ST_OP_DISABLED_OFFLINE), + .name = "DISABLED_NOTINSTALLED", + .onenter = st_op_disabled_notinstalled_on_enter, + .action = st_op_disabled_notinstalled, + }, + [NM_GPRS_CELL_ST_OP_DISABLED_DEPENDENCY] = { + .in_event_mask = + X(NM_EV_RX_SETATTR) | + X(NM_EV_RX_OPSTART) | /* backward compatibility, buggy BSC */ + X(NM_EV_OPSTART_ACK) | /* backward compatibility, buggy BSC */ + X(NM_EV_OPSTART_NACK), /* backward compatibility, buggy BSC */ + .out_state_mask = + X(NM_CHAN_ST_OP_DISABLED_NOTINSTALLED) | + X(NM_CHAN_ST_OP_DISABLED_OFFLINE) | + X(NM_CHAN_ST_OP_ENABLED), /* backward compatibility, buggy BSC */ + .name = "DISABLED_DEPENDENCY", + .onenter = st_op_disabled_dependency_on_enter, + .action = st_op_disabled_dependency, + }, + [NM_GPRS_CELL_ST_OP_DISABLED_OFFLINE] = { + .in_event_mask = + X(NM_EV_RX_SETATTR) | + X(NM_EV_RX_OPSTART) | + X(NM_EV_OPSTART_ACK) | + X(NM_EV_OPSTART_NACK), + .out_state_mask = + X(NM_GPRS_CELL_ST_OP_DISABLED_NOTINSTALLED) | + X(NM_GPRS_CELL_ST_OP_ENABLED), + .name = "DISABLED_OFFLINE", + .onenter = st_op_disabled_offline_on_enter, + .action = st_op_disabled_offline, + }, + [NM_GPRS_CELL_ST_OP_ENABLED] = { + .in_event_mask = 0, + .out_state_mask = + X(NM_GPRS_CELL_ST_OP_DISABLED_NOTINSTALLED), + .name = "ENABLED", + .onenter = st_op_enabled_on_enter, + .action = st_op_enabled, + }, +}; + +struct osmo_fsm nm_gprs_cell_fsm = { + .name = "NM_GPRS_CELL_OP", + .states = nm_gprs_cell_fsm_states, + .num_states = ARRAY_SIZE(nm_gprs_cell_fsm_states), + .event_names = nm_fsm_event_names, + .allstate_action = nm_gprs_cell_allstate, + .allstate_event_mask = X(NM_EV_SHUTDOWN_START) | + X(NM_EV_SHUTDOWN_FINISH), + .log_subsys = DOML, +}; + +static __attribute__((constructor)) void nm_gprs_cell_fsm_init(void) +{ + OSMO_ASSERT(osmo_fsm_register(&nm_gprs_cell_fsm) == 0); +} diff --git a/src/common/nm_gprs_nse_fsm.c b/src/common/nm_gprs_nse_fsm.c new file mode 100644 index 00000000..2818c868 --- /dev/null +++ b/src/common/nm_gprs_nse_fsm.c @@ -0,0 +1,280 @@ +/* NM GPRS NSE FSM */ + +/* (C) 2023 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> + * Author: Pau Espin Pedrol <pespin@sysmocom.de> + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU 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 <inttypes.h> + +#include <osmocom/core/fsm.h> +#include <osmocom/core/tdef.h> +#include <osmocom/gsm/protocol/gsm_12_21.h> + +#include <osmo-bts/logging.h> +#include <osmo-bts/gsm_data.h> +#include <osmo-bts/bts_model.h> +#include <osmo-bts/bts.h> +#include <osmo-bts/bts_sm.h> +#include <osmo-bts/rsl.h> +#include <osmo-bts/nm_common_fsm.h> +#include <osmo-bts/phy_link.h> +#include <osmo-bts/cbch.h> + +#define X(s) (1 << (s)) + +#define nm_gprs_nse_fsm_state_chg(fi, NEXT_STATE) \ + osmo_fsm_inst_state_chg(fi, NEXT_STATE, 0, 0) + +static void ev_dispatch_children(struct gsm_gprs_nse *nse, uint32_t event) +{ + unsigned int i; + struct gsm_bts *bts = gsm_gprs_nse_get_bts(nse); + + osmo_fsm_inst_dispatch(bts->gprs.cell.mo.fi, event, NULL); + for (i = 0; i < ARRAY_SIZE(nse->nsvc); i++) { + struct gsm_gprs_nsvc *nsvc = &nse->nsvc[i]; + osmo_fsm_inst_dispatch(nsvc->mo.fi, event, NULL); + } +} + +/* Can the NSE be enabled (OPSTARTed)? aka should it stay in "Disabled Dependency" state? */ +static bool nse_can_be_enabled(struct gsm_gprs_nse *nse) +{ + struct gsm_bts *bts = gsm_gprs_nse_get_bts(nse); + return bts->mo.nm_state.operational == NM_OPSTATE_ENABLED; +} + + +////////////////////////// +// FSM STATE ACTIONS +////////////////////////// + +static void st_op_disabled_notinstalled_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct gsm_gprs_nse *nse = (struct gsm_gprs_nse *)fi->priv; + /* Reset state here: */ + + nse->mo.setattr_success = false; + nse->mo.opstart_success = false; + oml_mo_state_chg(&nse->mo, NM_OPSTATE_DISABLED, NM_AVSTATE_NOT_INSTALLED, NM_STATE_LOCKED); +} + +static void st_op_disabled_notinstalled(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_gprs_nse *nse = (struct gsm_gprs_nse *)fi->priv; + + switch (event) { + case NM_EV_OML_UP: + /* automatic SW_ACT upon OML link establishment: */ + oml_mo_tx_sw_act_rep(&nse->mo); + ev_dispatch_children(nse, event); + if (nse_can_be_enabled(nse)) + nm_gprs_nse_fsm_state_chg(fi, NM_GPRS_NSE_ST_OP_DISABLED_OFFLINE); + else + nm_gprs_nse_fsm_state_chg(fi, NM_GPRS_NSE_ST_OP_DISABLED_DEPENDENCY); + return; + default: + OSMO_ASSERT(0); + } +} + +static void st_op_disabled_dependency_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct gsm_gprs_nse *nse = (struct gsm_gprs_nse *)fi->priv; + nse->mo.setattr_success = false; + nse->mo.opstart_success = false; + oml_mo_state_chg(&nse->mo, NM_OPSTATE_DISABLED, NM_AVSTATE_DEPENDENCY, -1); +} + +static void st_op_disabled_dependency(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_gprs_nse *nse = (struct gsm_gprs_nse *)fi->priv; + struct gsm_bts *bts = gsm_gprs_nse_get_bts(nse); + struct nm_fsm_ev_setattr_data *setattr_data; + int rc; + + switch (event) { + case NM_EV_RX_SETATTR: + setattr_data = (struct nm_fsm_ev_setattr_data *)data; + rc = bts_model_apply_oml(bts, setattr_data->msg, + &nse->mo, nse); + nse->mo.setattr_success = rc == 0; + oml_fom_ack_nack_copy_msg(setattr_data->msg, rc); + break; + case NM_EV_RX_OPSTART: + if (!nse->mo.setattr_success) { + oml_mo_opstart_nack(&nse->mo, NM_NACK_CANT_PERFORM); + return; + } + bts_model_opstart(bts, &nse->mo, nse); + break; + case NM_EV_OPSTART_ACK: + nse->mo.opstart_success = true; + oml_mo_opstart_ack(&nse->mo); + nm_gprs_nse_fsm_state_chg(fi, NM_CHAN_ST_OP_ENABLED); + return; + case NM_EV_OPSTART_NACK: + nse->mo.opstart_success = false; + oml_mo_opstart_nack(&nse->mo, (int)(intptr_t)data); + return; + default: + OSMO_ASSERT(0); + } +} + +static void st_op_disabled_offline_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct gsm_gprs_nse *nse = (struct gsm_gprs_nse *)fi->priv; + nse->mo.opstart_success = false; + oml_mo_state_chg(&nse->mo, NM_OPSTATE_DISABLED, NM_AVSTATE_OFF_LINE, -1); +} + +static void st_op_disabled_offline(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_gprs_nse *nse = (struct gsm_gprs_nse *)fi->priv; + struct gsm_bts *bts = gsm_gprs_nse_get_bts(nse); + struct nm_fsm_ev_setattr_data *setattr_data; + int rc; + + switch (event) { + case NM_EV_RX_SETATTR: + setattr_data = (struct nm_fsm_ev_setattr_data *)data; + rc = bts_model_apply_oml(bts, setattr_data->msg, &nse->mo, bts); + nse->mo.setattr_success = rc == 0; + oml_fom_ack_nack_copy_msg(setattr_data->msg, rc); + break; + case NM_EV_RX_OPSTART: + if (!nse->mo.setattr_success) { + oml_mo_opstart_nack(&nse->mo, NM_NACK_CANT_PERFORM); + return; + } + bts_model_opstart(bts, &nse->mo, bts); + break; + case NM_EV_OPSTART_ACK: + nse->mo.opstart_success = true; + oml_mo_opstart_ack(&nse->mo); + nm_gprs_nse_fsm_state_chg(fi, NM_GPRS_NSE_ST_OP_ENABLED); + break; /* check statechg below */ + case NM_EV_OPSTART_NACK: + nse->mo.opstart_success = false; + oml_mo_opstart_nack(&nse->mo, (int)(intptr_t)data); + return; + default: + OSMO_ASSERT(0); + } +} + +static void st_op_enabled_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct gsm_gprs_nse *nse = (struct gsm_gprs_nse *)fi->priv; + oml_mo_state_chg(&nse->mo, NM_OPSTATE_ENABLED, NM_AVSTATE_OK, -1); +} + +static void st_op_enabled(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ +} + +static void nm_gprs_nse_allstate(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_gprs_nse *nse = (struct gsm_gprs_nse *)fi->priv; + + switch (event) { + case NM_EV_SHUTDOWN_START: + /* Announce we start shutting down */ + oml_mo_state_chg(&nse->mo, -1, -1, NM_STATE_SHUTDOWN); + + /* Propagate event to children: */ + ev_dispatch_children(nse, event); + break; + case NM_EV_SHUTDOWN_FINISH: + /* Propagate event to children: */ + ev_dispatch_children(nse, event); + nm_gprs_nse_fsm_state_chg(fi, NM_GPRS_NSE_ST_OP_DISABLED_NOTINSTALLED); + break; + default: + OSMO_ASSERT(false); + } +} + +static struct osmo_fsm_state nm_gprs_nse_fsm_states[] = { + [NM_GPRS_NSE_ST_OP_DISABLED_NOTINSTALLED] = { + .in_event_mask = + X(NM_EV_OML_UP), + .out_state_mask = + X(NM_GPRS_NSE_ST_OP_DISABLED_NOTINSTALLED) | + X(NM_GPRS_NSE_ST_OP_DISABLED_DEPENDENCY) | + X(NM_GPRS_NSE_ST_OP_DISABLED_OFFLINE), + .name = "DISABLED_NOTINSTALLED", + .onenter = st_op_disabled_notinstalled_on_enter, + .action = st_op_disabled_notinstalled, + }, + [NM_GPRS_NSE_ST_OP_DISABLED_DEPENDENCY] = { + .in_event_mask = + X(NM_EV_RX_SETATTR) | + X(NM_EV_RX_OPSTART) | /* backward compatibility, buggy BSC */ + X(NM_EV_OPSTART_ACK) | /* backward compatibility, buggy BSC */ + X(NM_EV_OPSTART_NACK), /* backward compatibility, buggy BSC */ + .out_state_mask = + X(NM_CHAN_ST_OP_DISABLED_NOTINSTALLED) | + X(NM_CHAN_ST_OP_DISABLED_OFFLINE) | + X(NM_CHAN_ST_OP_ENABLED), /* backward compatibility, buggy BSC */ + .name = "DISABLED_DEPENDENCY", + .onenter = st_op_disabled_dependency_on_enter, + .action = st_op_disabled_dependency, + }, + [NM_GPRS_NSE_ST_OP_DISABLED_OFFLINE] = { + .in_event_mask = + X(NM_EV_RX_SETATTR) | + X(NM_EV_RX_OPSTART) | + X(NM_EV_OPSTART_ACK) | + X(NM_EV_OPSTART_NACK), + .out_state_mask = + X(NM_GPRS_NSE_ST_OP_DISABLED_NOTINSTALLED) | + X(NM_GPRS_NSE_ST_OP_ENABLED), + .name = "DISABLED_OFFLINE", + .onenter = st_op_disabled_offline_on_enter, + .action = st_op_disabled_offline, + }, + [NM_GPRS_NSE_ST_OP_ENABLED] = { + .in_event_mask = 0, + .out_state_mask = + X(NM_GPRS_NSE_ST_OP_DISABLED_NOTINSTALLED), + .name = "ENABLED", + .onenter = st_op_enabled_on_enter, + .action = st_op_enabled, + }, +}; + +struct osmo_fsm nm_gprs_nse_fsm = { + .name = "NM_GPRS_NSE_OP", + .states = nm_gprs_nse_fsm_states, + .num_states = ARRAY_SIZE(nm_gprs_nse_fsm_states), + .event_names = nm_fsm_event_names, + .allstate_action = nm_gprs_nse_allstate, + .allstate_event_mask = X(NM_EV_SHUTDOWN_START) | + X(NM_EV_SHUTDOWN_FINISH), + .log_subsys = DOML, +}; + +static __attribute__((constructor)) void nm_gprs_nse_fsm_init(void) +{ + OSMO_ASSERT(osmo_fsm_register(&nm_gprs_nse_fsm) == 0); +} diff --git a/src/common/nm_gprs_nsvc_fsm.c b/src/common/nm_gprs_nsvc_fsm.c new file mode 100644 index 00000000..cbfd97af --- /dev/null +++ b/src/common/nm_gprs_nsvc_fsm.c @@ -0,0 +1,259 @@ +/* NM GPRS NSVC FSM */ + +/* (C) 2023 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> + * Author: Pau Espin Pedrol <pespin@sysmocom.de> + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU 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 <inttypes.h> + +#include <osmocom/core/fsm.h> +#include <osmocom/core/tdef.h> +#include <osmocom/gsm/protocol/gsm_12_21.h> + +#include <osmo-bts/logging.h> +#include <osmo-bts/gsm_data.h> +#include <osmo-bts/bts_model.h> +#include <osmo-bts/bts.h> +#include <osmo-bts/bts_sm.h> +#include <osmo-bts/rsl.h> +#include <osmo-bts/nm_common_fsm.h> + +#define X(s) (1 << (s)) + +#define nm_gprs_nsvc_fsm_state_chg(fi, NEXT_STATE) \ + osmo_fsm_inst_state_chg(fi, NEXT_STATE, 0, 0) + +/* Can the GPRS Cell be enabled (OPSTARTed)? aka should it stay in "Disabled Dependency" state? */ +static bool gprs_nsvc_can_be_enabled(struct gsm_gprs_nsvc *nsvc) +{ + return nsvc->nse->mo.nm_state.operational == NM_OPSTATE_ENABLED; +} + + +////////////////////////// +// FSM STATE ACTIONS +////////////////////////// + +static void st_op_disabled_notinstalled_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct gsm_gprs_nsvc *nsvc = (struct gsm_gprs_nsvc *)fi->priv; + /* Reset state here: */ + + nsvc->mo.setattr_success = false; + nsvc->mo.opstart_success = false; + oml_mo_state_chg(&nsvc->mo, NM_OPSTATE_DISABLED, NM_AVSTATE_NOT_INSTALLED, NM_STATE_LOCKED); +} + +static void st_op_disabled_notinstalled(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_gprs_nsvc *nsvc = (struct gsm_gprs_nsvc *)fi->priv; + + switch (event) { + case NM_EV_OML_UP: + /* automatic SW_ACT upon OML link establishment: */ + oml_mo_tx_sw_act_rep(&nsvc->mo); + if (gprs_nsvc_can_be_enabled(nsvc)) + nm_gprs_nsvc_fsm_state_chg(fi, NM_GPRS_NSVC_ST_OP_DISABLED_OFFLINE); + else + nm_gprs_nsvc_fsm_state_chg(fi, NM_GPRS_NSVC_ST_OP_DISABLED_DEPENDENCY); + return; + default: + OSMO_ASSERT(0); + } +} + +static void st_op_disabled_dependency_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct gsm_gprs_nsvc *nsvc = (struct gsm_gprs_nsvc *)fi->priv; + nsvc->mo.setattr_success = false; + nsvc->mo.opstart_success = false; + oml_mo_state_chg(&nsvc->mo, NM_OPSTATE_DISABLED, NM_AVSTATE_DEPENDENCY, -1); +} + +static void st_op_disabled_dependency(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_gprs_nsvc *nsvc = (struct gsm_gprs_nsvc *)fi->priv; + struct gsm_bts *bts = gsm_gprs_nse_get_bts(nsvc->nse); + struct nm_fsm_ev_setattr_data *setattr_data; + int rc; + + switch (event) { + case NM_EV_RX_SETATTR: + setattr_data = (struct nm_fsm_ev_setattr_data *)data; + rc = bts_model_apply_oml(bts, setattr_data->msg, + &nsvc->mo, nsvc); + nsvc->mo.setattr_success = rc == 0; + oml_fom_ack_nack_copy_msg(setattr_data->msg, rc); + break; + case NM_EV_RX_OPSTART: + if (!nsvc->mo.setattr_success) { + oml_mo_opstart_nack(&nsvc->mo, NM_NACK_CANT_PERFORM); + return; + } + bts_model_opstart(bts, &nsvc->mo, nsvc); + break; + case NM_EV_OPSTART_ACK: + nsvc->mo.opstart_success = true; + oml_mo_opstart_ack(&nsvc->mo); + nm_gprs_nsvc_fsm_state_chg(fi, NM_CHAN_ST_OP_ENABLED); + return; + case NM_EV_OPSTART_NACK: + nsvc->mo.opstart_success = false; + oml_mo_opstart_nack(&nsvc->mo, (int)(intptr_t)data); + return; + default: + OSMO_ASSERT(0); + } +} + +static void st_op_disabled_offline_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct gsm_gprs_nsvc *nsvc = (struct gsm_gprs_nsvc *)fi->priv; + nsvc->mo.opstart_success = false; + oml_mo_state_chg(&nsvc->mo, NM_OPSTATE_DISABLED, NM_AVSTATE_OFF_LINE, -1); +} + +static void st_op_disabled_offline(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_gprs_nsvc *nsvc = (struct gsm_gprs_nsvc *)fi->priv; + struct gsm_bts *bts = gsm_gprs_nse_get_bts(nsvc->nse); + struct nm_fsm_ev_setattr_data *setattr_data; + int rc; + + switch (event) { + case NM_EV_RX_SETATTR: + setattr_data = (struct nm_fsm_ev_setattr_data *)data; + rc = bts_model_apply_oml(bts, setattr_data->msg, &nsvc->mo, bts); + nsvc->mo.setattr_success = rc == 0; + oml_fom_ack_nack_copy_msg(setattr_data->msg, rc); + break; + case NM_EV_RX_OPSTART: + if (!nsvc->mo.setattr_success) { + oml_mo_opstart_nack(&nsvc->mo, NM_NACK_CANT_PERFORM); + return; + } + bts_model_opstart(bts, &nsvc->mo, bts); + break; + case NM_EV_OPSTART_ACK: + nsvc->mo.opstart_success = true; + oml_mo_opstart_ack(&nsvc->mo); + nm_gprs_nsvc_fsm_state_chg(fi, NM_GPRS_NSVC_ST_OP_ENABLED); + break; /* check statechg below */ + case NM_EV_OPSTART_NACK: + nsvc->mo.opstart_success = false; + oml_mo_opstart_nack(&nsvc->mo, (int)(intptr_t)data); + return; + default: + OSMO_ASSERT(0); + } +} + +static void st_op_enabled_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct gsm_gprs_nsvc *nsvc = (struct gsm_gprs_nsvc *)fi->priv; + oml_mo_state_chg(&nsvc->mo, NM_OPSTATE_ENABLED, NM_AVSTATE_OK, -1); +} + +static void st_op_enabled(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ +} + +static void nm_gprs_nsvc_allstate(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_gprs_nsvc *nsvc = (struct gsm_gprs_nsvc *)fi->priv; + + switch (event) { + case NM_EV_SHUTDOWN_START: + /* Announce we start shutting down */ + oml_mo_state_chg(&nsvc->mo, -1, -1, NM_STATE_SHUTDOWN); + break; + case NM_EV_SHUTDOWN_FINISH: + nm_gprs_nsvc_fsm_state_chg(fi, NM_GPRS_NSVC_ST_OP_DISABLED_NOTINSTALLED); + break; + default: + OSMO_ASSERT(false); + } +} + +static struct osmo_fsm_state nm_gprs_nsvc_fsm_states[] = { + [NM_GPRS_NSVC_ST_OP_DISABLED_NOTINSTALLED] = { + .in_event_mask = + X(NM_EV_OML_UP), + .out_state_mask = + X(NM_GPRS_NSVC_ST_OP_DISABLED_NOTINSTALLED) | + X(NM_GPRS_NSVC_ST_OP_DISABLED_DEPENDENCY) | + X(NM_GPRS_NSVC_ST_OP_DISABLED_OFFLINE), + .name = "DISABLED_NOTINSTALLED", + .onenter = st_op_disabled_notinstalled_on_enter, + .action = st_op_disabled_notinstalled, + }, + [NM_GPRS_NSVC_ST_OP_DISABLED_DEPENDENCY] = { + .in_event_mask = + X(NM_EV_RX_SETATTR) | + X(NM_EV_RX_OPSTART) | /* backward compatibility, buggy BSC */ + X(NM_EV_OPSTART_ACK) | /* backward compatibility, buggy BSC */ + X(NM_EV_OPSTART_NACK), /* backward compatibility, buggy BSC */ + .out_state_mask = + X(NM_CHAN_ST_OP_DISABLED_NOTINSTALLED) | + X(NM_CHAN_ST_OP_DISABLED_OFFLINE) | + X(NM_CHAN_ST_OP_ENABLED), /* backward compatibility, buggy BSC */ + .name = "DISABLED_DEPENDENCY", + .onenter = st_op_disabled_dependency_on_enter, + .action = st_op_disabled_dependency, + }, + [NM_GPRS_NSVC_ST_OP_DISABLED_OFFLINE] = { + .in_event_mask = + X(NM_EV_RX_SETATTR) | + X(NM_EV_RX_OPSTART) | + X(NM_EV_OPSTART_ACK) | + X(NM_EV_OPSTART_NACK), + .out_state_mask = + X(NM_GPRS_NSVC_ST_OP_DISABLED_NOTINSTALLED) | + X(NM_GPRS_NSVC_ST_OP_ENABLED), + .name = "DISABLED_OFFLINE", + .onenter = st_op_disabled_offline_on_enter, + .action = st_op_disabled_offline, + }, + [NM_GPRS_NSVC_ST_OP_ENABLED] = { + .in_event_mask = 0, + .out_state_mask = + X(NM_GPRS_NSVC_ST_OP_DISABLED_NOTINSTALLED), + .name = "ENABLED", + .onenter = st_op_enabled_on_enter, + .action = st_op_enabled, + }, +}; + +struct osmo_fsm nm_gprs_nsvc_fsm = { + .name = "NM_GPRS_NSVC_OP", + .states = nm_gprs_nsvc_fsm_states, + .num_states = ARRAY_SIZE(nm_gprs_nsvc_fsm_states), + .event_names = nm_fsm_event_names, + .allstate_action = nm_gprs_nsvc_allstate, + .allstate_event_mask = X(NM_EV_SHUTDOWN_START) | + X(NM_EV_SHUTDOWN_FINISH), + .log_subsys = DOML, +}; + +static __attribute__((constructor)) void nm_gprs_nsvc_fsm_init(void) +{ + OSMO_ASSERT(osmo_fsm_register(&nm_gprs_nsvc_fsm) == 0); +} diff --git a/src/common/nm_radio_carrier_fsm.c b/src/common/nm_radio_carrier_fsm.c new file mode 100644 index 00000000..f9589532 --- /dev/null +++ b/src/common/nm_radio_carrier_fsm.c @@ -0,0 +1,285 @@ +/* NM Radio Carrier FSM */ + +/* (C) 2020 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> + * Author: Pau Espin Pedrol <pespin@sysmocom.de> + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU 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 <inttypes.h> + +#include <osmocom/core/fsm.h> +#include <osmocom/core/tdef.h> +#include <osmocom/gsm/protocol/gsm_12_21.h> + +#include <osmo-bts/logging.h> +#include <osmo-bts/gsm_data.h> +#include <osmo-bts/bts_model.h> +#include <osmo-bts/bts.h> +#include <osmo-bts/rsl.h> +#include <osmo-bts/nm_common_fsm.h> +#include <osmo-bts/phy_link.h> + +#define X(s) (1 << (s)) + +#define nm_rcarrier_fsm_state_chg(fi, NEXT_STATE) \ + osmo_fsm_inst_state_chg(fi, NEXT_STATE, 0, 0) + +////////////////////////// +// FSM STATE ACTIONS +////////////////////////// + +static void st_op_disabled_notinstalled_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct gsm_bts_trx *trx = (struct gsm_bts_trx *)fi->priv; + /* Reset state: */ + TALLOC_FREE(trx->mo.nm_attr); + + trx->mo.setattr_success = false; + trx->mo.opstart_success = false; + oml_mo_state_chg(&trx->mo, NM_OPSTATE_DISABLED, NM_AVSTATE_NOT_INSTALLED, NM_STATE_LOCKED); +} + +static void st_op_disabled_notinstalled(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_bts_trx *trx = (struct gsm_bts_trx *)fi->priv; + + switch (event) { + case NM_EV_SW_ACT: + oml_mo_tx_sw_act_rep(&trx->mo); + nm_rcarrier_fsm_state_chg(fi, NM_RCARRIER_ST_OP_DISABLED_OFFLINE); + return; + case NM_EV_OML_UP: + /* Report current state: */ + oml_tx_state_changed(&trx->mo); + return; + case NM_EV_RSL_UP: + return; + case NM_EV_RSL_DOWN: + return; + case NM_EV_PHYLINK_UP: + return; + case NM_EV_PHYLINK_DOWN: + return; + case NM_EV_DISABLE: + return; + default: + OSMO_ASSERT(0); + } +} + +static void st_op_disabled_offline_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct gsm_bts_trx *trx = (struct gsm_bts_trx *)fi->priv; + unsigned int i; + + trx->mo.setattr_success = false; + trx->mo.opstart_success = false; + oml_mo_state_chg(&trx->mo, NM_OPSTATE_DISABLED, NM_AVSTATE_OFF_LINE, -1); + + if (prev_state == NM_RCARRIER_ST_OP_ENABLED) { + for (i = 0; i < TRX_NR_TS; i++) { + struct gsm_bts_trx_ts *ts = &trx->ts[i]; + osmo_fsm_inst_dispatch(ts->mo.fi, NM_EV_RCARRIER_DISABLED, NULL); + } + } +} + +static void st_op_disabled_offline(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_bts_trx *trx = (struct gsm_bts_trx *)fi->priv; + struct nm_fsm_ev_setattr_data *setattr_data; + bool phy_state_connected; + bool rsl_link_connected; + int rc; + + switch (event) { + case NM_EV_OML_UP: + /* Report current state: */ + oml_tx_state_changed(&trx->mo); + return; + case NM_EV_RX_SETATTR: + setattr_data = (struct nm_fsm_ev_setattr_data *)data; + rc = bts_model_apply_oml(trx->bts, setattr_data->msg, + &trx->mo, trx); + trx->mo.setattr_success = rc == 0; + oml_fom_ack_nack_copy_msg(setattr_data->msg, rc); + break; /* check statechg below */ + case NM_EV_RX_OPSTART: + if (!trx->mo.setattr_success) { + oml_mo_opstart_nack(&trx->mo, NM_NACK_CANT_PERFORM); + return; + } + bts_model_opstart(trx->bts, &trx->mo, trx); + return; + case NM_EV_OPSTART_ACK: + trx->mo.opstart_success = true; + oml_mo_opstart_ack(&trx->mo); + break; /* check statechg below */ + case NM_EV_OPSTART_NACK: + trx->mo.opstart_success = false; + oml_mo_opstart_nack(&trx->mo, (int)(intptr_t)data); + return; + case NM_EV_RSL_UP: + break; /* check statechg below */ + case NM_EV_RSL_DOWN: + return; + case NM_EV_PHYLINK_UP: + break; /* check statechg below */ + case NM_EV_PHYLINK_DOWN: + return; + case NM_EV_DISABLE: + return; + default: + OSMO_ASSERT(0); + } + + if (trx->bts->variant != BTS_OSMO_OMLDUMMY) { /* In OMLDUMMY, phy=NULL */ + struct phy_instance *pinst = trx_phy_instance(trx); + phy_state_connected = phy_link_state_get(pinst->phy_link) == PHY_LINK_CONNECTED; + rsl_link_connected = !!trx->bb_transc.rsl.link; + } else { + phy_state_connected = true; + rsl_link_connected = true; + } + + if (rsl_link_connected && phy_state_connected && + trx->mo.setattr_success && trx->mo.opstart_success) { + nm_rcarrier_fsm_state_chg(fi, NM_RCARRIER_ST_OP_ENABLED); + } else { + LOGPFSML(fi, LOGL_INFO, "Delay switch to operative state Enabled, wait for:%s%s%s%s\n", + rsl_link_connected ? "" : " rsl", + phy_state_connected ? "" : " phy", + trx->mo.setattr_success ? "" : " setattr", + trx->mo.opstart_success ? "" : " opstart"); + + } +} + +static void st_op_enabled_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct gsm_bts_trx *trx = (struct gsm_bts_trx *)fi->priv; + unsigned int tn; + + oml_mo_state_chg(&trx->mo, NM_OPSTATE_ENABLED, NM_AVSTATE_OK, -1); + /* Mark Dependency TS as Offline (ready to be Opstarted) */ + for (tn = 0; tn < TRX_NR_TS; tn++) { + struct gsm_bts_trx_ts *ts = &trx->ts[tn]; + osmo_fsm_inst_dispatch(ts->mo.fi, NM_EV_RCARRIER_ENABLED, NULL); + } +} + +static void st_op_enabled(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + switch (event) { + case NM_EV_RSL_DOWN: + break; + case NM_EV_PHYLINK_DOWN: + break; + case NM_EV_DISABLE: + break; + default: + OSMO_ASSERT(0); + } + + nm_rcarrier_fsm_state_chg(fi, NM_RCARRIER_ST_OP_DISABLED_OFFLINE); +} + +static void nm_rcarrier_allstate(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_bts_trx *trx = (struct gsm_bts_trx *)fi->priv; + + switch (event) { + case NM_EV_SHUTDOWN_START: + /* Announce we start shutting down */ + oml_mo_state_chg(&trx->mo, -1, -1, NM_STATE_SHUTDOWN); + break; + case NM_EV_SHUTDOWN_FINISH: + nm_rcarrier_fsm_state_chg(fi, NM_RCARRIER_ST_OP_DISABLED_NOTINSTALLED); + break; + default: + OSMO_ASSERT(false); + } +} + +static struct osmo_fsm_state nm_rcarrier_fsm_states[] = { + [NM_RCARRIER_ST_OP_DISABLED_NOTINSTALLED] = { + .in_event_mask = + X(NM_EV_SW_ACT) | + X(NM_EV_OML_UP) | + X(NM_EV_RSL_UP) | + X(NM_EV_RSL_DOWN) | + X(NM_EV_PHYLINK_UP) | + X(NM_EV_PHYLINK_DOWN) | + X(NM_EV_DISABLE), + .out_state_mask = + X(NM_RCARRIER_ST_OP_DISABLED_NOTINSTALLED) | + X(NM_RCARRIER_ST_OP_DISABLED_OFFLINE), + .name = "DISABLED_NOTINSTALLED", + .onenter = st_op_disabled_notinstalled_on_enter, + .action = st_op_disabled_notinstalled, + }, + [NM_RCARRIER_ST_OP_DISABLED_OFFLINE] = { + .in_event_mask = + X(NM_EV_OML_UP) | + X(NM_EV_RX_SETATTR) | + X(NM_EV_RX_OPSTART) | + X(NM_EV_OPSTART_ACK) | + X(NM_EV_OPSTART_NACK) | + X(NM_EV_RSL_UP) | + X(NM_EV_RSL_DOWN) | + X(NM_EV_PHYLINK_UP) | + X(NM_EV_PHYLINK_DOWN) | + X(NM_EV_DISABLE), + .out_state_mask = + X(NM_RCARRIER_ST_OP_DISABLED_NOTINSTALLED) | + X(NM_RCARRIER_ST_OP_ENABLED), + .name = "DISABLED_OFFLINE", + .onenter = st_op_disabled_offline_on_enter, + .action = st_op_disabled_offline, + }, + [NM_RCARRIER_ST_OP_ENABLED] = { + .in_event_mask = + X(NM_EV_RSL_DOWN) | + X(NM_EV_PHYLINK_DOWN) | + X(NM_EV_DISABLE), + .out_state_mask = + X(NM_RCARRIER_ST_OP_DISABLED_NOTINSTALLED) | + X(NM_RCARRIER_ST_OP_DISABLED_OFFLINE), + .name = "ENABLED", + .onenter = st_op_enabled_on_enter, + .action = st_op_enabled, + }, +}; + +struct osmo_fsm nm_rcarrier_fsm = { + .name = "NM_RCARRIER_OP", + .states = nm_rcarrier_fsm_states, + .num_states = ARRAY_SIZE(nm_rcarrier_fsm_states), + .event_names = nm_fsm_event_names, + .allstate_action = nm_rcarrier_allstate, + .allstate_event_mask = X(NM_EV_SHUTDOWN_START) | + X(NM_EV_SHUTDOWN_FINISH), + .log_subsys = DOML, +}; + +static __attribute__((constructor)) void nm_rcarrier_fsm_init(void) +{ + OSMO_ASSERT(osmo_fsm_register(&nm_rcarrier_fsm) == 0); +} diff --git a/src/common/notification.c b/src/common/notification.c new file mode 100644 index 00000000..9351ec04 --- /dev/null +++ b/src/common/notification.c @@ -0,0 +1,256 @@ +/* Maintain and generate ASCI notifications */ + +/* + * (C) 2023 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> + * All Rights Reserved + * + * SPDX-License-Identifier: AGPL-3.0+ + * + * Author: Harald Welte + * + * 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/core/bitvec.h> +#include <osmocom/core/linuxlist.h> +#include <osmocom/gsm/protocol/gsm_08_58.h> + +#include <osmo-bts/bts.h> +#include <osmo-bts/logging.h> +#include <osmo-bts/notification.h> + +static struct asci_notification *bts_asci_notification_find(struct gsm_bts *bts, const uint8_t *group_call_ref) +{ + struct asci_notification *n; + llist_for_each_entry(n, &bts->asci.notifications, list) { + if (!memcmp(n->group_call_ref, group_call_ref, sizeof(n->group_call_ref))) + return n; + } + return NULL; +} + +int bts_asci_notification_add(struct gsm_bts *bts, const uint8_t *group_call_ref, const uint8_t *chan_desc, + uint8_t chan_desc_len, const struct rsl_ie_nch_drx_info *nch_drx_info) +{ + struct asci_notification *n; + + if (bts_asci_notification_find(bts, group_call_ref)) + return -EEXIST; + + n = talloc_zero(bts, struct asci_notification); + if (!n) + return -ENOMEM; + + memcpy(n->group_call_ref, group_call_ref, sizeof(n->group_call_ref)); + if (chan_desc && chan_desc_len) { + n->chan_desc.present = true; + n->chan_desc.len = chan_desc_len; + memcpy(&n->chan_desc.value, chan_desc, chan_desc_len); + } + if (nch_drx_info) { + n->nch_drx_info.present = true; + n->nch_drx_info.value = *nch_drx_info; + } + + LOGP(DASCI, LOGL_INFO, "Added ASCI Notification for group call reference %s\n", + osmo_hexdump_nospc(n->group_call_ref, ARRAY_SIZE(n->group_call_ref))); + + /* add at beginning of "queue" to make sure a new call is notified first */ + llist_add(&n->list, &bts->asci.notifications); + + bts->asci.notification_entries++; + bts->asci.notification_count = 0; + bts->asci.nln = (bts->asci.nln + 1) % 4; + + return 0; +} + +int bts_asci_notification_del(struct gsm_bts *bts, const uint8_t *group_call_ref) +{ + struct asci_notification *n = bts_asci_notification_find(bts, group_call_ref); + if (!n) + return -ENODEV; + + LOGP(DASCI, LOGL_INFO, "Deleting ASCI Notification for group call reference %s\n", + osmo_hexdump_nospc(n->group_call_ref, ARRAY_SIZE(n->group_call_ref))); + + llist_del(&n->list); + talloc_free(n); + + bts->asci.notification_entries--; + bts->asci.notification_count = 0; + bts->asci.nln_status = (bts->asci.nln_status + 1) % 2; + + return 0; +} + +int bts_asci_notification_reset(struct gsm_bts *bts) +{ + struct asci_notification *n, *n2; + + LOGP(DASCI, LOGL_INFO, "Deleting all %u ASCI Notifications of BTS\n", + llist_count(&bts->asci.notifications)); + + llist_for_each_entry_safe(n, n2, &bts->asci.notifications, list) { + llist_del(&n->list); + talloc_free(n); + } + + bts->asci.notification_entries = 0; + bts->asci.notification_count = 0; + bts->asci.nln_status = (bts->asci.nln_status + 1) % 2; + + return 0; +} + +const struct asci_notification *bts_asci_notification_get_next(struct gsm_bts *bts) +{ + struct asci_notification *n; + + n = llist_first_entry_or_null(&bts->asci.notifications, struct asci_notification, list); + if (!n) + return NULL; + + /* move to end of list to iterate over them */ + llist_del(&n->list); + llist_add_tail(&n->list, &bts->asci.notifications); + + return n; +} + + +/*! append a "Group Call Information" CSN.1 structure to the caller-provided bit-vector. + * \param[out] bv caller-provided output bit-vector + * \param[in] gcr 5-byte group call reference + * \param[in] ch_desc optional group channel description (may be NULL) + * \param[in] ch_desc_len length of group channel description (in bytes) */ +void append_group_call_information(struct bitvec *bv, const uint8_t *gcr, const uint8_t *ch_desc, uint8_t ch_desc_len) +{ + /* spec reference: TS 44.018 Section 9.1.21a */ + + /* <Group Call Reference : bit(36)> */ + struct bitvec *gcr_bv = bitvec_alloc(5*8, NULL); + OSMO_ASSERT(gcr_bv); + bitvec_unpack(gcr_bv, gcr); + for (unsigned int i = 0; i < 36; i++) + bitvec_set_bit(bv, bitvec_get_bit_pos(gcr_bv, i)); + + /* Group Channel Description */ + if (ch_desc && ch_desc_len) { + struct bitvec *chd_bv = bitvec_alloc(ch_desc_len*8, NULL); + OSMO_ASSERT(chd_bv); + bitvec_unpack(chd_bv, ch_desc); + bitvec_set_bit(bv, 1); + /* <Channel Description : bit(24)> */ + for (unsigned int i = 0; i < ch_desc_len * 8; i++) + bitvec_set_bit(bv, bitvec_get_bit_pos(chd_bv, i)); + bitvec_free(chd_bv); + /* FIXME: hopping */ + bitvec_set_bit(bv, 0); + } else { + bitvec_set_bit(bv, 0); + } + + bitvec_free(gcr_bv); +} + +#define L2_PLEN(len) (((len - 1) << 2) | 0x01) + +int bts_asci_notify_nch_gen_msg(struct gsm_bts *bts, uint8_t *out_buf) +{ + struct gsm48_notification_nch *nn = (struct gsm48_notification_nch *) out_buf; + const struct asci_notification *notif; + unsigned int ro_len; + + notif = bts_asci_notification_get_next(bts); + + *nn = (struct gsm48_notification_nch) { + .proto_discr = GSM48_PDISC_RR, + .msg_type = GSM48_MT_RR_NOTIF_NCH, + }; + + nn->l2_plen = L2_PLEN(nn->data - out_buf); + + /* Pad remaining octets with constant '2B'O */ + ro_len = GSM_MACBLOCK_LEN - (nn->data - out_buf); + memset(nn->data, GSM_MACBLOCK_PADDING, ro_len); + + struct bitvec bv = { + .data_len = ro_len, + .data = nn->data, + }; + + /* {0 | 1 < NLN(NCH) : bit (2) >} + * Only send NLN, at the last notifications. + * When the phone receives two NLN with the same value, it knows that all notifications has been received. + * Also send NLN if no notification is available. */ + if (bts->asci.notification_count >= bts->asci.notification_entries - 1) { + bitvec_set_bit(&bv, 1); + bitvec_set_uint(&bv, bts->asci.nln, 2); + } else { + bitvec_set_bit(&bv, 0); + } + + /* Count NLN. */ + if (++bts->asci.notification_count >= bts->asci.notification_entries) + bts->asci.notification_count = 0; + + /* < List of Group Call NCH information > ::= + * { 0 | 1 < Group Call information > < List of Group Call NCH information > } ; */ + if (notif) { + bitvec_set_bit(&bv, 1); + append_group_call_information(&bv, notif->group_call_ref, + notif->chan_desc.present ? notif->chan_desc.value : NULL, + notif->chan_desc.len); + } + bitvec_set_bit(&bv, 0); /* End of list */ + + /* TODO: Additions in Release 6 */ + /* TODO: Additions in Release 7 */ + + return GSM_MACBLOCK_LEN; +} + +int bts_asci_notify_facch_gen_msg(struct gsm_bts *bts, uint8_t *out_buf, const uint8_t *group_call_ref, + const uint8_t *chan_desc, uint8_t chan_desc_len) +{ + struct gsm48_hdr_sh *sh = (struct gsm48_hdr_sh *) out_buf; + unsigned int ro_len; + + *sh = (struct gsm48_hdr_sh) { + .rr_short_pd = GSM48_PDISC_SH_RR, + .msg_type = GSM48_MT_RR_SH_FACCH, + .l2_header = 0, + }; + + /* Pad remaining octets with constant '2B'O */ + ro_len = GSM_MACBLOCK_LEN - (sh->data - out_buf); + memset(sh->data, GSM_MACBLOCK_PADDING, ro_len); + + struct bitvec bv = { + .data_len = ro_len, + .data = sh->data, + }; + + /* 0 < Group Call information > */ + bitvec_set_bit(&bv, 0); + append_group_call_information(&bv, group_call_ref, chan_desc, chan_desc_len); + + /* TODO: Additions in Release 6 */ + /* TODO: Additions in Release 7 */ + + return GSM_MACBLOCK_LEN; +} diff --git a/src/common/oml.c b/src/common/oml.c index 3defa494..a9e13b52 100644 --- a/src/common/oml.c +++ b/src/common/oml.c @@ -13,7 +13,7 @@ * 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. + * 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/>. @@ -34,6 +34,7 @@ #include <osmocom/core/talloc.h> #include <osmocom/core/msgb.h> +#include <osmocom/core/utils.h> #include <osmocom/gsm/protocol/gsm_12_21.h> #include <osmocom/gsm/abis_nm.h> #include <osmocom/gsm/tlv.h> @@ -46,8 +47,10 @@ #include <osmo-bts/oml.h> #include <osmo-bts/bts_model.h> #include <osmo-bts/bts.h> +#include <osmo-bts/bts_sm.h> #include <osmo-bts/signal.h> #include <osmo-bts/phy_link.h> +#include <osmo-bts/nm_common_fsm.h> #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) @@ -131,6 +134,7 @@ int oml_send_msg(struct msgb *msg, int is_manuf) int oml_mo_send_msg(const struct gsm_abis_mo *mo, struct msgb *msg, uint8_t msg_type) { struct abis_om_fom_hdr *foh; + struct gsm_bts *bts; msg->l3h = msgb_push(msg, sizeof(*foh)); foh = (struct abis_om_fom_hdr *) msg->l3h; @@ -138,17 +142,31 @@ int oml_mo_send_msg(const struct gsm_abis_mo *mo, struct msgb *msg, uint8_t msg_ foh->obj_class = mo->obj_class; memcpy(&foh->obj_inst, &mo->obj_inst, sizeof(foh->obj_inst)); - /* FIXME: This assumption may not always be correct */ - msg->trx = mo->bts->c0; + /* Find and set OML TRX on msg: */ + switch (mo->obj_class) { + case NM_OC_SITE_MANAGER: + /* Pick the first BTS: */ + bts = gsm_bts_num(g_bts_sm, 0); + break; + default: + /* Other objects should have a valid BTS available: */ + bts = gsm_bts_num(g_bts_sm, mo->obj_inst.bts_nr); + } + if (OSMO_UNLIKELY(!bts)) { + LOGPFOH(DOML, LOGL_NOTICE, foh, + "Sending FOM failed (no related BTS object found)\n"); + return -EINVAL; + } + msg->trx = bts->c0; DEBUGPFOH(DOML, foh, "Tx %s\n", get_value_string(abis_nm_msgtype_names, foh->msg_type)); return oml_send_msg(msg, 0); } -static inline void add_bts_attrs(struct msgb *msg, const struct gsm_bts *bts) +/* Put NM_ATT_SW_CONFIG as per 9.4.61 "SW Configuration" */ +static int add_att_sw_config(struct msgb *msg, const struct gsm_abis_mo *mo) { - uint16_t total_len = 0; uint8_t *len; /* Put NM_ATT_SW_CONFIG as per 9.4.61 "SW Configuration". */ @@ -157,117 +175,291 @@ static inline void add_bts_attrs(struct msgb *msg, const struct gsm_bts *bts) /* We don't know the length yet, so we update it later. */ len = msgb_put(msg, 2); - total_len += abis_nm_put_sw_file(msg, "osmobts", PACKAGE_VERSION, true); - total_len += abis_nm_put_sw_file(msg, btsatttr2str(BTS_TYPE_VARIANT), - btsvariant2str(bts->variant), true); + switch (mo->obj_class) { + case NM_OC_BTS: + { + const struct gsm_bts *bts = mo->bts; + + abis_nm_put_sw_file(msg, "osmobts", PACKAGE_VERSION, true); + abis_nm_put_sw_file(msg, btsatttr2str(BTS_TYPE_VARIANT), + btsvariant2str(bts->variant), true); + if (strlen(bts->sub_model)) { + abis_nm_put_sw_file(msg, btsatttr2str(BTS_SUB_MODEL), + bts->sub_model, true); + } + break; + } + case NM_OC_BASEB_TRANSC: + { + const struct gsm_bts_trx *trx = container_of(mo, struct gsm_bts_trx, bb_transc.mo); + const struct phy_instance *pinst = trx->pinst; + const char *phy_version; - if (strlen(bts->sub_model)) { - total_len += abis_nm_put_sw_file(msg, btsatttr2str(BTS_SUB_MODEL), - bts->sub_model, true); + phy_version = pinst && strlen(pinst->version) ? pinst->version : "Unknown"; + abis_nm_put_sw_file(msg, btsatttr2str(TRX_PHY_VERSION), phy_version, true); + break; + } + default: + msgb_get(msg, 1 + 2); /* TL16 */ + return -ENOTSUP; } /* Finally, update the length */ - osmo_store16be(total_len, len); + osmo_store16be((uint16_t)(msg->tail - (len + 2)), len); + + return 0; } /* Add BTS features as 3GPP TS 52.021 §9.4.30 Manufacturer Id */ static inline void add_bts_feat(struct msgb *msg, const struct gsm_bts *bts) { - msgb_tl16v_put(msg, NM_ATT_MANUF_ID, _NUM_BTS_FEAT/8 + 1, bts->_features_data); + unsigned int len = OSMO_BYTES_FOR_BITS(_NUM_BTS_FEAT); + msgb_tl16v_put(msg, NM_ATT_MANUF_ID, len, bts->features->data); } -static inline void add_trx_attr(struct msgb *msg, const struct gsm_bts_trx *trx) +/* Add ip.access feature flags for the given MO */ +static int add_att_ipacc_features(struct msgb *msg, const struct gsm_abis_mo *mo) { - const struct phy_instance *pinst = trx_phy_instance(trx); - const char *phy_version; - uint16_t total_len; + const struct gsm_bts *bts = mo->bts; + const struct gsm_bts_trx *trx; + uint32_t val; uint8_t *len; - /* Put NM_ATT_SW_CONFIG as per 9.4.61 "SW Configuration". */ - msgb_v_put(msg, NM_ATT_SW_CONFIG); + msgb_v_put(msg, NM_ATT_IPACC_SUPP_FEATURES); /* We don't know the length yet, so we update it later. */ len = msgb_put(msg, 2); - phy_version = pinst && strlen(pinst->version) ? pinst->version : "Unknown"; - total_len = abis_nm_put_sw_file(msg, btsatttr2str(TRX_PHY_VERSION), phy_version, true); + switch (mo->obj_class) { + case NM_OC_BTS: + msgb_tv16_put(msg, NM_IPAC_EIE_MAX_TA, 1); /* TL16 */ + msgb_put_u8(msg, (bts->support.max_ta >> 0) & 0xff); + break; + case NM_OC_RADIO_CARRIER: + trx = container_of(mo, struct gsm_bts_trx, mo); + msgb_tv16_put(msg, NM_IPAC_EIE_FREQ_BANDS, 1); /* TL16 */ + msgb_put_u8(msg, (trx->support.freq_bands >> 0) & 0xff); + break; + case NM_OC_BASEB_TRANSC: + trx = container_of(mo, struct gsm_bts_trx, bb_transc.mo); + msgb_tv16_put(msg, NM_IPAC_EIE_CIPH_ALGOS, 1); /* TL16 */ + msgb_put_u8(msg, bts->support.ciphers); /* LSB is A5/1 */ + + msgb_tv16_put(msg, NM_IPAC_EIE_CHAN_TYPES, 2); /* TL16 */ + msgb_put_u8(msg, (trx->support.chan_types >> 0) & 0xff); + msgb_put_u8(msg, (trx->support.chan_types >> 8) & 0xff); + + msgb_tv16_put(msg, NM_IPAC_EIE_CHAN_MODES, 3); /* TL16 */ + msgb_put_u8(msg, (trx->support.chan_modes >> 0) & 0xff); + msgb_put_u8(msg, (trx->support.chan_modes >> 8) & 0xff); + msgb_put_u8(msg, (trx->support.chan_modes >> 16) & 0xff); + + msgb_tv16_put(msg, NM_IPAC_EIE_RTP_FEATURES, 1); /* TL16 */ + val = NM_IPAC_F_RTP_FEAT_IR_64k; + msgb_put_u8(msg, (val >> 0) & 0xff); + + msgb_tv16_put(msg, NM_IPAC_EIE_RSL_FEATURES, 1); /* TL16 */ + val = NM_IPAC_F_RSL_FEAT_DYN_PDCH_ACT + | NM_IPAC_F_RSL_FEAT_RTP_PT2; + msgb_put_u8(msg, (val >> 0) & 0xff); + break; + case NM_OC_GPRS_CELL: + msgb_tv16_put(msg, NM_IPAC_EIE_GPRS_CODING, 2); /* TL16 */ + msgb_put_u8(msg, (bts->gprs.cell.support.gprs_codings >> 0) & 0xff); + msgb_put_u8(msg, (bts->gprs.cell.support.gprs_codings >> 8) & 0xff); + break; + default: + msgb_get(msg, 1 + 2); /* TL16 */ + return -ENOTSUP; + } /* Finally, update the length */ - osmo_store16be(total_len, len); + osmo_store16be((uint16_t)(msg->tail - (len + 2)), len); + + return 0; } -/* Handle a list of attributes requested by the BSC, compose - * TRX-specific Get Attribute Response IE as per 9.4.64. */ -static inline int handle_attrs_trx(struct msgb *out_msg, const struct gsm_bts_trx *trx, - const uint8_t *attr, uint16_t attr_len) +/* Add attribute 9.4.8 BCCH ARFCN for BTS class */ +static inline void add_att_bcch_arfcn(struct msgb *msg, const struct gsm_bts *bts) { - uint8_t num_unsupported = 0; - uint8_t *buf; - int i; + /* type + 16 bit value */ + msgb_tv16_put(msg, NM_ATT_BCCH_ARFCN, bts->c0->arfcn); +} - if (!trx) { - LOGP(DOML, LOGL_ERROR, "%s: O&M Get Attributes for unknown TRX\n", gsm_trx_name(trx)); - return -NM_NACK_TRXNR_UNKN; - } +/* Add attribute 9.4.25 Interference Level Boundaries for BTS class */ +static inline void add_att_interf_bound(struct msgb *msg, const struct gsm_bts *bts) +{ + /* type + 8 bit values */ + msgb_put_u8(msg, NM_ATT_INTERF_BOUND); + for (int j = 0; j < ARRAY_SIZE(bts->interference.boundary); j++) + msgb_put_u8(msg, abs(bts->interference.boundary[j])); +} - for (i = 0; i < attr_len; i++) { - switch (attr[i]) { - case NM_ATT_SW_CONFIG: - add_trx_attr(out_msg, trx); - break; - default: - LOGP(DOML, LOGL_ERROR, "%s: O&M Get Attributes [%u], %s is unsupported by TRX.\n", - gsm_trx_name(trx), i, get_value_string(abis_nm_att_names, attr[i])); - /* Push this tag to the list of unsupported attributes */ - buf = msgb_push(out_msg, 1); - *buf = attr[i]; - num_unsupported++; - } - } +/* Add attribute 9.4.24 Intave Parameter for BTS class */ +static inline void add_att_intave_param(struct msgb *msg, const struct gsm_bts *bts) +{ + /* type + 8 bit value */ + msgb_tv_put(msg, NM_ATT_INTAVE_PARAM, bts->interference.intave); +} - /* Push the amount of unsupported attributes */ - buf = msgb_push(out_msg, 1); - *buf = num_unsupported; +/* Add attribute 9.4.14 Connection Failure Criterion for BTS class */ +static inline void add_att_conn_fail_crit(struct msgb *msg, const struct gsm_bts *bts) +{ + /* type + length + values */ + msgb_tv16_put(msg, NM_ATT_CONN_FAIL_CRIT, 2); + msgb_put_u8(msg, 0x01); + msgb_put_u8(msg, bts->radio_link_timeout.current); +} + +/* Add attribute 9.4.31 Maximum Timing Advance for BTS class */ +static inline void add_att_max_ta(struct msgb *msg, const struct gsm_bts *bts) +{ + /* type + 8 bit value */ + msgb_tv_put(msg, NM_ATT_MAX_TA, bts->max_ta); +} + +/* Add attribute 9.4.39 Overload Period for BTS class */ +static inline void add_att_overl_period(struct msgb *msg, const struct gsm_bts *bts) +{ + /* type + length + value */ + msgb_tv16_put(msg, NM_ATT_OVERL_PERIOD, 1); + msgb_put_u8(msg, bts->load.overload_period); +} +/* Add attribute 9.4.12 CCCH Load Threshold for BTS class */ +static inline void add_att_ccch_l_t(struct msgb *msg, const struct gsm_bts *bts) +{ + /* type + 8 bit value */ + msgb_tv_put(msg, NM_ATT_CCCH_L_T, bts->load.ccch.load_ind_thresh); +} + +/* Add attribute 9.4.11 CCCH Load Indication Period for BTS class */ +static inline void add_att_ccch_l_i_p(struct msgb *msg, const struct gsm_bts *bts) +{ + /* type + 8 bit value */ + msgb_tv_put(msg, NM_ATT_CCCH_L_I_P, bts->load.ccch.load_ind_period); +} + +/* Add attribute 9.4.44 RACH Busy Threshold for BTS class */ +static inline void add_att_rach_b_thresh(struct msgb *msg, const struct gsm_bts *bts) +{ + /* type + 8 bit value */ + msgb_tv_put(msg, NM_ATT_RACH_B_THRESH, abs(bts->load.rach.busy_thresh)); +} + +/* Add attribute 9.4.45 RACH Load Averaging Slots for BTS class */ +static inline void add_att_ldavg_slots(struct msgb *msg, const struct gsm_bts *bts) +{ + /* type + 16 bit value */ + msgb_tv16_put(msg, NM_ATT_LDAVG_SLOTS, bts->load.rach.averaging_slots); +} + +/* Add attribute 9.4.10 BTS Air Timer for BTS class */ +static inline void add_att_bts_air_timer(struct msgb *msg, const struct gsm_bts *bts) +{ + /* type + 8 bit value */ + msgb_tv_put(msg, NM_ATT_BTS_AIR_TIMER, bts->t3105_ms / 10); +} + +/* Add attribute 9.4.37 NY1 for BTS class */ +static inline void add_att_ny1(struct msgb *msg, const struct gsm_bts *bts) +{ + /* type + 8 bit value */ + msgb_tv_put(msg, NM_ATT_NY1, bts->ny1); +} + +/* Add attribute 9.4.9 BSIC for BTS class */ +static inline int add_att_bsic(struct msgb *msg, const struct gsm_bts *bts) +{ + /* BSIC must be configured. */ + if (!bts->bsic_configured) + return -EINVAL; + /* type + 8 bit value */ + msgb_tv_put(msg, NM_ATT_BSIC, bts->bsic); return 0; } -/* Handle a list of attributes requested by the BSC, compose - * BTS-specific Get Attribute Response IE as per 9.4.64. */ -static inline int handle_attrs_bts(struct msgb *out_msg, const struct gsm_bts *bts, - const uint8_t *attr, uint16_t attr_len) +/* Add attribute 9.4.20 GSM Time for BTS class */ +static inline void add_att_gsm_time(struct msgb *msg, const struct gsm_bts *bts) { - uint8_t num_unsupported = 0; - uint8_t *buf; - int i; + /* type + 16 bit value */ + msgb_tv16_put(msg, NM_ATT_GSM_TIME, bts->gsm_time.fn % GSM_RFN_MODULUS); +} - if (!bts) { - LOGP(DOML, LOGL_ERROR, "O&M Get Attributes for unknown BTS\n"); - return -NM_NACK_BTSNR_UNKN; - } +/* Add attribute 9.4.47 RF Max Power Reduction for radio carrier class */ +static inline void add_att_rf_maxpowr_r(struct msgb *msg, const struct gsm_bts_trx *trx) +{ + /* type + 8 bit value */ + msgb_tv_put(msg, NM_ATT_RF_MAXPOWR_R, trx->max_power_red / 2); +} - for (i = 0; i < attr_len; i++) { - switch (attr[i]) { - case NM_ATT_SW_CONFIG: - add_bts_attrs(out_msg, bts); - break; - case NM_ATT_MANUF_ID: - add_bts_feat(out_msg, bts); - break; - default: - LOGP(DOML, LOGL_ERROR, "O&M Get Attributes [%u], %s is unsupported by BTS.\n", i, - get_value_string(abis_nm_att_names, attr[i])); - /* Push this tag to the list of unsupported attributes */ - buf = msgb_push(out_msg, 1); - *buf = attr[i]; - num_unsupported++; - } +/* Add attribute 9.4.5 ARFCN List for radio carrier class */ +static inline void add_att_arfcn_list(struct msgb *msg, const struct gsm_bts_trx *trx) +{ +#if 0 + /* type + length + values */ + msgb_tv16_put(msg, NM_ATT_ARFCN_LIST, trx->arfcn_num * 2); + for (int j = 0; j < trx->arfcn_num; j++) + msgb_put_u16(msg, trx->arfcn_list[j]); +#else + /* type + length + values */ + msgb_tv16_put(msg, NM_ATT_ARFCN_LIST, 2); + msgb_put_u16(msg, trx->arfcn); +#endif +} + +/* Add attribute 9.4.5 ARFCN List for channel class */ +static inline void add_att_arfcn_list_ts(struct msgb *msg, const struct gsm_bts_trx_ts *ts) +{ + if (ts->hopping.enabled) { + /* type + length + values */ + msgb_tv16_put(msg, NM_ATT_ARFCN_LIST, ts->hopping.arfcn_num * 2); + for (int j = 0; j < ts->hopping.arfcn_num; j++) + msgb_put_u16(msg, ts->hopping.arfcn_list[j]); + } else { + /* type + length + values */ + msgb_tv16_put(msg, NM_ATT_ARFCN_LIST, 2); + msgb_put_u16(msg, ts->trx->arfcn); } +} - /* Push the amount of unsupported attributes */ - buf = msgb_push(out_msg, 1); - *buf = num_unsupported; +/* Add attribute 9.4.13 Channel Combination for channel class */ +static inline int add_att_chan_comb(struct msgb *msg, const struct gsm_bts_trx_ts *ts) +{ + int comb = abis_nm_chcomb4pchan(ts->pchan); + /* If current channel combination is not yet set, 0xff is returned. */ + if (comb < 0 || comb == 0xff) + return -EINVAL; + /* type + 8 bit value */ + msgb_tv_put(msg, NM_ATT_CHAN_COMB, comb); + return 0; +} + +/* Add attribute 9.4.60 TSC for channel class */ +static inline void add_att_tsc(struct msgb *msg, const struct gsm_bts_trx_ts *ts) +{ + /* type + 8 bit value */ + msgb_tv_put(msg, NM_ATT_TSC, ts->tsc); +} + +/* Add attribute 9.4.60 HSN for channel class */ +static inline int add_att_hsn(struct msgb *msg, const struct gsm_bts_trx_ts *ts) +{ + if (!ts->hopping.enabled) + return -EINVAL; + /* type + 8 bit value */ + msgb_tv_put(msg, NM_ATT_HSN, ts->hopping.hsn); + return 0; +} + +/* Add attribute 9.4.21 MAIO for channel class */ +static inline int add_att_maio(struct msgb *msg, const struct gsm_bts_trx_ts *ts) +{ + if (!ts->hopping.enabled) + return -EINVAL; + /* type + 8 bit value */ + msgb_tv_put(msg, NM_ATT_MAIO, ts->hopping.maio); return 0; } @@ -276,32 +468,177 @@ static int oml_tx_attr_resp(const struct gsm_abis_mo *mo, const uint8_t *attr, uint16_t attr_len) { struct msgb *nmsg = oml_msgb_alloc(); - const char *mo_name = gsm_abis_mo_name(mo); + unsigned int num_unsupported = 0; + struct gsm_bts_trx *trx = NULL; + struct gsm_bts_trx_ts *ts = NULL; int rc; if (!nmsg) return -NM_NACK_CANT_PERFORM; + /* Set TRX, if object class is Radio Carrier, Baseband Transceiver or Channel. */ switch (mo->obj_class) { - case NM_OC_BTS: - rc = handle_attrs_bts(nmsg, mo->bts, attr, attr_len); - break; + case NM_OC_RADIO_CARRIER: case NM_OC_BASEB_TRANSC: - rc = handle_attrs_trx(nmsg, gsm_bts_trx_num(mo->bts, mo->obj_inst.trx_nr), attr, attr_len); + case NM_OC_CHANNEL: + trx = gsm_bts_trx_num(mo->bts, mo->obj_inst.trx_nr); break; - default: - LOGP(DOML, LOGL_ERROR, "%s: Unsupported MO class in Get Attribute Response\n", - mo_name); - rc = -NM_NACK_OBJCLASS_NOTSUPP; } - if (rc < 0) { - LOGP(DOML, LOGL_ERROR, "%s: Tx Get Attribute Response FAILED with rc=%d\n", - mo_name, rc); - msgb_free(nmsg); - return rc; + /* Set TS, if object class is Channel. */ + if (mo->obj_class == NM_OC_CHANNEL && trx) + ts = &trx->ts[mo->obj_inst.ts_nr]; + + for (unsigned int i = 0; i < attr_len; i++) { + switch (attr[i]) { + case NM_ATT_OPER_STATE: + msgb_tv16_put(nmsg, attr[i], 1); + msgb_put_u8(nmsg, mo->nm_state.operational); + break; + case NM_ATT_ADM_STATE: + msgb_tv16_put(nmsg, attr[i], 1); + msgb_put_u8(nmsg, mo->nm_state.administrative); + break; + case NM_ATT_AVAIL_STATUS: + msgb_tv16_put(nmsg, attr[i], 1); + msgb_put_u8(nmsg, mo->nm_state.availability); + break; + case NM_ATT_SW_CONFIG: + if (add_att_sw_config(nmsg, mo) != 0) + goto unsupported; + break; + case NM_ATT_MANUF_ID: + if (mo->obj_class == NM_OC_BTS) + add_bts_feat(nmsg, mo->bts); + else + goto unsupported; + break; + case NM_ATT_IPACC_SUPP_FEATURES: + if (add_att_ipacc_features(nmsg, mo) != 0) + goto unsupported; + break; + case NM_ATT_BCCH_ARFCN: + if (mo->obj_class != NM_OC_BTS) + goto unsupported; + add_att_bcch_arfcn(nmsg, mo->bts); + break; + case NM_ATT_INTERF_BOUND: + if (mo->obj_class != NM_OC_BTS) + goto unsupported; + add_att_interf_bound(nmsg, mo->bts); + break; + case NM_ATT_INTAVE_PARAM: + if (mo->obj_class != NM_OC_BTS) + goto unsupported; + add_att_intave_param(nmsg, mo->bts); + break; + case NM_ATT_CONN_FAIL_CRIT: + if (mo->obj_class != NM_OC_BTS) + goto unsupported; + add_att_conn_fail_crit(nmsg, mo->bts); + break; + case NM_ATT_MAX_TA: + if (mo->obj_class != NM_OC_BTS) + goto unsupported; + add_att_max_ta(nmsg, mo->bts); + break; + case NM_ATT_OVERL_PERIOD: + if (mo->obj_class != NM_OC_BTS) + goto unsupported; + add_att_overl_period(nmsg, mo->bts); + break; + case NM_ATT_CCCH_L_T: + if (mo->obj_class != NM_OC_BTS) + goto unsupported; + add_att_ccch_l_t(nmsg, mo->bts); + break; + case NM_ATT_CCCH_L_I_P: + if (mo->obj_class != NM_OC_BTS) + goto unsupported; + add_att_ccch_l_i_p(nmsg, mo->bts); + break; + case NM_ATT_RACH_B_THRESH: + if (mo->obj_class != NM_OC_BTS) + goto unsupported; + add_att_rach_b_thresh(nmsg, mo->bts); + break; + case NM_ATT_LDAVG_SLOTS: + if (mo->obj_class != NM_OC_BTS) + goto unsupported; + add_att_ldavg_slots(nmsg, mo->bts); + break; + case NM_ATT_BTS_AIR_TIMER: + if (mo->obj_class != NM_OC_BTS) + goto unsupported; + add_att_bts_air_timer(nmsg, mo->bts); + break; + case NM_ATT_NY1: + if (mo->obj_class != NM_OC_BTS) + goto unsupported; + add_att_ny1(nmsg, mo->bts); + break; + case NM_ATT_BSIC: + if (mo->obj_class != NM_OC_BTS) + goto unsupported; + if (add_att_bsic(nmsg, mo->bts) != 0) + goto unsupported; + break; + case NM_ATT_GSM_TIME: + if (mo->obj_class != NM_OC_BTS) + goto unsupported; + add_att_gsm_time(nmsg, mo->bts); + break; + case NM_ATT_RF_MAXPOWR_R: + if (mo->obj_class != NM_OC_RADIO_CARRIER || !trx) + goto unsupported; + add_att_rf_maxpowr_r(nmsg, trx); + break; + case NM_ATT_ARFCN_LIST: + if (mo->obj_class == NM_OC_RADIO_CARRIER && trx) { + add_att_arfcn_list(nmsg, trx); + break; + } + if (mo->obj_class == NM_OC_CHANNEL && ts) { + add_att_arfcn_list_ts(nmsg, ts); + break; + } + goto unsupported; + case NM_ATT_CHAN_COMB: + if (mo->obj_class != NM_OC_CHANNEL || !ts) + goto unsupported; + if (add_att_chan_comb(nmsg, ts) != 0) + goto unsupported; + break; + case NM_ATT_TSC: + if (mo->obj_class != NM_OC_CHANNEL || !ts) + goto unsupported; + add_att_tsc(nmsg, ts); + break; + case NM_ATT_HSN: + if (mo->obj_class != NM_OC_CHANNEL || !ts) + goto unsupported; + if (add_att_hsn(nmsg, ts) != 0) + goto unsupported; + break; + case NM_ATT_MAIO: + if (mo->obj_class != NM_OC_CHANNEL || !ts) + goto unsupported; + if (add_att_maio(nmsg, ts) != 0) + goto unsupported; + break; + default: +unsupported: + LOGP(DOML, LOGL_ERROR, "%s: O&M Get Attributes [%u], %s is unsupported\n", + gsm_abis_mo_name(mo), i, get_value_string(abis_nm_att_names, attr[i])); + /* Push this tag to the list of unsupported attributes */ + msgb_push_u8(nmsg, attr[i]); + num_unsupported++; + } } + /* Push the amount of unsupported attributes */ + msgb_push_u8(nmsg, num_unsupported); + /* Push Get Attribute Response Info TL (actually TV where V is L) */ msgb_tv16_push(nmsg, NM_ATT_GET_ARI, msgb_length(nmsg)); @@ -313,6 +650,7 @@ static int oml_tx_attr_resp(const struct gsm_abis_mo *mo, int oml_tx_state_changed(const struct gsm_abis_mo *mo) { struct msgb *nmsg; + uint8_t avail_state; nmsg = oml_msgb_alloc(); if (!nmsg) @@ -322,7 +660,8 @@ int oml_tx_state_changed(const struct gsm_abis_mo *mo) msgb_tv_put(nmsg, NM_ATT_OPER_STATE, mo->nm_state.operational); /* 9.4.7 Availability Status */ - msgb_tl16v_put(nmsg, NM_ATT_AVAIL_STATUS, 1, &mo->nm_state.availability); + avail_state = (uint8_t) mo->nm_state.availability; + msgb_tl16v_put(nmsg, NM_ATT_AVAIL_STATUS, 1, &avail_state); /* 9.4.4 Administrative Status -- not in spec but also sent by nanobts */ msgb_tv_put(nmsg, NM_ATT_ADM_STATE, mo->nm_state.administrative); @@ -337,12 +676,13 @@ void oml_mo_state_init(struct gsm_abis_mo *mo, int op_state, int avail_state) mo->nm_state.operational = op_state; } -int oml_mo_state_chg(struct gsm_abis_mo *mo, int op_state, int avail_state) +int oml_mo_state_chg(struct gsm_abis_mo *mo, int op_state, int avail_state, int adm_state) { int rc = 0; if ((op_state != -1 && mo->nm_state.operational != op_state) || - (avail_state != -1 && mo->nm_state.availability != avail_state)) { + (avail_state != -1 && mo->nm_state.availability != avail_state) || + (adm_state != -1 && mo->nm_state.administrative != adm_state)) { if (avail_state != -1) { LOGP(DOML, LOGL_INFO, "%s AVAIL STATE %s -> %s\n", gsm_abis_mo_name(mo), @@ -351,13 +691,25 @@ int oml_mo_state_chg(struct gsm_abis_mo *mo, int op_state, int avail_state) mo->nm_state.availability = avail_state; } if (op_state != -1) { + struct nm_statechg_signal_data nsd; LOGP(DOML, LOGL_INFO, "%s OPER STATE %s -> %s\n", gsm_abis_mo_name(mo), abis_nm_opstate_name(mo->nm_state.operational), abis_nm_opstate_name(op_state)); + nsd.mo = mo; + nsd.old_state = mo->nm_state.operational; + nsd.new_state = op_state; mo->nm_state.operational = op_state; - osmo_signal_dispatch(SS_GLOBAL, S_NEW_OP_STATE, NULL); + osmo_signal_dispatch(SS_GLOBAL, S_NEW_OP_STATE, &nsd); } + if (adm_state != -1) { + LOGP(DOML, LOGL_INFO, "%s ADMIN STATE %s -> %s\n", + gsm_abis_mo_name(mo), + abis_nm_admin_name(mo->nm_state.administrative), + abis_nm_admin_name(adm_state)); + mo->nm_state.administrative = adm_state; + } + /* send state change report */ rc = oml_tx_state_changed(mo); @@ -421,42 +773,48 @@ int oml_mo_opstart_nack(const struct gsm_abis_mo *mo, uint8_t nack_cause) return oml_mo_fom_ack_nack(mo, NM_MT_OPSTART, nack_cause); } -/* Send an ACK or NACK response for 'msg' to BSC, deriving message - * type, obj class, obj inst from 'msg' and copying all attributes - * contained in 'msg'. ACK is sent if cause == 0; NACK otherwise */ -int oml_fom_ack_nack(struct msgb *old_msg, uint8_t cause) +/* Send an ACK or NACK response to BSC for the given OML message, + * reusing it. ACK is sent if cause == 0; NACK otherwise. */ +int oml_fom_ack_nack(struct msgb *msg, uint8_t cause) { - struct msgb *msg; struct abis_om_fom_hdr *foh; - msg = msgb_copy(old_msg, "OML_fom_ack_nack"); - if (!msg) - return -ENOMEM; - - /* remove any l2/l1 that may be present in copy */ + /* remove any l2/l1 that may be already present */ msgb_pull_to_l2(msg); - msg->trx = old_msg->trx; - foh = (struct abis_om_fom_hdr *) msg->l3h; /* alter message type */ if (cause) { - LOGPFOH(DOML, LOGL_NOTICE, foh, "Sending FOM NACK with cause %s.\n", + LOGPFOH(DOML, LOGL_NOTICE, foh, "Sending FOM NACK with cause %s\n", abis_nm_nack_cause_name(cause)); foh->msg_type += 2; /* nack */ /* add cause */ msgb_tv_put(msg, NM_ATT_NACK_CAUSES, cause); - /* update the length as we just made the message larger */ - struct abis_om_hdr *omh = (struct abis_om_hdr *) msgb_l2(msg); - omh->length = msgb_l3len(msg); } else { - LOGPFOH(DOML, LOGL_DEBUG, foh, "Sending FOM ACK.\n"); + LOGPFOH(DOML, LOGL_DEBUG, foh, "Sending FOM ACK\n"); foh->msg_type++; /* ack */ } + /* ensure that the message length is up to date */ + struct abis_om_hdr *omh = (struct abis_om_hdr *) msgb_l2(msg); + omh->length = msgb_l3len(msg); + /* we cannot use oml_send_msg() as we already have the OML header */ - return abis_oml_sendmsg(msg); + if (abis_oml_sendmsg(msg) != 0) + LOGPFOH(DOML, LOGL_ERROR, foh, "Failed to send ACK/NACK\n"); + + /* msgb was reused, do not free() */ + return 1; +} + +/* Copy msg before calling oml_fom_ack_nack(), which takes its ownership */ +int oml_fom_ack_nack_copy_msg(const struct msgb *old_msg, uint8_t cause) +{ + struct msgb *msg = msgb_copy(old_msg, "OML-ack_nack"); + msg->trx = old_msg->trx; + oml_fom_ack_nack(msg, cause); + return 0; } /* @@ -472,20 +830,24 @@ int oml_mo_tx_sw_act_rep(const struct gsm_abis_mo *mo) if (!nmsg) return -ENOMEM; - msgb_put(nmsg, sizeof(struct abis_om_fom_hdr)); return oml_mo_send_msg(mo, nmsg, NM_MT_SW_ACTIVATED_REP); } -/* The defaults below correspond to various sources/recommendations that could be found online. - * The BSC should override this via OML anyway. */ -const unsigned int oml_default_t200_ms[7] = { - [T200_SDCCH] = 1000, - [T200_FACCH_F] = 1000, - [T200_FACCH_H] = 1000, - [T200_SACCH_TCH_SAPI0] = 2000, - [T200_SACCH_SDCCH] = 2000, - [T200_SDCCH_SAPI3] = 1000, - [T200_SACCH_TCH_SAPI3] = 2000, +/* The defaults below correspond to the number of frames until a response from the MS is expected. + * It defines the FN distance between the frame number when a message is sent (first frame) and when the response is + * received (first frame). On SACCH the duration is two frames, because SAPI0 and SAPI3 are are transmitted in + * alternating order. On DCCH with SAPI3 the duration is two seconds, because SAPI0 has priority over SAPI3. + * + * See Table 8 if 3GPP TS 44.006. Note that the table only shows the FN distance between frames. + */ +const uint32_t oml_default_t200_fn[7] = { + [T200_SDCCH] = 4+32, + [T200_FACCH_F] = 8+9, + [T200_FACCH_H] = 6+10, + [T200_SACCH_TCH_SAPI0] = 79+25+104, + [T200_SACCH_SDCCH] = 4+32+51, + [T200_SDCCH_SAPI3] = 4+32+408, /* two seconds */ + [T200_SACCH_TCH_SAPI3] = 79+25+104, }; /* 3GPP TS 52.021 §8.11.1 Get Attributes has been received */ @@ -495,6 +857,7 @@ static int oml_rx_get_attr(struct gsm_bts *bts, struct msgb *msg) const struct gsm_abis_mo *mo; struct tlv_parsed tp; int rc; + enum abis_nm_nack_cause c; if (!foh || !bts) return -EINVAL; @@ -502,10 +865,9 @@ static int oml_rx_get_attr(struct gsm_bts *bts, struct msgb *msg) DEBUGPFOH(DOML, foh, "Rx GET ATTR\n"); /* Determine which OML object is addressed */ - mo = gsm_objclass2mo(bts, foh->obj_class, &foh->obj_inst); - if (!mo) { + if ((mo = gsm_objclass2mo(bts, foh->obj_class, &foh->obj_inst, &c)) == NULL) { LOGPFOH(DOML, LOGL_ERROR, foh, "Get Attributes for unknown Object Instance\n"); - return oml_fom_ack_nack(msg, NM_NACK_OBJINST_UNKN); + return oml_fom_ack_nack(msg, c); } rc = oml_tlv_parse(&tp, foh->data, msgb_l3len(msg) - sizeof(*foh)); @@ -537,6 +899,7 @@ static int oml_rx_set_bts_attr(struct gsm_bts *bts, struct msgb *msg) struct tlv_parsed tp, *tp_merged; int rc, i; const uint8_t *payload; + struct nm_fsm_ev_setattr_data ev_data; DEBUGPFOH(DOML, foh, "Rx SET BTS ATTR\n"); @@ -550,11 +913,11 @@ static int oml_rx_set_bts_attr(struct gsm_bts *bts, struct msgb *msg) /* Test for globally unsupported stuff here */ if (TLVP_PRES_LEN(&tp, NM_ATT_BCCH_ARFCN, 2)) { uint16_t arfcn = ntohs(tlvp_val16_unal(&tp, NM_ATT_BCCH_ARFCN)); - if (arfcn > 1024) { + if (arfcn >= 1024) { /* 0 .. 1023 (1024 channels total) */ oml_tx_failure_event_rep(&bts->mo, NM_SEVER_MAJOR, OSMO_EVT_WARN_SW_WARN, "Given ARFCN %u is not supported", arfcn); - LOGPFOH(DOML, LOGL_ERROR, foh, "Given ARFCN %u is not supported.\n", arfcn); + LOGPFOH(DOML, LOGL_ERROR, foh, "Given ARFCN %u is not supported\n", arfcn); return oml_fom_ack_nack(msg, NM_NACK_FREQ_NOTAVAIL); } } @@ -568,6 +931,7 @@ static int oml_rx_set_bts_attr(struct gsm_bts *bts, struct msgb *msg) /* merge existing BTS attributes with new attributes */ tp_merged = osmo_tlvp_copy(bts->mo.nm_attr, bts); + talloc_set_name_const(tp_merged, "oml_bts_attr"); osmo_tlvp_merge(tp_merged, &tp); /* Ask BTS driver to validate new merged attributes */ @@ -586,8 +950,8 @@ static int oml_rx_set_bts_attr(struct gsm_bts *bts, struct msgb *msg) /* 9.4.25 Interference Level Boundaries */ if (TLVP_PRES_LEN(&tp, NM_ATT_INTERF_BOUND, 6)) { payload = TLVP_VAL(&tp, NM_ATT_INTERF_BOUND); - for (i = 0; i < 6; i++) { - int16_t boundary = *payload; + for (i = 0; i < ARRAY_SIZE(bts->interference.boundary); i++) { + const int16_t boundary = payload[i]; bts->interference.boundary[i] = -1 * boundary; } } @@ -603,12 +967,16 @@ static int oml_rx_set_bts_attr(struct gsm_bts *bts, struct msgb *msg) case 0xFF: /* Osmocom specific Extension of TS 12.21 */ LOGPFOH(DOML, LOGL_NOTICE, foh, "WARNING: Radio Link Timeout " "explicitly disabled, only use this for lab testing!\n"); - bts->radio_link_timeout = -1; + bts->radio_link_timeout.oml = -1; + if (!bts->radio_link_timeout.vty_override) + bts->radio_link_timeout.current = bts->radio_link_timeout.oml; break; case 0x01: /* Based on uplink SACCH (radio link timeout) */ if (TLVP_LEN(&tp, NM_ATT_CONN_FAIL_CRIT) >= 2 && val[1] >= 4 && val[1] <= 64) { - bts->radio_link_timeout = val[1]; + bts->radio_link_timeout.oml = val[1]; + if (!bts->radio_link_timeout.vty_override) + bts->radio_link_timeout.current = bts->radio_link_timeout.oml; break; } /* fall-through */ @@ -622,25 +990,26 @@ static int oml_rx_set_bts_attr(struct gsm_bts *bts, struct msgb *msg) } /* 9.4.53 T200 */ - if (TLVP_PRES_LEN(&tp, NM_ATT_T200, ARRAY_SIZE(bts->t200_ms))) { + if (TLVP_PRES_LEN(&tp, NM_ATT_T200, ARRAY_SIZE(bts->t200_fn))) { + /* The OML message NM_ATT_T200 is ignored, because T200 timeouts are set to + * the minimal response time. Longer timeouts would cause lower throughput + * in case of lost frames. Shorter timeouts would cause LAPDm to fail. */ + DEBUGPFOH(DOML, foh, "Ignoring T200 BTS attribute.\n"); +#if 0 payload = TLVP_VAL(&tp, NM_ATT_T200); - for (i = 0; i < ARRAY_SIZE(bts->t200_ms); i++) { + for (i = 0; i < ARRAY_SIZE(bts->t200_fn); i++) { uint32_t t200_ms = payload[i] * abis_nm_t200_ms[i]; -#if 0 - bts->t200_ms[i] = t200_ms; - DEBUGPFOH(DOML, foh, "T200[%u]: OML=%u, mult=%u => %u ms\n", + uint32_t t200_fn = t200_ms * 1000 + (GSM_TDMA_FN_DURATION_uS - 1) / GSM_TDMA_FN_DURATION_uS; + /* Values must not be less than absolute minimum. */ + if (oml_default_t200_fn[i] <= t200_fn) + bts->t200_fn[i] = t200_fn; + else + bts->t200_fn[i] = oml_default_t200_fn[i]; + DEBUGPFOH(DOML, foh, "T200[%u]: OML=%u, mult=%u => %u ms -> %u fn\n", i, payload[i], abis_nm_t200_ms[i], - bts->t200_ms[i]); -#else - /* we'd rather use the 1s/2s (long) defaults by - * libosmocore, as we appear to have some bug(s) - * related to handling T200 expiration in - * libosmogsm lapd(m) code? */ - LOGPFOH(DOML, LOGL_NOTICE, foh, "Ignoring T200[%u] (%u ms) " - "as sent by BSC due to suspected LAPDm bug!\n", - i, t200_ms); -#endif + t200_ms, bts->t200_fn[i]); } +#endif } /* 9.4.31 Maximum Timing Advance */ @@ -658,7 +1027,10 @@ static int oml_rx_set_bts_attr(struct gsm_bts *bts, struct msgb *msg) /* 9.4.11 CCCH Load Indication Period */ if (TLVP_PRES_LEN(&tp, NM_ATT_CCCH_L_I_P, 1)) { bts->load.ccch.load_ind_period = *TLVP_VAL(&tp, NM_ATT_CCCH_L_I_P); - load_timer_start(bts); + if (load_timer_is_running(bts)) { + load_timer_stop(bts); + load_timer_start(bts); + } } /* 9.4.44 RACH Busy Threshold */ @@ -677,26 +1049,64 @@ static int oml_rx_set_bts_attr(struct gsm_bts *bts, struct msgb *msg) if (TLVP_PRES_LEN(&tp, NM_ATT_BTS_AIR_TIMER, 1)) { uint8_t t3105 = *TLVP_VAL(&tp, NM_ATT_BTS_AIR_TIMER); if (t3105 == 0) { - LOGPFOH(DOML, LOGL_NOTICE, foh, "T3105 must have a value != 0.\n"); + LOGPFOH(DOML, LOGL_NOTICE, foh, "T3105 must have a value != 0\n"); return oml_fom_ack_nack(msg, NM_NACK_PARAM_RANGE); } bts->t3105_ms = t3105 * 10; + /* there are no OML IEs for T3115; let's use T3105 as HO detection is a similar procedure */ + bts->t3115_ms = bts->t3105_ms; } /* 9.4.37 NY1 */ - if (TLVP_PRES_LEN(&tp, NM_ATT_NY1, 1)) + if (TLVP_PRES_LEN(&tp, NM_ATT_NY1, 1)) { bts->ny1 = *TLVP_VAL(&tp, NM_ATT_NY1); + /* there are no OML IEs for NY2; let's use NY1 as HO detection is a similar procedure */ + bts->ny2 = bts->ny1; + } /* 9.4.8 BCCH ARFCN */ if (TLVP_PRES_LEN(&tp, NM_ATT_BCCH_ARFCN, 2)) bts->c0->arfcn = ntohs(tlvp_val16_unal(&tp, NM_ATT_BCCH_ARFCN)); /* 9.4.9 BSIC */ - if (TLVP_PRES_LEN(&tp, NM_ATT_BSIC, 1)) + if (TLVP_PRES_LEN(&tp, NM_ATT_BSIC, 1)) { + struct gsm_bts_trx *trx; + uint8_t bts_tsc; + bts->bsic = *TLVP_VAL(&tp, NM_ATT_BSIC); + bts->bsic_configured = true; + bts_tsc = BTS_TSC(bts); + + /* Apply TSC update on each TS if required: */ + llist_for_each_entry(trx, &bts->trx_list, list) { /* C0..n */ + unsigned int tn; + for (tn = 0; tn < ARRAY_SIZE(trx->ts); tn++) { + struct gsm_bts_trx_ts *ts = &trx->ts[tn]; + + /* First some config validation: */ + if (ts->tsc_oml_configured && + ts->tsc_oml != bts_tsc && + !osmo_bts_has_feature(bts->features, BTS_FEAT_MULTI_TSC)) { + LOGPFOH(DOML, LOGL_ERROR, foh, "SET BTS ATTR: this BTS model does not " + "support TSC %u != BSIC-BCC %u (TSC %u)\n", + ts->tsc_oml, bts->bsic, bts_tsc); + return oml_fom_ack_nack(msg, NM_NACK_PARAM_RANGE); + } + + /* Now update TS TSC if needed: */ + gsm_ts_apply_configured_tsc(ts); + } + } + } + + ev_data = (struct nm_fsm_ev_setattr_data){ + .msg = msg, + }; - /* call into BTS driver to apply new attributes to hardware */ - return bts_model_apply_oml(bts, msg, tp_merged, NM_OC_BTS, bts); + rc = osmo_fsm_inst_dispatch(bts->mo.fi, NM_EV_RX_SETATTR, &ev_data); + if (rc < 0) + return oml_fom_ack_nack(msg, NM_NACK_CANT_PERFORM); + return rc; } /* 8.6.2 Set Radio Attributes has been received */ @@ -705,6 +1115,7 @@ static int oml_rx_set_radio_attr(struct gsm_bts_trx *trx, struct msgb *msg) struct abis_om_fom_hdr *foh = msgb_l3(msg); struct tlv_parsed tp, *tp_merged; int rc; + struct nm_fsm_ev_setattr_data ev_data; DEBUGPFOH(DOML, foh, "Rx SET RADIO CARRIER ATTR\n"); @@ -716,8 +1127,9 @@ static int oml_rx_set_radio_attr(struct gsm_bts_trx *trx, struct msgb *msg) return oml_fom_ack_nack(msg, NM_NACK_INCORR_STRUCT); } - /* merge existing BTS attributes with new attributes */ - tp_merged = osmo_tlvp_copy(trx->mo.nm_attr, trx->bts); + /* merge existing TRX attributes with new attributes */ + tp_merged = osmo_tlvp_copy(trx->mo.nm_attr, trx); + talloc_set_name_const(tp_merged, "oml_trx_attr"); osmo_tlvp_merge(tp_merged, &tp); /* Ask BTS driver to validate new merged attributes */ @@ -727,7 +1139,7 @@ static int oml_rx_set_radio_attr(struct gsm_bts_trx *trx, struct msgb *msg) return oml_fom_ack_nack(msg, -rc); } - /* Success: replace old BTS attributes with new */ + /* Success: replace old TRX attributes with new */ talloc_free(trx->mo.nm_attr); trx->mo.nm_attr = tp_merged; @@ -773,22 +1185,33 @@ static int oml_rx_set_radio_attr(struct gsm_bts_trx *trx, struct msgb *msg) memcpy(&_value, value, 2); arfcn = ntohs(_value); value += 2; - if (arfcn > 1024) { + if (arfcn >= 1024) { /* 0 .. 1023 (1024 channels total) */ oml_tx_failure_event_rep(&trx->bts->mo, NM_SEVER_MAJOR, OSMO_EVT_WARN_SW_WARN, "Given ARFCN %u is unsupported", arfcn); - LOGPFOH(DOML, LOGL_NOTICE, foh, "Given ARFCN %u is unsupported.\n", arfcn); + LOGPFOH(DOML, LOGL_NOTICE, foh, "Given ARFCN %u is unsupported\n", arfcn); return oml_fom_ack_nack(msg, NM_NACK_FREQ_NOTAVAIL); } trx->arfcn = arfcn; } #endif - /* call into BTS driver to apply new attributes to hardware */ - return bts_model_apply_oml(trx->bts, msg, tp_merged, NM_OC_RADIO_CARRIER, trx); + + ev_data = (struct nm_fsm_ev_setattr_data){ + .msg = msg, + }; + + rc = osmo_fsm_inst_dispatch(trx->mo.fi, NM_EV_RX_SETATTR, &ev_data); + if (rc < 0) + return oml_fom_ack_nack(msg, NM_NACK_CANT_PERFORM); + return rc; + } -static int conf_lchans(struct gsm_bts_trx_ts *ts) +static int handle_chan_comb(struct gsm_bts_trx_ts *ts, const uint8_t comb) { - enum gsm_phys_chan_config pchan = ts->pchan; + enum gsm_phys_chan_config pchan; + + pchan = abis_nm_pchan4chcomb(comb); + ts->pchan = pchan; /* RSL_MT_IPAC_PDCH_ACT style dyn PDCH */ if (pchan == GSM_PCHAN_TCH_F_PDCH) @@ -796,7 +1219,7 @@ static int conf_lchans(struct gsm_bts_trx_ts *ts) : GSM_PCHAN_TCH_F; /* Osmocom RSL CHAN ACT style dyn TS */ - if (pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH) { + if (pchan == GSM_PCHAN_OSMO_DYN) { pchan = ts->dyn.pchan_is; /* If the dyn TS doesn't have a pchan yet, do nothing. */ @@ -807,62 +1230,65 @@ static int conf_lchans(struct gsm_bts_trx_ts *ts) return conf_lchans_as_pchan(ts, pchan); } +static inline void lchans_type_set(struct gsm_bts_trx_ts *ts, + enum gsm_chan_t lchan_type, + unsigned int num_lchans) +{ + unsigned int i; + + for (i = 0; i < num_lchans; i++) + ts->lchan[i].type = lchan_type; +} + int conf_lchans_as_pchan(struct gsm_bts_trx_ts *ts, enum gsm_phys_chan_config pchan) { - struct gsm_lchan *lchan; - unsigned int i; + /* Initialize all lchans with GSM_LCHAN_NONE first */ + lchans_type_set(ts, GSM_LCHAN_NONE, ARRAY_SIZE(ts->lchan)); switch (pchan) { case GSM_PCHAN_CCCH_SDCCH4_CBCH: - /* fallthrough */ case GSM_PCHAN_CCCH_SDCCH4: - for (i = 0; i < 4; i++) { - lchan = &ts->lchan[i]; - if (pchan == GSM_PCHAN_CCCH_SDCCH4_CBCH - && i == 2) { - lchan->type = GSM_LCHAN_CBCH; - } else { - lchan->type = GSM_LCHAN_SDCCH; - } - } + lchans_type_set(ts, GSM_LCHAN_SDCCH, 4); + if (pchan == GSM_PCHAN_CCCH_SDCCH4_CBCH) + ts->lchan[2].type = GSM_LCHAN_CBCH; /* fallthrough */ case GSM_PCHAN_CCCH: - lchan = &ts->lchan[CCCH_LCHAN]; - lchan->type = GSM_LCHAN_CCCH; + ts->lchan[CCCH_LCHAN].type = GSM_LCHAN_CCCH; break; case GSM_PCHAN_TCH_F: - lchan = &ts->lchan[0]; - lchan->type = GSM_LCHAN_TCH_F; + if (ts->vamos.peer != NULL) { /* VAMOS: enable shadow lchans */ + lchans_type_set(ts->vamos.peer, GSM_LCHAN_TCH_F, 1); + ts->vamos.peer->pchan = GSM_PCHAN_TCH_F; + } + lchans_type_set(ts, GSM_LCHAN_TCH_F, 1); break; case GSM_PCHAN_TCH_H: - for (i = 0; i < 2; i++) { - lchan = &ts->lchan[i]; - lchan->type = GSM_LCHAN_TCH_H; + if (ts->vamos.peer != NULL) { /* VAMOS: enable shadow lchans */ + lchans_type_set(ts->vamos.peer, GSM_LCHAN_TCH_H, 2); + ts->vamos.peer->pchan = GSM_PCHAN_TCH_H; } + lchans_type_set(ts, GSM_LCHAN_TCH_H, 2); break; case GSM_PCHAN_SDCCH8_SACCH8C_CBCH: - /* fallthrough */ case GSM_PCHAN_SDCCH8_SACCH8C: - for (i = 0; i < 8; i++) { - lchan = &ts->lchan[i]; - if (pchan == GSM_PCHAN_SDCCH8_SACCH8C_CBCH - && i == 2) { - lchan->type = GSM_LCHAN_CBCH; - } else { - lchan->type = GSM_LCHAN_SDCCH; - } - } + lchans_type_set(ts, GSM_LCHAN_SDCCH, 8); + if (pchan == GSM_PCHAN_SDCCH8_SACCH8C_CBCH) + ts->lchan[2].type = GSM_LCHAN_CBCH; break; case GSM_PCHAN_PDCH: - lchan = &ts->lchan[0]; - lchan->type = GSM_LCHAN_PDTCH; + if (ts->vamos.peer != NULL) { /* VAMOS: disable shadow lchans */ + lchans_type_set(ts->vamos.peer, GSM_LCHAN_NONE, 1); + ts->vamos.peer->pchan = GSM_PCHAN_NONE; + } + lchans_type_set(ts, GSM_LCHAN_PDTCH, 1); break; default: LOGP(DOML, LOGL_ERROR, "Unknown/unhandled PCHAN type: %u %s\n", ts->pchan, gsm_pchan_name(ts->pchan)); return -NM_NACK_PARAM_RANGE; } + return 0; } @@ -872,7 +1298,8 @@ static int oml_rx_set_chan_attr(struct gsm_bts_trx_ts *ts, struct msgb *msg) struct abis_om_fom_hdr *foh = msgb_l3(msg); struct gsm_bts *bts = ts->trx->bts; struct tlv_parsed tp, *tp_merged; - int rc; + int rc, i; + struct nm_fsm_ev_setattr_data ev_data; DEBUGPFOH(DOML, foh, "Rx SET CHAN ATTR\n"); @@ -883,21 +1310,50 @@ static int oml_rx_set_chan_attr(struct gsm_bts_trx_ts *ts, struct msgb *msg) return oml_fom_ack_nack(msg, NM_NACK_INCORR_STRUCT); } - /* 9.4.21 HSN... */ - /* 9.4.27 MAIO */ + /* Check frequency hopping parameters (HSN, MAIO, ARFCN list) */ if (TLVP_PRESENT(&tp, NM_ATT_HSN) || TLVP_PRESENT(&tp, NM_ATT_MAIO)) { - LOGPFOH(DOML, LOGL_NOTICE, foh, "SET CHAN ATTR: Frequency hopping not supported.\n"); - return oml_fom_ack_nack(msg, NM_NACK_SPEC_IMPL_NOTSUPP); + if (!osmo_bts_has_feature(bts->features, BTS_FEAT_HOPPING)) { + LOGPFOH(DOML, LOGL_ERROR, foh, "SET CHAN ATTR: Frequency hopping not supported\n"); + return oml_fom_ack_nack(msg, NM_NACK_SPEC_IMPL_NOTSUPP); + } + + if (!TLVP_PRES_LEN(&tp, NM_ATT_HSN, 1) || !TLVP_PRES_LEN(&tp, NM_ATT_MAIO, 1)) { + LOGPFOH(DOML, LOGL_ERROR, foh, "SET CHAN ATTR: HSN and/or MAIO is missing: " + "hsn=%u, maio=%u\n", TLVP_LEN(&tp, NM_ATT_HSN), TLVP_LEN(&tp, NM_ATT_MAIO)); + return oml_fom_ack_nack(msg, NM_NACK_ATTRLIST_INCONSISTENT); + } + + if (!TLVP_PRES_LEN(&tp, NM_ATT_ARFCN_LIST, 2)) { /* At least one ARFCN */ + LOGPFOH(DOML, LOGL_ERROR, foh, "SET CHAN ATTR: ARFCN list is missing\n"); + return oml_fom_ack_nack(msg, NM_NACK_ATTRLIST_INCONSISTENT); + } + + if (TLVP_LEN(&tp, NM_ATT_ARFCN_LIST) > sizeof(ts->hopping.arfcn_list)) { + LOGPFOH(DOML, LOGL_ERROR, foh, "SET CHAN ATTR: ARFCN list is too long\n"); + return oml_fom_ack_nack(msg, NM_NACK_ATTRLIST_INCONSISTENT); + } else if (TLVP_LEN(&tp, NM_ATT_ARFCN_LIST) % 2 != 0) { + LOGPFOH(DOML, LOGL_ERROR, foh, "SET CHAN ATTR: ARFCN list has odd length\n"); + return oml_fom_ack_nack(msg, NM_NACK_ATTRLIST_INCONSISTENT); + } + + ts->hopping.enabled = true; + ts->hopping.hsn = *TLVP_VAL(&tp, NM_ATT_HSN); + ts->hopping.maio = *TLVP_VAL(&tp, NM_ATT_MAIO); + + ts->hopping.arfcn_num = TLVP_LEN(&tp, NM_ATT_ARFCN_LIST) / sizeof(uint16_t); + for (i = 0; i < ts->hopping.arfcn_num; i++) + ts->hopping.arfcn_list[i] = osmo_load16be(TLVP_VAL(&tp, NM_ATT_ARFCN_LIST) + i * 2); } /* 9.4.52 Starting Time */ if (TLVP_PRESENT(&tp, NM_ATT_START_TIME)) { - LOGPFOH(DOML, LOGL_NOTICE, foh, "SET CHAN ATTR: Starting time not supported.\n"); + LOGPFOH(DOML, LOGL_NOTICE, foh, "SET CHAN ATTR: Starting time not supported\n"); return oml_fom_ack_nack(msg, NM_NACK_SPEC_IMPL_NOTSUPP); } - /* merge existing BTS attributes with new attributes */ - tp_merged = osmo_tlvp_copy(ts->mo.nm_attr, bts); + /* merge existing CHAN attributes with new attributes */ + tp_merged = osmo_tlvp_copy(ts->mo.nm_attr, ts->trx); + talloc_set_name_const(tp_merged, "oml_chan_attr"); osmo_tlvp_merge(tp_merged, &tp); /* Call into BTS driver to check attribute values */ @@ -909,18 +1365,16 @@ static int oml_rx_set_chan_attr(struct gsm_bts_trx_ts *ts, struct msgb *msg) return oml_fom_ack_nack(msg, -rc); } - /* Success: replace old BTS attributes with new */ + /* Success: replace old CHAN attributes with new */ talloc_free(ts->mo.nm_attr); ts->mo.nm_attr = tp_merged; /* 9.4.13 Channel Combination */ if (TLVP_PRES_LEN(&tp, NM_ATT_CHAN_COMB, 1)) { - uint8_t comb = *TLVP_VAL(&tp, NM_ATT_CHAN_COMB); - ts->pchan = abis_nm_pchan4chcomb(comb); - rc = conf_lchans(ts); - if (rc < 0) { + const uint8_t comb = *TLVP_VAL(&tp, NM_ATT_CHAN_COMB); + if ((rc = handle_chan_comb(ts, comb)) != 0) { LOGPFOH(DOML, LOGL_ERROR, foh, "SET CHAN ATTR: invalid Chan Comb 0x%x" - " (pchan=%s, conf_lchans()->%d)\n", + " (pchan=%s, handle_chan_comb() returns %d)\n", comb, gsm_pchan_name(ts->pchan), rc); talloc_free(tp_merged); /* Send NACK */ @@ -928,20 +1382,40 @@ static int oml_rx_set_chan_attr(struct gsm_bts_trx_ts *ts, struct msgb *msg) } } - /* 9.4.5 ARFCN List */ - /* 9.4.60 TSC */ if (TLVP_PRES_LEN(&tp, NM_ATT_TSC, 1)) { - ts->tsc = *TLVP_VAL(&tp, NM_ATT_TSC); - } else { - /* If there is no TSC specified, use the BCC */ - ts->tsc = BSIC2BCC(bts->bsic); + ts->tsc_oml = *TLVP_VAL(&tp, NM_ATT_TSC); + ts->tsc_oml_configured = true; + } + + if (ts->tsc_oml_configured) { + if (bts->bsic_configured && + ts->tsc_oml != BTS_TSC(bts) && + !osmo_bts_has_feature(bts->features, BTS_FEAT_MULTI_TSC)) { + LOGPFOH(DOML, LOGL_ERROR, foh, "SET CHAN ATTR: this BTS model does not " + "support TSC %u != BSIC-BCC %u\n", ts->tsc_oml, BTS_TSC(bts)); + talloc_free(tp_merged); + return oml_fom_ack_nack(msg, NM_NACK_PARAM_RANGE); + } + gsm_ts_apply_configured_tsc(ts); } - LOGPFOH(DOML, LOGL_INFO, foh, "%s SET CHAN ATTR (TSC=%u pchan=%s)\n", - gsm_abis_mo_name(&ts->mo), ts->tsc, gsm_pchan_name(ts->pchan)); - /* call into BTS driver to apply new attributes to hardware */ - return bts_model_apply_oml(bts, msg, tp_merged, NM_OC_CHANNEL, ts); + LOGPFOH(DOML, LOGL_INFO, foh, "SET CHAN ATTR (TSC=%d pchan=%s", + ts->tsc_oml_configured ? (int)ts->tsc_oml : -1, + gsm_pchan_name(ts->pchan)); + if (ts->hopping.enabled) + LOGPC(DOML, LOGL_INFO, " hsn=%u maio=%u chan_num=%u", + ts->hopping.hsn, ts->hopping.maio, ts->hopping.arfcn_num); + LOGPC(DOML, LOGL_INFO, ")\n"); + + ev_data = (struct nm_fsm_ev_setattr_data){ + .msg = msg, + }; + + rc = osmo_fsm_inst_dispatch(ts->mo.fi, NM_EV_RX_SETATTR, &ev_data); + if (rc < 0) + return oml_fom_ack_nack(msg, NM_NACK_CANT_PERFORM); + return rc; } /* 8.9.2 Opstart has been received */ @@ -950,14 +1424,16 @@ static int oml_rx_opstart(struct gsm_bts *bts, struct msgb *msg) struct abis_om_fom_hdr *foh = msgb_l3(msg); struct gsm_abis_mo *mo; void *obj; + int rc; + enum abis_nm_nack_cause c; DEBUGPFOH(DOML, foh, "Rx OPSTART\n"); /* Step 1: Resolve MO by obj_class/obj_inst */ - mo = gsm_objclass2mo(bts, foh->obj_class, &foh->obj_inst); - obj = gsm_objclass2obj(bts, foh->obj_class, &foh->obj_inst); - if (!mo || !obj) - return oml_fom_ack_nack(msg, NM_NACK_OBJINST_UNKN); + if ((mo = gsm_objclass2mo(bts, foh->obj_class, &foh->obj_inst, &c)) == NULL) + return oml_fom_ack_nack(msg, c); + if ((obj = gsm_objclass2obj(bts, foh->obj_class, &foh->obj_inst, &c)) == NULL) + return oml_fom_ack_nack(msg, c); /* Step 2: Do some global dependency/consistency checking */ if (mo->nm_state.operational == NM_OPSTATE_ENABLED) { @@ -965,8 +1441,13 @@ static int oml_rx_opstart(struct gsm_bts *bts, struct msgb *msg) return oml_mo_opstart_ack(mo); } - /* Step 3: Ask BTS driver to apply the opstart */ - return bts_model_opstart(bts, mo, obj); + /* Make sure all NM objects already have an FSM implemented: */ + OSMO_ASSERT(mo->fi); + + rc = osmo_fsm_inst_dispatch(mo->fi, NM_EV_RX_OPSTART, NULL); + if (rc < 0) + return oml_fom_ack_nack(msg, NM_NACK_CANT_PERFORM); + return rc; } static int oml_rx_chg_adm_state(struct gsm_bts *bts, struct msgb *msg) @@ -977,6 +1458,7 @@ static int oml_rx_chg_adm_state(struct gsm_bts *bts, struct msgb *msg) uint8_t adm_state; void *obj; int rc; + enum abis_nm_nack_cause c; DEBUGPFOH(DOML, foh, "Rx CHG ADM STATE\n"); @@ -994,15 +1476,20 @@ static int oml_rx_chg_adm_state(struct gsm_bts *bts, struct msgb *msg) adm_state = *TLVP_VAL(&tp, NM_ATT_ADM_STATE); /* Step 1: Resolve MO by obj_class/obj_inst */ - mo = gsm_objclass2mo(bts, foh->obj_class, &foh->obj_inst); - obj = gsm_objclass2obj(bts, foh->obj_class, &foh->obj_inst); - if (!mo || !obj) - return oml_fom_ack_nack(msg, NM_NACK_OBJINST_UNKN); + if ((mo = gsm_objclass2mo(bts, foh->obj_class, &foh->obj_inst, &c)) == NULL) + return oml_fom_ack_nack(msg, c); + if ((obj = gsm_objclass2obj(bts, foh->obj_class, &foh->obj_inst, &c)) == NULL) + return oml_fom_ack_nack(msg, c); /* Step 2: Do some global dependency/consistency checking */ - if (mo->nm_state.administrative == adm_state) + if (mo->nm_state.administrative == adm_state) { LOGPFOH(DOML, LOGL_NOTICE, foh, "ADM state already was %s\n", get_value_string(abis_nm_adm_state_names, adm_state)); + return oml_mo_statechg_ack(mo); + } + LOGPFOH(DOML, LOGL_NOTICE, foh, "ADM STATE %s -> %s\n", + get_value_string(abis_nm_adm_state_names, mo->nm_state.administrative), + get_value_string(abis_nm_adm_state_names, adm_state)); /* Step 3: Ask BTS driver to apply the state chg */ return bts_model_chg_adm_state(bts, mo, obj, adm_state); @@ -1114,16 +1601,21 @@ static int down_fom(struct gsm_bts *bts, struct msgb *msg) * manufacturer related messages */ -static int oml_ipa_mo_set_attr_nse(void *obj, struct tlv_parsed *tp) +static int oml_ipa_mo_set_attr_nse(void *obj, + const struct msgb *msg, + const struct tlv_parsed *tp) { - struct gsm_bts *bts = container_of(obj, struct gsm_bts, gprs.nse); + struct gsm_gprs_nse *nse = obj; + struct gsm_bts *bts = gsm_gprs_nse_get_bts(nse); + struct nm_fsm_ev_setattr_data ev_data; + int rc; if (TLVP_PRES_LEN(tp, NM_ATT_IPACC_NSEI, 2)) - bts->gprs.nse.nsei = + nse->nsei = ntohs(tlvp_val16_unal(tp, NM_ATT_IPACC_NSEI)); if (TLVP_PRES_LEN(tp, NM_ATT_IPACC_NS_CFG, 7)) { - memcpy(&bts->gprs.nse.timer, + memcpy(&nse->timer, TLVP_VAL(tp, NM_ATT_IPACC_NS_CFG), 7); } @@ -1132,15 +1624,27 @@ static int oml_ipa_mo_set_attr_nse(void *obj, struct tlv_parsed *tp) TLVP_VAL(tp, NM_ATT_IPACC_BSSGP_CFG), 11); } + ev_data = (struct nm_fsm_ev_setattr_data){ + .msg = msg, + }; + rc = osmo_fsm_inst_dispatch(nse->mo.fi, NM_EV_RX_SETATTR, &ev_data); + if (rc < 0) + return NM_NACK_CANT_PERFORM; + osmo_signal_dispatch(SS_GLOBAL, S_NEW_NSE_ATTR, bts); return 0; } -static int oml_ipa_mo_set_attr_cell(void *obj, struct tlv_parsed *tp) +static int oml_ipa_mo_set_attr_cell(void *obj, + const struct msgb *msg, + const struct tlv_parsed *tp) { - struct gsm_bts *bts = container_of(obj, struct gsm_bts, gprs.cell); - struct gprs_rlc_cfg *rlcc = &bts->gprs.cell.rlc_cfg; + struct gsm_gprs_cell *gprs_cell = obj; + struct gsm_bts *bts = gsm_gprs_cell_get_bts(gprs_cell); + struct gprs_rlc_cfg *rlcc = &gprs_cell->rlc_cfg; + struct nm_fsm_ev_setattr_data ev_data; + int rc; const uint8_t *cur; uint16_t _cur_s; @@ -1154,8 +1658,7 @@ static int oml_ipa_mo_set_attr_cell(void *obj, struct tlv_parsed *tp) } if (TLVP_PRES_LEN(tp, NM_ATT_IPACC_BVCI, 2)) - bts->gprs.cell.bvci = - ntohs(tlvp_val16_unal(tp, NM_ATT_IPACC_BVCI)); + gprs_cell->bvci = ntohs(tlvp_val16_unal(tp, NM_ATT_IPACC_BVCI)); if (TLVP_PRES_LEN(tp, NM_ATT_IPACC_RLC_CFG, 9)) { cur = TLVP_VAL(tp, NM_ATT_IPACC_RLC_CFG); @@ -1202,14 +1705,25 @@ static int oml_ipa_mo_set_attr_cell(void *obj, struct tlv_parsed *tp) rlcc->initial_mcs = *TLVP_VAL(tp, NM_ATT_IPACC_RLC_CFG_3); } + ev_data = (struct nm_fsm_ev_setattr_data){ + .msg = msg, + }; + rc = osmo_fsm_inst_dispatch(gprs_cell->mo.fi, NM_EV_RX_SETATTR, &ev_data); + if (rc < 0) + return NM_NACK_CANT_PERFORM; + osmo_signal_dispatch(SS_GLOBAL, S_NEW_CELL_ATTR, bts); return 0; } -static int oml_ipa_mo_set_attr_nsvc(struct gsm_bts_gprs_nsvc *nsvc, - struct tlv_parsed *tp) +static int oml_ipa_mo_set_attr_nsvc(struct gsm_gprs_nsvc *nsvc, + const struct msgb *msg, + const struct tlv_parsed *tp) { + struct nm_fsm_ev_setattr_data ev_data; + int rc; + if (TLVP_PRES_LEN(tp, NM_ATT_IPACC_NSVCI, 2)) nsvc->nsvci = ntohs(tlvp_val16_unal(tp, NM_ATT_IPACC_NSVCI)); @@ -1218,41 +1732,69 @@ static int oml_ipa_mo_set_attr_nsvc(struct gsm_bts_gprs_nsvc *nsvc, uint16_t _cur_s; uint32_t _cur_l; + memset(&nsvc->local, 0, sizeof(nsvc->local)); + memset(&nsvc->remote, 0, sizeof(nsvc->remote)); + + nsvc->local.u.sin.sin_family = AF_INET; + nsvc->remote.u.sin.sin_family = AF_INET; + memcpy(&_cur_s, cur, 2); - nsvc->remote_port = ntohs(_cur_s); + nsvc->remote.u.sin.sin_port = _cur_s; cur += 2; memcpy(&_cur_l, cur, 4); - nsvc->remote_ip = ntohl(_cur_l); + nsvc->remote.u.sin.sin_addr.s_addr = _cur_l; cur += 4; memcpy(&_cur_s, cur, 2); - nsvc->local_port = ntohs(_cur_s); + nsvc->local.u.sin.sin_port = _cur_s; } - osmo_signal_dispatch(SS_GLOBAL, S_NEW_NSVC_ATTR, nsvc); + if (TLVP_PRES_LEN(tp, NM_ATT_OSMO_NS_LINK_CFG, 10)) { + const uint8_t *cur = TLVP_VAL(tp, NM_ATT_OSMO_NS_LINK_CFG); + uint8_t address_family; - return 0; -} + memset(&nsvc->local, 0, sizeof(nsvc->local)); + memset(&nsvc->remote, 0, sizeof(nsvc->remote)); -static int oml_ipa_mo_set_attr(struct gsm_bts *bts, const struct gsm_abis_mo *mo, - void *obj, struct tlv_parsed *tp) -{ - int rc; + address_family = *cur; + /* 1byte padding */ + cur += 2; - switch (mo->obj_class) { - case NM_OC_GPRS_NSE: - rc = oml_ipa_mo_set_attr_nse(obj, tp); - break; - case NM_OC_GPRS_CELL: - rc = oml_ipa_mo_set_attr_cell(obj, tp); - break; - case NM_OC_GPRS_NSVC: - rc = oml_ipa_mo_set_attr_nsvc(obj, tp); - break; - default: - rc = NM_NACK_OBJINST_UNKN; + memcpy(&nsvc->local.u.sin.sin_port, cur, 2); + cur += 2; + + memcpy(&nsvc->remote.u.sin.sin_port, cur, 2); + cur += 2; + + switch (address_family) { + case OSMO_NSVC_ADDR_IPV4: + /* we already checked for 10 bytes */ + nsvc->remote.u.sas.ss_family = AF_INET; + nsvc->local.u.sas.ss_family = AF_INET; + memcpy(&nsvc->remote.u.sin.sin_addr.s_addr, cur, sizeof(in_addr_t)); + break; + case OSMO_NSVC_ADDR_IPV6: + if (TLVP_LEN(tp, NM_ATT_OSMO_NS_LINK_CFG) < 22) { + return -1; + } + nsvc->remote.u.sas.ss_family = AF_INET6; + nsvc->local.u.sas.ss_family = AF_INET6; + memcpy(&nsvc->remote.u.sin6.sin6_addr, cur, sizeof(struct in6_addr)); + break; + default: + return -1; + } } - return rc; + ev_data = (struct nm_fsm_ev_setattr_data){ + .msg = msg, + }; + rc = osmo_fsm_inst_dispatch(nsvc->mo.fi, NM_EV_RX_SETATTR, &ev_data); + if (rc < 0) + return NM_NACK_CANT_PERFORM; + + osmo_signal_dispatch(SS_GLOBAL, S_NEW_NSVC_ATTR, nsvc); + + return 0; } static int oml_ipa_set_attr(struct gsm_bts *bts, struct msgb *msg) @@ -1262,75 +1804,107 @@ static int oml_ipa_set_attr(struct gsm_bts *bts, struct msgb *msg) struct tlv_parsed tp, *tp_merged; void *obj; int rc; + enum abis_nm_nack_cause c; DEBUGPFOH(DOML, foh, "Rx IPA SET ATTR\n"); rc = oml_tlv_parse(&tp, foh->data, msgb_l3len(msg) - sizeof(*foh)); if (rc < 0) { - mo = gsm_objclass2mo(bts, foh->obj_class, &foh->obj_inst); - if (!mo) - return oml_fom_ack_nack(msg, NM_NACK_OBJINST_UNKN); + if ((mo = gsm_objclass2mo(bts, foh->obj_class, &foh->obj_inst, &c)) == NULL) + return oml_fom_ack_nack(msg, c); oml_tx_failure_event_rep(mo, NM_SEVER_MAJOR, OSMO_EVT_MAJ_UNSUP_ATTR, "New value for IPAC Set Attribute not supported\n"); return oml_fom_ack_nack(msg, NM_NACK_INCORR_STRUCT); } /* Resolve MO by obj_class/obj_inst */ - mo = gsm_objclass2mo(bts, foh->obj_class, &foh->obj_inst); - obj = gsm_objclass2obj(bts, foh->obj_class, &foh->obj_inst); - if (!mo || !obj) - return oml_fom_ack_nack(msg, NM_NACK_OBJINST_UNKN); + if ((mo = gsm_objclass2mo(bts, foh->obj_class, &foh->obj_inst, &c)) == NULL) + return oml_fom_ack_nack(msg, c); + if ((obj = gsm_objclass2obj(bts, foh->obj_class, &foh->obj_inst, &c)) == NULL) + return oml_fom_ack_nack(msg, c); + - rc = oml_ipa_mo_set_attr(bts, mo, obj, &tp); - if (rc == 0) { - /* Success: replace old MO attributes with new */ - /* merge existing MO attributes with new attributes */ - tp_merged = osmo_tlvp_copy(mo->nm_attr, bts); - osmo_tlvp_merge(tp_merged, &tp); - talloc_free(mo->nm_attr); - mo->nm_attr = tp_merged; + switch (mo->obj_class) { + case NM_OC_GPRS_NSE: + rc = oml_ipa_mo_set_attr_nse(obj, msg, &tp); + break; + case NM_OC_GPRS_CELL: + rc = oml_ipa_mo_set_attr_cell(obj, msg, &tp); + break; + case NM_OC_GPRS_NSVC: + rc = oml_ipa_mo_set_attr_nsvc(obj, msg, &tp); + break; + default: + rc = NM_NACK_OBJINST_UNKN; } - return oml_fom_ack_nack(msg, rc); + if (rc != 0) + return oml_fom_ack_nack(msg, rc); + + /* Success: replace old MO attributes with new */ + /* merge existing MO attributes with new attributes */ + tp_merged = osmo_tlvp_copy(mo->nm_attr, bts); + talloc_set_name_const(tp_merged, "oml_ipa_attr"); + osmo_tlvp_merge(tp_merged, &tp); + talloc_free(mo->nm_attr); + mo->nm_attr = tp_merged; + + return rc; } -static int rx_oml_ipa_rsl_connect(struct gsm_bts_trx *trx, struct msgb *msg, - struct tlv_parsed *tp) +static int rx_oml_ipa_rsl_connect(struct gsm_bts *bts, struct msgb *msg, + const struct tlv_parsed *tp) { - struct e1inp_sign_link *oml_link = trx->bts->oml_link; - uint16_t port = IPA_TCP_PORT_RSL; - uint32_t ip = get_signlink_remote_ip(oml_link); - const char *trx_name = gsm_trx_name(trx); + struct e1inp_sign_link *oml_link = bts->oml_link; + const struct abis_om_fom_hdr *foh = msgb_l3(msg); + struct gsm_bts_trx *trx = gsm_bts_trx_num(bts, foh->obj_inst.trx_nr); + struct gsm_bts_bb_trx *bb_transc; + const char *trx_name; struct in_addr in; + uint16_t port = IPA_TCP_PORT_RSL; + uint8_t stream_id = 0; int rc; - uint8_t stream_id = 0; + if (TLVP_PRESENT(tp, NM_ATT_IPACC_DST_IP)) + in.s_addr = tlvp_val32_unal(tp, NM_ATT_IPACC_DST_IP); + else + in.s_addr = htonl(get_signlink_remote_ip(oml_link)); - if (TLVP_PRES_LEN(tp, NM_ATT_IPACC_DST_IP, 4)) { - ip = ntohl(tlvp_val32_unal(tp, NM_ATT_IPACC_DST_IP)); - } - if (TLVP_PRES_LEN(tp, NM_ATT_IPACC_DST_IP_PORT, 2)) { + if (TLVP_PRESENT(tp, NM_ATT_IPACC_DST_IP_PORT)) port = ntohs(tlvp_val16_unal(tp, NM_ATT_IPACC_DST_IP_PORT)); - } - if (TLVP_PRES_LEN(tp, NM_ATT_IPACC_STREAM_ID, 1)) { + + if (TLVP_PRESENT(tp, NM_ATT_IPACC_STREAM_ID)) stream_id = *TLVP_VAL(tp, NM_ATT_IPACC_STREAM_ID); + + if (!trx) { + LOGP(DOML, LOGL_ERROR, "Rx IPA RSL CONNECT IP=%s PORT=%u STREAM=0x%02x for unknown TRX_NR=%u\n", + inet_ntoa(in), port, stream_id, foh->obj_inst.trx_nr); + rc = NM_NACK_TRXNR_UNKN; + goto tx_ack_nack; } - in.s_addr = htonl(ip); - LOGP(DOML, LOGL_INFO, "%s: Rx IPA RSL CONNECT IP=%s PORT=%u STREAM=0x%02x\n", - trx_name, inet_ntoa(in), port, stream_id); + bb_transc = &trx->bb_transc; + osmo_sockaddr_str_from_in_addr(&bb_transc->rsl.rem_addrstr, &in, port); + bb_transc->rsl.tei = stream_id; - if (trx->bts->variant == BTS_OSMO_OMLDUMMY) { - rc = 0; - LOGP(DOML, LOGL_NOTICE, "%s: Not connecting RSL in OML-DUMMY!\n", trx_name); - } else - rc = e1inp_ipa_bts_rsl_connect_n(oml_link->ts->line, inet_ntoa(in), port, trx->nr); - if (rc < 0) { - LOGP(DOML, LOGL_ERROR, "%s: Error in abis_open(RSL): %d\n", trx_name, rc); - return oml_fom_ack_nack(msg, NM_NACK_CANT_PERFORM); - } + trx_name = gsm_trx_name(trx); + LOGP(DOML, LOGL_INFO, "%s: Rx IPA RSL CONNECT IP=%s PORT=%u STREAM=0x%02x\n", + trx_name, bb_transc->rsl.rem_addrstr.ip, bb_transc->rsl.rem_addrstr.port, + bb_transc->rsl.tei); + + rc = 0; - return oml_fom_ack_nack(msg, 0); +tx_ack_nack: + /* The ACK/NACK is expected to contain all IEs */ + if (!TLVP_PRESENT(tp, NM_ATT_IPACC_DST_IP)) /* TV32 */ + msgb_tv_fixed_put(msg, NM_ATT_IPACC_DST_IP, sizeof(in), + (const uint8_t *) &in); + if (!TLVP_PRESENT(tp, NM_ATT_IPACC_DST_IP_PORT)) /* TV16 */ + msgb_tv16_put(msg, NM_ATT_IPACC_DST_IP_PORT, port); + if (!TLVP_PRESENT(tp, NM_ATT_IPACC_STREAM_ID)) /* TV */ + msgb_tv_put(msg, NM_ATT_IPACC_STREAM_ID, stream_id); + + return oml_fom_ack_nack(msg, rc); } static int down_mom(struct gsm_bts *bts, struct msgb *msg) @@ -1379,8 +1953,7 @@ static int down_mom(struct gsm_bts *bts, struct msgb *msg) switch (foh->msg_type) { case NM_MT_IPACC_RSL_CONNECT: - trx = gsm_bts_trx_num(bts, foh->obj_inst.trx_nr); - ret = rx_oml_ipa_rsl_connect(trx, msg, &tp); + ret = rx_oml_ipa_rsl_connect(bts, msg, &tp); break; case NM_MT_IPACC_SET_ATTR: ret = oml_ipa_set_attr(bts, msg); @@ -1452,16 +2025,144 @@ int down_oml(struct gsm_bts *bts, struct msgb *msg) ret = -EINVAL; } + /* msgb was reused, do not free() */ + if (ret == 1) + return 0; + msgb_free(msg); return ret; } -int oml_init(struct gsm_abis_mo *mo) +int oml_init() { DEBUGP(DOML, "Initializing OML attribute definitions\n"); tlv_def_patch(&abis_nm_att_tlvdef_ipa_local, &abis_nm_att_tlvdef_ipa); tlv_def_patch(&abis_nm_att_tlvdef_ipa_local, &abis_nm_att_tlvdef); + tlv_def_patch(&abis_nm_att_tlvdef_ipa_local, &abis_nm_osmo_att_tlvdef); return 0; } + +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; + mo->nm_state.operational = NM_OPSTATE_DISABLED; + mo->nm_state.availability = NM_AVSTATE_POWER_OFF; + mo->nm_state.administrative = NM_STATE_LOCKED; +} + +/* Obtain the MO structure for a given object instance + * \param[out] c nack cause for reply in case of error. Ignored if NULL */ +struct gsm_abis_mo *gsm_objclass2mo(struct gsm_bts *bts, uint8_t obj_class, + const struct abis_om_obj_inst *obj_inst, + enum abis_nm_nack_cause *c) +{ + struct gsm_bts_trx *trx; + + switch ((enum abis_nm_obj_class)obj_class) { + case NM_OC_BTS: + return &bts->mo; + case NM_OC_RADIO_CARRIER: + if (!(trx = gsm_bts_trx_num(bts, obj_inst->trx_nr))) + goto nm_nack_trxnr_unkn; + return &trx->mo; + case NM_OC_BASEB_TRANSC: + if (!(trx = gsm_bts_trx_num(bts, obj_inst->trx_nr))) + goto nm_nack_trxnr_unkn; + return &trx->bb_transc.mo; + case NM_OC_CHANNEL: + if (!(trx = gsm_bts_trx_num(bts, obj_inst->trx_nr))) + goto nm_nack_trxnr_unkn; + if (obj_inst->ts_nr >= TRX_NR_TS) + goto nm_nack_objinst_unkn; + return &trx->ts[obj_inst->ts_nr].mo; + case NM_OC_SITE_MANAGER: + return &g_bts_sm->mo; + case NM_OC_GPRS_NSE: + if (obj_inst->bts_nr > 0) + goto nm_nack_objinst_unkn; + return &g_bts_sm->gprs.nse.mo; + case NM_OC_GPRS_CELL: + return &bts->gprs.cell.mo; + case NM_OC_GPRS_NSVC: + if (obj_inst->bts_nr > 0) + goto nm_nack_objinst_unkn; + if (obj_inst->trx_nr >= ARRAY_SIZE(g_bts_sm->gprs.nse.nsvc)) + goto nm_nack_objinst_unkn; + return &g_bts_sm->gprs.nse.nsvc[obj_inst->trx_nr].mo; + default: + if (c != NULL) + *c = NM_NACK_OBJCLASS_NOTSUPP; + return NULL; + } + +nm_nack_trxnr_unkn: + if (c != NULL) + *c = NM_NACK_TRXNR_UNKN; + return NULL; +nm_nack_objinst_unkn: + if (c != NULL) + *c = NM_NACK_OBJINST_UNKN; + return NULL; +} + +/* Obtain the in-memory data structure of a given object instance + * \param[out] c nack cause for reply in case of error. Ignored if NULL */ +void *gsm_objclass2obj(struct gsm_bts *bts, uint8_t obj_class, + const struct abis_om_obj_inst *obj_inst, + enum abis_nm_nack_cause *c) +{ + struct gsm_bts_trx *trx; + + switch ((enum abis_nm_obj_class)obj_class) { + case NM_OC_BTS: + return bts; + case NM_OC_RADIO_CARRIER: + if (!(trx = gsm_bts_trx_num(bts, obj_inst->trx_nr))) + goto nm_nack_trxnr_unkn; + return trx; + case NM_OC_BASEB_TRANSC: + if (!(trx = gsm_bts_trx_num(bts, obj_inst->trx_nr))) + goto nm_nack_trxnr_unkn; + return &trx->bb_transc; + case NM_OC_CHANNEL: + if (!(trx = gsm_bts_trx_num(bts, obj_inst->trx_nr))) + goto nm_nack_trxnr_unkn; + if (obj_inst->ts_nr >= TRX_NR_TS) + goto nm_nack_objinst_unkn; + return &trx->ts[obj_inst->ts_nr]; + case NM_OC_SITE_MANAGER: + return g_bts_sm; + case NM_OC_GPRS_NSE: + if (obj_inst->bts_nr > 0) + goto nm_nack_objinst_unkn; + return &g_bts_sm->gprs.nse; + case NM_OC_GPRS_CELL: + return &bts->gprs.cell; + case NM_OC_GPRS_NSVC: + if (obj_inst->bts_nr > 0) + goto nm_nack_objinst_unkn; + if (obj_inst->trx_nr >= ARRAY_SIZE(g_bts_sm->gprs.nse.nsvc)) + goto nm_nack_objinst_unkn; + return &g_bts_sm->gprs.nse.nsvc[obj_inst->trx_nr]; + default: + if (c != NULL) + *c = NM_NACK_OBJCLASS_NOTSUPP; + return NULL; + } + +nm_nack_trxnr_unkn: + if (c != NULL) + *c = NM_NACK_TRXNR_UNKN; + return NULL; +nm_nack_objinst_unkn: + if (c != NULL) + *c = NM_NACK_OBJINST_UNKN; + return NULL; +} diff --git a/src/common/osmux.c b/src/common/osmux.c new file mode 100644 index 00000000..9513bdc4 --- /dev/null +++ b/src/common/osmux.c @@ -0,0 +1,545 @@ +/* Osmux related routines & logic */ + +/* (C) 2022 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> + * All Rights Reserved + * Author: Pau Espin Pedrol <pespin@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 <errno.h> +#include <sys/socket.h> +#include <stdint.h> +#include <stdbool.h> +#include <inttypes.h> +#include <unistd.h> + +#include <osmocom/core/logging.h> +#include <osmocom/core/utils.h> +#include <osmocom/core/msgb.h> +#include <osmocom/core/socket.h> +#include <osmocom/netif/rtp.h> + +#include <osmo-bts/bts.h> +#include <osmo-bts/logging.h> +#include <osmo-bts/osmux.h> +#include <osmo-bts/lchan.h> +#include <osmo-bts/msg_utils.h> +#include <osmo-bts/l1sap.h> + +/* Bitmask containing Allocated Osmux circuit ID. +7 to round up to 8 bit boundary. */ +static uint8_t osmux_cid_bitmap[OSMO_BYTES_FOR_BITS(OSMUX_CID_MAX + 1)]; + +/*! Find and reserve a free OSMUX cid. Keep state of last allocated CID to + * rotate allocated CIDs over time. This helps in letting CIDs unused for some + * time after last use. + * \returns OSMUX cid */ +static int osmux_get_local_cid(void) +{ + static uint8_t next_free_osmux_cid_lookup = 0; + uint8_t start_i, start_j; + uint8_t i, j, cid; + + /* i = octet index, j = bit index inside ith octet */ + start_i = next_free_osmux_cid_lookup >> 3; + start_j = next_free_osmux_cid_lookup & 0x07; + + for (i = start_i; i < sizeof(osmux_cid_bitmap); i++) { + for (j = start_j; j < 8; j++) { + if (osmux_cid_bitmap[i] & (1 << j)) + continue; + goto found; + } + } + + for (i = 0; i <= start_i; i++) { + for (j = 0; j < start_j; j++) { + if (osmux_cid_bitmap[i] & (1 << j)) + continue; + goto found; + } + } + + LOGP(DOSMUX, LOGL_ERROR, "All Osmux circuits are in use!\n"); + return -1; + +found: + osmux_cid_bitmap[i] |= (1 << j); + cid = (i << 3) | j; + next_free_osmux_cid_lookup = (cid + 1) & 0xff; + LOGP(DOSMUX, LOGL_DEBUG, + "Allocating Osmux CID %u from pool\n", cid); + return cid; +} + +/*! put back a no longer used OSMUX cid. + * \param[in] osmux_cid OSMUX cid */ +void osmux_put_local_cid(uint8_t osmux_cid) +{ + LOGP(DOSMUX, LOGL_DEBUG, "Osmux CID %u is back to the pool\n", osmux_cid); + osmux_cid_bitmap[osmux_cid / 8] &= ~(1 << (osmux_cid % 8)); +} + +/* Deliver OSMUX batch to the remote end */ +static void osmux_deliver_cb(struct msgb *batch_msg, void *data) +{ + struct osmux_handle *handle = data; + struct gsm_bts *bts = handle->bts; + socklen_t dest_len; + ssize_t rc; + + switch (handle->rem_addr.u.sa.sa_family) { + case AF_INET6: + dest_len = sizeof(handle->rem_addr.u.sin6); + break; + case AF_INET: + default: + dest_len = sizeof(handle->rem_addr.u.sin); + break; + } + rc = sendto(bts->osmux.fd.fd, batch_msg->data, batch_msg->len, 0, + (struct sockaddr *)&handle->rem_addr.u.sa, dest_len); + if (rc < 0) { + char errbuf[129]; + strerror_r(errno, errbuf, sizeof(errbuf)); + LOGP(DOSMUX, LOGL_ERROR, "osmux sendto(%s) failed: %s\n", + osmo_sockaddr_to_str(&handle->rem_addr), errbuf); + } + msgb_free(batch_msg); +} + +/* Lookup existing OSMUX handle for specified destination address. */ +static struct osmux_handle *osmux_handle_find_get(const struct gsm_bts *bts, + const struct osmo_sockaddr *rem_addr) +{ + struct osmux_handle *h; + + llist_for_each_entry(h, &bts->osmux.osmux_handle_list, head) { + if (osmo_sockaddr_cmp(&h->rem_addr, rem_addr) == 0) { + LOGP(DOSMUX, LOGL_DEBUG, + "Using existing OSMUX handle for rem_addr=%s\n", + osmo_sockaddr_to_str(rem_addr)); + h->refcnt++; + return h; + } + } + + return NULL; +} + +/* Put down no longer needed OSMUX handle */ +static void osmux_handle_put(struct gsm_bts *bts, struct osmux_in_handle *in) +{ + struct osmux_handle *h; + + llist_for_each_entry(h, &bts->osmux.osmux_handle_list, head) { + if (h->in == in) { + if (--h->refcnt == 0) { + LOGP(DOSMUX, LOGL_INFO, + "Releasing unused osmux handle for %s\n", + osmo_sockaddr_to_str(&h->rem_addr)); + llist_del(&h->head); + TALLOC_FREE(h->in); + talloc_free(h); + } + return; + } + } + LOGP(DOSMUX, LOGL_ERROR, "Cannot find Osmux input handle %p\n", in); +} + +/* Allocate free OSMUX handle */ +static struct osmux_handle *osmux_handle_alloc(struct gsm_bts *bts, const struct osmo_sockaddr *rem_addr) +{ + struct osmux_handle *h; + char name[128] = "r="; + + h = talloc_zero(bts, struct osmux_handle); + if (!h) + return NULL; + h->bts = bts; + h->rem_addr = *rem_addr; + h->refcnt++; + + h->in = osmux_xfrm_input_alloc(h); + if (!h->in) { + talloc_free(h); + return NULL; + } + + osmo_sockaddr_to_str_buf(name + 2, sizeof(name) - 2, rem_addr); + osmux_xfrm_input_set_name(h->in, name); + /* sequence number to start OSMUX message from */ + osmux_xfrm_input_set_initial_seqnum(h->in, 0); + osmux_xfrm_input_set_batch_factor(h->in, bts->osmux.batch_factor); + /* If batch size is zero, the library defaults to 1472 bytes. */ + osmux_xfrm_input_set_batch_size(h->in, bts->osmux.batch_size); + osmux_xfrm_input_set_deliver_cb(h->in, osmux_deliver_cb, h); + + llist_add(&h->head, &bts->osmux.osmux_handle_list); + + LOGP(DOSMUX, LOGL_DEBUG, "Created new OSMUX handle for rem_addr=%s\n", + osmo_sockaddr_to_str(rem_addr)); + + return h; +} + +/* Lookup existing handle for a specified address, if the handle can not be + * found, the function will automatically allocate one */ +static struct osmux_in_handle * +osmux_handle_find_or_create(struct gsm_bts *bts, const struct osmo_sockaddr *rem_addr) +{ + struct osmux_handle *h; + + if (rem_addr->u.sa.sa_family != AF_INET) { + LOGP(DOSMUX, LOGL_DEBUG, "IPv6 not supported in osmux yet!\n"); + return NULL; + } + + h = osmux_handle_find_get(bts, rem_addr); + if (h != NULL) + return h->in; + + h = osmux_handle_alloc(bts, rem_addr); + if (h == NULL) + return NULL; + + return h->in; +} + + +static struct msgb *osmux_recv(struct osmo_fd *ofd, struct osmo_sockaddr *addr) +{ + struct msgb *msg; + socklen_t slen = sizeof(addr->u.sas); + int ret; + + msg = msgb_alloc(4096, "OSMUX"); /* TODO: pool? */ + if (!msg) { + LOGP(DOSMUX, LOGL_ERROR, "cannot allocate message\n"); + return NULL; + } + ret = recvfrom(ofd->fd, msg->data, msg->data_len, 0, &addr->u.sa, &slen); + if (ret <= 0) { + msgb_free(msg); + LOGP(DOSMUX, LOGL_ERROR, "cannot receive message\n"); + return NULL; + } + msgb_put(msg, ret); + + return msg; +} + +static struct gsm_lchan *osmux_lchan_find(struct gsm_bts *bts, const struct osmo_sockaddr *rem_addr, uint8_t osmux_cid) +{ + /* TODO: Optimize this by maintaining a hashmap local_cid->lchan in bts */ + struct gsm_bts_trx *trx; + + llist_for_each_entry(trx, &bts->trx_list, list) { /* C0..n */ + unsigned int tn; + for (tn = 0; tn < ARRAY_SIZE(trx->ts); tn++) { + struct gsm_bts_trx_ts *ts = &trx->ts[tn]; + uint8_t subslot, subslots; + if (!ts_is_tch(ts)) + continue; + + subslots = ts_subslots(ts); + for (subslot = 0; subslot < subslots; subslot++) { + struct gsm_lchan *lchan = &ts->lchan[subslot]; + struct osmux_handle *h; + if (!lchan->abis_ip.osmux.use) + continue; + if (!lchan_osmux_connected(lchan)) + continue; + if (lchan->abis_ip.osmux.local_cid != osmux_cid) + continue; + h = osmux_xfrm_input_get_deliver_cb_data(lchan->abis_ip.osmux.in); + if (osmo_sockaddr_cmp(&h->rem_addr, rem_addr) != 0) + continue; + return lchan; /* Found it! */ + } + } + } + return NULL; +} + +static int osmux_read_fd_cb(struct osmo_fd *ofd, unsigned int what) +{ + struct msgb *msg; + struct osmux_hdr *osmuxh; + struct osmo_sockaddr rem_addr; + struct gsm_bts *bts = ofd->data; + + msg = osmux_recv(ofd, &rem_addr); + if (!msg) + return -1; + + while ((osmuxh = osmux_xfrm_output_pull(msg)) != NULL) { + struct gsm_lchan *lchan = osmux_lchan_find(bts, &rem_addr, osmuxh->circuit_id); + if (!lchan) { + char addr_str[64]; + osmo_sockaddr_to_str_buf(addr_str, sizeof(addr_str), &rem_addr); + LOGP(DOSMUX, LOGL_DEBUG, + "Cannot find lchan for %s CID=%d\n", + addr_str, osmuxh->circuit_id); + continue; + } + osmux_xfrm_output_sched(lchan->abis_ip.osmux.out, osmuxh); + } + msgb_free(msg); + return 0; +} + +/* Called before config file read, set defaults */ +int bts_osmux_init(struct gsm_bts *bts) +{ + bts->osmux.use = OSMUX_USAGE_OFF; + bts->osmux.local_addr = talloc_strdup(bts, "127.0.0.1"); + bts->osmux.local_port = OSMUX_DEFAULT_PORT; + bts->osmux.batch_factor = 4; + bts->osmux.batch_size = OSMUX_BATCH_DEFAULT_MAX; + bts->osmux.dummy_padding = false; + INIT_LLIST_HEAD(&bts->osmux.osmux_handle_list); + bts->osmux.fd.fd = -1; + return 0; +} + +void bts_osmux_release(struct gsm_bts *bts) +{ + /* bts->osmux.osmux_handle_list should end up empty when all lchans are + * released/freed upon talloc_free(bts). */ + /* If bts->osmux.fd.data is NULL, bts is being released/freed without + * passing bts_osmux_init()/through bts_osmux_open() and hence fd is + * probably 0 (memzeored). Avoid accessing it if not initialized. */ + if (bts->osmux.fd.fd != -1 && bts->osmux.fd.data) + osmo_fd_close(&bts->osmux.fd); +} + +/* Called after config file read, start services */ +int bts_osmux_open(struct gsm_bts *bts) +{ + int rc; + + /* If Osmux is not enabled by VTY, don't initialize stuff */ + if (bts->osmux.use == OSMUX_USAGE_OFF) + return 0; + + bts->osmux.fd.cb = osmux_read_fd_cb; + bts->osmux.fd.data = bts; + rc = osmo_sock_init2_ofd(&bts->osmux.fd, AF_UNSPEC, SOCK_DGRAM, IPPROTO_UDP, + bts->osmux.local_addr, bts->osmux.local_port, + NULL, 0, OSMO_SOCK_F_BIND); + if (rc < 0) { + LOGP(DOSMUX, LOGL_ERROR, + "Failed binding Osmux socket to %s:%u\n", + bts->osmux.local_addr ? : "*", bts->osmux.local_port); + return rc; + } + + LOGP(DOSMUX, LOGL_INFO, + "Osmux socket listening on %s:%u\n", + bts->osmux.local_addr ? : "*", bts->osmux.local_port); + + osmo_bts_set_feature(bts->features, BTS_FEAT_OSMUX); + return rc; +} + +static struct msgb *osmux_rtp_msgb_alloc_cb(void *rtp_msgb_alloc_priv_data, + unsigned int msg_len) +{ + struct msgb *msg; + msg = l1sap_msgb_alloc(msg_len); + /* We have size for "struct osmo_phsap_prim" reserved & aligned at the + * start of the msg. Osmux will start filling RTP Header at the tail. + * Later on, when pushing it down the stack (scheduled_from_osmux_tx_rtp_cb) + * we'll want to get rid of the RTP header and have RTP payload + * immediately follow the the struct osmo_phsap_prim. Hence, we rework + * reserved space so that end of RTP header (12 bytes) filled by Osmux + * ends up at the same position where "struct osmo_phsap_prim" currently + * ends up */ + msg->l2h = msgb_get(msg, sizeof(struct rtp_hdr)); + return msg; +} + +static void scheduled_from_osmux_tx_rtp_cb(struct msgb *msg, void *data) +{ + struct gsm_lchan *lchan = data; + struct rtp_hdr *rtph; + + /* if we're in loopback mode, we don't accept frames from the + * RTP socket anymore */ + if (lchan->loopback) { + msgb_free(msg); + return; + } + + /* This is where start of rtp_hdr was prepared in osmux_rtp_msgb_alloc_cb() */ + rtph = (struct rtp_hdr *)msg->l2h; + if (msgb_l2len(msg) < sizeof(*rtph)) { + LOGPLCHAN(lchan, DOSMUX, LOGL_ERROR, "received RTP frame too short (len = %d)\n", + msgb_l2len(msg)); + msgb_free(msg); + return; + } + + /* Store RTP header Marker bit in control buffer */ + rtpmsg_marker_bit(msg) = rtph->marker; + /* Store RTP header Sequence Number in control buffer */ + rtpmsg_seq(msg) = ntohs(rtph->sequence); + /* Store RTP header Timestamp in control buffer */ + rtpmsg_ts(msg) = ntohl(rtph->timestamp); + + /* No need to pull() rtph out of msg here, because it was written inside + * initial space reserved for "struct osmo_phsap_prim". We need to pull + * the whole "struct osmo_phsap_prim" since it will be pushed and filled + * by lower layers: + */ + msgb_pull(msg, sizeof(struct osmo_phsap_prim)); + + /* enqueue making sure the queue doesn't get too long */ + lchan_dl_tch_queue_enqueue(lchan, msg, 16); +} + +int lchan_osmux_init(struct gsm_lchan *lchan, uint8_t rtp_payload) +{ + struct gsm_bts_trx *trx = lchan->ts->trx; + int local_cid = osmux_get_local_cid(); + struct in_addr ia; + + if (local_cid < 0) + return local_cid; + + if (inet_pton(AF_INET, trx->bts->osmux.local_addr, &ia) != 1) + return -1; + + lchan->abis_ip.osmux.out = osmux_xfrm_output_alloc(trx); + osmux_xfrm_output_set_rtp_ssrc(lchan->abis_ip.osmux.out, random() /*TODO: SSRC */); + osmux_xfrm_output_set_rtp_pl_type(lchan->abis_ip.osmux.out, rtp_payload); + osmux_xfrm_output_set_tx_cb(lchan->abis_ip.osmux.out, scheduled_from_osmux_tx_rtp_cb, lchan); + osmux_xfrm_output_set_rtp_msgb_alloc_cb(lchan->abis_ip.osmux.out, osmux_rtp_msgb_alloc_cb, lchan); + + lchan->abis_ip.bound_ip = ntohl(ia.s_addr); + lchan->abis_ip.bound_port = trx->bts->osmux.local_port; + lchan->abis_ip.osmux.local_cid = local_cid; + lchan->abis_ip.osmux.rtpst = osmo_rtp_handle_create(trx); + lchan->abis_ip.osmux.use = true; + return 0; +} + +void lchan_osmux_release(struct gsm_lchan *lchan) +{ + struct gsm_bts *bts = lchan->ts->trx->bts; + OSMO_ASSERT(lchan->abis_ip.osmux.use); + /* We are closing, we don't need pending RTP packets to be transmitted */ + osmux_xfrm_output_set_tx_cb(lchan->abis_ip.osmux.out, NULL, NULL); + TALLOC_FREE(lchan->abis_ip.osmux.out); + + msgb_queue_free(&lchan->dl_tch_queue); + lchan->dl_tch_queue_len = 0; + + osmux_put_local_cid(lchan->abis_ip.osmux.local_cid); + + /* Now the remote / tx part, if ever set (connected): */ + if (lchan->abis_ip.osmux.in) { + osmux_xfrm_input_close_circuit(lchan->abis_ip.osmux.in, + lchan->abis_ip.osmux.remote_cid); + osmux_handle_put(bts, lchan->abis_ip.osmux.in); + lchan->abis_ip.osmux.in = NULL; + } + if (lchan->abis_ip.osmux.rtpst) { + osmo_rtp_handle_free(lchan->abis_ip.osmux.rtpst); + lchan->abis_ip.osmux.rtpst = NULL; + } + + lchan->abis_ip.osmux.use = false; +} + +bool lchan_osmux_connected(const struct gsm_lchan *lchan) +{ + return lchan->abis_ip.osmux.in != NULL; +} + +int lchan_osmux_connect(struct gsm_lchan *lchan) +{ + struct osmo_sockaddr rem_addr; + struct gsm_bts *bts = lchan->ts->trx->bts; + OSMO_ASSERT(lchan->abis_ip.connect_ip != 0); + OSMO_ASSERT(lchan->abis_ip.connect_port != 0); + + memset(&rem_addr, 0, sizeof(rem_addr)); + rem_addr.u.sa.sa_family = AF_INET; + rem_addr.u.sin.sin_addr.s_addr = lchan->abis_ip.connect_ip; + rem_addr.u.sin.sin_port = htons(lchan->abis_ip.connect_port); + lchan->abis_ip.osmux.in = osmux_handle_find_or_create(bts, &rem_addr); + if (!lchan->abis_ip.osmux.in) { + LOGPLCHAN(lchan, DOSMUX, LOGL_ERROR, "Cannot allocate input osmux handle\n"); + return -1; + } + if (osmux_xfrm_input_open_circuit(lchan->abis_ip.osmux.in, + lchan->abis_ip.osmux.remote_cid, + bts->osmux.dummy_padding) < 0) { + LOGPLCHAN(lchan, DOSMUX, LOGL_ERROR, "Cannot open osmux circuit %u\n", + lchan->abis_ip.osmux.remote_cid); + osmux_handle_put(bts, lchan->abis_ip.osmux.in); + lchan->abis_ip.osmux.in = NULL; + return -1; + } + return 0; +} + +/* Create RTP packet from l1sap payload and feed it to osmux */ +int lchan_osmux_send_frame(struct gsm_lchan *lchan, const uint8_t *payload, + unsigned int payload_len, unsigned int duration, bool marker) +{ + struct msgb *msg; + struct rtp_hdr *rtph; + int rc; + + msg = osmo_rtp_build(lchan->abis_ip.osmux.rtpst, lchan->abis_ip.rtp_payload, + payload_len, payload, duration); + if (!msg) + return -1; + + /* Set marker bit: */ + rtph = (struct rtp_hdr *)msgb_data(msg); + rtph->marker = marker; + + /* Avoid using the osmux.in if not yet connected. */ + if (!lchan_osmux_connected(lchan)) { + msgb_free(msg); + return -1; + } + + while ((rc = osmux_xfrm_input(lchan->abis_ip.osmux.in, msg, + lchan->abis_ip.osmux.remote_cid)) > 0) { + /* batch full, build and deliver it */ + osmux_xfrm_input_deliver(lchan->abis_ip.osmux.in); + } + return 0; +} + +int lchan_osmux_skipped_frame(struct gsm_lchan *lchan, unsigned int duration) +{ + struct msgb *msg; + + /* Let osmo_rtp_handle take care of updating state, and send nothing: */ + msg = osmo_rtp_build(lchan->abis_ip.osmux.rtpst, lchan->abis_ip.rtp_payload, + 0, NULL, duration); + if (!msg) + return -1; + msgb_free(msg); + return 0; +} diff --git a/src/common/paging.c b/src/common/paging.c index fca58b5f..bdd51801 100644 --- a/src/common/paging.c +++ b/src/common/paging.c @@ -12,7 +12,7 @@ * 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. + * 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/>. @@ -43,13 +43,14 @@ #include <osmo-bts/paging.h> #include <osmo-bts/signal.h> #include <osmo-bts/pcu_if.h> +#include <osmo-bts/notification.h> #define MAX_PAGING_BLOCKS_CCCH 9 #define MAX_BS_PA_MFRMS 9 enum paging_record_type { - PAGING_RECORD_PAGING, - PAGING_RECORD_IMM_ASS + PAGING_RECORD_NORMAL, + PAGING_RECORD_MACBLOCK }; struct paging_record { @@ -60,10 +61,12 @@ struct paging_record { time_t expiration_time; uint8_t chan_needed; uint8_t identity_lv[9]; - } paging; + } normal; struct { uint8_t msg[GSM_MACBLOCK_LEN]; - } imm_ass; + bool confirm; + uint32_t msg_id; /* used as identifier for confirmation */ + } macblock; } u; }; @@ -80,8 +83,37 @@ struct paging_state { /* total number of currently active paging records in queue */ unsigned int num_paging; struct llist_head paging_queue[MAX_PAGING_BLOCKS_CCCH*MAX_BS_PA_MFRMS]; + + /* prioritization of cs pagings will automatically become + * active on congestions (queue almost full) */ + bool cs_priority_active; }; +/* The prioritization of cs pagings is controlled by a hysteresis. When the + * fill state of the paging queue exceeds the upper fill level + * THRESHOLD_CONGESTED [%], then PS pagings (immediate assignments and pagings + * from the PCU) will be dropped until fill state of the paging queue drops + * under the lower fill level THRESHOLD_CLEAR [%]. */ +#define THRESHOLD_CONGESTED 66 /* (percent of num_paging_max) */ +#define THRESHOLD_CLEAR 50 /* (percent of num_paging_max) */ + +/* Check the queue fill status and decide if prioritization of CS pagings + * must be turned on to flatten the negative effects of the congestion + * situation on the CS domain. */ +static void check_congestion(struct paging_state *ps) +{ + int pag_queue_len = paging_queue_length(ps); + int pag_queue_max = paging_get_queue_max(ps); + unsigned int treshold_upper = pag_queue_max * THRESHOLD_CONGESTED / 100; + unsigned int treshold_lower = pag_queue_max * THRESHOLD_CLEAR / 100; + + if (pag_queue_len > treshold_upper && ps->cs_priority_active == false) { + ps->cs_priority_active = true; + rate_ctr_inc2(ps->bts->ctrs, BTS_CTR_PAGING_CONG); + } else if (pag_queue_len < treshold_lower) + ps->cs_priority_active = false; +} + unsigned int paging_get_lifetime(struct paging_state *ps) { return ps->paging_lifetime; @@ -181,6 +213,8 @@ int paging_add_identity(struct paging_state *ps, uint8_t paging_group, int blocks = gsm48_number_of_paging_subchannels(&ps->chan_desc); struct paging_record *pr; + check_congestion(ps); + rate_ctr_inc2(ps->bts->ctrs, BTS_CTR_PAGING_RCVD); if (paging_group >= blocks) { @@ -199,13 +233,13 @@ int paging_add_identity(struct paging_state *ps, uint8_t paging_group, /* Check if we already have this identity */ llist_for_each_entry(pr, group_q, list) { - if (pr->type != PAGING_RECORD_PAGING) + if (pr->type != PAGING_RECORD_NORMAL) continue; - if (identity_lv[0] == pr->u.paging.identity_lv[0] && - !memcmp(identity_lv+1, pr->u.paging.identity_lv+1, + if (identity_lv[0] == pr->u.normal.identity_lv[0] && + !memcmp(identity_lv+1, pr->u.normal.identity_lv+1, identity_lv[0])) { LOGP(DPAG, LOGL_INFO, "Ignoring duplicate paging\n"); - pr->u.paging.expiration_time = + pr->u.normal.expiration_time = time(NULL) + ps->paging_lifetime; return -EEXIST; } @@ -214,9 +248,9 @@ int paging_add_identity(struct paging_state *ps, uint8_t paging_group, pr = talloc_zero(ps, struct paging_record); if (!pr) return -ENOMEM; - pr->type = PAGING_RECORD_PAGING; + pr->type = PAGING_RECORD_NORMAL; - if (*identity_lv + 1 > sizeof(pr->u.paging.identity_lv)) { + if (*identity_lv + 1 > sizeof(pr->u.normal.identity_lv)) { talloc_free(pr); return -E2BIG; } @@ -224,9 +258,9 @@ int paging_add_identity(struct paging_state *ps, uint8_t paging_group, LOGP(DPAG, LOGL_INFO, "Add paging to queue (group=%u, queue_len=%u)\n", paging_group, ps->num_paging+1); - pr->u.paging.expiration_time = time(NULL) + ps->paging_lifetime; - pr->u.paging.chan_needed = chan_needed; - memcpy(&pr->u.paging.identity_lv, identity_lv, identity_lv[0]+1); + pr->u.normal.expiration_time = time(NULL) + ps->paging_lifetime; + pr->u.normal.chan_needed = chan_needed; + memcpy(&pr->u.normal.identity_lv, identity_lv, identity_lv[0]+1); /* enqueue the new identity to the HEAD of the queue, * to ensure it will be paged quickly at least once. */ @@ -236,35 +270,59 @@ int paging_add_identity(struct paging_state *ps, uint8_t paging_group, return 0; } -/* Add an IMM.ASS message to the paging queue */ -int paging_add_imm_ass(struct paging_state *ps, const uint8_t *data, - uint8_t len) +/* Convert the last three digits of a given IMSI string to their decimal representation. In case the given IMSI string + * is shorter than three or zero digits, it will be assumed as "000" */ +static uint16_t convert_imsi_to_decimal(const char *imsi) +{ + uint16_t _imsi; + size_t imsi_len = strlen(imsi); + + /* Tha paging group is calculated from the last three digits of the IMSI */ + if (imsi_len < 3) { + LOGP(DPAG, LOGL_ERROR, "short IMSI (%zu digits), will assume \"000\" to calculate paging group\n", imsi_len); + _imsi = 0; + } else { + imsi = imsi + imsi_len - 3; + _imsi = 100 * ((*(imsi++)) - '0'); + _imsi += 10 * ((*(imsi++)) - '0'); + _imsi += (*(imsi++)) - '0'; + } + + return _imsi; +} + +/* Add a ready formatted MAC block message to the paging queue, this can be an IMMEDIATE ASSIGNMENT, or a + * PAGING COMMAND (from the PCU) */ +int paging_add_macblock(struct paging_state *ps, uint32_t msg_id, const char *imsi, bool confirm, const uint8_t *macblock) { struct llist_head *group_q; struct paging_record *pr; - uint16_t imsi, paging_group; + uint16_t paging_group; + uint16_t _imsi; - if (len != GSM_MACBLOCK_LEN + 3) { - LOGP(DPAG, LOGL_ERROR, "IMM.ASS invalid length %d\n", len); - return -EINVAL; - } - len -= 3; + check_congestion(ps); - imsi = 100 * ((*(data++)) - '0'); - imsi += 10 * ((*(data++)) - '0'); - imsi += (*(data++)) - '0'; - paging_group = gsm0502_calc_paging_group(&ps->chan_desc, imsi); + if (ps->cs_priority_active) { + LOGP(DPAG, LOGL_NOTICE, "Dropping paging for PS, queue congested (%u)\n", + ps->num_paging); + rate_ctr_inc2(ps->bts->ctrs, BTS_CTR_PAGING_DROP_PS); + return -ENOSPC; + } + _imsi = convert_imsi_to_decimal(imsi); + paging_group = gsm0502_calc_paging_group(&ps->chan_desc, _imsi); group_q = &ps->paging_queue[paging_group]; pr = talloc_zero(ps, struct paging_record); if (!pr) return -ENOMEM; - pr->type = PAGING_RECORD_IMM_ASS; + pr->type = PAGING_RECORD_MACBLOCK; - LOGP(DPAG, LOGL_INFO, "Add IMM.ASS to queue (group=%u)\n", + LOGP(DPAG, LOGL_INFO, "Add MAC block to paging queue (group=%u)\n", paging_group); - memcpy(pr->u.imm_ass.msg, data, GSM_MACBLOCK_LEN); + memcpy(pr->u.macblock.msg, macblock, GSM_MACBLOCK_LEN); + pr->u.macblock.confirm = confirm; + pr->u.macblock.msg_id = msg_id; /* enqueue the new message to the HEAD of the queue */ llist_add(&pr->list, group_q); @@ -274,22 +332,6 @@ int paging_add_imm_ass(struct paging_state *ps, const uint8_t *data, #define L2_PLEN(len) (((len - 1) << 2) | 0x01) -/* abstract representation of P1 rest octets; we only implement those parts we need for now */ -struct p1_rest_octets { - bool packet_page_ind[2]; - bool r8_present; - struct { - bool prio_ul_access; - bool etws_present; - struct { - bool is_first; - uint8_t page_nr; - const uint8_t *page; - size_t page_bytes; - } etws; - } r8; -}; - /* 3GPP TS 44.018 10.5.2.23 append a segment/page of an ETWS primary notification to given bitvec */ static void append_etws_prim_notif(struct bitvec *bv, bool is_first, uint8_t page_nr, const uint8_t *etws, ssize_t etws_len) @@ -316,13 +358,27 @@ static void append_etws_prim_notif(struct bitvec *bv, bool is_first, uint8_t pag } /* 3GPP TS 44.018 10.5.2.23 append P1 Rest Octets to given bit-vector */ -static void append_p1_rest_octets(struct bitvec *bv, const struct p1_rest_octets *p1ro) +void append_p1_rest_octets(struct bitvec *bv, const struct p1_rest_octets *p1ro, + const struct asci_notification *notif) { /* Paging 1 RO (at least 10 bits before ETWS struct) */ - bitvec_set_bit(bv, L); /* no NLN */ + if (p1ro->nln_pch.present) { + bitvec_set_bit(bv, H); + bitvec_set_uint(bv, p1ro->nln_pch.nln, 2); + bitvec_set_uint(bv, p1ro->nln_pch.nln_status, 1); + } else { + bitvec_set_bit(bv, L); /* no NLN */ + } bitvec_set_bit(bv, L); /* no Priority1 */ bitvec_set_bit(bv, L); /* no Priority2 */ - bitvec_set_bit(bv, L); /* no Group Call Info */ + if (notif) { + bitvec_set_bit(bv, H); /* Group Call Info */ + append_group_call_information(bv, notif->group_call_ref, + notif->chan_desc.present ? notif->chan_desc.value : NULL, + notif->chan_desc.len); + } else { + bitvec_set_bit(bv, L); /* no Group Call Info */ + } if (p1ro->packet_page_ind[0]) bitvec_set_bit(bv, H); /* Packet Page Indication 1 */ else @@ -347,49 +403,97 @@ static void append_p1_rest_octets(struct bitvec *bv, const struct p1_rest_octets } } +/* 3GPP TS 44.018 10.5.2.24 append P2 Rest Octets to given bit-vector */ +void append_p2_rest_octets(struct bitvec *bv, const struct p2_rest_octets *p2ro) +{ + /* {L | H <CN3: bit (2)>} */ + if (p2ro->cneed.present) { + bitvec_set_bit(bv, H); + bitvec_set_uint(bv, p2ro->cneed.cn3, 2); + } else + bitvec_set_bit(bv, L); /* no CN3 */ + + /* {L | H < NLN(PCH) : bit (2) <NLN status(PCH) : bit>} */ + if (p2ro->nln_pch.present) { + bitvec_set_bit(bv, H); + bitvec_set_uint(bv, p2ro->nln_pch.nln, 2); + bitvec_set_uint(bv, p2ro->nln_pch.nln_status, 1); + } else + bitvec_set_bit(bv, L); /* no NLN */ + + /* Note: If this needs to be extended in the future, check if it actually fits into rest of P2! */ +} + +/* 3GPP TS 44.018 10.5.2.25 append P3 Rest Octets to given bit-vector */ +void append_p3_rest_octets(struct bitvec *bv, const struct p3_rest_octets *p3ro) +{ + /* {L | H <CN3: bit (2)> <CN3: bit (2)>} */ + if (p3ro->cneed.present) { + bitvec_set_bit(bv, H); + bitvec_set_uint(bv, p3ro->cneed.cn3, 2); + bitvec_set_uint(bv, p3ro->cneed.cn4, 2); + } else + bitvec_set_bit(bv, L); /* no CN3/CN4 */ + + /* {L | H < NLN(PCH) : bit (2) <NLN status(PCH) : bit>} */ + if (p3ro->nln_pch.present) { + bitvec_set_bit(bv, H); + bitvec_set_uint(bv, p3ro->nln_pch.nln, 2); + bitvec_set_uint(bv, p3ro->nln_pch.nln_status, 1); + } else + bitvec_set_bit(bv, L); /* no NLN */ + + /* Note: If this needs to be extended in the future, check if it actually fits into 3 octets! */ +} + static int fill_paging_type_1(uint8_t *out_buf, const uint8_t *identity1_lv, uint8_t chan1, const uint8_t *identity2_lv, - uint8_t chan2, const struct p1_rest_octets *p1ro) + uint8_t chan2, const struct p1_rest_octets *p1ro, + const struct asci_notification *notif) { struct gsm48_paging1 *pt1 = (struct gsm48_paging1 *) out_buf; - struct bitvec bv; - unsigned int paging_len; + unsigned int ro_len; uint8_t *cur; - memset(out_buf, 0, sizeof(*pt1)); + *pt1 = (struct gsm48_paging1) { + .proto_discr = GSM48_PDISC_RR, + .msg_type = GSM48_MT_RR_PAG_REQ_1, + .pag_mode = GSM48_PM_NORMAL, + .cneed1 = chan1 & 3, + .cneed2 = chan2 & 3, + }; - pt1->proto_discr = GSM48_PDISC_RR; - pt1->msg_type = GSM48_MT_RR_PAG_REQ_1; - pt1->pag_mode = GSM48_PM_NORMAL; - pt1->cneed1 = chan1 & 3; - pt1->cneed2 = chan2 & 3; cur = lv_put(pt1->data, identity1_lv[0], identity1_lv+1); if (identity2_lv) cur = tlv_put(cur, GSM48_IE_MOBILE_ID, identity2_lv[0], identity2_lv+1); pt1->l2_plen = L2_PLEN(cur - out_buf); - paging_len = cur - out_buf; - - memset(&bv, 0, sizeof(bv)); - bv.data = cur; - bv.data_len = GSM_MACBLOCK_LEN - paging_len; + /* Pad remaining octets with constant '2B'O */ + ro_len = GSM_MACBLOCK_LEN - (cur - out_buf); + memset(cur, GSM_MACBLOCK_PADDING, ro_len); - if (p1ro) - append_p1_rest_octets(&bv, p1ro); + /* Optional P1 Rest Octets */ + if (p1ro) { + struct bitvec bv = { + .data_len = ro_len, + .data = cur, + }; - /* pad to the end of the MAC block */ - bitvec_spare_padding(&bv, bv.data_len *8); + append_p1_rest_octets(&bv, p1ro, notif); + } return GSM_MACBLOCK_LEN; } static int fill_paging_type_2(uint8_t *out_buf, const uint8_t *tmsi1_lv, uint8_t cneed1, const uint8_t *tmsi2_lv, - uint8_t cneed2, const uint8_t *identity3_lv) + uint8_t cneed2, const uint8_t *identity3_lv, + const struct p2_rest_octets *p2ro) { struct gsm48_paging2 *pt2 = (struct gsm48_paging2 *) out_buf; uint32_t tmsi; + unsigned int ro_len; uint8_t *cur; int rc; @@ -413,16 +517,32 @@ static int fill_paging_type_2(uint8_t *out_buf, const uint8_t *tmsi1_lv, pt2->l2_plen = L2_PLEN(cur - out_buf); - return cur - out_buf; + /* Pad remaining octets with constant '2B'O */ + ro_len = GSM_MACBLOCK_LEN - (cur - out_buf); + memset(cur, GSM_MACBLOCK_PADDING, ro_len); + + /* Optional P2 Rest Octets */ + if (p2ro) { + struct bitvec bv = { + .data_len = ro_len, + .data = cur, + }; + + append_p2_rest_octets(&bv, p2ro); + } + + return GSM_MACBLOCK_LEN; } static int fill_paging_type_3(uint8_t *out_buf, const uint8_t *tmsi1_lv, uint8_t cneed1, const uint8_t *tmsi2_lv, uint8_t cneed2, - const uint8_t *tmsi3_lv, uint8_t cneed3, - const uint8_t *tmsi4_lv, uint8_t cneed4) + const uint8_t *tmsi3_lv, const uint8_t *tmsi4_lv, + const struct p3_rest_octets *p3ro) { struct gsm48_paging3 *pt3 = (struct gsm48_paging3 *) out_buf; uint32_t tmsi; + unsigned int ro_len; + uint8_t *cur; int rc; memset(out_buf, 0, sizeof(*pt3)); @@ -444,13 +564,25 @@ static int fill_paging_type_3(uint8_t *out_buf, const uint8_t *tmsi1_lv, uint8_t rc = tmsi_mi_to_uint(&tmsi, tmsi4_lv); if (rc == 0) pt3->tmsi4 = tmsi; + cur = out_buf + 20; /* Cannot use sizeof(*pt3), because it has more octets. */ - /* The structure definition in libosmocore is wrong. It includes as last - * byte some invalid definition of chneed3/chneed4, so we must do this by hand - * here and cannot rely on sizeof(*pt3) */ - out_buf[20] = (0x23 & ~0xf8) | 0x80 | (cneed3 & 3) << 5 | (cneed4 & 3) << 3; + pt3->l2_plen = L2_PLEN(cur - out_buf); - return 21; + /* Pad remaining octets with constant '2B'O */ + ro_len = GSM_MACBLOCK_LEN - (cur - out_buf); + memset(cur, GSM_MACBLOCK_PADDING, ro_len); + + /* Optional P3 Rest Octets */ + if (p3ro) { + struct bitvec bv = { + .data_len = ro_len, + .data = cur, + }; + + append_p3_rest_octets(&bv, p3ro); + } + + return GSM_MACBLOCK_LEN; } static const uint8_t empty_id_lv[] = { 0x01, 0xF0 }; @@ -467,7 +599,7 @@ static struct paging_record *dequeue_pr(struct llist_head *group_q) static int pr_is_imsi(struct paging_record *pr) { - if ((pr->u.paging.identity_lv[1] & 7) == GSM_MI_TYPE_IMSI) + if ((pr->u.normal.identity_lv[1] & 7) == GSM_MI_TYPE_IMSI) return 1; else return 0; @@ -496,6 +628,7 @@ static void sort_pr_tmsi_imsi(struct paging_record *pr[], unsigned int n) static void build_p1_rest_octets(struct p1_rest_octets *p1ro, struct gsm_bts *bts) { memset(p1ro, 0, sizeof(*p1ro)); + p1ro->nln_pch.present = false; p1ro->packet_page_ind[0] = false; p1ro->packet_page_ind[1] = false; p1ro->r8_present = true; @@ -528,6 +661,10 @@ int paging_gen_msg(struct paging_state *ps, uint8_t *out_buf, struct gsm_time *g int group; int len; + /* This will have no effect on behavior of this function, we just need + * need to check the congestion status of the queue from time to time. */ + check_congestion(ps); + *is_empty = 0; bts->load.ccch.pch_total += 1; @@ -544,16 +681,24 @@ int paging_gen_msg(struct paging_state *ps, uint8_t *out_buf, struct gsm_time *g if (ps->bts->etws.prim_notif) { struct p1_rest_octets p1ro; build_p1_rest_octets(&p1ro, bts); - len = fill_paging_type_1(out_buf, empty_id_lv, 0, NULL, 0, &p1ro); + /* we intentioanally don't try to add notifications here, as ETWS is more critical */ + len = fill_paging_type_1(out_buf, empty_id_lv, 0, NULL, 0, &p1ro, NULL); } else if (llist_empty(group_q)) { + struct p1_rest_octets p1ro; + memset(&p1ro, 0, sizeof(p1ro)); + /* Use NLN to notify MS about ongoing VGCS/VBS calls. + * This is required to make the phone read the NCH to get an updated list of ongoing calls. + * Without this the phone will not allow making VGCS/VBS calls. */ + p1ro.nln_pch.present = (bts->asci.pos_nch >= 0); + p1ro.nln_pch.nln = bts->asci.nln; + p1ro.nln_pch.nln_status = bts->asci.nln_status; /* There is nobody to be paged, send Type1 with two empty ID */ //DEBUGP(DPAG, "Tx PAGING TYPE 1 (empty)\n"); - len = fill_paging_type_1(out_buf, empty_id_lv, 0, - NULL, 0, NULL); + len = fill_paging_type_1(out_buf, empty_id_lv, 0, NULL, 0, &p1ro, NULL); *is_empty = 1; } else { struct paging_record *pr[4]; - unsigned int num_pr = 0, imm_ass = 0; + unsigned int num_pr = 0, macblock = 0; time_t now = time(NULL); unsigned int i, num_imsi = 0; @@ -565,9 +710,9 @@ int paging_gen_msg(struct paging_state *ps, uint8_t *out_buf, struct gsm_time *g break; pr[i] = dequeue_pr(group_q); - /* check for IMM.ASS */ - if (pr[i]->type == PAGING_RECORD_IMM_ASS) { - imm_ass = 1; + /* check for MAC block */ + if (pr[i]->type == PAGING_RECORD_MACBLOCK) { + macblock = 1; break; } @@ -578,17 +723,18 @@ int paging_gen_msg(struct paging_state *ps, uint8_t *out_buf, struct gsm_time *g num_imsi++; } - /* if we have an IMMEDIATE ASSIGNMENT */ - if (imm_ass) { - /* re-add paging records */ + /* Handle MAC block (from the PCU) */ + if (macblock) { + /* re-add normal paging records */ for (i = 0; i < num_pr; i++) llist_add(&pr[i]->list, group_q); - /* get message and free record */ - memcpy(out_buf, pr[num_pr]->u.imm_ass.msg, - GSM_MACBLOCK_LEN); - pcu_tx_pch_data_cnf(gt->fn, pr[num_pr]->u.imm_ass.msg, + /* get MAC block message and free record */ + memcpy(out_buf, pr[num_pr]->u.macblock.msg, GSM_MACBLOCK_LEN); + /* send a confirmation back (if required) */ + if (pr[num_pr]->u.macblock.confirm) + pcu_tx_data_cnf(pr[num_pr]->u.macblock.msg_id, PCU_IF_SAPI_PCH_2); talloc_free(pr[num_pr]); return GSM_MACBLOCK_LEN; } @@ -599,24 +745,39 @@ int paging_gen_msg(struct paging_state *ps, uint8_t *out_buf, struct gsm_time *g if (num_pr == 4 && num_imsi == 0) { /* No IMSI: easy case, can use TYPE 3 */ DEBUGP(DPAG, "Tx PAGING TYPE 3 (4 TMSI)\n"); + struct p3_rest_octets p3ro; + memset(&p3ro, 0, sizeof(p3ro)); + p3ro.cneed.present = true; + p3ro.cneed.cn3 = pr[2]->u.normal.chan_needed; + p3ro.cneed.cn4 = pr[3]->u.normal.chan_needed; + p3ro.nln_pch.present = (bts->asci.pos_nch >= 0); + p3ro.nln_pch.nln = bts->asci.nln; + p3ro.nln_pch.nln_status = bts->asci.nln_status; len = fill_paging_type_3(out_buf, - pr[0]->u.paging.identity_lv, - pr[0]->u.paging.chan_needed, - pr[1]->u.paging.identity_lv, - pr[1]->u.paging.chan_needed, - pr[2]->u.paging.identity_lv, - pr[2]->u.paging.chan_needed, - pr[3]->u.paging.identity_lv, - pr[3]->u.paging.chan_needed); + pr[0]->u.normal.identity_lv, + pr[0]->u.normal.chan_needed, + pr[1]->u.normal.identity_lv, + pr[1]->u.normal.chan_needed, + pr[2]->u.normal.identity_lv, + pr[3]->u.normal.identity_lv, + &p3ro); } else if (num_pr >= 3 && num_imsi <= 1) { /* 3 or 4, of which only up to 1 is IMSI */ DEBUGP(DPAG, "Tx PAGING TYPE 2 (2 TMSI,1 xMSI)\n"); + struct p2_rest_octets p2ro; + memset(&p2ro, 0, sizeof(p2ro)); + p2ro.cneed.present = true; + p2ro.cneed.cn3 = pr[2]->u.normal.chan_needed; + p2ro.nln_pch.present = (bts->asci.pos_nch >= 0); + p2ro.nln_pch.nln = bts->asci.nln; + p2ro.nln_pch.nln_status = bts->asci.nln_status; len = fill_paging_type_2(out_buf, - pr[0]->u.paging.identity_lv, - pr[0]->u.paging.chan_needed, - pr[1]->u.paging.identity_lv, - pr[1]->u.paging.chan_needed, - pr[2]->u.paging.identity_lv); + pr[0]->u.normal.identity_lv, + pr[0]->u.normal.chan_needed, + pr[1]->u.normal.identity_lv, + pr[1]->u.normal.chan_needed, + pr[2]->u.normal.identity_lv, + &p2ro); if (num_pr == 4) { /* re-add #4 for next time */ llist_add(&pr[3]->list, group_q); @@ -624,19 +785,21 @@ int paging_gen_msg(struct paging_state *ps, uint8_t *out_buf, struct gsm_time *g } } else if (num_pr == 1) { DEBUGP(DPAG, "Tx PAGING TYPE 1 (1 xMSI,1 empty)\n"); + /* TODO: check if we can include an ASCI notification */ len = fill_paging_type_1(out_buf, - pr[0]->u.paging.identity_lv, - pr[0]->u.paging.chan_needed, - NULL, 0, NULL); + pr[0]->u.normal.identity_lv, + pr[0]->u.normal.chan_needed, + NULL, 0, NULL, NULL); } else { /* 2 (any type) or * 3 or 4, of which only 2 will be sent */ DEBUGP(DPAG, "Tx PAGING TYPE 1 (2 xMSI)\n"); + /* TODO: check if we can include an ASCI notification */ len = fill_paging_type_1(out_buf, - pr[0]->u.paging.identity_lv, - pr[0]->u.paging.chan_needed, - pr[1]->u.paging.identity_lv, - pr[1]->u.paging.chan_needed, NULL); + pr[0]->u.normal.identity_lv, + pr[0]->u.normal.chan_needed, + pr[1]->u.normal.identity_lv, + pr[1]->u.normal.chan_needed, NULL, NULL); if (num_pr >= 3) { /* re-add #4 for next time */ llist_add(&pr[2]->list, group_q); @@ -656,7 +819,7 @@ int paging_gen_msg(struct paging_state *ps, uint8_t *out_buf, struct gsm_time *g rate_ctr_inc2(bts->ctrs, BTS_CTR_PAGING_SENT); /* check if we can expire the paging record, * or if we need to re-queue it */ - if (pr[i]->u.paging.expiration_time <= now) { + if (pr[i]->u.normal.expiration_time <= now) { talloc_free(pr[i]); ps->num_paging--; LOGP(DPAG, LOGL_INFO, "Removed paging record, queue_len=%u\n", @@ -709,6 +872,7 @@ struct paging_state *paging_init(struct gsm_bts *bts, ps->bts = bts; ps->paging_lifetime = paging_lifetime; ps->num_paging_max = num_paging_max; + ps->cs_priority_active = false; for (i = 0; i < ARRAY_SIZE(ps->paging_queue); i++) INIT_LLIST_HEAD(&ps->paging_queue[i]); diff --git a/src/common/pcu_sock.c b/src/common/pcu_sock.c index ba9e1721..048e7668 100644 --- a/src/common/pcu_sock.c +++ b/src/common/pcu_sock.c @@ -15,10 +15,6 @@ * 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> @@ -32,37 +28,39 @@ #include <inttypes.h> #include <osmocom/core/talloc.h> +#include <osmocom/core/utils.h> #include <osmocom/core/select.h> #include <osmocom/core/socket.h> +#include <osmocom/core/write_queue.h> #include <osmocom/gsm/gsm23003.h> +#include <osmocom/gsm/abis_nm.h> #include <osmo-bts/logging.h> #include <osmo-bts/gsm_data.h> #include <osmo-bts/pcu_if.h> #include <osmo-bts/pcuif_proto.h> #include <osmo-bts/bts.h> +#include <osmo-bts/bts_sm.h> #include <osmo-bts/rsl.h> #include <osmo-bts/signal.h> #include <osmo-bts/l1sap.h> #include <osmo-bts/oml.h> +#include <osmo-bts/abis_osmo.h> -uint32_t trx_get_hlayer1(struct gsm_bts_trx *trx); +uint32_t trx_get_hlayer1(const struct gsm_bts_trx *trx); -extern struct gsm_network bts_gsmnet; int pcu_direct = 0; static int avail_lai = 0, avail_nse = 0, avail_cell = 0, avail_nsvc[2] = {0, 0}; 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_PCH_2] = "PCH_2", + [PCU_IF_SAPI_AGCH_2] = "AGCH_2", }; -static int pcu_sock_send(struct gsm_network *net, struct msgb *msg); - /* * PCU messages */ @@ -83,10 +81,12 @@ struct msgb *pcu_msgb_alloc(uint8_t msg_type, uint8_t bts_nr) return msg; } -static bool ts_should_be_pdch(struct gsm_bts_trx_ts *ts) { - if (ts->pchan == GSM_PCHAN_PDCH) +static bool ts_should_be_pdch(const struct gsm_bts_trx_ts *ts) +{ + switch (ts->pchan) { + case GSM_PCHAN_PDCH: return true; - if (ts->pchan == GSM_PCHAN_TCH_F_PDCH) { + case 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. @@ -97,35 +97,174 @@ static bool ts_should_be_pdch(struct gsm_bts_trx_ts *ts) { 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) { + case GSM_PCHAN_OSMO_DYN: /* * 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. + * response. To make it available to PCU, we want to make sure + * it's already configured by phy (pchan_is==PDCH) and that we + * are not in progress of removing it (pchan_want=None). */ - return ts->dyn.pchan_want == GSM_PCHAN_PDCH; + + return ts->dyn.pchan_is == GSM_PCHAN_PDCH && ts->dyn.pchan_want == GSM_PCHAN_PDCH; + default: + return false; + } +} + +/* As a BTS, we do not (and neither need to) know the Mobile Allocation, because + * in CS domain it's responsibility of the BSC to encode RR messages containing + * this IE. However, a BTS co-located PCU needs to know all hopping parameters, + * including the Mobile Allocation, because it's responsible for encoding of the + * packet resource assignment messages. + * + * This function, similar to generate_ma_for_ts() in osmo-bsc, computes the + * Mobile Allocation bit-mask and populates the given part of INFO.ind with + * the hopping parameters for the given timeslot. */ +static void info_ind_fill_fhp(struct gsm_pcu_if_info_trx_ts *ts_info, + const struct gsm_bts_trx_ts *ts) +{ + const struct gsm_bts *bts = ts->trx->bts; + const struct gsm_bts_trx *trx; + uint8_t ca_buf[1024 / 8] = { 0 }; + uint8_t sa_buf[1024 / 8] = { 0 }; + struct bitvec ca, sa, ma; + unsigned int i; + + ts_info->maio = ts->hopping.maio; + ts_info->hsn = ts->hopping.hsn; + ts_info->hopping = 0x01; + + /* Cell Allocation bit-mask */ + ca = (struct bitvec) { + .data_len = sizeof(ca_buf), + .data = &ca_buf[0], + }; + + llist_for_each_entry(trx, &bts->trx_list, list) { + /* Skip non-provisioned transceivers */ + if (trx->mo.nm_attr == NULL) { + LOGPTRX(trx, DPCU, LOGL_NOTICE, "not (yet) provisioned\n"); + continue; + } + + bitvec_set_bit_pos(&ca, trx->arfcn, ONE); + ts_info->ma_bit_len++; + } + + /* Slot Allocation bit-mask */ + sa = (struct bitvec) { + .data_len = sizeof(sa_buf), + .data = &sa_buf[0], + }; + + for (i = 0; i < ts->hopping.arfcn_num; i++) { + bitvec_set_bit_pos(&sa, ts->hopping.arfcn_list[i], ONE); + if (bitvec_get_bit_pos(&ca, ts->hopping.arfcn_list[i]) != ONE) { + LOGP(DPCU, LOGL_NOTICE, "A transceiver with ARFCN %u " + "is not (yet) provisioned\n", ts->hopping.arfcn_list[i]); + bitvec_set_bit_pos(&ca, ts->hopping.arfcn_list[i], ONE); + ts_info->ma_bit_len++; + } + } + + /* Mobile Allocation bit-mask */ + ma = (struct bitvec) { + .cur_bit = sizeof(ts_info->ma) * 8 - 1, + .data_len = sizeof(ts_info->ma), + .data = &ts_info->ma[0], + }; + + /* Skip ARFCN 0, it goes to the end of MA bit-mask */ + for (i = 1; i < sizeof(ca_buf) * 8; i++) { + if (bitvec_get_bit_pos(&ca, i) != ONE) + continue; + if (bitvec_get_bit_pos(&sa, i) == ONE) + bitvec_set_bit_pos(&ma, ma.cur_bit, ONE); + ma.cur_bit--; + } + + if (bitvec_get_bit_pos(&sa, 0) == ONE) + bitvec_set_bit_pos(&ma, ma.cur_bit, ONE); +} + +static void info_ind_fill_trx(struct gsm_pcu_if_info_trx *trx_info, + const struct gsm_bts_trx *trx) +{ + unsigned int tn; + + trx_info->pdch_mask = 0; + trx_info->arfcn = trx->arfcn; + trx_info->hlayer1 = trx_get_hlayer1(trx); + + if (trx->mo.nm_state.operational != NM_OPSTATE_ENABLED || + trx->mo.nm_state.administrative != NM_STATE_UNLOCKED) { + LOGPTRX(trx, DPCU, LOGL_INFO, "unavailable for PCU (op=%s adm=%s)\n", + abis_nm_opstate_name(trx->mo.nm_state.operational), + abis_nm_admin_name(trx->mo.nm_state.administrative)); + return; + } + + for (tn = 0; tn < ARRAY_SIZE(trx->ts); tn++) { + const struct gsm_bts_trx_ts *ts = &trx->ts[tn]; + + if (ts->mo.nm_state.operational != NM_OPSTATE_ENABLED) + continue; + if (!ts_should_be_pdch(ts)) + continue; + + trx_info->pdch_mask |= (1 << tn); + trx_info->ts[tn].tsc = ts->tsc; + + if (ts->hopping.enabled) + info_ind_fill_fhp(&trx_info->ts[tn], ts); + + LOGPTRX(trx, DPCU, LOGL_INFO, "PDCH on ts=%u is available " + "(tsc=%u ", ts->nr, trx_info->ts[tn].tsc); + if (ts->hopping.enabled) { + LOGPC(DPCU, LOGL_INFO, "hopping=yes hsn=%u maio=%u ma_bit_len=%u)\n", + ts->hopping.hsn, ts->hopping.maio, trx_info->ts[tn].ma_bit_len); + } else { + LOGPC(DPCU, LOGL_INFO, "hopping=no arfcn=%u)\n", trx->arfcn); + } + } +} + +static enum gsm_pcuif_bts_model bts_model_from_variant(enum gsm_bts_type_variant variant) +{ + switch (variant) { + case BTS_OSMO_LITECELL15: + return PCU_IF_BTS_MODEL_LC15; + case BTS_OSMO_OC2G: + return PCU_IF_BTS_MODEL_OC2G; + case BTS_OSMO_OCTPHY: + return PCU_IF_BTS_MODEL_OCTPHY; + case BTS_OSMO_SYSMO: + return PCU_IF_BTS_MODEL_SYSMO; + case BTS_OSMO_TRX: + case BTS_OSMO_VIRTUAL: + return PCU_IF_BTS_MODEL_TRX; + default: + return PCU_IF_BTS_MODEL_UNSPEC; } - return false; } int pcu_tx_info_ind(void) { - struct gsm_network *net = &bts_gsmnet; struct msgb *msg; struct gsm_pcu_if *pcu_prim; struct gsm_pcu_if_info_ind *info_ind; struct gsm_bts *bts; 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; + int i; + struct gsm_gprs_nse *nse; LOGP(DPCU, LOGL_INFO, "Sending info\n"); + nse = &g_bts_sm->gprs.nse; /* FIXME: allow multiple BTS */ - bts = llist_entry(net->bts_list.next, struct gsm_bts, list); + bts = llist_entry(g_bts_sm->bts_list.next, struct gsm_bts, list); rlcc = &bts->gprs.cell.rlc_cfg; msg = pcu_msgb_alloc(PCU_IF_MSG_INFO_IND, bts->nr); @@ -142,18 +281,19 @@ int pcu_tx_info_ind(void) LOGP(DPCU, LOGL_INFO, "BTS is down\n"); if (pcu_direct) - info_ind->flags |= PCU_IF_FLAG_SYSMO; + info_ind->flags |= PCU_IF_FLAG_DIRECT_PHY; + info_ind->bsic = bts->bsic; /* RAI */ - info_ind->mcc = net->plmn.mcc; - info_ind->mnc = net->plmn.mnc; - info_ind->mnc_3_digits = net->plmn.mnc_3_digits; + info_ind->mcc = g_bts_sm->plmn.mcc; + info_ind->mnc = g_bts_sm->plmn.mnc; + info_ind->mnc_3_digits = g_bts_sm->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); + info_ind->nsei = nse->nsei; + memcpy(info_ind->nse_timer, nse->timer, 7); memcpy(info_ind->cell_timer, bts->gprs.cell.timer, 11); /* cell attributes */ @@ -196,53 +336,56 @@ int pcu_tx_info_ind(void) 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" + /* FIXME: 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" + /* FIXME: 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 < 2; i++) { - nsvc = &bts->gprs.nsvc[i]; + for (i = 0; i < ARRAY_SIZE(nse->nsvc); i++) { + const struct gsm_gprs_nsvc *nsvc = &nse->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; + /* PCUIF beauty: the NSVC addresses are sent in the network byte order, + * while the port numbers need to be send in the host order. Sigh. */ + info_ind->local_port[i] = ntohs(nsvc->local.u.sin.sin_port); + info_ind->remote_port[i] = ntohs(nsvc->remote.u.sin.sin_port); + switch (nsvc->remote.u.sas.ss_family) { + case AF_INET: + info_ind->address_type[i] = PCU_IF_ADDR_TYPE_IPV4; + info_ind->remote_ip[i].v4 = nsvc->remote.u.sin.sin_addr; + break; + case AF_INET6: + info_ind->address_type[i] = PCU_IF_ADDR_TYPE_IPV6; + info_ind->remote_ip[i].v6 = nsvc->remote.u.sin6.sin6_addr; + break; + default: + info_ind->address_type[i] = PCU_IF_ADDR_TYPE_UNSPEC; + break; + } } - for (i = 0; i < 8; i++) { - trx = gsm_bts_trx_num(bts, i); - if (!trx) + llist_for_each_entry(trx, &bts->trx_list, list) { + if (trx->nr >= ARRAY_SIZE(info_ind->trx)) { + LOGPTRX(trx, DPCU, LOGL_NOTICE, "PCU interface (version %u) " + "cannot handle more than %zu transceivers => skipped\n", + PCU_IF_VERSION, ARRAY_SIZE(info_ind->trx)); break; - info_ind->trx[i].pdch_mask = 0; - info_ind->trx[i].arfcn = trx->arfcn; - info_ind->trx[i].hlayer1 = trx_get_hlayer1(trx); - for (j = 0; j < 8; 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] = gsm_ts_tsc(ts); - - 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); - } } + + info_ind_fill_trx(&info_ind->trx[trx->nr], trx); } - return pcu_sock_send(net, msg); + info_ind->bts_model = bts_model_from_variant(bts->variant); + + return pcu_sock_send(msg); } static int pcu_if_signal_cb(unsigned int subsys, unsigned int signal, void *hdlr_data, void *signal_data) { - struct gsm_network *net = &bts_gsmnet; - struct gsm_bts_gprs_nsvc *nsvc; + struct gsm_gprs_nsvc *nsvc; struct gsm_bts *bts; struct gsm48_system_information_type_3 *si3; int id; @@ -257,7 +400,7 @@ static int pcu_if_signal_cb(unsigned int subsys, unsigned int signal, break; si3 = (struct gsm48_system_information_type_3 *) bts->si_buf[SYSINFO_TYPE_3]; - osmo_plmn_from_bcd(si3->lai.digits, &net->plmn); + osmo_plmn_from_bcd(si3->lai.digits, &g_bts_sm->plmn); bts->location_area_code = ntohs(si3->lai.lac); bts->cell_identity = ntohs(si3->cell_identity); avail_lai = 1; @@ -283,6 +426,10 @@ static int pcu_if_signal_cb(unsigned int subsys, unsigned int signal, return -EINVAL; } + /* Do not send INFO.ind if PCU is not connected */ + if (!pcu_connected()) + return 0; + /* If all infos have been received, of if one info is updated after * all infos have been received, transmit info update. */ if (avail_lai && avail_nse && avail_cell && avail_nsvc[0]) @@ -309,7 +456,7 @@ int pcu_tx_app_info_req(struct gsm_bts *bts, uint8_t app_type, uint8_t len, cons ai_req->len = len; memcpy(ai_req->data, app_data, ai_req->len); - return pcu_sock_send(&bts_gsmnet, msg); + return pcu_sock_send(msg); } int pcu_tx_rts_req(struct gsm_bts_trx_ts *ts, uint8_t is_ptcch, uint32_t fn, @@ -336,7 +483,7 @@ int pcu_tx_rts_req(struct gsm_bts_trx_ts *ts, uint8_t is_ptcch, uint32_t fn, rts_req->ts_nr = ts->nr; rts_req->block_nr = block_nr; - return pcu_sock_send(&bts_gsmnet, msg); + return pcu_sock_send(msg); } int pcu_tx_data_ind(struct gsm_bts_trx_ts *ts, uint8_t sapi, uint32_t fn, @@ -351,12 +498,6 @@ int pcu_tx_data_ind(struct gsm_bts_trx_ts *ts, uint8_t sapi, uint32_t fn, LOGP(DPCU, LOGL_DEBUG, "Sending data indication: sapi=%s arfcn=%d block=%d data=%s\n", sapi_string[sapi], arfcn, block_nr, osmo_hexdump(data, len)); - if (lqual < bts->min_qual_norm) { - LOGP(DPCU, LOGL_DEBUG, "Link quality %"PRId16" is below threshold %d, dropping packet\n", - lqual, bts->min_qual_norm); - return 0; - } - msg = pcu_msgb_alloc(PCU_IF_MSG_DATA_IND, bts->nr); if (!msg) return -ENOMEM; @@ -373,14 +514,16 @@ int pcu_tx_data_ind(struct gsm_bts_trx_ts *ts, uint8_t sapi, uint32_t fn, data_ind->ber10k = ber10k; data_ind->ta_offs_qbits = bto; data_ind->lqual_cb = lqual; - memcpy(data_ind->data, data, len); + if (len) + memcpy(data_ind->data, data, len); data_ind->len = len; - return pcu_sock_send(&bts_gsmnet, msg); + return pcu_sock_send(msg); } -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) +int pcu_tx_rach_ind(uint8_t bts_nr, uint8_t trx_nr, uint8_t ts_nr, + int16_t qta, uint16_t ra, uint32_t fn, uint8_t is_11bit, + enum ph_burst_type burst_type, uint8_t sapi) { struct msgb *msg; struct gsm_pcu_if *pcu_prim; @@ -389,20 +532,22 @@ int pcu_tx_rach_ind(struct gsm_bts *bts, int16_t qta, uint16_t ra, uint32_t fn, 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); + 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->sapi = sapi; rach_ind->ra = ra; rach_ind->qta = qta; rach_ind->fn = fn; rach_ind->is_11bit = is_11bit; rach_ind->burst_type = burst_type; + rach_ind->trx_nr = trx_nr; + rach_ind->ts_nr = ts_nr; - return pcu_sock_send(&bts_gsmnet, msg); + return pcu_sock_send(msg); } int pcu_tx_time_ind(uint32_t fn) @@ -424,12 +569,45 @@ int pcu_tx_time_ind(uint32_t fn) time_ind->fn = fn; - return pcu_sock_send(&bts_gsmnet, msg); + return pcu_sock_send(msg); +} + +int pcu_tx_interf_ind(const struct gsm_bts_trx *trx, uint32_t fn) +{ + struct gsm_pcu_if_interf_ind *interf_ind; + struct gsm_pcu_if *pcu_prim; + struct msgb *msg; + unsigned int tn; + + msg = pcu_msgb_alloc(PCU_IF_MSG_INTERF_IND, trx->bts->nr); + if (!msg) + return -ENOMEM; + pcu_prim = (struct gsm_pcu_if *) msg->data; + interf_ind = &pcu_prim->u.interf_ind; + + interf_ind->trx_nr = trx->nr; + interf_ind->fn = fn; + + for (tn = 0; tn < ARRAY_SIZE(trx->ts); tn++) { + const struct gsm_bts_trx_ts *ts = &trx->ts[tn]; + const struct gsm_lchan *lchan = &ts->lchan[0]; + + if (ts->mo.nm_state.operational != NM_OPSTATE_ENABLED) + continue; + if (ts->mo.nm_state.availability != NM_AVSTATE_OK) + continue; + if (ts_pchan(ts) != GSM_PCHAN_PDCH) + continue; + + interf_ind->interf[tn] = -1 * lchan->meas.interf_meas_avg_dbm; + } + + return pcu_sock_send(msg); } int pcu_tx_pag_req(const uint8_t *identity_lv, uint8_t chan_needed) { - struct pcu_sock_state *state = bts_gsmnet.pcu_state; + struct pcu_sock_state *state = g_bts_sm->gprs.pcu_state; struct msgb *msg; struct gsm_pcu_if *pcu_prim; struct gsm_pcu_if_pag_req *pag_req; @@ -457,34 +635,31 @@ int pcu_tx_pag_req(const uint8_t *identity_lv, uint8_t chan_needed) pag_req->chan_needed = chan_needed; memcpy(pag_req->identity_lv, identity_lv, identity_lv[0] + 1); - return pcu_sock_send(&bts_gsmnet, msg); + return pcu_sock_send(msg); } -int pcu_tx_pch_data_cnf(uint32_t fn, uint8_t *data, uint8_t len) +int pcu_tx_data_cnf(uint32_t msg_id, uint8_t sapi) { - struct gsm_network *net = &bts_gsmnet; struct gsm_bts *bts; struct msgb *msg; struct gsm_pcu_if *pcu_prim; - struct gsm_pcu_if_data *data_cnf; /* FIXME: allow multiple BTS */ - bts = llist_entry(net->bts_list.next, struct gsm_bts, list); + bts = llist_entry(g_bts_sm->bts_list.next, struct gsm_bts, list); - LOGP(DPCU, LOGL_INFO, "Sending PCH confirm\n"); + LOGP(DPCU, LOGL_DEBUG, "Sending DATA.cnf: sapi=%s msg_id=%08x\n", + sapi_string[sapi], msg_id); - msg = pcu_msgb_alloc(PCU_IF_MSG_DATA_CNF, bts->nr); + msg = pcu_msgb_alloc(PCU_IF_MSG_DATA_CNF_2, bts->nr); if (!msg) return -ENOMEM; pcu_prim = (struct gsm_pcu_if *) msg->data; - data_cnf = &pcu_prim->u.data_cnf; + pcu_prim->u.data_cnf2 = (struct gsm_pcu_if_data_cnf) { + .sapi = sapi, + .msg_id = msg_id, + }; - data_cnf->sapi = PCU_IF_SAPI_PCH; - data_cnf->fn = fn; - memcpy(data_cnf->data, data, len); - data_cnf->len = len; - - return pcu_sock_send(&bts_gsmnet, msg); + return pcu_sock_send(msg); } /* forward data from a RR GPRS SUSPEND REQ towards PCU */ @@ -501,7 +676,7 @@ int pcu_tx_susp_req(struct gsm_lchan *lchan, uint32_t tlli, const uint8_t *ra_id memcpy(pcu_prim->u.susp_req.ra_id, ra_id, sizeof(pcu_prim->u.susp_req.ra_id)); pcu_prim->u.susp_req.cause = cause; - return pcu_sock_send(&bts_gsmnet, msg); + return pcu_sock_send(msg); } static int pcu_rx_data_req(struct gsm_bts *bts, uint8_t msg_type, @@ -519,22 +694,45 @@ static int pcu_rx_data_req(struct gsm_bts *bts, uint8_t msg_type, osmo_hexdump(data_req->data, data_req->len)); switch (data_req->sapi) { - case PCU_IF_SAPI_PCH: - paging_add_imm_ass(bts->paging_state, data_req->data, data_req->len); + case PCU_IF_SAPI_PCH_2: + { + const struct gsm_pcu_if_pch *gsm_pcu_if_pch; + + if (OSMO_UNLIKELY(data_req->len != sizeof(*gsm_pcu_if_pch))) { + LOGP(DPCU, LOGL_ERROR, "Rx malformed DATA.req for PCH\n"); + rc = -EINVAL; + break; + } + + gsm_pcu_if_pch = (struct gsm_pcu_if_pch *)data_req->data; + rc = paging_add_macblock(bts->paging_state, gsm_pcu_if_pch->msg_id, + gsm_pcu_if_pch->imsi, gsm_pcu_if_pch->confirm, gsm_pcu_if_pch->data); break; - case PCU_IF_SAPI_AGCH: - msg = msgb_alloc(data_req->len, "pcu_agch"); + } + case PCU_IF_SAPI_AGCH_2: + { + const struct gsm_pcu_if_agch *gsm_pcu_if_agch; + struct bts_agch_msg_cb *msg_cb; + + gsm_pcu_if_agch = (struct gsm_pcu_if_agch *)data_req->data; + + msg = msgb_alloc(GSM_MACBLOCK_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); + msg->l3h = msgb_put(msg, GSM_MACBLOCK_LEN); + memcpy(msg->l3h, gsm_pcu_if_agch->data, GSM_MACBLOCK_LEN); + + msg_cb = (struct bts_agch_msg_cb *) msg->cb; + msg_cb->confirm = gsm_pcu_if_agch->confirm; + msg_cb->msg_id = gsm_pcu_if_agch->msg_id; if (bts_agch_enqueue(bts, msg) < 0) { msgb_free(msg); rc = -EIO; } break; + } case PCU_IF_SAPI_PDTCH: case PCU_IF_SAPI_PTCCH: trx = gsm_bts_trx_num(bts, data_req->trx_nr); @@ -594,24 +792,74 @@ static int pcu_rx_pag_req(struct gsm_bts *bts, uint8_t msg_type, return rc; } -int pcu_tx_si13(const struct gsm_bts *bts, bool enable) +int pcu_tx_si(const struct gsm_bts *bts, enum osmo_sysinfo_type si_type, + bool enable) { /* the SI is per-BTS so it doesn't matter which TRX we use */ struct gsm_bts_trx *trx = gsm_bts_trx_num(bts, 0); - /* The low-level data like FN, ARFCN etc will be ignored but we have to set lqual high enough to bypass - the check at lower levels */ - int rc = pcu_tx_data_ind(&trx->ts[0], PCU_IF_SAPI_BCCH, 0, 0, 0, GSM_BTS_SI(bts, SYSINFO_TYPE_13), - enable ? GSM_MACBLOCK_LEN : 0, 0, 0, 0, INT16_MAX); + uint8_t si_buf[GSM_MACBLOCK_LEN]; + uint8_t len; + int rc; + + if (enable) { + memcpy(si_buf, GSM_BTS_SI(bts, si_type), GSM_MACBLOCK_LEN); + len = GSM_MACBLOCK_LEN; + LOGP(DPCU, LOGL_DEBUG, "Updating SI%s to PCU: %s\n", + get_value_string(osmo_sitype_strs, si_type), + osmo_hexdump_nospc(si_buf, GSM_MACBLOCK_LEN)); + } else { + si_buf[0] = si_type; + len = 1; + + /* Note: SI13 is the only system information type that is revked + * by just sending a completely empty message. This is due to + * historical reasons */ + if (si_type != SYSINFO_TYPE_13) + len = 0; + + LOGP(DPCU, LOGL_DEBUG, "Revoking SI%s from PCU\n", + get_value_string(osmo_sitype_strs, si_buf[0])); + } + + /* The low-level data like FN, ARFCN etc will be ignored but we have to + * set lqual high enough to bypass the check at lower levels */ + rc = pcu_tx_data_ind(&trx->ts[0], PCU_IF_SAPI_BCCH, 0, 0, 0, si_buf, len, + 0, 0, 0, INT16_MAX); if (rc < 0) - LOGP(DPCU, LOGL_NOTICE, "Failed to send SI13 to PCU: %d\n", rc); + LOGP(DPCU, LOGL_NOTICE, "Failed to send SI%s to PCU: rc=%d\n", + get_value_string(osmo_sitype_strs, si_type), rc); return rc; } +static int pcu_tx_si_all(struct gsm_bts *bts) +{ + const enum osmo_sysinfo_type si_types[] = + { SYSINFO_TYPE_1, SYSINFO_TYPE_2, SYSINFO_TYPE_3, SYSINFO_TYPE_13 }; + unsigned int i; + int rc = 0; + + for (i = 0; i < ARRAY_SIZE(si_types); i++) { + if (GSM_BTS_HAS_SI(bts, si_types[i])) { + rc = pcu_tx_si(bts, si_types[i], true); + if (rc < 0) + return rc; + } else { + LOGP(DPCU, LOGL_INFO, + "SI%s is not available on PCU connection\n", + get_value_string(osmo_sitype_strs, si_types[i])); + } + } + + return 0; +} + static int pcu_rx_txt_ind(struct gsm_bts *bts, struct gsm_pcu_if_txt_ind *txt) { + int rc; + switch (txt->type) { case PCU_VERSION: LOGP(DPCU, LOGL_INFO, "OsmoPCU version %s connected\n", @@ -619,13 +867,14 @@ static int pcu_rx_txt_ind(struct gsm_bts *bts, oml_tx_failure_event_rep(&bts->gprs.cell.mo, NM_SEVER_CEASED, OSMO_EVT_PCU_VERS, txt->text); osmo_strlcpy(bts->pcu_version, txt->text, MAX_VERSION_LENGTH); - /* patch SI3 to advertise GPRS, *if* the SI3 sent by BSC said so */ + /* patch SI to advertise GPRS, *if* the SI sent by BSC said so */ regenerate_si3_restoctets(bts); + regenerate_si4_restoctets(bts); - if (GSM_BTS_HAS_SI(bts, SYSINFO_TYPE_13)) - return pcu_tx_si13(bts, true); + rc = pcu_tx_si_all(bts); + if (rc < 0) + return -EINVAL; - LOGP(DPCU, LOGL_INFO, "SI13 is not available on PCU connection\n"); break; case PCU_OML_ALERT: oml_tx_failure_event_rep(&bts->gprs.cell.mo, NM_SEVER_INDETERMINATE, OSMO_EVT_EXT_ALARM, @@ -646,7 +895,7 @@ static int pcu_rx_act_req(struct gsm_bts *bts, struct gsm_bts_trx *trx; struct gsm_lchan *lchan; - LOGP(DPCU, LOGL_INFO, "%s request received: TRX=%d TX=%d\n", + LOGP(DPCU, LOGL_INFO, "%s request received: TRX=%d TS=%d\n", (act_req->activate) ? "Activate" : "Deactivate", act_req->trx_nr, act_req->ts_nr); @@ -663,40 +912,70 @@ static int pcu_rx_act_req(struct gsm_bts *bts, gsm_lchant_name(lchan->type)); return -EINVAL; } + if (lchan->ts->pchan == GSM_PCHAN_OSMO_DYN && + lchan->ts->dyn.pchan_is != GSM_PCHAN_PDCH) { + LOGP(DPCU, LOGL_ERROR, + "%s request, but lchan in dyn TS is not configured as PDCH in lower layers (is %s)\n", + (act_req->activate) ? "Activate" : "Deactivate", + gsm_pchan_name(lchan->ts->dyn.pchan_is)); + return -EINVAL; + } if (act_req->activate) - l1sap_chan_act(trx, gsm_lchan2chan_nr(lchan), NULL); + l1sap_chan_act(trx, gsm_lchan2chan_nr(lchan)); else l1sap_chan_rel(trx, gsm_lchan2chan_nr(lchan)); return 0; } -static int pcu_rx(struct gsm_network *net, uint8_t msg_type, - struct gsm_pcu_if *pcu_prim) +#define CHECK_IF_MSG_SIZE(prim_len, prim_msg) \ + do { \ + size_t _len = PCUIF_HDR_SIZE + sizeof(prim_msg); \ + if (prim_len < _len) { \ + LOGP(DPCU, LOGL_ERROR, "Received %zu bytes on PCU Socket, but primitive %s " \ + "size is %zu, discarding\n", prim_len, #prim_msg, _len); \ + return -EINVAL; \ + } \ + } while (0) +static int pcu_rx(uint8_t msg_type, struct gsm_pcu_if *pcu_prim, size_t prim_len) { int rc = 0; struct gsm_bts *bts; + size_t exp_len; - /* FIXME: allow multiple BTS */ - if (pcu_prim->bts_nr != 0) { + if ((bts = gsm_bts_num(g_bts_sm, pcu_prim->bts_nr)) == NULL) { LOGP(DPCU, LOGL_ERROR, "Received PCU Prim for non-existent BTS %u\n", pcu_prim->bts_nr); return -EINVAL; } - bts = llist_entry(net->bts_list.next, struct gsm_bts, list); switch (msg_type) { case PCU_IF_MSG_DATA_REQ: + CHECK_IF_MSG_SIZE(prim_len, pcu_prim->u.data_req); rc = pcu_rx_data_req(bts, msg_type, &pcu_prim->u.data_req); break; case PCU_IF_MSG_PAG_REQ: + CHECK_IF_MSG_SIZE(prim_len, pcu_prim->u.pag_req); rc = pcu_rx_pag_req(bts, msg_type, &pcu_prim->u.pag_req); break; case PCU_IF_MSG_ACT_REQ: + CHECK_IF_MSG_SIZE(prim_len, pcu_prim->u.act_req); rc = pcu_rx_act_req(bts, &pcu_prim->u.act_req); break; case PCU_IF_MSG_TXT_IND: + CHECK_IF_MSG_SIZE(prim_len, pcu_prim->u.txt_ind); rc = pcu_rx_txt_ind(bts, &pcu_prim->u.txt_ind); break; + case PCU_IF_MSG_CONTAINER: + CHECK_IF_MSG_SIZE(prim_len, pcu_prim->u.container); + /* ^ check if we can access container fields, v check with container data length */ + exp_len = PCUIF_HDR_SIZE + sizeof(pcu_prim->u.container) + osmo_load16be(&pcu_prim->u.container.length); + if (prim_len < exp_len) { + LOGP(DPCU, LOGL_ERROR, "Received %zu bytes on PCU Socket, but primitive " + "container size is %zu, discarding\n", prim_len, exp_len); + return -EINVAL; + } + rc = abis_osmo_pcu_tx_container(bts, &pcu_prim->u.container); + break; default: LOGP(DPCU, LOGL_ERROR, "Received unknown PCU msg type %d\n", msg_type); @@ -711,49 +990,58 @@ static int pcu_rx(struct gsm_network *net, uint8_t msg_type, */ struct pcu_sock_state { - struct gsm_network *net; struct osmo_fd listen_bfd; /* fd for listen socket */ - struct osmo_fd conn_bfd; /* fd for connection to lcr */ - struct llist_head upqueue; /* queue for sending messages */ + struct osmo_wqueue upqueue; /* For sending messages; has fd for conn. to PCU */ }; -static int pcu_sock_send(struct gsm_network *net, struct msgb *msg) +static void pcu_sock_close(struct pcu_sock_state *state); + +int pcu_sock_send(struct msgb *msg) { - struct pcu_sock_state *state = net->pcu_state; + struct pcu_sock_state *state = g_bts_sm->gprs.pcu_state; struct osmo_fd *conn_bfd; struct gsm_pcu_if *pcu_prim = (struct gsm_pcu_if *) msg->data; + int rc; if (!state) { - if (pcu_prim->msg_type != PCU_IF_MSG_TIME_IND) + if (pcu_prim->msg_type != PCU_IF_MSG_TIME_IND && + pcu_prim->msg_type != PCU_IF_MSG_INTERF_IND) LOGP(DPCU, LOGL_INFO, "PCU socket not created, " "dropping message\n"); msgb_free(msg); return -EINVAL; } - conn_bfd = &state->conn_bfd; + conn_bfd = &state->upqueue.bfd; if (conn_bfd->fd <= 0) { - if (pcu_prim->msg_type != PCU_IF_MSG_TIME_IND) + if (pcu_prim->msg_type != PCU_IF_MSG_TIME_IND && + pcu_prim->msg_type != PCU_IF_MSG_INTERF_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; + rc = osmo_wqueue_enqueue(&state->upqueue, msg); + if (rc < 0) { + if (rc == -ENOSPC) + LOGP(DPCU, LOGL_NOTICE, "PCU not reacting (more than %u messages waiting). Closing connection\n", + state->upqueue.max_length); + pcu_sock_close(state); + msgb_free(msg); + return rc; + } return 0; } static void pcu_sock_close(struct pcu_sock_state *state) { - struct osmo_fd *bfd = &state->conn_bfd; + struct osmo_fd *bfd = &state->upqueue.bfd; struct gsm_bts *bts; struct gsm_bts_trx *trx; - struct gsm_bts_trx_ts *ts; - int i, j; + unsigned int tn; /* FIXME: allow multiple BTS */ - bts = llist_entry(state->net->bts_list.next, struct gsm_bts, list); + bts = llist_entry(g_bts_sm->bts_list.next, struct gsm_bts, list); LOGP(DPCU, LOGL_NOTICE, "PCU socket has LOST connection\n"); oml_tx_failure_event_rep(&bts->gprs.cell.mo, NM_SEVER_MAJOR, OSMO_EVT_PCU_VERS, @@ -761,15 +1049,16 @@ static void pcu_sock_close(struct pcu_sock_state *state) bts->pcu_version[0] = '\0'; + osmo_fd_unregister(bfd); close(bfd->fd); bfd->fd = -1; - osmo_fd_unregister(bfd); /* patch SI3 to remove GPRS indicator */ regenerate_si3_restoctets(bts); + regenerate_si4_restoctets(bts); /* re-enable the generation of ACCEPT for new connections */ - state->listen_bfd.when |= BSC_FD_READ; + osmo_fd_read_enable(&state->listen_bfd); #if 0 /* remove si13, ... */ @@ -777,27 +1066,22 @@ static void pcu_sock_close(struct pcu_sock_state *state) 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) { - ts->lchan[0].rel_act_kind = LCHAN_REL_ACT_PCU; - l1sap_chan_rel(trx, - gsm_lchan2chan_nr(&ts->lchan[0])); - } + /* Deactivate all active PDCH timeslots */ + llist_for_each_entry(trx, &bts->trx_list, list) { + for (tn = 0; tn < 8; tn++) { + struct gsm_bts_trx_ts *ts = &trx->ts[tn]; + + if (ts->mo.nm_state.operational != NM_OPSTATE_ENABLED) + continue; + if (!ts_should_be_pdch(ts)) + continue; + + ts->lchan[0].rel_act_kind = LCHAN_REL_ACT_PCU; + l1sap_chan_rel(trx, gsm_lchan2chan_nr(&ts->lchan[0])); } } - /* flush the queue */ - while (!llist_empty(&state->upqueue)) { - struct msgb *msg = msgb_dequeue(&state->upqueue); - msgb_free(msg); - } + osmo_wqueue_clear(&state->upqueue); } static int pcu_sock_read(struct osmo_fd *bfd) @@ -807,7 +1091,7 @@ static int pcu_sock_read(struct osmo_fd *bfd) struct msgb *msg; int rc; - msg = msgb_alloc(sizeof(*pcu_prim), "pcu_sock_rx"); + msg = msgb_alloc(sizeof(*pcu_prim) + 1000, "pcu_sock_rx"); if (!msg) return -ENOMEM; @@ -825,14 +1109,14 @@ static int pcu_sock_read(struct osmo_fd *bfd) goto close; } - if (rc < sizeof(*pcu_prim)) { - LOGP(DPCU, LOGL_ERROR, "Received %d bytes on PCU Socket, but primitive size " - "is %zu, discarding\n", rc, sizeof(*pcu_prim)); + if (rc < PCUIF_HDR_SIZE) { + LOGP(DPCU, LOGL_ERROR, "Received %d bytes on PCU Socket, but primitive hdr size " + "is %zu, discarding\n", rc, PCUIF_HDR_SIZE); msgb_free(msg); return 0; } - rc = pcu_rx(state->net, pcu_prim->msg_type, pcu_prim); + rc = pcu_rx(pcu_prim->msg_type, pcu_prim, rc); /* as we always synchronously process the message in pcu_rx() and * its callbacks, we can free the message here. */ @@ -846,102 +1130,57 @@ close: return -1; } -static int pcu_sock_write(struct osmo_fd *bfd) +static int pcu_sock_write(struct osmo_fd *bfd, struct msgb *msg) { 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); + /* bug hunter 8-): maybe someone forgot msgb_put(...) ? */ + OSMO_ASSERT(msgb_length(msg) > 0); + /* try to send it over the socket */ + rc = write(bfd->fd, msgb_data(msg), msgb_length(msg)); + if (OSMO_UNLIKELY(rc == 0)) + goto close; + if (OSMO_UNLIKELY(rc < 0)) { + if (errno == EAGAIN) + return -EAGAIN; + return -1; } 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 */ +/* accept connection coming 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 osmo_fd *conn_bfd = &state->upqueue.bfd; struct sockaddr_un un_addr; socklen_t len; - int rc; + int fd; len = sizeof(un_addr); - rc = accept(bfd->fd, (struct sockaddr *) &un_addr, &len); - if (rc < 0) { + fd = accept(bfd->fd, (struct sockaddr *)&un_addr, &len); + if (fd < 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"); + 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); + osmo_fd_read_disable(&state->listen_bfd); + close(fd); return 0; } - conn_bfd->fd = rc; - conn_bfd->when = BSC_FD_READ; - conn_bfd->cb = pcu_sock_cb; - conn_bfd->data = state; + osmo_fd_setup(conn_bfd, fd, OSMO_FD_READ, osmo_wqueue_bfd_cb, state, 0); if (osmo_fd_register(conn_bfd) != 0) { - LOGP(DPCU, LOGL_ERROR, "Failed to register new connection " - "fd\n"); + LOGP(DPCU, LOGL_ERROR, "Failed to register new connection fd\n"); close(conn_bfd->fd); conn_bfd->fd = -1; return -1; @@ -955,34 +1194,32 @@ static int pcu_sock_accept(struct osmo_fd *bfd, unsigned int flags) return 0; } -int pcu_sock_init(const char *path) +int pcu_sock_init(const char *path, int qlength_max) { struct pcu_sock_state *state; struct osmo_fd *bfd; int rc; - state = talloc_zero(NULL, struct pcu_sock_state); + state = talloc_zero(g_bts_sm, struct pcu_sock_state); if (!state) return -ENOMEM; - INIT_LLIST_HEAD(&state->upqueue); - state->net = &bts_gsmnet; - state->conn_bfd.fd = -1; + osmo_wqueue_init(&state->upqueue, qlength_max); + state->upqueue.read_cb = pcu_sock_read; + state->upqueue.write_cb = pcu_sock_write; + state->upqueue.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) { + rc = osmo_sock_unix_init(SOCK_SEQPACKET, 0, path, OSMO_SOCK_F_BIND); + if (rc < 0) { LOGP(DPCU, LOGL_ERROR, "Could not create %s unix socket: %s\n", path, strerror(errno)); talloc_free(state); return -1; } - bfd->when = BSC_FD_READ; - bfd->cb = pcu_sock_accept; - bfd->data = state; + osmo_fd_setup(bfd, rc, OSMO_FD_READ, pcu_sock_accept, state, 0); rc = osmo_fd_register(bfd); if (rc < 0) { @@ -995,39 +1232,38 @@ int pcu_sock_init(const char *path) osmo_signal_register_handler(SS_GLOBAL, pcu_if_signal_cb, NULL); - bts_gsmnet.pcu_state = state; + g_bts_sm->gprs.pcu_state = state; - LOGP(DPCU, LOGL_INFO, "Started listening on PCU socket: %s\n", path); + LOGP(DPCU, LOGL_INFO, "Started listening on PCU socket (PCU IF v%u): %s\n", PCU_IF_VERSION, path); return 0; } void pcu_sock_exit(void) { - struct pcu_sock_state *state = bts_gsmnet.pcu_state; + struct pcu_sock_state *state = g_bts_sm->gprs.pcu_state; struct osmo_fd *bfd, *conn_bfd; if (!state) return; osmo_signal_unregister_handler(SS_GLOBAL, pcu_if_signal_cb, NULL); - conn_bfd = &state->conn_bfd; + conn_bfd = &state->upqueue.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_gsmnet.pcu_state = NULL; + g_bts_sm->gprs.pcu_state = NULL; } bool pcu_connected(void) { - struct gsm_network *net = &bts_gsmnet; - struct pcu_sock_state *state = net->pcu_state; + struct pcu_sock_state *state = g_bts_sm->gprs.pcu_state; if (!state) return false; - if (state->conn_bfd.fd <= 0) + if (state->upqueue.bfd.fd <= 0) return false; return true; } diff --git a/src/common/phy_link.c b/src/common/phy_link.c index 588fcc91..352d8f72 100644 --- a/src/common/phy_link.c +++ b/src/common/phy_link.c @@ -2,6 +2,7 @@ #include <osmocom/core/linuxlist.h> #include <osmocom/core/talloc.h> +#include <osmocom/core/fsm.h> #include <osmo-bts/bts.h> #include <osmo-bts/gsm_data.h> @@ -9,6 +10,7 @@ #include <osmo-bts/oml.h> #include <osmo-bts/logging.h> #include <osmo-bts/bts_model.h> +#include <osmo-bts/nm_common_fsm.h> static LLIST_HEAD(g_phy_links); @@ -53,9 +55,11 @@ void phy_link_state_set(struct phy_link *plink, enum phy_link_state state) { struct phy_instance *pinst; - LOGP(DL1C, LOGL_INFO, "PHY link state change %s -> %s\n", - get_value_string(phy_link_state_vals, plink->state), - get_value_string(phy_link_state_vals, state)); + LOGPPHL(plink, DL1C, LOGL_INFO, "PHY link state change %s -> %s\n", + get_value_string(phy_link_state_vals, plink->state), + get_value_string(phy_link_state_vals, state)); + + plink->state = state; /* notify all TRX associated with this phy */ llist_for_each_entry(pinst, &plink->instances, list) { @@ -63,25 +67,28 @@ void phy_link_state_set(struct phy_link *plink, enum phy_link_state state) if (!trx) continue; - switch (state) { - case PHY_LINK_CONNECTED: - LOGP(DL1C, LOGL_INFO, "trx_set_avail(1)\n"); - trx_set_available(trx, 1); - break; - case PHY_LINK_SHUTDOWN: - LOGP(DL1C, LOGL_INFO, "trx_set_avail(0)\n"); - trx_set_available(trx, 0); - break; - case PHY_LINK_CONNECTING: - /* nothing to do */ - break; - } + osmo_fsm_inst_dispatch(trx->mo.fi, + state == PHY_LINK_CONNECTED ? NM_EV_PHYLINK_UP : + NM_EV_PHYLINK_DOWN, + NULL); + osmo_fsm_inst_dispatch(trx->bb_transc.mo.fi, + state == PHY_LINK_CONNECTED ? NM_EV_PHYLINK_UP : + NM_EV_PHYLINK_DOWN, + NULL); } +} - plink->state = state; +enum phy_link_state phy_link_state_get(struct phy_link *plink) +{ + return plink->state; +} + +const char *phy_link_state_name(enum phy_link_state state) +{ + return get_value_string(phy_link_state_vals, state); } -struct phy_instance *phy_instance_by_num(struct phy_link *plink, int num) +struct phy_instance *phy_instance_by_num(const struct phy_link *plink, int num) { struct phy_instance *pinst; @@ -111,7 +118,9 @@ struct phy_instance *phy_instance_create(struct phy_link *plink, int num) void phy_instance_link_to_trx(struct phy_instance *pinst, struct gsm_bts_trx *trx) { - trx->role_bts.l1h = pinst; + /* There might already be an associated TRX */ + OSMO_ASSERT(pinst->trx == NULL) + trx->pinst = pinst; pinst->trx = trx; } @@ -120,10 +129,12 @@ void phy_instance_destroy(struct phy_instance *pinst) /* remove from list of instances in the link */ llist_del(&pinst->list); - /* remove reverse link from TRX */ - OSMO_ASSERT(pinst->trx->role_bts.l1h == pinst); - pinst->trx->role_bts.l1h = NULL; - pinst->trx = NULL; + /* remove reverse link from TRX (if associated) */ + if (pinst->trx != NULL) { + OSMO_ASSERT(pinst->trx->pinst == pinst); + pinst->trx->pinst = NULL; + pinst->trx = NULL; + } talloc_free(pinst); } @@ -138,13 +149,29 @@ void phy_link_destroy(struct phy_link *plink) talloc_free(plink); } +static char name_buf[32]; +const char *phy_link_name(const struct phy_link *plink) +{ + snprintf(name_buf, sizeof(name_buf), "phy%u", plink->num); + return name_buf; +} + int phy_links_open(void) { + const struct phy_instance *pinst; struct phy_link *plink; llist_for_each_entry(plink, &g_phy_links, list) { int rc; + /* Warn about dangling PHY instances */ + llist_for_each_entry(pinst, &plink->instances, list) { + if (pinst->trx != NULL) + continue; + LOGPPHI(pinst, DL1C, LOGL_NOTICE, "This PHY instance is not associated " + "with a TRX instance, check the configuration file!\n"); + } + rc = bts_model_phy_link_open(plink); if (rc < 0) return rc; @@ -153,11 +180,9 @@ int phy_links_open(void) return 0; } -const char *phy_instance_name(struct phy_instance *pinst) +const char *phy_instance_name(const struct phy_instance *pinst) { - static char buf[32]; - - snprintf(buf, sizeof(buf), "phy%u.%u", pinst->phy_link->num, + snprintf(name_buf, sizeof(name_buf), "phy%u.%u", pinst->phy_link->num, pinst->num); - return buf; + return name_buf; } diff --git a/src/common/power_control.c b/src/common/power_control.c index b1728705..7f98a417 100644 --- a/src/common/power_control.c +++ b/src/common/power_control.c @@ -1,6 +1,8 @@ /* MS Power Control Loop L1 */ /* (C) 2014 by Holger Hans Peter Freyther + * (C) 2020-2021 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> + * Author: Vadim Yanitskiy <vyanitskiy@sysmocom.de> * * All Rights Reserved * @@ -12,7 +14,7 @@ * 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. + * 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/>. @@ -22,6 +24,7 @@ #include <stdint.h> #include <unistd.h> #include <errno.h> +#include <inttypes.h> #include <osmo-bts/logging.h> #include <osmo-bts/bts.h> @@ -29,61 +32,544 @@ #include <osmo-bts/measurement.h> #include <osmo-bts/bts_model.h> #include <osmo-bts/l1sap.h> +#include <osmo-bts/power_control.h> -/* - * Check if manual power control is needed - * Check if fixed power was selected - * Check if the MS is already using our level if not - * the value is bogus.. - * TODO: Add a timeout.. e.g. if the ms is not capable of reaching - * the value we have set. +/* We don't want to deal with floating point, so we scale up */ +#define EWMA_SCALE_FACTOR 100 +/* EWMA_SCALE_FACTOR/2 = +50: Round to nearest value when downscaling, otherwise floor() is applied. */ +#define EWMA_ROUND_FACTOR (EWMA_SCALE_FACTOR / 2) + +/* Base Low-Pass Single-Pole IIR Filter (EWMA) formula: + * + * Avg[n] = a * Val[n] + (1 - a) * Avg[n - 1] + * + * where parameter 'a' determines how much weight of the latest measurement value + * 'Val[n]' carries vs the weight of the accumulated average 'Avg[n - 1]'. The + * value of 'a' is usually a float in range 0 .. 1, so: + * + * - value 0.5 gives equal weight to both 'Val[n]' and 'Avg[n - 1]'; + * - value 1.0 means no filtering at all (pass through); + * - value 0.0 makes no sense. + * + * Further optimization: + * + * Avg[n] = a * Val[n] + Avg[n - 1] - a * Avg[n - 1] + * ^^^^^^ ^^^^^^^^^^ + * + * a) this can be implemented in C using '+=' operator: + * + * Avg += a * Val - a * Avg + * Avg += a * (Val - Avg) + * + * b) everything is scaled up by 100 to avoid floating point stuff: + * + * Avg100 += A * (Val - Avg) + * + * where 'Avg100' is 'Avg * 100' and 'A' is 'a * 100'. + * + * For more details, see: + * + * https://en.wikipedia.org/wiki/Moving_average + * https://en.wikipedia.org/wiki/Low-pass_filter#Simple_infinite_impulse_response_filter + * https://tomroelandts.com/articles/low-pass-single-pole-iir-filter + */ +static int do_pf_ewma(const struct gsm_power_ctrl_meas_params *mp, + struct gsm_power_ctrl_meas_proc_state *mps, + const int Val) +{ + const uint8_t A = mp->ewma.alpha; + int *Avg100 = &mps->ewma.Avg100; + + /* We don't have 'Avg[n - 1]' if this is the first run */ + if (mps->meas_num++ == 0) { + *Avg100 = Val * EWMA_SCALE_FACTOR; + return Val; + } + + *Avg100 += A * (Val - (*Avg100 + EWMA_ROUND_FACTOR) / EWMA_SCALE_FACTOR); + return (*Avg100 + EWMA_ROUND_FACTOR) / EWMA_SCALE_FACTOR; +} + +/* Calculate target RxLev value from lower/upper thresholds */ +#define CALC_TARGET(mp) \ + ((mp).lower_thresh + (mp).upper_thresh) / 2 + +static int do_avg_algo(const struct gsm_power_ctrl_meas_params *mp, + struct gsm_power_ctrl_meas_proc_state *mps, + const int val) +{ + int val_avg; + switch (mp->algo) { + case GSM_PWR_CTRL_MEAS_AVG_ALGO_OSMO_EWMA: + val_avg = do_pf_ewma(mp, mps, val); + break; + /* TODO: implement other pre-processing methods */ + case GSM_PWR_CTRL_MEAS_AVG_ALGO_NONE: + default: + /* No filtering (pass through) */ + val_avg = val; + } + return val_avg; +} +/* Calculate a 'delta' value (for the given MS/BS power control parameters) + * to be applied to the current Tx power level to approach the target level. */ +static int calc_delta_rxlev(const struct gsm_power_ctrl_params *params, const uint8_t rxlev) +{ + int delta; + + /* Check if RxLev is within the threshold window */ + if (rxlev >= params->rxlev_meas.lower_thresh && + rxlev <= params->rxlev_meas.upper_thresh) + return 0; + + /* How many dBs measured power should be increased (+) or decreased (-) + * to reach expected power. */ + delta = CALC_TARGET(params->rxlev_meas) - rxlev; + + /* Don't ever change more than PWR_{LOWER,RAISE}_MAX_DBM during one loop + * iteration, i.e. reduce the speed at which the MS transmit power can + * change. A higher value means a lower level (and vice versa) */ + if (delta > params->inc_step_size_db) + delta = params->inc_step_size_db; + else if (delta < -params->red_step_size_db) + delta = -params->red_step_size_db; + + return delta; +} + +/* Shall we skip current block based on configured interval? */ +static bool ctrl_interval_skip_block(const struct gsm_power_ctrl_params *params, + struct lchan_power_ctrl_state *state) +{ + /* Power control interval: how many blocks do we skip? */ + if (state->skip_block_num-- > 0) + return true; + + /* Reset the number of SACCH blocks to be skipped: + * ctrl_interval=0 => 0 blocks to skip, + * ctrl_interval=1 => 1 blocks to skip, + * ctrl_interval=2 => 3 blocks to skip, + * so basically ctrl_interval * 2 - 1. */ + state->skip_block_num = params->ctrl_interval * 2 - 1; + return false; +} + +static const struct gsm_power_ctrl_meas_params *lchan_get_ci_thresholds(const struct gsm_lchan *lchan) +{ + const struct gsm_power_ctrl_params *params = lchan->ms_power_ctrl.dpc_params; + + switch (lchan->type) { + case GSM_LCHAN_SDCCH: + return ¶ms->ci_sdcch_meas; + case GSM_LCHAN_PDTCH: + return ¶ms->ci_gprs_meas; + case GSM_LCHAN_TCH_F: + if (lchan->tch_mode == GSM48_CMODE_SPEECH_AMR) + return ¶ms->ci_amr_fr_meas; + else + return ¶ms->ci_fr_meas; + case GSM_LCHAN_TCH_H: + if (lchan->tch_mode == GSM48_CMODE_SPEECH_AMR) + return ¶ms->ci_amr_hr_meas; + else + return ¶ms->ci_hr_meas; + default: + OSMO_ASSERT(0); + } +} + +/*! compute the new MS POWER LEVEL communicated to the MS and store it in lchan. + * \param lchan logical channel for which to compute (and in which to store) new power value. + * \param[in] ms_power_lvl MS Power Level received from Uplink L1 SACCH Header in SACCH block. + * \param[in] ul_rssi_dbm Signal level of the received SACCH block, in dBm. + * \param[in] ul_lqual_cb C/I of the received SACCH block, in dB. */ int lchan_ms_pwr_ctrl(struct gsm_lchan *lchan, - const uint8_t ms_power, const int rxLevel) + const uint8_t ms_power_lvl, + const int8_t ul_rssi_dbm, + const int16_t ul_lqual_cb) { - int rx; - int cur_dBm, new_dBm, new_pwr; - struct gsm_bts *bts = lchan->ts->trx->bts; - const enum gsm_band band = bts->band; + struct lchan_power_ctrl_state *state = &lchan->ms_power_ctrl; + const struct gsm_power_ctrl_params *params = state->dpc_params; + struct gsm_bts_trx *trx = lchan->ts->trx; + struct gsm_bts *bts = trx->bts; + enum gsm_band band = bts->band; + int8_t new_power_lvl; /* TS 05.05 power level */ + int8_t ms_dbm, new_dbm, current_dbm, bsc_max_dbm; + uint8_t rxlev_avg; + int16_t ul_lqual_cb_avg; + const struct gsm_power_ctrl_meas_params *ci_meas; + bool ignore, ci_on; - if (!trx_ms_pwr_ctrl_is_osmo(lchan->ts->trx)) + if (!trx_ms_pwr_ctrl_is_osmo(trx)) return 0; - if (lchan->ms_power_ctrl.fixed) + if (params == NULL) return 0; - /* The phone hasn't reached the power level yet */ - if (lchan->ms_power_ctrl.current != ms_power) + /* Shall we skip current block based on configured interval? */ + if (ctrl_interval_skip_block(params, state)) return 0; - /* What is the difference between what we want and received? */ - rx = bts->ul_power_target - rxLevel; + ms_dbm = ms_pwr_dbm(band, ms_power_lvl); + if (ms_dbm < 0) { + LOGPLCHAN(lchan, DLOOP, LOGL_NOTICE, + "Failed to calculate dBm for power ctl level %" PRIu8 " on band %s\n", + ms_power_lvl, gsm_band_name(band)); + return 0; + } + bsc_max_dbm = ms_pwr_dbm(band, state->max); + if (bsc_max_dbm < 0) { + LOGPLCHAN(lchan, DLOOP, LOGL_NOTICE, + "Failed to calculate dBm for power ctl level %" PRIu8 " on band %s\n", + state->max, gsm_band_name(band)); + return 0; + } - cur_dBm = ms_pwr_dbm(band, ms_power); - new_dBm = cur_dBm + rx; + ci_meas = lchan_get_ci_thresholds(lchan); - /* Clamp negative values and do it depending on the band */ - if (new_dBm < 0) - new_dBm = 0; + /* Is C/I based algo enabled by config? + * FIXME: this can later be generalized when properly implementing P & N counting. */ + ci_on = ci_meas->lower_cmp_n && ci_meas->upper_cmp_n; - switch (band) { - case GSM_BAND_1800: - /* If MS_TX_PWR_MAX_CCH is set the values 29, - * 30, 31 are not used. Avoid specifying a dBm - * that would lead to these power levels. The - * phone might not be able to reach them. */ - if (new_dBm > 30) - new_dBm = 30; - break; - default: - break; + ul_lqual_cb_avg = do_avg_algo(ci_meas, &state->ci_meas_proc, ul_lqual_cb); + rxlev_avg = do_avg_algo(¶ms->rxlev_meas, &state->rxlev_meas_proc, dbm2rxlev(ul_rssi_dbm)); + + /* If computed C/I is enabled and out of acceptable thresholds: */ + if (ci_on && ul_lqual_cb_avg < ci_meas->lower_thresh * 10) { + new_dbm = ms_dbm + params->inc_step_size_db; + } else if (ci_on && ul_lqual_cb_avg > ci_meas->upper_thresh * 10) { + new_dbm = ms_dbm - params->red_step_size_db; + } else { + /* Calculate the new Tx power value (in dBm) */ + new_dbm = ms_dbm + calc_delta_rxlev(params, rxlev_avg); } - new_pwr = ms_pwr_ctl_lvl(band, new_dBm); - if (lchan->ms_power_ctrl.current != new_pwr) { - lchan->ms_power_ctrl.current = new_pwr; - bts_model_adjst_ms_pwr(lchan); - return 1; + /* Make sure new_dbm is never negative. ms_pwr_ctl_lvl() can later on + cope with any unsigned dbm value, regardless of band minimal value. */ + if (new_dbm < 0) + new_dbm = 0; + + /* Don't ask for smaller ms power level than the one set by BSC upon RSL CHAN ACT */ + if (new_dbm > bsc_max_dbm) + new_dbm = bsc_max_dbm; + + new_power_lvl = ms_pwr_ctl_lvl(band, new_dbm); + if (new_power_lvl < 0) { + LOGPLCHAN(lchan, DLOOP, LOGL_NOTICE, + "Failed to retrieve power level for %" PRId8 " dBm on band %d\n", + new_dbm, band); + return 0; } - return 0; + current_dbm = ms_pwr_dbm(band, state->current); + + /* In this Power Control Loop, we infer a new good MS Power Level based + * on the previous MS Power Level announced by the MS (not the previous + * one we requested!) together with the related computed measurements. + * Hence, and since we allow for several good MS Power Levels falling into our + * thresholds, we could finally converge into an oscillation loop where + * the MS bounces between 2 different correct MS Power levels all the + * time, due to the fact that we "accept" and "request back" whatever + * good MS Power Level we received from the MS, but at that time the MS + * will be transmitting using the previous MS Power Level we + * requested, which we will later "accept" and "request back" on next loop + * iteration. As a result MS effectively bounces between those 2 MS + * Power Levels. + * In order to fix this permanent oscillation, if current MS_PWR used/announced + * by MS is good ("ms_dbm == new_dbm", hence within thresholds and no change + * required) but has higher Tx power than the one we last requested, we ignore + * it and keep requesting for one with lower Tx power. This way we converge to + * the lowest good Tx power avoiding oscillating over values within thresholds. + */ + ignore = (ms_dbm == new_dbm && ms_dbm > current_dbm); + + if (state->current == new_power_lvl || ignore) { + LOGPLCHAN(lchan, DLOOP, LOGL_INFO, "Keeping MS power at control level %d (%d dBm): " + "ms-pwr-lvl[curr %" PRIu8 ", max %" PRIu8 "], RSSI[curr %d, avg %d, thresh %d..%d] dBm," + " C/I[curr %d, avg %d, thresh %d..%d] dB\n", + new_power_lvl, new_dbm, ms_power_lvl, state->max, ul_rssi_dbm, rxlev2dbm(rxlev_avg), + rxlev2dbm(params->rxlev_meas.lower_thresh), rxlev2dbm(params->rxlev_meas.upper_thresh), + ul_lqual_cb/10, ul_lqual_cb_avg/10, ci_meas->lower_thresh, ci_meas->upper_thresh); + return 0; + } + + LOGPLCHAN(lchan, DLOOP, LOGL_INFO, "%s MS power control level %d (%d dBm) => %d (%d dBm): " + "ms-pwr-lvl[curr %" PRIu8 ", max %" PRIu8 "], RSSI[curr %d, avg %d, thresh %d..%d] dBm," + " C/I[curr %d, avg %d, thresh %d..%d] dB\n", + (new_dbm > current_dbm) ? "Raising" : "Lowering", + state->current, current_dbm, new_power_lvl, new_dbm, ms_power_lvl, + state->max, ul_rssi_dbm, rxlev2dbm(rxlev_avg), + rxlev2dbm(params->rxlev_meas.lower_thresh), rxlev2dbm(params->rxlev_meas.upper_thresh), + ul_lqual_cb/10, ul_lqual_cb_avg/10, ci_meas->lower_thresh, ci_meas->upper_thresh); + + /* store the resulting new MS power level in the lchan */ + state->current = new_power_lvl; + bts_model_adjst_ms_pwr(lchan); + + return 1; +} + +/*! compute the new Downlink attenuation value for the given logical channel. + * \param lchan logical channel for which to compute (and in which to store) new power value. + * \param[in] mr pointer to a *valid* Measurement Report. + */ +int lchan_bs_pwr_ctrl(struct gsm_lchan *lchan, + const struct gsm48_meas_res *mr) +{ + struct lchan_power_ctrl_state *state = &lchan->bs_power_ctrl; + const struct gsm_power_ctrl_params *params = state->dpc_params; + uint8_t rxqual, rxqual_avg, rxlev, rxlev_avg; + int new_att; + + /* Check if dynamic BS Power Control is enabled */ + if (params == NULL) + return 0; + + LOGPLCHAN(lchan, DLOOP, LOGL_DEBUG, "Rx DL Measurement Report: " + "RXLEV-FULL(%02u), RXQUAL-FULL(%u), " + "RXLEV-SUB(%02u), RXQUAL-SUB(%u), " + "DTx is %s => using %s\n", + mr->rxlev_full, mr->rxqual_full, + mr->rxlev_sub, mr->rxqual_sub, + lchan->tch.dtx.dl_active ? "enabled" : "disabled", + lchan->tch.dtx.dl_active ? "SUB" : "FULL"); + + /* Shall we skip current block based on configured interval? */ + if (ctrl_interval_skip_block(params, state)) + return 0; + + /* If DTx is active on Downlink, use the '-SUB' */ + if (lchan->tch.dtx.dl_active) { + rxqual = mr->rxqual_sub; + rxlev = mr->rxlev_sub; + } else { /* ... otherwise use the '-FULL' */ + rxqual = mr->rxqual_full; + rxlev = mr->rxlev_full; + } + + rxlev_avg = do_avg_algo(¶ms->rxlev_meas, &state->rxlev_meas_proc, rxlev); + rxqual_avg = do_avg_algo(¶ms->rxqual_meas, &state->rxqual_meas_proc, rxqual); + /* If RxQual > L_RXQUAL_XX_P, try to increase Tx power */ + if (rxqual_avg > params->rxqual_meas.lower_thresh) { + /* Increase Tx power by reducing Tx attenuation */ + new_att = state->current - params->inc_step_size_db; + } else if (rxqual_avg < params->rxqual_meas.upper_thresh) { + /* Increase Tx power by Increasing Tx attenuation */ + new_att = state->current + params->red_step_size_db; + } else { + /* Basic signal transmission / reception formula: + * + * RxLev = TxPwr - (PathLoss + TxAtt) + * + * Here we want to change RxLev at the MS side, so: + * + * RxLev + Delta = TxPwr - (PathLoss + TxAtt) + Delta + * + * The only parameter we can change here is TxAtt, so: + * + * RxLev + Delta = TxPwr - PathLoss - TxAtt + Delta + * RxLev + Delta = TxPwr - PathLoss - (TxAtt - Delta) + */ + new_att = state->current - calc_delta_rxlev(params, rxlev_avg); + } + + /* Make sure new TxAtt is never negative: */ + if (new_att < 0) + new_att = 0; + + /* Don't ask for higher TxAtt than permitted: */ + if (new_att > state->max) + new_att = state->max; + + if (state->current == new_att) { + LOGPLCHAN(lchan, DLOOP, LOGL_INFO, "Keeping DL attenuation at %u dB: " + "max %u dB, RSSI[curr %d, avg %d, thresh %d..%d] dBm, " + "RxQual[curr %d, avg %d, thresh %d..%d]\n", + state->current, state->max, rxlev2dbm(rxlev), rxlev2dbm(rxlev_avg), + rxlev2dbm(params->rxlev_meas.lower_thresh), rxlev2dbm(params->rxlev_meas.upper_thresh), + rxqual, rxqual_avg, params->rxqual_meas.lower_thresh, params->rxqual_meas.upper_thresh); + return 0; + } + + LOGPLCHAN(lchan, DLOOP, LOGL_INFO, "%s DL attenuation %u dB => %u dB:" + "max %u dB, RSSI[curr %d, avg %d, thresh %d..%d] dBm, " + "RxQual[curr %d, avg %d, thresh %d..%d]\n", + (new_att > state->current) ? "Raising" : "Lowering", + state->current, new_att, state->max, rxlev2dbm(rxlev), rxlev2dbm(rxlev_avg), + rxlev2dbm(params->rxlev_meas.lower_thresh), rxlev2dbm(params->rxlev_meas.upper_thresh), + rxqual, rxqual_avg, params->rxqual_meas.lower_thresh, params->rxqual_meas.upper_thresh); + state->current = new_att; + return 1; +} + +/* Default MS/BS Power Control parameters (see 3GPP TS 45.008, table A.1) */ +const struct gsm_power_ctrl_params power_ctrl_params_def = { + /* Power increasing/reducing step size (optimal defaults) */ + .inc_step_size_db = 4, /* quickly increase MS/BS power */ + .red_step_size_db = 2, /* slowly decrease MS/BS power */ + + /* RxLev measurement parameters */ + .rxlev_meas = { + /* Thresholds for RxLev (see 3GPP TS 45.008, A.3.2.1) */ + .lower_thresh = 32, /* L_RXLEV_XX_P (-78 dBm) */ + .upper_thresh = 38, /* U_RXLEV_XX_P (-72 dBm) */ + + /* NOTE: only Osmocom specific EWMA is supported */ + .algo = GSM_PWR_CTRL_MEAS_AVG_ALGO_OSMO_EWMA, + .ewma.alpha = 50, /* Smoothing factor 50% */ + }, + + /* RxQual measurement parameters */ + .rxqual_meas = { + /* Thresholds for RxQual (see 3GPP TS 45.008, A.3.2.1) */ + .lower_thresh = 3, /* L_RXQUAL_XX_P (0.8% <= BER < 1.6%) */ + .upper_thresh = 0, /* U_RXQUAL_XX_P (BER < 0.2%) */ + + /* No averaging (filtering) by default. + * NOTE: only Osmocom specific EWMA is supported */ + .algo = GSM_PWR_CTRL_MEAS_AVG_ALGO_NONE, + }, + + /* C/I measurement parameters. + * Target C/I retrieved from "GSM/EDGE: Evolution and Performance" Table 10.3. + * Set lower and upper so that (lower + upper) / 2 is equal or slightly + * above the target. + */ + .ci_fr_meas = { /* FR: Target C/I = 15 dB, Soft blocking threshold = 10 dB */ + .lower_thresh = 13, + .upper_thresh = 17, + + /* Increase {UL,DL}_TXPWR if at least LOWER_CMP_P averages + * out of LOWER_CMP_N averages are lower than L_CI_FR_XX_P */ + .lower_cmp_p = 5, /* P3 as in 3GPP TS 45.008, A.3.2.1 (case c) */ + .lower_cmp_n = 7, /* N3 as in 3GPP TS 45.008, A.3.2.1 (case c) */ + /* Decrease {UL,DL}_TXPWR if at least UPPER_CMP_P averages + * out of UPPER_CMP_N averages are greater than L_CI_FR_XX_P */ + .upper_cmp_p = 15, /* P4 as in 3GPP TS 45.008, A.3.2.1 (case d) */ + .upper_cmp_n = 18, /* N4 as in 3GPP TS 45.008, A.3.2.1 (case d) */ + + /* No averaging (filtering) by default */ + .algo = GSM_PWR_CTRL_MEAS_AVG_ALGO_NONE, + + /* Hreqave: the period over which an average is produced */ + .h_reqave = 4, /* TODO: investigate a reasonable default value */ + /* Hreqt: the number of averaged results maintained */ + .h_reqt = 6, /* TODO: investigate a reasonable default value */ + }, + .ci_hr_meas = { /* HR: Target C/I = 18 dB, Soft blocking threshold = 13 dB */ + .lower_thresh = 16, + .upper_thresh = 21, + + /* Increase {UL,DL}_TXPWR if at least LOWER_CMP_P averages + * out of LOWER_CMP_N averages are lower than L_CI_HR_XX_P */ + .lower_cmp_p = 5, /* P3 as in 3GPP TS 45.008, A.3.2.1 (case c) */ + .lower_cmp_n = 7, /* N3 as in 3GPP TS 45.008, A.3.2.1 (case c) */ + /* Decrease {UL,DL}_TXPWR if at least UPPER_CMP_P averages + * out of UPPER_CMP_N averages are greater than L_CI_HR_XX_P */ + .upper_cmp_p = 15, /* P4 as in 3GPP TS 45.008, A.3.2.1 (case d) */ + .upper_cmp_n = 18, /* N4 as in 3GPP TS 45.008, A.3.2.1 (case d) */ + + /* No averaging (filtering) by default */ + .algo = GSM_PWR_CTRL_MEAS_AVG_ALGO_NONE, + + /* Hreqave: the period over which an average is produced */ + .h_reqave = 4, /* TODO: investigate a reasonable default value */ + /* Hreqt: the number of averaged results maintained */ + .h_reqt = 6, /* TODO: investigate a reasonable default value */ + }, + .ci_amr_fr_meas = { /* AMR-FR: Target C/I = 9 dB, Soft blocking threshold = 4 dB */ + .lower_thresh = 7, + .upper_thresh = 11, + + /* Increase {UL,DL}_TXPWR if at least LOWER_CMP_P averages + * out of LOWER_CMP_N averages are lower than L_CI_AMR_FR_XX_P */ + .lower_cmp_p = 5, /* P3 as in 3GPP TS 45.008, A.3.2.1 (case c) */ + .lower_cmp_n = 7, /* N3 as in 3GPP TS 45.008, A.3.2.1 (case c) */ + /* Decrease {UL,DL}_TXPWR if at least UPPER_CMP_P averages + * out of UPPER_CMP_N averages are greater than L_CI_AMR_FR_XX_P */ + .upper_cmp_p = 15, /* P4 as in 3GPP TS 45.008, A.3.2.1 (case d) */ + .upper_cmp_n = 18, /* N4 as in 3GPP TS 45.008, A.3.2.1 (case d) */ + + /* No averaging (filtering) by default */ + .algo = GSM_PWR_CTRL_MEAS_AVG_ALGO_NONE, + + /* Hreqave: the period over which an average is produced */ + .h_reqave = 4, /* TODO: investigate a reasonable default value */ + /* Hreqt: the number of averaged results maintained */ + .h_reqt = 6, /* TODO: investigate a reasonable default value */ + }, + .ci_amr_hr_meas = { /* AMR-HR: Target C/I = 15 dB, Soft blocking threshold = 10 dB */ + .lower_thresh = 13, + .upper_thresh = 17, + + /* Increase {UL,DL}_TXPWR if at least LOWER_CMP_P averages + * out of LOWER_CMP_N averages are lower than L_CI_AMR_HR_XX_P */ + .lower_cmp_p = 5, /* P3 as in 3GPP TS 45.008, A.3.2.1 (case c) */ + .lower_cmp_n = 7, /* N3 as in 3GPP TS 45.008, A.3.2.1 (case c) */ + /* Decrease {UL,DL}_TXPWR if at least UPPER_CMP_P averages + * out of UPPER_CMP_N averages are greater than L_CI_AMR_HR_XX_P */ + .upper_cmp_p = 15, /* P4 as in 3GPP TS 45.008, A.3.2.1 (case d) */ + .upper_cmp_n = 18, /* N4 as in 3GPP TS 45.008, A.3.2.1 (case d) */ + + /* No averaging (filtering) by default */ + .algo = GSM_PWR_CTRL_MEAS_AVG_ALGO_NONE, + + /* Hreqave: the period over which an average is produced */ + .h_reqave = 4, /* TODO: investigate a reasonable default value */ + /* Hreqt: the number of averaged results maintained */ + .h_reqt = 6, /* TODO: investigate a reasonable default value */ + }, + .ci_sdcch_meas = { /* SDCCH: Target C/I = 14 dB, Soft blocking threshold = 9 dB */ + .lower_thresh = 12, + .upper_thresh = 16, + + /* Increase {UL,DL}_TXPWR if at least LOWER_CMP_P averages + * out of LOWER_CMP_N averages are lower than L_CI_SDCCH_XX_P */ + .lower_cmp_p = 5, /* P3 as in 3GPP TS 45.008, A.3.2.1 (case c) */ + .lower_cmp_n = 7, /* N3 as in 3GPP TS 45.008, A.3.2.1 (case c) */ + /* Decrease {UL,DL}_TXPWR if at least UPPER_CMP_P averages + * out of UPPER_CMP_N averages are greater than L_CI_SDCCH_XX_P */ + .upper_cmp_p = 15, /* P4 as in 3GPP TS 45.008, A.3.2.1 (case d) */ + .upper_cmp_n = 18, /* N4 as in 3GPP TS 45.008, A.3.2.1 (case d) */ + + /* No averaging (filtering) by default */ + .algo = GSM_PWR_CTRL_MEAS_AVG_ALGO_NONE, + + /* Hreqave: the period over which an average is produced */ + .h_reqave = 4, /* TODO: investigate a reasonable default value */ + /* Hreqt: the number of averaged results maintained */ + .h_reqt = 6, /* TODO: investigate a reasonable default value */ + }, + .ci_gprs_meas = { /* GPRS: Target C/I = 20 dB, Soft blocking threshold = 15 dB */ + .lower_thresh = 18, + .upper_thresh = 24, + + /* Increase {UL,DL}_TXPWR if at least LOWER_CMP_P averages + * out of LOWER_CMP_N averages are lower than L_CI_GPRS_XX_P */ + .lower_cmp_p = 5, /* P3 as in 3GPP TS 45.008, A.3.2.1 (case c) */ + .lower_cmp_n = 7, /* N3 as in 3GPP TS 45.008, A.3.2.1 (case c) */ + /* Decrease {UL,DL}_TXPWR if at least UPPER_CMP_P averages + * out of UPPER_CMP_N averages are greater than L_CI_GPRS_XX_P */ + .upper_cmp_p = 15, /* P4 as in 3GPP TS 45.008, A.3.2.1 (case d) */ + .upper_cmp_n = 18, /* N4 as in 3GPP TS 45.008, A.3.2.1 (case d) */ + + /* No averaging (filtering) by default */ + .algo = GSM_PWR_CTRL_MEAS_AVG_ALGO_NONE, + + /* Hreqave: the period over which an average is produced */ + .h_reqave = 4, /* TODO: investigate a reasonable default value */ + /* Hreqt: the number of averaged results maintained */ + .h_reqt = 6, /* TODO: investigate a reasonable default value */ + }, +}; + +void power_ctrl_params_def_reset(struct gsm_power_ctrl_params *params, bool is_bs_pwr) +{ + *params = power_ctrl_params_def; + + /* Trigger loop every N-th SACCH block. See 3GPP TS 45.008 section 4.7.1. */ + if (!is_bs_pwr) + params->ctrl_interval = 2; /* N=4 (1.92s) */ + else + params->ctrl_interval = 1; /* N=2 (0.960) */ } diff --git a/src/common/probes.d b/src/common/probes.d new file mode 100644 index 00000000..aaf9030e --- /dev/null +++ b/src/common/probes.d @@ -0,0 +1,2 @@ +provider osmo_bts { +}; diff --git a/src/common/rsl.c b/src/common/rsl.c index c0d43d0e..cc802c8d 100644 --- a/src/common/rsl.c +++ b/src/common/rsl.c @@ -2,6 +2,7 @@ /* (C) 2011 by Andreas Eversberg <jolly@eversberg.eu> * (C) 2011-2019 by Harald Welte <laforge@gnumonks.org> + * (C) 2020 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> * * All Rights Reserved * @@ -13,19 +14,18 @@ * 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. + * 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 "btsconfig.h" /* for PACKAGE_VERSION */ - #include <stdio.h> #include <errno.h> #include <netdb.h> #include <stdbool.h> +#include <inttypes.h> #include <sys/types.h> #include <arpa/inet.h> @@ -56,9 +56,39 @@ #include <osmo-bts/l1sap.h> #include <osmo-bts/bts_model.h> #include <osmo-bts/pcuif_proto.h> +#include <osmo-bts/notification.h> +#include <osmo-bts/asci.h> //#define FAKE_CIPH_MODE_COMPL +/* Parse power attenuation (in dB) from BS Power IE (see 9.3.4) */ +#define BS_POWER2DB(bs_power) \ + ((bs_power & 0x0f) * 2) + +bool rsl_chan_rt_is_asci(enum rsl_cmod_crt chan_rt) +{ + switch (chan_rt) { + case RSL_CMOD_CRT_TCH_GROUP_Bm: + case RSL_CMOD_CRT_TCH_GROUP_Lm: + case RSL_CMOD_CRT_TCH_BCAST_Bm: + case RSL_CMOD_CRT_TCH_BCAST_Lm: + return true; + default: + return false; + } +} + +bool rsl_chan_rt_is_vgcs(enum rsl_cmod_crt chan_rt) +{ + switch (chan_rt) { + case RSL_CMOD_CRT_TCH_GROUP_Bm: + case RSL_CMOD_CRT_TCH_GROUP_Lm: + return true; + default: + return false; + } +} + static int rsl_tx_error_report(struct gsm_bts_trx *trx, uint8_t cause, const uint8_t *chan_nr, const uint8_t *link_id, const struct msgb *orig_msg); @@ -69,6 +99,7 @@ static const unsigned int rsl_sacch_sitypes[] = { RSL_SYSTEM_INFO_6, RSL_SYSTEM_INFO_5bis, RSL_SYSTEM_INFO_5ter, + RSL_SYSTEM_INFO_10, RSL_EXT_MEAS_ORDER, RSL_MEAS_INFO, }; @@ -85,19 +116,6 @@ int osmo_in_array(unsigned int search, const unsigned int *arr, unsigned int siz } #define OSMO_IN_ARRAY(search, arr) osmo_in_array(search, arr, ARRAY_SIZE(arr)) -int msgb_queue_flush(struct llist_head *list) -{ - struct msgb *msg, *msg2; - int count = 0; - - llist_for_each_entry_safe(msg, msg2, list, list) { - msgb_free(msg); - count++; - } - - return count; -} - /* FIXME: move this to libosmocore */ void gsm48_gen_starting_time(uint8_t *out, struct gsm_time *gtime) { @@ -106,33 +124,236 @@ void gsm48_gen_starting_time(uint8_t *out, struct gsm_time *gtime) out[1] = (gtime->t3 << 5) | gtime->t2; } -/* compute lchan->rsl_cmode and lchan->tch_mode from RSL CHAN MODE IE */ -static void lchan_tchmode_from_cmode(struct gsm_lchan *lchan, - struct rsl_ie_chan_mode *cm) +/* Handle RSL Channel Mode IE (see section 9.3.6) */ +static int rsl_handle_chan_mod_ie(struct gsm_lchan *lchan, + const struct tlv_parsed *tp, + uint8_t *cause) { + const struct rsl_ie_chan_mode *cm; + + if (!TLVP_PRES_LEN(tp, RSL_IE_CHAN_MODE, sizeof(*cm))) { + LOGPLCHAN(lchan, DRSL, LOGL_ERROR, "Channel Mode IE is not present\n"); + *cause = RSL_ERR_MAND_IE_ERROR; + return -ENODEV; + } + + cm = (const struct rsl_ie_chan_mode *) TLVP_VAL(tp, RSL_IE_CHAN_MODE); lchan->rsl_cmode = cm->spd_ind; + lchan->rsl_chan_rt = cm->chan_rt; lchan->ts->trx->bts->dtxd = (cm->dtx_dtu & RSL_CMOD_DTXd) ? true : false; - switch (cm->chan_rate) { - case RSL_CMOD_SP_GSM1: + /* Octet 5: Channel rate and type */ + switch (cm->chan_rt) { + case RSL_CMOD_CRT_SDCCH: + case RSL_CMOD_CRT_TCH_Bm: + case RSL_CMOD_CRT_TCH_Lm: + case RSL_CMOD_CRT_TCH_GROUP_Bm: + case RSL_CMOD_CRT_TCH_GROUP_Lm: + case RSL_CMOD_CRT_TCH_BCAST_Bm: + case RSL_CMOD_CRT_TCH_BCAST_Lm: + break; + case RSL_CMOD_CRT_OSMO_TCH_VAMOS_Bm: + case RSL_CMOD_CRT_OSMO_TCH_VAMOS_Lm: + /* Make sure that Osmocom specific TSC IE is present */ + if (!TLVP_PRES_LEN(tp, RSL_IE_OSMO_TRAINING_SEQUENCE, 2)) { + LOGPLCHAN(lchan, DRSL, LOGL_ERROR, + "Training Sequence IE is not present\n"); + *cause = RSL_ERR_MAND_IE_ERROR; + return -ENODEV; + } + break; + default: + LOGPLCHAN(lchan, DRSL, LOGL_ERROR, "Channel Mode IE contains " + "unknown 'Channel rate and type' value 0x%02x\n", + cm->chan_rt); + *cause = RSL_ERR_IE_CONTENT; + return -ENOTSUP; + } + +#define RSL_CMODE(spd_ind, chan_rate) \ + ((spd_ind << 8) | chan_rate) + + /* Octet 6: Speech coding algorithm/data rate + transparency indicator. + * NOTE: coding of this octet depends on 'Speech or data indicator' */ + switch (RSL_CMODE(cm->spd_ind, cm->chan_rate)) { + /* If octet 4 indicates signalling */ + case RSL_CMODE(RSL_CMOD_SPD_SIGN, 0x00): + /* No resources required, all other values are reserved */ + lchan->tch_mode = GSM48_CMODE_SIGN; + break; + + /* If octet 4 indicates speech */ + case RSL_CMODE(RSL_CMOD_SPD_SPEECH, RSL_CMOD_SP_GSM1): lchan->tch_mode = GSM48_CMODE_SPEECH_V1; break; - case RSL_CMOD_SP_GSM2: + case RSL_CMODE(RSL_CMOD_SPD_SPEECH, RSL_CMOD_SP_GSM2): lchan->tch_mode = GSM48_CMODE_SPEECH_EFR; break; - case RSL_CMOD_SP_GSM3: + case RSL_CMODE(RSL_CMOD_SPD_SPEECH, RSL_CMOD_SP_GSM3): lchan->tch_mode = GSM48_CMODE_SPEECH_AMR; break; - case RSL_CMOD_SP_NT_14k5: + case RSL_CMODE(RSL_CMOD_SPD_SPEECH, RSL_CMOD_SP_GSM4): + lchan->tch_mode = GSM48_CMODE_SPEECH_V4; + break; + case RSL_CMODE(RSL_CMOD_SPD_SPEECH, RSL_CMOD_SP_GSM5): + lchan->tch_mode = GSM48_CMODE_SPEECH_V5; + break; + case RSL_CMODE(RSL_CMOD_SPD_SPEECH, RSL_CMOD_SP_GSM6): + lchan->tch_mode = GSM48_CMODE_SPEECH_V6; + break; + + /* If octet 4 indicates non-transparent data */ + case RSL_CMODE(RSL_CMOD_SPD_DATA, RSL_CMOD_CSD_NT_14k5): + lchan->tch_mode = GSM48_CMODE_DATA_14k5; + break; + case RSL_CMODE(RSL_CMOD_SPD_DATA, RSL_CMOD_CSD_NT_12k0): + lchan->tch_mode = GSM48_CMODE_DATA_12k0; + break; + case RSL_CMODE(RSL_CMOD_SPD_DATA, RSL_CMOD_CSD_NT_6k0): + lchan->tch_mode = GSM48_CMODE_DATA_6k0; + break; + case RSL_CMODE(RSL_CMOD_SPD_DATA, RSL_CMOD_CSD_NT_43k5): + lchan->tch_mode = GSM48_CMODE_DATA_43k5; + break; + case RSL_CMODE(RSL_CMOD_SPD_DATA, RSL_CMOD_CSD_NT_28k8): + /* 28.8 kbit/s services, 29.0 kbit/s radio interface rate */ + lchan->tch_mode = GSM48_CMODE_DATA_29k0; + break; + case RSL_CMODE(RSL_CMOD_SPD_DATA, RSL_CMOD_CSD_NTA_43k5_14k5): + lchan->tch_mode = GSM48_CMODE_DATA_43k5_14k5; + break; + case RSL_CMODE(RSL_CMOD_SPD_DATA, RSL_CMOD_CSD_NTA_29k0_14k5): + lchan->tch_mode = GSM48_CMODE_DATA_29k0_14k5; + break; + case RSL_CMODE(RSL_CMOD_SPD_DATA, RSL_CMOD_CSD_NTA_43k5_29k0): + lchan->tch_mode = GSM48_CMODE_DATA_43k5_29k0; + break; + case RSL_CMODE(RSL_CMOD_SPD_DATA, RSL_CMOD_CSD_NTA_14k5_43k5): + lchan->tch_mode = GSM48_CMODE_DATA_14k5_43k5; + break; + case RSL_CMODE(RSL_CMOD_SPD_DATA, RSL_CMOD_CSD_NTA_14k5_29k0): + lchan->tch_mode = GSM48_CMODE_DATA_14k5_29k0; + break; + case RSL_CMODE(RSL_CMOD_SPD_DATA, RSL_CMOD_CSD_NTA_29k0_43k5): + lchan->tch_mode = GSM48_CMODE_DATA_29k0_43k5; + break; + + /* If octet 4 indicates transparent data */ + case RSL_CMODE(RSL_CMOD_SPD_DATA, RSL_CMOD_CSD_T_32k0): + /* 32.0 kbit/s services, 32.0 kbit/s radio interface rate */ + lchan->tch_mode = GSM48_CMODE_DATA_32k0; + lchan->csd_mode = LCHAN_CSD_M_T_32000; + break; + case RSL_CMODE(RSL_CMOD_SPD_DATA, RSL_CMOD_CSD_T_29k0): + /* 29.0 kbit/s services, 29.0 kbit/s radio interface rate */ + lchan->tch_mode = GSM48_CMODE_DATA_29k0; + lchan->csd_mode = LCHAN_CSD_M_T_29000; + break; + case RSL_CMODE(RSL_CMOD_SPD_DATA, RSL_CMOD_CSD_T_14k4): + /* 14.4 kbit/s services, 14.5 kbit/s radio interface rate */ lchan->tch_mode = GSM48_CMODE_DATA_14k5; + lchan->csd_mode = LCHAN_CSD_M_T_14400; break; - case RSL_CMOD_SP_NT_12k0: + case RSL_CMODE(RSL_CMOD_SPD_DATA, RSL_CMOD_CSD_T_9k6): + /* 9.6 kbit/s services, 12.0 kbit/s radio interface rate */ lchan->tch_mode = GSM48_CMODE_DATA_12k0; + lchan->csd_mode = LCHAN_CSD_M_T_9600; break; - case RSL_CMOD_SP_NT_6k0: + case RSL_CMODE(RSL_CMOD_SPD_DATA, RSL_CMOD_CSD_T_4k8): + /* 4.8 kbit/s services, 6.0 kbit/s radio interface rate */ lchan->tch_mode = GSM48_CMODE_DATA_6k0; + lchan->csd_mode = LCHAN_CSD_M_T_4800; + break; + case RSL_CMODE(RSL_CMOD_SPD_DATA, RSL_CMOD_CSD_T_2k4): + /* 2.4 kbit/s *and less* services, 3.6 kbit/s radio interface rate */ + lchan->tch_mode = GSM48_CMODE_DATA_3k6; + lchan->csd_mode = LCHAN_CSD_M_T_2400; + break; + case RSL_CMODE(RSL_CMOD_SPD_DATA, RSL_CMOD_CSD_T_1k2): + /* 2.4 kbit/s *and less* services, 3.6 kbit/s radio interface rate */ + lchan->tch_mode = GSM48_CMODE_DATA_3k6; + lchan->csd_mode = LCHAN_CSD_M_T_1200; break; + case RSL_CMODE(RSL_CMOD_SPD_DATA, RSL_CMOD_CSD_T_600): + /* 2.4 kbit/s *and less* services, 3.6 kbit/s radio interface rate */ + lchan->tch_mode = GSM48_CMODE_DATA_3k6; + lchan->csd_mode = LCHAN_CSD_M_T_600; + break; + case RSL_CMODE(RSL_CMOD_SPD_DATA, RSL_CMOD_CSD_T_1200_75): + /* 2.4 kbit/s *and less* services, 3.6 kbit/s radio interface rate */ + lchan->tch_mode = GSM48_CMODE_DATA_3k6; + lchan->csd_mode = LCHAN_CSD_M_T_1200_75; + break; + + default: + LOGPLCHAN(lchan, DRSL, LOGL_ERROR, "Channel Mode IE contains " + "an unknown/unhandled combination of " + "'Speech or data indicator' 0x%02x and " + "'Speech coding algorithm/data rate' 0x%02x\n", + cm->spd_ind, cm->chan_rate); + *cause = RSL_ERR_IE_CONTENT; + return -ENOPROTOOPT; } + +#undef RSL_CMODE + + if (!bts_supports_cm(lchan->ts->trx->bts, cm)) { + LOGPLCHAN(lchan, DRSL, LOGL_ERROR, "Channel type=0x%02x/mode=%s " + "is not supported by the PHY\n", cm->chan_rt, + gsm48_chan_mode_name(lchan->tch_mode)); + *cause = RSL_ERR_SERV_OPT_UNAVAIL; + return -ENOTSUP; + } + + return 0; +} + +/* Handle RSL Channel Identification IE (see section 9.3.5) */ +static int rsl_handle_chan_ident_ie(struct gsm_lchan *lchan, + const struct tlv_parsed *tp, + uint8_t *cause) +{ + const struct gsm_bts_trx_ts *ts = lchan->ts; + const struct gsm_bts *bts = ts->trx->bts; + const struct gsm48_chan_desc *cd; + + if (TLVP_PRES_LEN(tp, RSL_IE_CHAN_IDENT, sizeof(*cd) + 1)) { + /* Channel Description IE comes together with its IEI (see 9.3.5) */ + cd = (const struct gsm48_chan_desc *) (TLVP_VAL(tp, RSL_IE_CHAN_IDENT) + 1); + + /* The PHY may not support using different TSCs */ + if (!osmo_bts_has_feature(bts->features, BTS_FEAT_MULTI_TSC) + && cd->h0.tsc != BTS_TSC(bts)) { + LOGPLCHAN(lchan, DL1C, LOGL_ERROR, "This PHY does not support " + "lchan TSC %u != BSIC-TSC %u, sending NACK\n", + cd->h0.tsc, BTS_TSC(bts)); + *cause = RSL_ERR_SERV_OPT_UNIMPL; + return -ENOTSUP; + } + } + + return 0; +} + +/* Handle Osmocom specific TSC IE */ +static int rsl_handle_osmo_tsc_ie(struct gsm_lchan *lchan, + const struct tlv_parsed *tp, + uint8_t *cause) +{ + /* Osmocom specific IE indicating Training Sequence Code and Set */ + if (TLVP_PRES_LEN(tp, RSL_IE_OSMO_TRAINING_SEQUENCE, 2)) { + const uint8_t *ie = TLVP_VAL(tp, RSL_IE_OSMO_TRAINING_SEQUENCE); + lchan->ts->tsc_set = ie[0] & 0x03; /* Range: 0..3 */ + lchan->ts->tsc_rsl = ie[1] & 0x07; /* Range: 0..7 */ + lchan->ts->tsc_rsl_configured = true; + } else { + lchan->ts->tsc_rsl_configured = false; + lchan->ts->tsc_rsl = 0xff; + lchan->ts->tsc_set = 0; + } + gsm_ts_apply_configured_tsc(lchan->ts); + + return 0; } @@ -150,6 +371,16 @@ static bool chan_nr_is_dchan(uint8_t chan_nr) return true; } +static struct gsm_bts_trx *trx_lookup_by_arfcn(struct llist_head *trx_list, uint16_t arfcn) +{ + struct gsm_bts_trx *trx; + llist_for_each_entry(trx, trx_list, list) { + if (trx->arfcn == arfcn) + return trx; + } + return NULL; +} + static struct gsm_lchan *lchan_lookup(struct gsm_bts_trx *trx, uint8_t chan_nr, const char *log_name) { @@ -269,6 +500,7 @@ static int rsl_tx_error_report(struct gsm_bts_trx *trx, uint8_t cause, const uin /* 8.6.1 sending RF RESOURCE INDICATION */ int rsl_tx_rf_res(struct gsm_bts_trx *trx) { + unsigned int tn, ln; struct msgb *nmsg; LOGP(DRSL, LOGL_INFO, "Tx RSL RF RESource INDication\n"); @@ -276,8 +508,48 @@ int rsl_tx_rf_res(struct gsm_bts_trx *trx) nmsg = rsl_msgb_alloc(sizeof(struct abis_rsl_common_hdr)); if (!nmsg) return -ENOMEM; - // FIXME: add interference levels of TRX - msgb_tlv_put(nmsg, RSL_IE_RESOURCE_INFO, 0, NULL); + + /* Add interference levels for each logical channel */ + uint8_t *len = msgb_tl_put(nmsg, RSL_IE_RESOURCE_INFO); + + for (tn = 0; tn < ARRAY_SIZE(trx->ts); tn++) { + const struct gsm_bts_trx_ts *ts = &trx->ts[tn]; + + if (ts->mo.nm_state.operational != NM_OPSTATE_ENABLED) + continue; + if (ts->mo.nm_state.availability != NM_AVSTATE_OK) + continue; + + for (ln = 0; ln < ARRAY_SIZE(ts->lchan); ln++) { + const struct gsm_lchan *lchan = &ts->lchan[ln]; + + /* No average interference value => no band */ + if (lchan->meas.interf_meas_avg_dbm == 0) + continue; + + /* Only for GSM_LCHAN_{SDCCH,TCH_F,TCH_H,PDTCH} */ + switch (lchan->type) { + case GSM_LCHAN_SDCCH: + case GSM_LCHAN_TCH_F: + case GSM_LCHAN_TCH_H: + /* We're not interested in active CS lchans */ + if (lchan->state == LCHAN_S_ACTIVE) + continue; + break; + case GSM_LCHAN_PDTCH: + break; + default: + continue; + } + + msgb_v_put(nmsg, gsm_lchan2chan_nr_rsl(lchan)); + msgb_v_put(nmsg, (lchan->meas.interf_band & 0x07) << 5); + } + } + + /* Calculate length of the V part */ + *len = msgb_l3len(nmsg) - 2; + rsl_trx_push_hdr(nmsg, RSL_MT_RF_RES_IND); nmsg->trx = trx; @@ -285,7 +557,7 @@ int rsl_tx_rf_res(struct gsm_bts_trx *trx) } /* - * common channel releated messages + * common channel related messages */ /* 8.5.1 BCCH INFOrmation is received */ @@ -298,7 +570,13 @@ static int rsl_rx_bcch_info(struct gsm_bts_trx *trx, struct msgb *msg) enum osmo_sysinfo_type osmo_si; struct gsm48_system_information_type_2quater *si2q; struct bitvec bv; - rsl_tlv_parse(&tp, msgb_l3(msg), msgb_l3len(msg)); + const uint8_t *si_buf; + uint8_t prev_bs_ag_blks_res = 0xff; /* 0xff = unknown */ + + if (rsl_tlv_parse(&tp, msgb_l3(msg), msgb_l3len(msg)) < 0) { + LOGPTRX(trx, DRSL, LOGL_ERROR, "%s(): rsl_tlv_parse() failed\n", __func__); + return rsl_tx_error_report(trx, RSL_ERR_PROTO, &cch->chan_nr, NULL, msg); + } /* 9.3.30 System Info Type */ if (!TLVP_PRESENT(&tp, RSL_IE_SYSINFO_TYPE)) @@ -325,7 +603,8 @@ static int rsl_rx_bcch_info(struct gsm_bts_trx *trx, struct msgb *msg) LOGP(DRSL, LOGL_INFO, " Rx RSL BCCH INFO (SI%s, %u bytes)\n", get_value_string(osmo_sitype_strs, osmo_si), len); - if (SYSINFO_TYPE_2quater == osmo_si) { + switch (osmo_si) { + case SYSINFO_TYPE_2quater: si2q = (struct gsm48_system_information_type_2quater *) TLVP_VAL(&tp, RSL_IE_FULL_BCCH_INFO); bv.data = si2q->rest_octets; bv.data_len = GSM_MACBLOCK_LEN; @@ -353,32 +632,68 @@ static int rsl_rx_bcch_info(struct gsm_bts_trx *trx, struct msgb *msg) memset(GSM_BTS_SI2Q(bts, bts->si2q_index), GSM_MACBLOCK_PADDING, sizeof(sysinfo_buf_t)); memcpy(GSM_BTS_SI2Q(bts, bts->si2q_index), TLVP_VAL(&tp, RSL_IE_FULL_BCCH_INFO), len); - } else { + break; + case SYSINFO_TYPE_3: + /* Keep previous BS_AG_BLKS_RES, used below */ + if (GSM_BTS_HAS_SI(bts, SYSINFO_TYPE_3)) { + const struct gsm48_system_information_type_3 *si3 = GSM_BTS_SI(bts, SYSINFO_TYPE_3); + prev_bs_ag_blks_res = si3->control_channel_desc.bs_ag_blks_res; + } + /* fall-through */ + default: memset(bts->si_buf[osmo_si], GSM_MACBLOCK_PADDING, sizeof(sysinfo_buf_t)); memcpy(bts->si_buf[osmo_si], TLVP_VAL(&tp, RSL_IE_FULL_BCCH_INFO), len); } bts->si_valid |= (1 << osmo_si); - if (SYSINFO_TYPE_3 == osmo_si) { - if (trx->nr == 0 && num_agch(trx, "RSL") != 1) { - lchan_deactivate(&trx->bts->c0->ts[0].lchan[CCCH_LCHAN]); - /* will be reactivated by sapi_deactivate_cb() */ + switch (osmo_si) { + case SYSINFO_TYPE_3: + /* If CCCH config on TS0 changed, reactivate the chan with the new config: */ + if (trx->nr == 0 && trx->bts->c0->ts[0].lchan[CCCH_LCHAN].state != LCHAN_S_NONE && + num_agch(trx, "RSL") != prev_bs_ag_blks_res) { trx->bts->c0->ts[0].lchan[CCCH_LCHAN].rel_act_kind = LCHAN_REL_ACT_REACT; + lchan_deactivate(&trx->bts->c0->ts[0].lchan[CCCH_LCHAN]); + /* will be reactivated by (see OS#1575): + * - bts-trx: lchan_deactivate() + * - sysmo,lc15,oc2g: lchan_deactivate()....[async]...sapi_deactivate_cb() */ } /* decode original SI3 Rest Octets as sent by BSC */ - const uint8_t *si3_ro_buf = (uint8_t *) GSM_BTS_SI(bts, osmo_si); - si3_ro_buf += offsetof(struct gsm48_system_information_type_3, rest_octets); - osmo_gsm48_rest_octets_si3_decode(&bts->si3_ro_decoded, si3_ro_buf); + si_buf = (const uint8_t *) GSM_BTS_SI(bts, osmo_si); + si_buf += offsetof(struct gsm48_system_information_type_3, rest_octets); + osmo_gsm48_rest_octets_si3_decode(&bts->si3_ro_decoded, si_buf); /* patch out GPRS indicator from binary if PCU is not connected; will be enabled * after PCU connects */ regenerate_si3_restoctets(bts); + pcu_tx_si(trx->bts, SYSINFO_TYPE_3, true); + break; + case SYSINFO_TYPE_4: + /* decode original SI4 Rest Octets as sent by BSC */ + si_buf = (const uint8_t *) GSM_BTS_SI(bts, osmo_si); + int si4_ro_offset = get_si4_ro_offset(si_buf); + if (si4_ro_offset > 0) { + osmo_gsm48_rest_octets_si4_decode(&bts->si4_ro_decoded, + si_buf + si4_ro_offset, + GSM_MACBLOCK_LEN - si4_ro_offset); + /* patch out GPRS indicator from binary if PCU is not connected; will be + * enabled after PCU connects */ + regenerate_si4_restoctets(bts); + } + break; + case SYSINFO_TYPE_1: + /* Get the position of the NCH, if enabled. */ + trx->bts->asci.pos_nch = pos_nch(trx, "BCCH INFO"); + pcu_tx_si(trx->bts, SYSINFO_TYPE_1, true); + break; + case SYSINFO_TYPE_2: + case SYSINFO_TYPE_13: + pcu_tx_si(trx->bts, osmo_si, true); + break; + default: + break; } - if (SYSINFO_TYPE_13 == osmo_si) - pcu_tx_si13(trx->bts, true); - } else if (TLVP_PRESENT(&tp, RSL_IE_L3_INFO)) { uint16_t len = TLVP_LEN(&tp, RSL_IE_L3_INFO); if (len > sizeof(sysinfo_buf_t)) @@ -393,10 +708,20 @@ static int rsl_rx_bcch_info(struct gsm_bts_trx *trx, struct msgb *msg) bts->si_valid &= ~(1 << osmo_si); LOGP(DRSL, LOGL_INFO, " RX RSL Disabling BCCH INFO (SI%s)\n", get_value_string(osmo_sitype_strs, osmo_si)); - if (SYSINFO_TYPE_13 == osmo_si) - pcu_tx_si13(trx->bts, false); - if (SYSINFO_TYPE_3 == osmo_si) + switch (osmo_si) { + case SYSINFO_TYPE_13: + pcu_tx_si(trx->bts, SYSINFO_TYPE_13, false); + break; + case SYSINFO_TYPE_3: memset(&bts->si3_ro_decoded, 0, sizeof(bts->si3_ro_decoded)); + pcu_tx_si(trx->bts, SYSINFO_TYPE_3, false); + break; + case SYSINFO_TYPE_1: + pcu_tx_si(trx->bts, SYSINFO_TYPE_1, false); + break; + default: + break; + } } osmo_signal_dispatch(SS_GLOBAL, S_NEW_SYSINFO, bts); @@ -465,7 +790,10 @@ static int rsl_rx_paging_cmd(struct gsm_bts_trx *trx, struct msgb *msg) const uint8_t *identity_lv; int rc; - rsl_tlv_parse(&tp, msgb_l3(msg), msgb_l3len(msg)); + if (rsl_tlv_parse(&tp, msgb_l3(msg), msgb_l3len(msg)) < 0) { + LOGPTRX(trx, DRSL, LOGL_ERROR, "%s(): rsl_tlv_parse() failed\n", __func__); + return rsl_tx_error_report(trx, RSL_ERR_PROTO, &cch->chan_nr, NULL, msg); + } if (!TLVP_PRESENT(&tp, RSL_IE_PAGING_GROUP) || !TLVP_PRESENT(&tp, RSL_IE_MS_IDENTITY)) @@ -500,7 +828,10 @@ static int rsl_rx_sms_bcast_cmd(struct gsm_bts_trx *trx, struct msgb *msg) bool extended_cbch = false; int rc; - rsl_tlv_parse(&tp, msgb_l3(msg), msgb_l3len(msg)); + if (rsl_tlv_parse(&tp, msgb_l3(msg), msgb_l3len(msg)) < 0) { + LOGPTRX(trx, DRSL, LOGL_ERROR, "%s(): rsl_tlv_parse() failed\n", __func__); + return rsl_tx_error_report(trx, RSL_ERR_PROTO, &cch->chan_nr, NULL, msg); + } if (!TLVP_PRESENT(&tp, RSL_IE_CB_CMD_TYPE) || !TLVP_PRESENT(&tp, RSL_IE_SMSCB_MSG)) @@ -522,6 +853,100 @@ static int rsl_rx_sms_bcast_cmd(struct gsm_bts_trx *trx, struct msgb *msg) return 0; } +/* Broadcast notification about new VGCS/VBS call on every dedicated channel. + * This is required for MSs that are currently in dedicated mode that there is an ongoing call and on which channel + * the call is active. Most MSs in dedicated mode may not be able to receive the NCH otherwise. + * MSs that do not support ASCI will ignore it, as it is an unsupported message for them. + */ +static int asci_broadcast_facch(struct gsm_bts *bts, const uint8_t *group_call_ref, const uint8_t *chan_desc, + uint8_t chan_desc_len, unsigned int count) +{ + uint8_t notif[23]; + struct msgb *msg, *cmsg; + struct gsm_bts_trx *trx; + struct gsm_lchan *lchan; + unsigned int tn, ln, n; + int rc; + + rc = bts_asci_notify_facch_gen_msg(bts, notif, group_call_ref, chan_desc, chan_desc_len); + if (rc < 0) + return rc; + + llist_for_each_entry(trx, &bts->trx_list, list) { + for (tn = 0; tn < ARRAY_SIZE(trx->ts); tn++) { + for (ln = 0; ln < ARRAY_SIZE(trx->ts[tn].lchan); ln++) { + lchan = &trx->ts[tn].lchan[ln]; + if (!lchan_is_dcch(lchan)) + continue; + if (lchan->state != LCHAN_S_ACTIVE) + continue; + msg = rsl_rll_simple(RSL_MT_UNIT_DATA_REQ, gsm_lchan2chan_nr(lchan), 0x00, 0); + msg->l3h = msg->tail; /* emulate rsl_rx_rll() behaviour */ + msgb_tl16v_put(msg, RSL_IE_L3_INFO, sizeof(notif), (uint8_t *) ¬if); + for (n = 1; n < count; n++) { + cmsg = msgb_copy(msg, "FACCH copy"); + lapdm_rslms_recvmsg(cmsg, &lchan->lapdm_ch); + } + lapdm_rslms_recvmsg(msg, &lchan->lapdm_ch); + } + } + } + + return 0; +} + +/* Number of times to broadcast ASCI call on every dedicated channel. */ +#define ASCI_BROADCAST_NUM 3 + +/* 8.5.10 NOTIFICATION COMMAND */ +static int rsl_rx_notification_cmd(struct gsm_bts_trx *trx, struct msgb *msg) +{ + struct abis_rsl_cchan_hdr *cch = msgb_l2(msg); + struct tlv_parsed tp; + uint8_t command_indicator; + int rc; + + if (rsl_tlv_parse(&tp, msgb_l3(msg), msgb_l3len(msg)) < 0) { + LOGPTRX(trx, DRSL, LOGL_ERROR, "%s(): rsl_tlv_parse() failed\n", __func__); + return rsl_tx_error_report(trx, RSL_ERR_PROTO, &cch->chan_nr, NULL, msg); + } + + if (cch->chan_nr != RSL_CHAN_PCH_AGCH) { + LOGPTRX(trx, DRSL, LOGL_ERROR, "%s(): chan nr is not Downlink CCCH\n", __func__); + return rsl_tx_error_report(trx, RSL_ERR_IE_CONTENT, &cch->chan_nr, NULL, msg); + } + + if (!TLVP_PRES_LEN(&tp, RSL_IE_CMD_INDICATOR, 1)) + return rsl_tx_error_report(trx, RSL_ERR_MAND_IE_ERROR, &cch->chan_nr, NULL, msg); + command_indicator = *TLVP_VAL(&tp, RSL_IE_CMD_INDICATOR); + + switch (command_indicator) { + case RSL_CMD_INDICATOR_START: + /* we need at least a Group Call Reference to start notification */ + if (!TLVP_PRES_LEN(&tp, RSL_IE_GROUP_CALL_REF, 5)) + return rsl_tx_error_report(trx, RSL_ERR_OPT_IE_ERROR, &cch->chan_nr, NULL, msg); + rc = bts_asci_notification_add(trx->bts, TLVP_VAL(&tp, RSL_IE_GROUP_CALL_REF), + TLVP_VAL(&tp, RSL_IE_CHAN_DESC), TLVP_LEN(&tp, RSL_IE_CHAN_DESC), + (struct rsl_ie_nch_drx_info *) TLVP_VAL(&tp, RSL_IE_NCH_DRX_INFO)); + /* Broadcast to FACCH */ + asci_broadcast_facch(trx->bts, TLVP_VAL(&tp, RSL_IE_GROUP_CALL_REF), TLVP_VAL(&tp, RSL_IE_CHAN_DESC), + TLVP_LEN(&tp, RSL_IE_CHAN_DESC), ASCI_BROADCAST_NUM); + break; + case RSL_CMD_INDICATOR_STOP: + if (!TLVP_PRES_LEN(&tp, RSL_IE_GROUP_CALL_REF, 5)) { + /* interpret this as stopping of all notification */ + rc = bts_asci_notification_reset(trx->bts); + } else { + rc = bts_asci_notification_del(trx->bts, TLVP_VAL(&tp, RSL_IE_GROUP_CALL_REF)); + } + break; + default: + return rsl_tx_error_report(trx, RSL_ERR_IE_CONTENT, &cch->chan_nr, NULL, msg); + } + + return rc; +} + /* OSMO_ETWS_CMD - proprietary extension as TS 48.058 has no standardized way to do this :( */ static int rsl_rx_osmo_etws_cmd(struct gsm_bts_trx *trx, struct msgb *msg) { @@ -529,7 +954,10 @@ static int rsl_rx_osmo_etws_cmd(struct gsm_bts_trx *trx, struct msgb *msg) struct gsm_bts *bts = trx->bts; struct tlv_parsed tp; - rsl_tlv_parse(&tp, msgb_l3(msg), msgb_l3len(msg)); + if (rsl_tlv_parse(&tp, msgb_l3(msg), msgb_l3len(msg)) < 0) { + LOGPTRX(trx, DRSL, LOGL_ERROR, "%s(): rsl_tlv_parse() failed\n", __func__); + return rsl_tx_error_report(trx, RSL_ERR_PROTO, &cch->chan_nr, NULL, msg); + } if (!TLVP_PRESENT(&tp, RSL_IE_SMSCB_MSG)) return rsl_tx_error_report(trx, RSL_ERR_MAND_IE_ERROR, &cch->chan_nr, NULL, msg); @@ -571,10 +999,19 @@ static int rsl_rx_osmo_etws_cmd(struct gsm_bts_trx *trx, struct msgb *msg) * \param[out] buf Output buffer, must be caller-allocated and hold at least len + 2 or sizeof(sysinfo_buf_t) bytes * \param[out] valid pointer to bit-mask of 'valid' System information types * \param[in] current input data (L3 without L2/L1 header) - * \param[in] osmo_si Sytstem Infrormation Type (SYSINFO_TYPE_*) + * \param[in] osmo_si Sytstem Information Type (SYSINFO_TYPE_*) * \param[in] len length of \a current in octets */ static inline void lapdm_ui_prefix(uint8_t *buf, uint32_t *valid, const uint8_t *current, uint8_t osmo_si, uint16_t len) { + /* Special case for short header SI. Do not pre-fix the two-byte UI header. */ + switch (osmo_si) { + case SYSINFO_TYPE_10: + (*valid) |= (1 << osmo_si); + memset(buf, GSM_MACBLOCK_PADDING, sizeof(sysinfo_buf_t)); + memcpy(buf, current, len); + return; + } + /* We have to pre-fix with the two-byte LAPDM UI header */ if (len > sizeof(sysinfo_buf_t) - 2) { LOGP(DRSL, LOGL_ERROR, "Truncating received SI%s (%u -> %zu) to prepend LAPDM UI header (2 bytes)\n", @@ -593,7 +1030,7 @@ static inline void lapdm_ui_prefix(uint8_t *buf, uint32_t *valid, const uint8_t /*! Prefix a given SACCH frame with a L2/LAPDm UI header and store it in given BTS SACCH buffer * \param[out] bts BTS in whose System Information State we shall store * \param[in] current input data (L3 without L2/L1 header) - * \param[in] osmo_si Sytstem Infrormation Type (SYSINFO_TYPE_*) + * \param[in] osmo_si Sytstem Information Type (SYSINFO_TYPE_*) * \param[in] len length of \a current in octets */ static inline void lapdm_ui_prefix_bts(struct gsm_bts *bts, const uint8_t *current, uint8_t osmo_si, uint16_t len) { @@ -603,7 +1040,7 @@ static inline void lapdm_ui_prefix_bts(struct gsm_bts *bts, const uint8_t *curre /*! Prefix a given SACCH frame with a L2/LAPDm UI header and store it in given lchan SACCH buffer * \param[out] lchan Logical Channel in whose System Information State we shall store * \param[in] current input data (L3 without L2/L1 header) - * \param[in] osmo_si Sytstem Infrormation Type (SYSINFO_TYPE_*) + * \param[in] osmo_si Sytstem Information Type (SYSINFO_TYPE_*) * \param[in] len length of \a current in octets */ static inline void lapdm_ui_prefix_lchan(struct gsm_lchan *lchan, const uint8_t *current, uint8_t osmo_si, uint16_t len) { @@ -618,7 +1055,10 @@ static int rsl_rx_sacch_fill(struct gsm_bts_trx *trx, struct msgb *msg) uint8_t rsl_si; enum osmo_sysinfo_type osmo_si; - rsl_tlv_parse(&tp, msgb_l3(msg), msgb_l3len(msg)); + if (rsl_tlv_parse(&tp, msgb_l3(msg), msgb_l3len(msg)) < 0) { + LOGPTRX(trx, DRSL, LOGL_ERROR, "%s(): rsl_tlv_parse() failed\n", __func__); + return rsl_tx_error_report(trx, RSL_ERR_PROTO, NULL, NULL, msg); + } /* 9.3.30 System Info Type */ if (!TLVP_PRESENT(&tp, RSL_IE_SYSINFO_TYPE)) @@ -682,13 +1122,256 @@ static int rsl_rx_sacch_fill(struct gsm_bts_trx *trx, struct msgb *msg) } +/* Parser for ip.access specific MS/BS Power parameters */ +static int parse_power_ctrl_params(struct gsm_power_ctrl_params *params, + const uint8_t *data, size_t data_len) +{ + const struct tlv_p_entry *ie; + struct tlv_parsed tp[3]; + unsigned int i; + int rc; + + /* There can be multiple RSL_IPAC_EIE_MEAS_AVG_CFG, so we use tlv_parse2() */ + rc = tlv_parse2(&tp[0], ARRAY_SIZE(tp), &rsl_ipac_eie_tlvdef, + data, data_len, 0, 0); + if (rc < 0) + return rc; + + /* Either of RSL_IPAC_EIE_{BS,MS}_PWR_CTL must be present */ + if (TLVP_PRESENT(&tp[0], RSL_IPAC_EIE_BS_PWR_CTL) && + TLVP_PRESENT(&tp[0], RSL_IPAC_EIE_MS_PWR_CTL)) + return -EINVAL; + + /* (TV) Thresholds: {L,U}_RXLEV_XX_P and {L,U}_RXQUAL_XX_P */ + if ((ie = TLVP_GET(&tp[0], RSL_IPAC_EIE_BS_PWR_CTL)) != NULL || + (ie = TLVP_GET(&tp[0], RSL_IPAC_EIE_MS_PWR_CTL)) != NULL) { + const struct ipac_preproc_pc_thresh *thresh; + + thresh = (const struct ipac_preproc_pc_thresh *) ie->val; + + params->rxlev_meas.lower_thresh = thresh->l_rxlev; + params->rxlev_meas.upper_thresh = thresh->u_rxlev; + + params->rxqual_meas.lower_thresh = thresh->l_rxqual; + params->rxqual_meas.upper_thresh = thresh->u_rxqual; + } + + /* Osmocom extension, C/I related thresholds: */ + if (TLVP_PRES_LEN(&tp[0], RSL_IPAC_EIE_OSMO_MS_PWR_CTL, sizeof(struct osmo_preproc_pc_thresh))) { + const struct osmo_preproc_pc_thresh *osmo_thresh; + ie = TLVP_GET(&tp[0], RSL_IPAC_EIE_OSMO_MS_PWR_CTL); + osmo_thresh = (const struct osmo_preproc_pc_thresh *) ie->val; + params->ci_fr_meas.lower_thresh = osmo_thresh->l_ci_fr; + params->ci_fr_meas.upper_thresh = osmo_thresh->u_ci_fr; + + params->ci_hr_meas.lower_thresh = osmo_thresh->l_ci_hr; + params->ci_hr_meas.upper_thresh = osmo_thresh->u_ci_hr; + + params->ci_amr_fr_meas.lower_thresh = osmo_thresh->l_ci_amr_fr; + params->ci_amr_fr_meas.upper_thresh = osmo_thresh->u_ci_amr_fr; + + params->ci_amr_hr_meas.lower_thresh = osmo_thresh->l_ci_amr_hr; + params->ci_amr_hr_meas.upper_thresh = osmo_thresh->u_ci_amr_hr; + + params->ci_sdcch_meas.lower_thresh = osmo_thresh->l_ci_sdcch; + params->ci_sdcch_meas.upper_thresh = osmo_thresh->u_ci_sdcch; + + params->ci_gprs_meas.lower_thresh = osmo_thresh->l_ci_gprs; + params->ci_gprs_meas.upper_thresh = osmo_thresh->u_ci_gprs; + } + + /* (TV) PC Threshold Comparators */ + if ((ie = TLVP_GET(&tp[0], RSL_IPAC_EIE_PC_THRESH_COMP)) != NULL) { + const struct ipac_preproc_pc_comp *thresh_comp; + + thresh_comp = (const struct ipac_preproc_pc_comp *) ie->val; + + /* RxLev: P1, N1, P2, N2 (see 3GPP TS 45.008, A.3.2.1, a & b) */ + params->rxlev_meas.lower_cmp_p = thresh_comp->p1; + params->rxlev_meas.lower_cmp_n = thresh_comp->n1; + params->rxlev_meas.upper_cmp_p = thresh_comp->p2; + params->rxlev_meas.upper_cmp_n = thresh_comp->n2; + + /* RxQual: P3, N3, P4, N4 (see 3GPP TS 45.008, A.3.2.1, c & d) */ + params->rxqual_meas.lower_cmp_p = thresh_comp->p3; + params->rxqual_meas.lower_cmp_n = thresh_comp->n3; + params->rxqual_meas.upper_cmp_p = thresh_comp->p4; + params->rxqual_meas.upper_cmp_n = thresh_comp->n4; + + /* Minimum interval between power level changes (P_Con_INTERVAL) */ + params->ctrl_interval = thresh_comp->pc_interval; + + /* Power increase / reduce step size: POWER_{INC,RED}_STEP_SIZE */ + params->inc_step_size_db = thresh_comp->inc_step_size; + params->red_step_size_db = thresh_comp->red_step_size; + } + + /* Osmocom extension, C/I related thresholds: */ + if (TLVP_PRES_LEN(&tp[0], RSL_IPAC_EIE_OSMO_PC_THRESH_COMP, sizeof(struct osmo_preproc_pc_thresh))) { + const struct osmo_preproc_pc_comp *osmo_thresh_comp; + ie = TLVP_GET(&tp[0], RSL_IPAC_EIE_OSMO_PC_THRESH_COMP); + osmo_thresh_comp = (const struct osmo_preproc_pc_comp *) ie->val; + #define SET_PREPROC_PC(PARAMS, FROM, TYPE) \ + (PARAMS)->TYPE##_meas.lower_cmp_p = (FROM)->TYPE.lower_p; \ + (PARAMS)->TYPE##_meas.lower_cmp_n = (FROM)->TYPE.lower_n; \ + (PARAMS)->TYPE##_meas.upper_cmp_p = (FROM)->TYPE.upper_p; \ + (PARAMS)->TYPE##_meas.upper_cmp_n = (FROM)->TYPE.upper_n + SET_PREPROC_PC(params, osmo_thresh_comp, ci_fr); + SET_PREPROC_PC(params, osmo_thresh_comp, ci_hr); + SET_PREPROC_PC(params, osmo_thresh_comp, ci_amr_fr); + SET_PREPROC_PC(params, osmo_thresh_comp, ci_amr_hr); + SET_PREPROC_PC(params, osmo_thresh_comp, ci_sdcch); + SET_PREPROC_PC(params, osmo_thresh_comp, ci_gprs); + #undef SET_PREPROC_PC + } + + /* (TLV) Measurement Averaging parameters for RxLev/RxQual */ + for (i = 0; i < ARRAY_SIZE(tp); i++) { + const struct ipac_preproc_ave_cfg *ave_cfg; + struct gsm_power_ctrl_meas_params *mp; + + ie = TLVP_GET(&tp[i], RSL_IPAC_EIE_MEAS_AVG_CFG); + if (ie == NULL) + break; + + if (ie->len < sizeof(*ave_cfg)) + return -EINVAL; + + ave_cfg = (const struct ipac_preproc_ave_cfg *) ie->val; + + switch (ave_cfg->param_id) { + case IPAC_RXQUAL_AVE: + mp = ¶ms->rxqual_meas; + break; + case IPAC_RXLEV_AVE: + mp = ¶ms->rxlev_meas; + break; + default: + /* Skip unknown parameters */ + continue; + } + + mp->h_reqave = ave_cfg->h_reqave; + mp->h_reqt = ave_cfg->h_reqt; + + mp->algo = ave_cfg->ave_method + 1; + switch (ave_cfg->ave_method) { + case IPAC_OSMO_EWMA_AVE: + if (ie->len > sizeof(*ave_cfg)) + mp->ewma.alpha = ave_cfg->params[0]; + break; + + /* FIXME: not implemented */ + case IPAC_UNWEIGHTED_AVE: + case IPAC_WEIGHTED_AVE: + case IPAC_MEDIAN_AVE: + break; + } + } + + /* (TLV) Measurement Averaging parameters for C/I (Osmocom extension)*/ + if (TLVP_PRES_LEN(&tp[0], RSL_IPAC_EIE_OSMO_MEAS_AVG_CFG, sizeof(struct osmo_preproc_ave_cfg))) { + ie = TLVP_GET(&tp[0], RSL_IPAC_EIE_OSMO_MEAS_AVG_CFG); + const struct osmo_preproc_ave_cfg *cfg = (const struct osmo_preproc_ave_cfg *) ie->val; + unsigned params_offset = 0; + #define SET_AVE_CFG(PARAMS, FROM, TYPE, PARAM_OFFSET) do {\ + if ((FROM)->TYPE.ave_enabled) { \ + (PARAMS)->TYPE##_meas.h_reqave = (FROM)->TYPE.h_reqave; \ + (PARAMS)->TYPE##_meas.h_reqt = (FROM)->TYPE.h_reqt; \ + (PARAMS)->TYPE##_meas.algo = (FROM)->TYPE.ave_method + 1; \ + switch ((FROM)->TYPE.ave_method) { \ + case IPAC_OSMO_EWMA_AVE: \ + if (ie->len > sizeof(*cfg) + (PARAM_OFFSET)) { \ + (PARAMS)->TYPE##_meas.ewma.alpha = (FROM)->params[PARAM_OFFSET]; \ + (PARAM_OFFSET)++; \ + } \ + break; \ + /* FIXME: not implemented */ \ + case IPAC_UNWEIGHTED_AVE: \ + case IPAC_WEIGHTED_AVE: \ + case IPAC_MEDIAN_AVE: \ + break; \ + } \ + } else { \ + (PARAMS)->TYPE##_meas.algo = GSM_PWR_CTRL_MEAS_AVG_ALGO_NONE; \ + } \ + } while(0) + SET_AVE_CFG(params, cfg, ci_fr, params_offset); + SET_AVE_CFG(params, cfg, ci_hr, params_offset); + SET_AVE_CFG(params, cfg, ci_amr_fr, params_offset); + SET_AVE_CFG(params, cfg, ci_amr_hr, params_offset); + SET_AVE_CFG(params, cfg, ci_sdcch, params_offset); + SET_AVE_CFG(params, cfg, ci_gprs, params_offset); + #undef SET_AVE_CFG + } + + return 0; +} + +/* ip.access specific Measurement Pre-processing Defaults for MS/BS Power control */ +static int rsl_rx_meas_preproc_dft(struct gsm_bts_trx *trx, struct msgb *msg) +{ + const struct gsm_bts *bts = trx->bts; + struct gsm_power_ctrl_params *params; + const struct tlv_p_entry *ie; + struct tlv_parsed tp; + + LOGPTRX(trx, DRSL, LOGL_INFO, "Rx Measurement Pre-processing Defaults\n"); + + if (rsl_tlv_parse(&tp, msgb_l3(msg), msgb_l3len(msg)) < 0) { + LOGPTRX(trx, DRSL, LOGL_ERROR, "%s(): rsl_tlv_parse() failed\n", __func__); + return rsl_tx_error_report(trx, RSL_ERR_PROTO, NULL, NULL, msg); + } + + /* TLV (O) BS Power Parameters IE */ + if ((ie = TLVP_GET(&tp, RSL_IE_BS_POWER_PARAM)) != NULL) { + /* Allocate a new chunk and initialize with default values */ + params = talloc(trx, struct gsm_power_ctrl_params); + power_ctrl_params_def_reset(params, true); + + if (ie->len && parse_power_ctrl_params(params, ie->val, ie->len) == 0) { + /* Initially it points to the global defaults */ + if (trx->bs_dpc_params != &bts->bs_dpc_params) + talloc_free(trx->bs_dpc_params); + trx->bs_dpc_params = params; + } else { + LOGPTRX(trx, DRSL, LOGL_ERROR, "Failed to parse BS Power Parameters IE\n"); + rsl_tx_error_report(trx, RSL_ERR_IE_CONTENT, NULL, NULL, msg); + talloc_free(params); + } + } + + /* TLV (O) MS Power Parameters IE */ + if ((ie = TLVP_GET(&tp, RSL_IE_MS_POWER_PARAM)) != NULL) { + /* Allocate a new chunk and initialize with default values */ + params = talloc(trx, struct gsm_power_ctrl_params); + power_ctrl_params_def_reset(params, false); + + if (ie->len && parse_power_ctrl_params(params, ie->val, ie->len) == 0) { + /* Initially it points to the global defaults */ + if (trx->ms_dpc_params != &bts->ms_dpc_params) + talloc_free(trx->ms_dpc_params); + trx->ms_dpc_params = params; + } else { + LOGPTRX(trx, DRSL, LOGL_ERROR, "Failed to parse MS Power Parameters IE\n"); + rsl_tx_error_report(trx, RSL_ERR_IE_CONTENT, NULL, NULL, msg); + talloc_free(params); + } + } + + return 0; +} + /* 8.5.6 IMMEDIATE ASSIGN COMMAND is received */ static int rsl_rx_imm_ass(struct gsm_bts_trx *trx, struct msgb *msg) { struct abis_rsl_cchan_hdr *cch = msgb_l2(msg); struct tlv_parsed tp; - rsl_tlv_parse(&tp, msgb_l3(msg), msgb_l3len(msg)); + if (rsl_tlv_parse(&tp, msgb_l3(msg), msgb_l3len(msg)) < 0) { + LOGPTRX(trx, DRSL, LOGL_ERROR, "%s(): rsl_tlv_parse() failed\n", __func__); + return rsl_tx_error_report(trx, RSL_ERR_PROTO, &cch->chan_nr, NULL, msg); + } if (!TLVP_PRESENT(&tp, RSL_IE_FULL_IMM_ASS_INFO)) return rsl_tx_error_report(trx, RSL_ERR_MAND_IE_ERROR, &cch->chan_nr, NULL, msg); @@ -701,6 +1384,45 @@ static int rsl_rx_imm_ass(struct gsm_bts_trx *trx, struct msgb *msg) msg->l2h = NULL; msg->len = TLVP_LEN(&tp, RSL_IE_FULL_IMM_ASS_INFO); + /* Early Immediate Assignment: when there is a lot of latency on Abis, the Abis roundtrip of Chan Activ -> Chan + * Activ ACK -> Immediate Assignment may take so long that each MS sends a second RACH for Chan Rqd, reserving + * two SDCCH for each request but using only one. To help with that, the Early IA feature in osmo-bsc sends the + * Immediate Assignment without waiting for the Channel Activation ACK. This may then be too early, and the MS + * may not be able to establish a channel. So to help with Early IA, look up whether the target lchan is already + * active. If not, then hold back the RR Immediate Assignment message, and send it once L1 has confirmed that + * the channel is active. Hence we still wait for the activation, but don't need the Abis roundtrip of Activ ACK + * -> Immediate Assignment via the BSC. + * If anything is wrong with the sizes or the lchan lookup, behave normally, i.e. do not do the RR IA caching, + * but just send the RR message to the MS as-is. + * 'trx' here is the TRX of the BCCH channel. To find the correct TRX for the IMM ASS target, we need to look up + * the ARFCN that is contained in the IMM ASS message. When frequency hopping is enabled, there will not be an + * ARFCN, so we cannot support early-IA with frequency hopping enabled. */ + if (msg->len >= sizeof(struct gsm48_imm_ass)) { + struct gsm48_imm_ass *rr_ia = (void*)msg->data; + if (rr_ia->chan_desc.h0.h == 0) { + /* hopping is disabled. */ + struct gsm_bts_trx *ia_target_trx; + uint16_t arfcn; + arfcn = (rr_ia->chan_desc.h0.arfcn_high << 8) + rr_ia->chan_desc.h0.arfcn_low; + + ia_target_trx = trx_lookup_by_arfcn(&trx->bts->trx_list, arfcn); + if (ia_target_trx) { + /* found the ARFCN's trx */ + struct gsm_lchan *ia_target_lchan; + ia_target_lchan = lchan_lookup(ia_target_trx, rr_ia->chan_desc.chan_nr, "Early IA check: "); + if (ia_target_lchan && ia_target_lchan->state != LCHAN_S_ACTIVE) { + /* Target lchan is not yet active. Cache the IA. + * If a previous IA is still lingering, free it. */ + msgb_free(ia_target_lchan->early_rr_ia); + ia_target_lchan->early_rr_ia = msg; + + /* return 1 means: don't msgb_free() the msg */ + return 1; + } + } + } + } + /* put into the AGCH queue of the BTS */ if (bts_agch_enqueue(trx->bts, msg) < 0) { /* if there is no space in the queue: send DELETE IND */ @@ -738,7 +1460,7 @@ static int tx_rf_rel_ack(struct gsm_lchan *lchan, uint8_t chan_nr) /* 8.4.19 sending RF CHANnel RELease ACKnowledge */ int rsl_tx_rf_rel_ack(struct gsm_lchan *lchan) { - uint8_t chan_nr = gsm_lchan2chan_nr(lchan); + uint8_t chan_nr = gsm_lchan2chan_nr_rsl(lchan); bool send_rel_ack; switch (lchan->rel_act_kind) { @@ -748,26 +1470,40 @@ int rsl_tx_rf_rel_ack(struct gsm_lchan *lchan) case LCHAN_REL_ACT_PCU: switch (lchan->ts->pchan) { - case GSM_PCHAN_TCH_F_TCH_H_PDCH: + case GSM_PCHAN_OSMO_DYN: if (lchan->ts->dyn.pchan_is != GSM_PCHAN_PDCH) { LOGP(DRSL, LOGL_ERROR, "%s (ss=%d) PDCH release: not in PDCH mode\n", gsm_ts_and_pchan_name(lchan->ts), lchan->nr); /* well, what to do about it ... carry on and hope it's fine. */ } - /* remember the fact that the TS is now released */ - lchan->ts->dyn.pchan_is = GSM_PCHAN_NONE; - /* Continue to ack the release below. (This is a non-standard rel ack invented - * specifically for GSM_PCHAN_TCH_F_TCH_H_PDCH). */ - send_rel_ack = true; + if (lchan->ts->dyn.pchan_want != GSM_PCHAN_PDCH) { + /* Continue to ack the release below. (This is a non-standard rel ack invented + * specifically for GSM_PCHAN_OSMO_DYN). */ + /* remember the fact that the TS is now released */ + lchan->ts->dyn.pchan_is = GSM_PCHAN_NONE; + send_rel_ack = true; + } else { + /* Administrteively locked TRX, no need to + inform BSC. Keep pchan_is for when we are + unlocked again, since lower layers are stil + lconfigured for PDCH but we simply annonced + non-availability to PCU */ + send_rel_ack = false; + } break; case GSM_PCHAN_TCH_F_PDCH: /* GSM_PCHAN_TCH_F_PDCH, does not require a rel ack. The caller * l1sap_info_rel_cnf() will continue with bts_model_ts_disconnect(). */ send_rel_ack = false; break; + case GSM_PCHAN_PDCH: + /* Release was instructed by the BTS, for instance because the TRX is + * administrateively Locked */ + send_rel_ack = false; + break; default: LOGPLCHAN(lchan, DRSL, LOGL_ERROR, "PCU rel ack for unexpected lchan kind %s\n", - gsm_pchan_name(lchan->rel_act_kind)); + gsm_pchan_name(lchan->ts->pchan)); /* Release certainly was not requested by the BSC via RSL, so don't ack. */ send_rel_ack = false; break; @@ -782,20 +1518,14 @@ int rsl_tx_rf_rel_ack(struct gsm_lchan *lchan) } if (!send_rel_ack) { - LOGPLCHAN(lchan, DRSL, LOGL_NOTICE, "%s not sending REL ACK\n", gsm_lchan_name(lchan)); + LOGPLCHAN(lchan, DRSL, LOGL_INFO, "not sending REL ACK\n"); return 0; } - LOGP(DRSL, LOGL_NOTICE, "%s (ss=%d) %s Tx CHAN REL ACK\n", + LOGP(DRSL, LOGL_INFO, "%s (ss=%d) %s Tx CHAN REL ACK\n", gsm_ts_and_pchan_name(lchan->ts), lchan->nr, gsm_lchant_name(lchan->type)); - /* - * Free the LAPDm resources now that the BTS - * has released all the resources. - */ - lapdm_channel_exit(&lchan->lapdm_ch); - return tx_rf_rel_ack(lchan, chan_nr); } @@ -804,10 +1534,10 @@ static int rsl_tx_chan_act_ack(struct gsm_lchan *lchan) { struct gsm_time *gtime = get_time(lchan->ts->trx->bts); struct msgb *msg; - uint8_t chan_nr = gsm_lchan2chan_nr(lchan); + uint8_t chan_nr = gsm_lchan2chan_nr_rsl(lchan); uint8_t ie[2]; - LOGP(DRSL, LOGL_NOTICE, "%s (ss=%d) %s Tx CHAN ACT ACK\n", + LOGP(DRSL, LOGL_INFO, "%s (ss=%d) %s Tx CHAN ACT ACK\n", gsm_ts_and_pchan_name(lchan->ts), lchan->nr, gsm_lchant_name(lchan->type)); @@ -826,28 +1556,50 @@ static int rsl_tx_chan_act_ack(struct gsm_lchan *lchan) return abis_bts_rsl_sendmsg(msg); } -/* 8.4.7 sending HANDOver DETection */ -int rsl_tx_hando_det(struct gsm_lchan *lchan, uint8_t *ho_delay) +/* common helper function for *_DETECT */ +static int _rsl_tx_detect(struct gsm_lchan *lchan, uint8_t msg_type, uint8_t *acc_delay) { struct msgb *msg; - uint8_t chan_nr = gsm_lchan2chan_nr(lchan); - - LOGPLCHAN(lchan, DRSL, LOGL_INFO, "Sending HANDOver DETect\n"); + uint8_t chan_nr = gsm_lchan2chan_nr_rsl(lchan); msg = rsl_msgb_alloc(sizeof(struct abis_rsl_dchan_hdr)); if (!msg) return -ENOMEM; /* 9.3.17 Access Delay */ - if (ho_delay) - msgb_tv_put(msg, RSL_IE_ACCESS_DELAY, *ho_delay); + if (acc_delay) + msgb_tv_put(msg, RSL_IE_ACCESS_DELAY, *acc_delay); - rsl_dch_push_hdr(msg, RSL_MT_HANDO_DET, chan_nr); + rsl_dch_push_hdr(msg, msg_type, chan_nr); msg->trx = lchan->ts->trx; return abis_bts_rsl_sendmsg(msg); } +/* 8.4.7 sending HANDOver DETection */ +int rsl_tx_hando_det(struct gsm_lchan *lchan, uint8_t *ho_delay) +{ + LOGPLCHAN(lchan, DRSL, LOGL_INFO, "Sending HANDOver DETect\n"); + + return _rsl_tx_detect(lchan, RSL_MT_HANDO_DET, ho_delay); +} + +/* 8.4.22 sending LISTENER DETection */ +int rsl_tx_listener_det(struct gsm_lchan *lchan, uint8_t *acc_delay) +{ + LOGPLCHAN(lchan, DRSL, LOGL_INFO, "Sending LISTENER DETect\n"); + + return _rsl_tx_detect(lchan, RSL_MT_LISTENER_DET, acc_delay); +} + +/* 8.4.21 sending TALKER DETection */ +int rsl_tx_talker_det(struct gsm_lchan *lchan, uint8_t *acc_delay) +{ + LOGPLCHAN(lchan, DRSL, LOGL_INFO, "Sending TALKER DETect\n"); + + return _rsl_tx_detect(lchan, RSL_MT_TALKER_DET, acc_delay); +} + /* 8.4.3 sending CHANnel ACTIVation Negative ACK */ static int _rsl_tx_chan_act_nack(struct gsm_bts_trx *trx, uint8_t chan_nr, uint8_t cause, struct gsm_lchan *lchan) @@ -855,7 +1607,7 @@ static int _rsl_tx_chan_act_nack(struct gsm_bts_trx *trx, uint8_t chan_nr, uint8 struct msgb *msg; if (lchan) - LOGP(DRSL, LOGL_NOTICE, "%s: ", gsm_lchan_name(lchan)); + LOGPLCHAN(lchan, DRSL, LOGL_NOTICE, ""); else LOGP(DRSL, LOGL_NOTICE, "0x%02x: ", chan_nr); LOGPC(DRSL, LOGL_NOTICE, "Sending Channel Activated NACK: cause = 0x%02x\n", cause); @@ -872,14 +1624,14 @@ static int _rsl_tx_chan_act_nack(struct gsm_bts_trx *trx, uint8_t chan_nr, uint8 return abis_bts_rsl_sendmsg(msg); } static int rsl_tx_chan_act_nack(struct gsm_lchan *lchan, uint8_t cause) { - return _rsl_tx_chan_act_nack(lchan->ts->trx, gsm_lchan2chan_nr(lchan), cause, lchan); + return _rsl_tx_chan_act_nack(lchan->ts->trx, gsm_lchan2chan_nr_rsl(lchan), cause, lchan); } /* Send an RSL Channel Activation Ack if cause is zero, a Nack otherwise. */ int rsl_tx_chan_act_acknack(struct gsm_lchan *lchan, uint8_t cause) { if (lchan->rel_act_kind != LCHAN_REL_ACT_RSL) { - LOGPLCHAN(lchan, DRSL, LOGL_NOTICE, "not sending CHAN ACT %s\n", + LOGPLCHAN(lchan, DRSL, LOGL_INFO, "not sending CHAN ACT %s\n", cause ? "NACK" : "ACK"); return 0; } @@ -890,10 +1642,10 @@ int rsl_tx_chan_act_acknack(struct gsm_lchan *lchan, uint8_t cause) } /* 8.4.4 sending CONNection FAILure */ -int rsl_tx_conn_fail(struct gsm_lchan *lchan, uint8_t cause) +int rsl_tx_conn_fail(const struct gsm_lchan *lchan, uint8_t cause) { struct msgb *msg; - uint8_t chan_nr = gsm_lchan2chan_nr(lchan); + uint8_t chan_nr = gsm_lchan2chan_nr_rsl(lchan); LOGPLCHAN(lchan, DRSL, LOGL_NOTICE, "Sending Connection Failure: cause = 0x%02x\n", cause); @@ -996,17 +1748,13 @@ static void clear_lchan_for_pdch_activ(struct gsm_lchan *lchan) { /* These values don't apply to PDCH, just clear them. Particularly the encryption must be * cleared, or we would enable encryption on PDCH with parameters remaining from the TCH. */ - lchan->ms_power = ms_pwr_ctl_lvl(lchan->ts->trx->bts->band, 0); - lchan->ms_power_ctrl.current = lchan->ms_power; - lchan->ms_power_ctrl.fixed = 0; lchan->rsl_cmode = 0; lchan->tch_mode = 0; memset(&lchan->encr, 0, sizeof(lchan->encr)); memset(&lchan->ho, 0, sizeof(lchan->ho)); - lchan->bs_power = 0; - lchan->ms_power = 0; memset(&lchan->ms_power_ctrl, 0, sizeof(lchan->ms_power_ctrl)); - lchan->rqd_ta = 0; + memset(&lchan->bs_power_ctrl, 0, sizeof(lchan->bs_power_ctrl)); + lchan->ta_ctrl.current = 0; copy_sacch_si_to_lchan(lchan); memset(&lchan->tch, 0, sizeof(lchan->tch)); } @@ -1015,13 +1763,14 @@ static void clear_lchan_for_pdch_activ(struct gsm_lchan *lchan) * Store the CHAN_ACTIV msg, connect the L1 timeslot in the proper type and * then invoke rsl_rx_chan_activ() with msg. */ -static int dyn_ts_l1_reconnect(struct gsm_bts_trx_ts *ts, struct msgb *msg) +static int dyn_ts_l1_reconnect(struct gsm_bts_trx_ts *ts) { DEBUGP(DRSL, "%s dyn_ts_l1_reconnect\n", gsm_ts_and_pchan_name(ts)); switch (ts->dyn.pchan_want) { case GSM_PCHAN_TCH_F: case GSM_PCHAN_TCH_H: + case GSM_PCHAN_SDCCH8_SACCH8C: break; case GSM_PCHAN_PDCH: /* Only the first lchan matters for PDCH */ @@ -1035,9 +1784,6 @@ static int dyn_ts_l1_reconnect(struct gsm_bts_trx_ts *ts, struct msgb *msg) return -EINVAL; } - /* We will feed this back to rsl_rx_chan_activ() later */ - ts->dyn.pending_chan_activ = msg; - /* Disconnect, continue connecting from cb_ts_disconnected(). */ DEBUGP(DRSL, "%s Disconnect\n", gsm_ts_and_pchan_name(ts)); return bts_model_ts_disconnect(ts); @@ -1045,14 +1791,23 @@ static int dyn_ts_l1_reconnect(struct gsm_bts_trx_ts *ts, struct msgb *msg) static enum gsm_phys_chan_config dyn_pchan_from_chan_nr(uint8_t chan_nr) { - uint8_t cbits = chan_nr & RSL_CHAN_NR_MASK; + uint8_t cbits = chan_nr >> 3; switch (cbits) { - case RSL_CHAN_Bm_ACCHs: + case ABIS_RSL_CHAN_NR_CBITS_Bm_ACCHs: return GSM_PCHAN_TCH_F; - case RSL_CHAN_Lm_ACCHs: - case (RSL_CHAN_Lm_ACCHs + RSL_CHAN_NR_1): + case ABIS_RSL_CHAN_NR_CBITS_Lm_ACCHs(0): + case ABIS_RSL_CHAN_NR_CBITS_Lm_ACCHs(1): return GSM_PCHAN_TCH_H; - case RSL_CHAN_OSMO_PDCH: + case ABIS_RSL_CHAN_NR_CBITS_SDCCH8_ACCH(0): + case ABIS_RSL_CHAN_NR_CBITS_SDCCH8_ACCH(1): + case ABIS_RSL_CHAN_NR_CBITS_SDCCH8_ACCH(2): + case ABIS_RSL_CHAN_NR_CBITS_SDCCH8_ACCH(3): + case ABIS_RSL_CHAN_NR_CBITS_SDCCH8_ACCH(4): + case ABIS_RSL_CHAN_NR_CBITS_SDCCH8_ACCH(5): + case ABIS_RSL_CHAN_NR_CBITS_SDCCH8_ACCH(6): + case ABIS_RSL_CHAN_NR_CBITS_SDCCH8_ACCH(7): + return GSM_PCHAN_SDCCH8_SACCH8C; + case ABIS_RSL_CHAN_NR_CBITS_OSMO_PDCH: return GSM_PCHAN_PDCH; default: LOGP(DRSL, LOGL_ERROR, @@ -1062,24 +1817,145 @@ static enum gsm_phys_chan_config dyn_pchan_from_chan_nr(uint8_t chan_nr) } } +/* Parse RSL_IE_OSMO_REP_ACCH_CAP */ +static int parse_repeated_acch_capability(struct gsm_lchan *lchan, struct tlv_parsed *tp) +{ + /* 3GPP TS 24.008, section 10.5.1.7 defines a Repeated ACCH Capability + * bit that indicates if REPEATED FACCH/SACCH is supported or not. + * Unfortunately there is not 3gpp spec that describes how this bit + * should be communicated in the RSL CHANNEL ACTIVATION. For osmo-bts + * we will use a propritary IE. */ + + memset(&lchan->rep_acch_cap, 0, sizeof(lchan->rep_acch_cap)); + + if (!TLVP_PRES_LEN(tp, RSL_IE_OSMO_REP_ACCH_CAP, sizeof(lchan->rep_acch_cap))) + return 0; + + if (!osmo_bts_has_feature(lchan->ts->trx->bts->features, BTS_FEAT_ACCH_REP)) + return -RSL_ERR_OPT_IE_ERROR; + + memcpy(&lchan->rep_acch_cap, TLVP_VAL(tp, RSL_IE_OSMO_REP_ACCH_CAP), + sizeof(lchan->rep_acch_cap)); + + return 0; +} + +/* Parse RSL_IE_OSMO_TOP_ACCH_CAP */ +static int parse_temporary_overpower_acch_capability(struct gsm_lchan *lchan, + const struct tlv_parsed *tp) +{ + memset(&lchan->top_acch_cap, 0, sizeof(lchan->top_acch_cap)); + + if (!TLVP_PRES_LEN(tp, RSL_IE_OSMO_TEMP_OVP_ACCH_CAP, sizeof(lchan->top_acch_cap))) + return 0; + + if (!osmo_bts_has_feature(lchan->ts->trx->bts->features, BTS_FEAT_ACCH_TEMP_OVP)) + return -RSL_ERR_OPT_IE_ERROR; + + memcpy(&lchan->top_acch_cap, + TLVP_VAL(tp, RSL_IE_OSMO_TEMP_OVP_ACCH_CAP), + sizeof(lchan->top_acch_cap)); + + /* Simplify checking whether the overpower is enabled at all: allow + * testing just one parameter (overpower_db > 0) instead of all three. */ + if (!lchan->top_acch_cap.sacch_enable && !lchan->top_acch_cap.facch_enable) + lchan->top_acch_cap.overpower_db = 0; + + return 0; +} + +/* Parse (O) MultiRate configuration IE (see 9.3.52) */ +static int parse_multirate_config(struct gsm_lchan *lchan, + const struct tlv_parsed *tp) +{ + int rc; + + if (!TLVP_PRESENT(tp, RSL_IE_MR_CONFIG)) { + /* Included if the Channel Mode indicates that a multi-rate codec is used */ + if (lchan->tch_mode == GSM48_CMODE_SPEECH_AMR) { + LOGPLCHAN(lchan, DRSL, LOGL_NOTICE, "Missing MultiRate conf IE " + "(TCH mode is %s)\n", gsm48_chan_mode_name(lchan->tch_mode)); + /* Init lchan->tch.amr_mr with hard-coded default values */ + amr_init_mr_conf_def(lchan); + goto parsed; + } + return 0; + } + + /* Included if the Channel Mode indicates that a multi-rate codec is used */ + if (lchan->tch_mode != GSM48_CMODE_SPEECH_AMR) { + LOGPLCHAN(lchan, DRSL, LOGL_ERROR, "Unexpected MultiRate conf IE " + "(TCH mode is %s)\n", gsm48_chan_mode_name(lchan->tch_mode)); + return -RSL_ERR_OPT_IE_ERROR; + } + + rc = amr_parse_mr_conf(&lchan->tch.amr_mr, + TLVP_VAL(tp, RSL_IE_MR_CONFIG), + TLVP_LEN(tp, RSL_IE_MR_CONFIG)); + if (rc < 0) { + LOGPLCHAN(lchan, DRSL, LOGL_ERROR, "Error parsing MultiRate conf IE\n"); + return -RSL_ERR_IE_CONTENT; + } + +parsed: + amr_log_mr_conf(DRTP, LOGL_DEBUG, gsm_lchan_name(lchan), &lchan->tch.amr_mr); + lchan->tch.last_cmr = AMR_CMR_NONE; + return 0; +} + /* 8.4.1 CHANnel ACTIVation is received */ static int rsl_rx_chan_activ(struct msgb *msg) { struct abis_rsl_dchan_hdr *dch = msgb_l2(msg); struct gsm_lchan *lchan = msg->lchan; struct gsm_bts_trx_ts *ts = lchan->ts; - struct rsl_ie_chan_mode *cm; + struct gsm_bts_trx_ts *primary_ts; struct tlv_parsed tp; - uint8_t type; + const struct tlv_p_entry *ie; + uint8_t type, cause; + bool reactivation = false; int rc; - if (lchan->state != LCHAN_S_NONE) { + if (rsl_tlv_parse(&tp, msgb_l3(msg), msgb_l3len(msg)) < 0) { + LOGPLCHAN(lchan, DRSL, LOGL_ERROR, "%s(): rsl_tlv_parse() failed\n", __func__); + return rsl_tx_chan_act_nack(lchan, RSL_ERR_PROTO); + } + + /* 9.3.3 Activation Type */ + if (!TLVP_PRESENT(&tp, RSL_IE_ACT_TYPE)) { + LOGPLCHAN(lchan, DRSL, LOGL_NOTICE, "missing Activation Type\n"); + return rsl_tx_chan_act_nack(lchan, RSL_ERR_MAND_IE_ERROR); + } + type = *TLVP_VAL(&tp, RSL_IE_ACT_TYPE); + if ((type & RSL_ACT_TYPE_REACT)) { + type -= RSL_ACT_TYPE_REACT; + reactivation = true; + } + + /* If Activation Type is IMMEDIATE ASSIGNMENT, we expect L3 info with establishment. */ + lchan->l3_info_estab = (type == RSL_ACT_INTRA_IMM_ASS); + + if (!reactivation && lchan->state != LCHAN_S_NONE) { LOGPLCHAN(lchan, DRSL, LOGL_ERROR, "error: lchan is not available, but in state: %s.\n", gsm_lchans_name(lchan->state)); return rsl_tx_chan_act_nack(lchan, RSL_ERR_EQUIPMENT_FAIL); } - if (ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH) { + if (reactivation && lchan->state == LCHAN_S_NONE) { + LOGPLCHAN(lchan, DRSL, LOGL_ERROR, "error: reactivation on inactive lchan.\n"); + return rsl_tx_chan_act_nack(lchan, RSL_ERR_EQUIPMENT_FAIL); + } + + /* We need to pick the real TS here to check NM state: */ + primary_ts = ts->vamos.is_shadow ? ts->vamos.peer : ts; + if (primary_ts->mo.nm_state.operational != NM_OPSTATE_ENABLED || + primary_ts->mo.nm_state.availability != NM_AVSTATE_OK) { + LOGP(DRSL, LOGL_ERROR, "%s rx chan activ but TS not in nm_state oper=ENABLED avail=OK, nack!\n", + gsm_ts_and_pchan_name(ts)); + return rsl_tx_chan_act_nack(lchan, RSL_ERR_RR_UNAVAIL); + } + + if (ts->pchan == GSM_PCHAN_OSMO_DYN) { ts->dyn.pchan_want = dyn_pchan_from_chan_nr(dch->chan_nr); DEBUGP(DRSL, "%s rx chan activ\n", gsm_ts_and_pchan_name(ts)); @@ -1089,39 +1965,37 @@ static int rsl_rx_chan_activ(struct msgb *msg) * mode than this activation needs it to be. * Re-connect, then come back to rsl_rx_chan_activ(). */ - rc = dyn_ts_l1_reconnect(ts, msg); + rc = dyn_ts_l1_reconnect(ts); if (rc) return rsl_tx_chan_act_nack(lchan, RSL_ERR_NORMAL_UNSPEC); + /* will be fed back to rsl_rx_chan_activ() later */ + OSMO_ASSERT(lchan->pending_chan_activ == NULL); + lchan->pending_chan_activ = msg; /* indicate that the msgb should not be freed. */ return 1; } } - LOGPLCHAN(lchan, DRSL, LOGL_DEBUG, "rx Channel Activation in state: %s.\n", - gsm_lchans_name(lchan->state)); - - /* Initialize channel defaults */ - lchan->ms_power = ms_pwr_ctl_lvl(lchan->ts->trx->bts->band, 0); - lchan->ms_power_ctrl.current = lchan->ms_power; - lchan->ms_power_ctrl.fixed = 0; + /* Initialize MS Power Control defaults */ + lchan->ms_power_ctrl = (struct lchan_power_ctrl_state) { + .max = ms_pwr_ctl_lvl(lchan->ts->trx->bts->band, 0), + .current = lchan->ms_power_ctrl.max, + }; - rsl_tlv_parse(&tp, msgb_l3(msg), msgb_l3len(msg)); - - /* 9.3.3 Activation Type */ - if (!TLVP_PRESENT(&tp, RSL_IE_ACT_TYPE)) { - LOGPLCHAN(lchan, DRSL, LOGL_NOTICE, "missing Activation Type\n"); - return rsl_tx_chan_act_nack(lchan, RSL_ERR_MAND_IE_ERROR); - } - type = *TLVP_VAL(&tp, RSL_IE_ACT_TYPE); + /* Initialize BS Power Control defaults */ + lchan->bs_power_ctrl = (struct lchan_power_ctrl_state) { + .max = 2 * 15, /* maximum defined in 9.3.4 */ + .current = 0, + }; /* 9.3.6 Channel Mode */ if (type != RSL_ACT_OSMO_PDCH) { - if (!TLVP_PRESENT(&tp, RSL_IE_CHAN_MODE)) { - LOGPLCHAN(lchan, DRSL, LOGL_NOTICE, "missing Channel Mode\n"); - return rsl_tx_chan_act_nack(lchan, RSL_ERR_MAND_IE_ERROR); - } - cm = (struct rsl_ie_chan_mode *) TLVP_VAL(&tp, RSL_IE_CHAN_MODE); - lchan_tchmode_from_cmode(lchan, cm); + if (rsl_handle_chan_mod_ie(lchan, &tp, &cause) != 0) + return rsl_tx_chan_act_nack(lchan, cause); + if (rsl_handle_chan_ident_ie(lchan, &tp, &cause) != 0) + return rsl_tx_chan_act_nack(lchan, cause); + if (rsl_handle_osmo_tsc_ie(lchan, &tp, &cause) != 0) + return rsl_tx_chan_act_nack(lchan, cause); } /* 9.3.7 Encryption Information */ @@ -1149,28 +2023,73 @@ static int rsl_rx_chan_activ(struct msgb *msg) } /* 9.3.4 BS Power */ - if (TLVP_PRES_LEN(&tp, RSL_IE_BS_POWER, 1)) - lchan->bs_power = *TLVP_VAL(&tp, RSL_IE_BS_POWER); + if (TLVP_PRES_LEN(&tp, RSL_IE_BS_POWER, 1)) { + if (*TLVP_VAL(&tp, RSL_IE_BS_POWER) & (1 << 4)) { + LOGPLCHAN(lchan, DRSL, LOGL_NOTICE, + "Fast Power Control is not supported\n"); + return rsl_tx_chan_act_nack(lchan, RSL_ERR_SERV_OPT_UNIMPL); + } + + uint8_t red = BS_POWER2DB(*TLVP_VAL(&tp, RSL_IE_BS_POWER)); + + /* BS power reduction is generally not allowed on BCCH/CCCH carrier. + * However, we allow it in the BCCH carrier power reduction operation. + * Constrain BS power value by the maximum reduction for this timeslot. */ + if (ts->trx->bts->c0 == ts->trx) + red = OSMO_MIN(red, ts->c0_power_red_db); + + lchan->bs_power_ctrl.max = red; + lchan->bs_power_ctrl.current = red; + + LOGPLCHAN(lchan, DRSL, LOGL_DEBUG, "BS Power attenuation %u dB\n", + lchan->bs_power_ctrl.current); + } + /* 9.3.13 MS Power */ if (TLVP_PRES_LEN(&tp, RSL_IE_MS_POWER, 1)) { - lchan->ms_power = *TLVP_VAL(&tp, RSL_IE_MS_POWER); - lchan->ms_power_ctrl.current = lchan->ms_power; - lchan->ms_power_ctrl.fixed = 0; + lchan->ms_power_ctrl.max = *TLVP_VAL(&tp, RSL_IE_MS_POWER) & 0x1F; + lchan->ms_power_ctrl.current = lchan->ms_power_ctrl.max; } /* 9.3.24 Timing Advance */ if (TLVP_PRES_LEN(&tp, RSL_IE_TIMING_ADVANCE, 1)) - lchan->rqd_ta = *TLVP_VAL(&tp, RSL_IE_TIMING_ADVANCE); + lchan->ta_ctrl.current = *TLVP_VAL(&tp, RSL_IE_TIMING_ADVANCE); + + /* 9.3.31 (TLV) MS Power Parameters IE (vendor specific) */ + if ((ie = TLVP_GET(&tp, RSL_IE_MS_POWER_PARAM)) != NULL) { + struct gsm_power_ctrl_params *params = &lchan->ms_dpc_params; + + /* Parsed parameters will override per-TRX defaults */ + memcpy(params, ts->trx->ms_dpc_params, sizeof(*params)); + + if (ie->len && parse_power_ctrl_params(params, ie->val, ie->len) != 0) { + LOGPLCHAN(lchan, DRSL, LOGL_ERROR, "Failed to parse MS Power Parameters IE\n"); + return rsl_tx_chan_act_nack(lchan, RSL_ERR_IE_CONTENT); + } - /* 9.3.32 BS Power Parameters */ - /* 9.3.31 MS Power Parameters */ - if (TLVP_PRESENT(&tp, RSL_IE_MS_POWER_PARAM)) - lchan->ms_power_ctrl.fixed = 0; - else { /* Spec explicitly states BTS should only perform * autonomous MS power control loop in BTS if 'MS Power * Parameters' IE is present! */ - lchan->ms_power_ctrl.fixed = 1; + lchan->ms_power_ctrl.dpc_params = params; + } + + /* 9.3.32 (TLV) BS Power Parameters IE (vendor specific) */ + if ((ie = TLVP_GET(&tp, RSL_IE_BS_POWER_PARAM)) != NULL) { + struct gsm_power_ctrl_params *params = &lchan->bs_dpc_params; + + /* Parsed parameters will override per-TRX defaults */ + memcpy(params, ts->trx->bs_dpc_params, sizeof(*params)); + + /* Parsed parameters will override per-TRX defaults */ + if (ie->len && parse_power_ctrl_params(params, ie->val, ie->len) != 0) { + LOGPLCHAN(lchan, DRSL, LOGL_ERROR, "Failed to parse BS Power Parameters IE\n"); + return rsl_tx_chan_act_nack(lchan, RSL_ERR_IE_CONTENT); + } + + /* NOTE: it's safer to start from 0 */ + lchan->bs_power_ctrl.current = 0; + lchan->bs_power_ctrl.dpc_params = params; } + /* 9.3.16 Physical Context */ /* 9.3.29 SACCH Information */ @@ -1213,29 +2132,22 @@ static int rsl_rx_chan_activ(struct msgb *msg) /* use standard SACCH filling of the BTS */ copy_sacch_si_to_lchan(lchan); } + /* 9.3.52 MultiRate Configuration */ - if (TLVP_PRESENT(&tp, RSL_IE_MR_CONFIG)) { - if (TLVP_LEN(&tp, RSL_IE_MR_CONFIG) > sizeof(lchan->mr_bts_lv) - 1) { - LOGPLCHAN(lchan, DRSL, LOGL_ERROR, "Error parsing MultiRate conf IE\n"); - rsl_tx_error_report(msg->trx, RSL_ERR_IE_CONTENT, &dch->chan_nr, NULL, msg); - return rsl_tx_chan_act_acknack(lchan, RSL_ERR_IE_CONTENT); - } - memcpy(lchan->mr_bts_lv, TLVP_VAL(&tp, RSL_IE_MR_CONFIG) - 1, - TLVP_LEN(&tp, RSL_IE_MR_CONFIG) + 1); - amr_parse_mr_conf(&lchan->tch.amr_mr, TLVP_VAL(&tp, RSL_IE_MR_CONFIG), - TLVP_LEN(&tp, RSL_IE_MR_CONFIG)); - amr_log_mr_conf(DRTP, LOGL_DEBUG, gsm_lchan_name(lchan), - &lchan->tch.amr_mr); - lchan->tch.last_cmr = AMR_CMR_NONE; - } + rc = parse_multirate_config(lchan, &tp); + if (rc < 0) + return rsl_tx_chan_act_acknack(lchan, -rc); + /* 9.3.53 MultiRate Control */ /* 9.3.54 Supported Codec Types */ - LOGPLCHAN(lchan, DRSL, LOGL_INFO, "chan_nr=%s type=0x%02x mode=%s\n", - rsl_chan_nr_str(dch->chan_nr), type, gsm48_chan_mode_name(lchan->tch_mode)); + LOGPLCHAN(lchan, DRSL, LOGL_INFO, "chan_nr=%s type=0x%02x=%s mode=%s\n", + rsl_chan_nr_str(dch->chan_nr), + type, get_value_string(rsl_act_type_names, type), + gsm48_chan_mode_name(lchan->tch_mode)); /* Connecting PDCH on dyn TS goes via PCU instead. */ - if (ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH + if (ts->pchan == GSM_PCHAN_OSMO_DYN && ts->dyn.pchan_want == GSM_PCHAN_PDCH) { /* * We ack the activation to the BSC right away, regardless of @@ -1281,48 +2193,75 @@ static int rsl_rx_chan_activ(struct msgb *msg) return 0; } + /* Indicate which SAPIs should be enabled before the first RACH is received, for handover. See 3GPP TS 48.058 + * 4.1.3 and 4.1.4. + * + * | | Timing || transmit | activate | This implementation + * | MS Power | Advance || on main channel | dl SACCH | activates DL SACCH + * ----------------------------------------------------------------------------------------- + * async ho no * --> yes no no + * async ho yes * --> yes may be started no + * async ho yes yes --> yes may be started yes + * sync ho no no --> yes no no + * sync ho yes no --> yes may be started no + * sync ho yes yes --> yes shall be started yes + */ + switch (type) { + case RSL_ACT_INTER_ASYNC: + case RSL_ACT_INTER_SYNC: + lchan->want_dl_sacch_active = (TLVP_PRES_LEN(&tp, RSL_IE_MS_POWER, 1) + && TLVP_PRES_LEN(&tp, RSL_IE_TIMING_ADVANCE, 1)); + break; + default: + lchan->want_dl_sacch_active = true; + break; + } + /* Remember to send an RSL ACK once the lchan is active */ lchan->rel_act_kind = LCHAN_REL_ACT_RSL; - /* actually activate the channel in the BTS */ - rc = l1sap_chan_act(lchan->ts->trx, dch->chan_nr, &tp); + rc = parse_repeated_acch_capability(lchan, &tp); + if (rc < 0) + return rsl_tx_chan_act_acknack(lchan, -rc); + rc = parse_temporary_overpower_acch_capability(lchan, &tp); if (rc < 0) return rsl_tx_chan_act_acknack(lchan, -rc); - return 0; -} - -static int dyn_ts_pdch_release(struct gsm_lchan *lchan) -{ - struct gsm_bts_trx_ts *ts = lchan->ts; + /* Take the first ACCH overpower decision (if allowed): it can be + * enabled immediately if the RxQual threshold is disabled (0). */ + if (lchan->top_acch_cap.overpower_db > 0) + lchan->top_acch_active = !lchan->top_acch_cap.rxqual; + else + lchan->top_acch_active = false; - if (ts->dyn.pchan_is != ts->dyn.pchan_want) { - LOGP(DRSL, LOGL_ERROR, "%s: PDCH release requested but already" - " in switchover\n", gsm_ts_and_pchan_name(ts)); - return -EINVAL; + /* set ASCI channel into right state */ + if (rsl_chan_rt_is_asci(lchan->rsl_chan_rt)) { + if (reactivation) + vgcs_lchan_react(lchan); + else + vgcs_lchan_activate(lchan); } - /* - * Indicate PDCH Disconnect in dyn_pdch.want, let pcu_tx_info_ind() - * pick it up and wait for PCU to disable the channel. - */ - ts->dyn.pchan_want = GSM_PCHAN_NONE; - - if (!pcu_connected()) { - /* PCU not connected yet. Just record the new type and done, - * the PCU will pick it up once connected. */ - ts->dyn.pchan_is = GSM_PCHAN_NONE; - return 1; + /* on reactivation, the channel is already activated */ + if (reactivation) { + rc = rsl_tx_chan_act_ack(lchan); + if (rc < 0) + LOGP(DRSL, LOGL_ERROR, "%s Cannot send act ack: %d\n", + gsm_ts_and_pchan_name(ts), rc); + return 0; } - return pcu_tx_info_ind(); + /* actually activate the channel in the BTS */ + rc = l1sap_chan_act(lchan->ts->trx, dch->chan_nr); + if (rc < 0) + return rsl_tx_chan_act_acknack(lchan, -rc); + + return 0; } /* 8.4.14 RF CHANnel RELease is received */ static int rsl_rx_rf_chan_rel(struct gsm_lchan *lchan, uint8_t chan_nr) { - int rc; - if (lchan->state == LCHAN_S_NONE) { LOGP(DRSL, LOGL_ERROR, "%s ss=%d state=%s Rx RSL RF Channel Release, but is already inactive;" @@ -1333,38 +2272,7 @@ static int rsl_rx_rf_chan_rel(struct gsm_lchan *lchan, uint8_t chan_nr) * not necessarily reflecting the current lchan state. */ return tx_rf_rel_ack(lchan, chan_nr); } - - if (lchan->abis_ip.rtp_socket) { - rsl_tx_ipac_dlcx_ind(lchan, RSL_ERR_NORMAL_UNSPEC); - osmo_rtp_socket_log_stats(lchan->abis_ip.rtp_socket, DRTP, LOGL_INFO, - "Closing RTP socket on Channel Release "); - osmo_rtp_socket_free(lchan->abis_ip.rtp_socket); - lchan->abis_ip.rtp_socket = NULL; - msgb_queue_flush(&lchan->dl_tch_queue); - } - - /* release handover state */ - handover_reset(lchan); - - lchan->rel_act_kind = LCHAN_REL_ACT_RSL; - - /* Dynamic channel in PDCH mode is released via PCU */ - if (lchan->ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH - && lchan->ts->dyn.pchan_is == GSM_PCHAN_PDCH) { - rc = dyn_ts_pdch_release(lchan); - if (rc == 1) { - /* If the PCU is not connected, continue to rel ack right away. */ - lchan->rel_act_kind = LCHAN_REL_ACT_PCU; - return rsl_tx_rf_rel_ack(lchan); - } - /* Waiting for PDCH release */ - return rc; - } - - l1sap_chan_rel(lchan->ts->trx, chan_nr); - - lapdm_channel_exit(&lchan->lapdm_ch); - + gsm_lchan_release(lchan, LCHAN_REL_ACT_RSL); return 0; } @@ -1398,7 +2306,7 @@ static int tx_ciph_mod_compl_hack(struct gsm_lchan *lchan, uint8_t link_id, } } - rsl_rll_push_l3(fake_msg, RSL_MT_DATA_IND, gsm_lchan2chan_nr(lchan), + rsl_rll_push_l3(fake_msg, RSL_MT_DATA_IND, gsm_lchan2chan_nr_rsl(lchan), link_id, 1); fake_msg->lchan = lchan; @@ -1443,7 +2351,8 @@ static int rsl_rx_encr_cmd(struct msgb *msg) uint8_t link_id; if (rsl_tlv_parse(&tp, msgb_l3(msg), msgb_l3len(msg)) < 0) { - return rsl_tx_error_report(msg->trx, RSL_ERR_IE_CONTENT, &dch->chan_nr, NULL, msg); + LOGPLCHAN(lchan, DRSL, LOGL_ERROR, "%s(): rsl_tlv_parse() failed\n", __func__); + return rsl_tx_error_report(msg->trx, RSL_ERR_PROTO, &dch->chan_nr, NULL, msg); } if (!TLVP_PRESENT(&tp, RSL_IE_ENCR_INFO) || @@ -1512,7 +2421,7 @@ static int _rsl_tx_mode_modif_nack(struct gsm_bts_trx *trx, uint8_t chan_nr, uin struct msgb *msg; if (lchan) - LOGP(DRSL, LOGL_NOTICE, "%s: ", gsm_lchan_name(lchan)); + LOGPLCHAN(lchan, DRSL, LOGL_NOTICE, ""); else LOGP(DRSL, LOGL_NOTICE, "0x%02x: ", chan_nr); LOGPC(DRSL, LOGL_NOTICE, "Tx MODE MODIFY NACK (cause = 0x%02x)\n", cause); @@ -1533,7 +2442,7 @@ static int _rsl_tx_mode_modif_nack(struct gsm_bts_trx *trx, uint8_t chan_nr, uin } static int rsl_tx_mode_modif_nack(struct gsm_lchan *lchan, uint8_t cause) { - return _rsl_tx_mode_modif_nack(lchan->ts->trx, gsm_lchan2chan_nr(lchan), cause, lchan); + return _rsl_tx_mode_modif_nack(lchan->ts->trx, gsm_lchan2chan_nr_rsl(lchan), cause, lchan); } @@ -1541,7 +2450,7 @@ static int rsl_tx_mode_modif_nack(struct gsm_lchan *lchan, uint8_t cause) static int rsl_tx_mode_modif_ack(struct gsm_lchan *lchan) { struct msgb *msg; - uint8_t chan_nr = gsm_lchan2chan_nr(lchan); + uint8_t chan_nr = gsm_lchan2chan_nr_rsl(lchan); LOGPLCHAN(lchan, DRSL, LOGL_INFO, "Tx MODE MODIF ACK\n"); @@ -1560,24 +2469,24 @@ static int rsl_rx_mode_modif(struct msgb *msg) { struct abis_rsl_dchan_hdr *dch = msgb_l2(msg); struct gsm_lchan *lchan = msg->lchan; - struct rsl_ie_chan_mode *cm; struct tlv_parsed tp; + uint8_t cause; + int rc; - rsl_tlv_parse(&tp, msgb_l3(msg), msgb_l3len(msg)); - - /* 9.3.6 Channel Mode */ - if (!TLVP_PRESENT(&tp, RSL_IE_CHAN_MODE)) { - LOGPLCHAN(lchan, DRSL, LOGL_NOTICE, "missing Channel Mode\n"); - return rsl_tx_mode_modif_nack(lchan, RSL_ERR_MAND_IE_ERROR); + if (rsl_tlv_parse(&tp, msgb_l3(msg), msgb_l3len(msg)) < 0) { + LOGPLCHAN(lchan, DRSL, LOGL_ERROR, "%s(): rsl_tlv_parse() failed\n", __func__); + return rsl_tx_mode_modif_nack(lchan, RSL_ERR_PROTO); } - cm = (struct rsl_ie_chan_mode *) TLVP_VAL(&tp, RSL_IE_CHAN_MODE); - lchan_tchmode_from_cmode(lchan, cm); - if (bts_supports_cm(lchan->ts->trx->bts, ts_pchan(lchan->ts), lchan->tch_mode) != 1) { - LOGPLCHAN(lchan, DRSL, LOGL_ERROR, "%s: invalid mode: %s (wrong BSC configuration?)\n", - gsm_ts_and_pchan_name(lchan->ts), gsm48_chan_mode_name(lchan->tch_mode)); - return rsl_tx_mode_modif_nack(lchan, RSL_ERR_SERV_OPT_UNAVAIL); - } + /* 9.3.6 Channel Mode */ + if (rsl_handle_chan_mod_ie(lchan, &tp, &cause) != 0) + return rsl_tx_mode_modif_nack(lchan, cause); + /* 9.3.5 Channel Identification */ + if (rsl_handle_chan_ident_ie(lchan, &tp, &cause) != 0) + return rsl_tx_mode_modif_nack(lchan, cause); + /* Osmocom specific TSC IE for VAMOS */ + if (rsl_handle_osmo_tsc_ie(lchan, &tp, &cause) != 0) + return rsl_tx_mode_modif_nack(lchan, cause); /* 9.3.7 Encryption Information */ if (TLVP_PRESENT(&tp, RSL_IE_ENCR_INFO)) { @@ -1593,23 +2502,27 @@ static int rsl_rx_mode_modif(struct msgb *msg) /* 9.3.45 Main channel reference */ /* 9.3.52 MultiRate Configuration */ - if (TLVP_PRESENT(&tp, RSL_IE_MR_CONFIG)) { - if (TLVP_LEN(&tp, RSL_IE_MR_CONFIG) > sizeof(lchan->mr_bts_lv) - 1) { - LOGPLCHAN(lchan, DRSL, LOGL_ERROR, "Error parsing MultiRate conf IE\n"); - rsl_tx_error_report(msg->trx, RSL_ERR_IE_CONTENT, &dch->chan_nr, NULL, msg); - return rsl_tx_mode_modif_nack(lchan, RSL_ERR_IE_CONTENT);; - } - memcpy(lchan->mr_bts_lv, TLVP_VAL(&tp, RSL_IE_MR_CONFIG) - 1, - TLVP_LEN(&tp, RSL_IE_MR_CONFIG) + 1); - amr_parse_mr_conf(&lchan->tch.amr_mr, TLVP_VAL(&tp, RSL_IE_MR_CONFIG), - TLVP_LEN(&tp, RSL_IE_MR_CONFIG)); - amr_log_mr_conf(DRTP, LOGL_DEBUG, gsm_lchan_name(lchan), - &lchan->tch.amr_mr); - lchan->tch.last_cmr = AMR_CMR_NONE; - } + rc = parse_multirate_config(lchan, &tp); + if (rc < 0) + return rsl_tx_mode_modif_nack(lchan, -rc); + /* 9.3.53 MultiRate Control */ /* 9.3.54 Supported Codec Types */ + rc = parse_repeated_acch_capability(lchan, &tp); + if (rc < 0) + return rsl_tx_mode_modif_nack(lchan, -rc); + rc = parse_temporary_overpower_acch_capability(lchan, &tp); + if (rc < 0) + return rsl_tx_mode_modif_nack(lchan, -rc); + + /* Immediately disable ACCH overpower if the value is 0 dB, + * or enable if the RxQual threshold becomes disabled (0). */ + if (lchan->top_acch_cap.overpower_db == 0) + lchan->top_acch_active = false; + else if (lchan->top_acch_cap.rxqual == 0) + lchan->top_acch_active = true; + l1sap_chan_modify(lchan->ts->trx, dch->chan_nr); /* FIXME: delay this until L1 says OK? */ @@ -1623,28 +2536,61 @@ static int rsl_rx_ms_pwr_ctrl(struct msgb *msg) { struct abis_rsl_dchan_hdr *dch = msgb_l2(msg); struct gsm_lchan *lchan = msg->lchan; + struct gsm_bts *bts = lchan->ts->trx->bts; + const struct tlv_p_entry *ie; struct tlv_parsed tp; uint8_t pwr; + int max_pwr, curr_pwr; - rsl_tlv_parse(&tp, msgb_l3(msg), msgb_l3len(msg)); + if (rsl_tlv_parse(&tp, msgb_l3(msg), msgb_l3len(msg)) < 0) { + LOGPLCHAN(lchan, DRSL, LOGL_ERROR, "%s(): rsl_tlv_parse() failed\n", __func__); + return rsl_tx_error_report(msg->trx, RSL_ERR_PROTO, &dch->chan_nr, NULL, msg); + } /* 9.3.13 MS Power (M) */ if (!TLVP_PRES_LEN(&tp, RSL_IE_MS_POWER, 1)) return rsl_tx_error_report(msg->trx, RSL_ERR_MAND_IE_ERROR, &dch->chan_nr, NULL, msg); pwr = *TLVP_VAL(&tp, RSL_IE_MS_POWER) & 0x1F; - lchan->ms_power_ctrl.current = pwr; + lchan->ms_power_ctrl.max = pwr; - LOGPLCHAN(lchan, DRSL, LOGL_NOTICE, "Rx MS POWER CONTROL %d\n", lchan->ms_power_ctrl.current); + LOGPLCHAN(lchan, DRSL, LOGL_INFO, "Rx MS POWER CONTROL %" PRIu8 "\n", pwr); - /* 9.3.31 MS Power Parameters (O) */ - if (TLVP_PRESENT(&tp, RSL_IE_MS_POWER_PARAM)) - lchan->ms_power_ctrl.fixed = 0; - else { - /* Spec explicitly states BTS should only perform - * autonomous MS power control loop in BTS if 'MS Power - * Parameters' IE is present! */ - lchan->ms_power_ctrl.fixed = 1; + /* Spec explicitly states BTS should only perform autonomous MS Power + * control loop in BTS if 'MS Power Parameters' IE is present! */ + lchan->ms_power_ctrl.dpc_params = NULL; + + /* 9.3.31 (TLV) MS Power Parameters IE (vendor specific) */ + if ((ie = TLVP_GET(&tp, RSL_IE_MS_POWER_PARAM)) != NULL) { + struct gsm_power_ctrl_params *params = &lchan->ms_dpc_params; + + /* Parsed parameters will override per-TRX defaults */ + memcpy(params, msg->trx->ms_dpc_params, sizeof(*params)); + + /* Parsed parameters will override per-TRX defaults */ + if (ie->len && parse_power_ctrl_params(params, ie->val, ie->len) != 0) { + LOGPLCHAN(lchan, DRSL, LOGL_ERROR, "Failed to parse MS Power Parameters IE\n"); + return rsl_tx_chan_act_nack(lchan, RSL_ERR_IE_CONTENT); + } + + lchan->ms_power_ctrl.dpc_params = params; + } + + /* Only set current to max if actual value of current + in dBm > value in dBm from max, or if fixed. */ + if (lchan->ms_power_ctrl.dpc_params == NULL) { + lchan->ms_power_ctrl.current = lchan->ms_power_ctrl.max; + } else { + max_pwr = ms_pwr_dbm(bts->band, lchan->ms_power_ctrl.max); + curr_pwr = ms_pwr_dbm(bts->band, lchan->ms_power_ctrl.current); + if (max_pwr < 0 || curr_pwr < 0) { + LOGPLCHAN(lchan, DRSL, LOGL_ERROR, + "Unable to calculate power levels to dBm: %" PRIu8 " -> %d, %" PRIu8 " -> %d\n", + lchan->ms_power_ctrl.max, max_pwr, + lchan->ms_power_ctrl.current, curr_pwr); + } else if (curr_pwr > max_pwr) { + lchan->ms_power_ctrl.current = lchan->ms_power_ctrl.max; + } } bts_model_adjst_ms_pwr(lchan); @@ -1652,41 +2598,77 @@ static int rsl_rx_ms_pwr_ctrl(struct msgb *msg) return 0; } -/* See TS 48.058 Section 9.3.4 */ -static int bs_power_attenuation_dB(uint8_t bs_power) -{ - /* the lower nibble contains the number of 2dB steps that the BS power is reduced compared - * to its nominal transmit power */ - return - ((bs_power & 0xF) *2); -} - /* 8.4.16 BS POWER CONTROL */ static int rsl_rx_bs_pwr_ctrl(struct msgb *msg) { struct abis_rsl_dchan_hdr *dch = msgb_l2(msg); struct gsm_lchan *lchan = msg->lchan; + struct gsm_bts_trx *trx = msg->trx; + const struct tlv_p_entry *ie; struct tlv_parsed tp; - uint8_t new_bs_power; + uint8_t old, new; - rsl_tlv_parse(&tp, msgb_l3(msg), msgb_l3len(msg)); + if (rsl_tlv_parse(&tp, msgb_l3(msg), msgb_l3len(msg)) < 0) { + LOGPLCHAN(lchan, DRSL, LOGL_ERROR, "%s(): rsl_tlv_parse() failed\n", __func__); + return rsl_tx_error_report(trx, RSL_ERR_PROTO, &dch->chan_nr, NULL, msg); + } /* 9.3.4 BS Power (M) */ if (!TLVP_PRES_LEN(&tp, RSL_IE_BS_POWER, 1)) - return rsl_tx_error_report(msg->trx, RSL_ERR_MAND_IE_ERROR, &dch->chan_nr, NULL, msg); + return rsl_tx_error_report(trx, RSL_ERR_MAND_IE_ERROR, &dch->chan_nr, NULL, msg); + + if (*TLVP_VAL(&tp, RSL_IE_BS_POWER) & (1 << 4)) { + LOGPLCHAN(lchan, DRSL, LOGL_NOTICE, "Fast Power Control is not supported\n"); + return rsl_tx_error_report(trx, RSL_ERR_SERV_OPT_UNIMPL, &dch->chan_nr, NULL, msg); + } + + new = BS_POWER2DB(*TLVP_VAL(&tp, RSL_IE_BS_POWER)); + old = lchan->bs_power_ctrl.current; + + /* Osmocom specific extension for BCCH carrier power reduction */ + if (dch->chan_nr == RSL_CHAN_BCCH) { + int rc = bts_set_c0_pwr_red(trx->bts, new); + if (rc != 0) { + const uint8_t cause = (rc == -ENOTSUP) ? + RSL_ERR_SERV_OPT_UNIMPL : RSL_ERR_IE_CONTENT; + return rsl_tx_error_report(trx, cause, &dch->chan_nr, NULL, msg); + } + + return 0; + } + + /* BS power reduction is generally not allowed on BCCH/CCCH carrier. + * However, we allow it in the BCCH carrier power reduction operation. + * Constrain BS power value by the maximum reduction for this timeslot. */ + if (trx->bts->c0 == trx) + new = OSMO_MIN(new, lchan->ts->c0_power_red_db); - new_bs_power = *TLVP_VAL(&tp, RSL_IE_BS_POWER); + /* 9.3.32 (TLV) BS Power Parameters IE (vendor specific) */ + if ((ie = TLVP_GET(&tp, RSL_IE_BS_POWER_PARAM)) != NULL) { + struct gsm_power_ctrl_params *params = &lchan->bs_dpc_params; - LOGPLCHAN(lchan, DRSL, LOGL_INFO, "BS POWER CONTROL Attenuation %d -> %d dB\n", - bs_power_attenuation_dB(lchan->bs_power), bs_power_attenuation_dB(new_bs_power)); + /* Parsed parameters will override per-TRX defaults */ + memcpy(params, trx->bs_dpc_params, sizeof(*params)); - lchan->bs_power = new_bs_power; + /* Parsed parameters will override per-TRX defaults */ + if (ie->len && parse_power_ctrl_params(params, ie->val, ie->len) != 0) { + LOGPLCHAN(lchan, DRSL, LOGL_ERROR, "Failed to parse BS Power Parameters IE\n"); + return rsl_tx_chan_act_nack(lchan, RSL_ERR_IE_CONTENT); + } - /* 9.3.31 MS Power Parameters (O) */ - if (TLVP_PRESENT(&tp, RSL_IE_BS_POWER_PARAM)) { - /* Spec explicitly states BTS should perform autonomous - * BS power control loop in BTS if 'BS Power Parameters' - * IE is present! WE don't support that. */ - return rsl_tx_error_report(msg->trx, RSL_ERR_OPT_IE_ERROR, &dch->chan_nr, NULL, msg); + /* NOTE: it's safer to start from 0 */ + lchan->bs_power_ctrl.current = 0; + lchan->bs_power_ctrl.max = new; + lchan->bs_power_ctrl.dpc_params = params; + } else { + lchan->bs_power_ctrl.dpc_params = NULL; + lchan->bs_power_ctrl.current = new; + } + + if (lchan->bs_power_ctrl.current != old) { + LOGPLCHAN(lchan, DRSL, LOGL_INFO, "BS POWER CONTROL: " + "attenuation change %u -> %u dB\n", + old, lchan->bs_power_ctrl.current); } return 0; @@ -1702,7 +2684,10 @@ static int rsl_rx_sacch_inf_mod(struct msgb *msg) struct tlv_parsed tp; uint8_t rsl_si, osmo_si; - rsl_tlv_parse(&tp, msgb_l3(msg), msgb_l3len(msg)); + if (rsl_tlv_parse(&tp, msgb_l3(msg), msgb_l3len(msg)) < 0) { + LOGPLCHAN(lchan, DRSL, LOGL_ERROR, "%s(): rsl_tlv_parse() failed\n", __func__); + return rsl_tx_error_report(msg->trx, RSL_ERR_PROTO, &dch->chan_nr, NULL, msg); + } if (TLVP_PRESENT(&tp, RSL_IE_STARTNG_TIME)) { LOGPLCHAN(lchan, DRSL, LOGL_NOTICE, "Starting time not supported\n"); @@ -1758,7 +2743,7 @@ int rsl_tx_cbch_load_indication(struct gsm_bts *bts, bool ext_cbch, bool overflo return -ENOMEM; /* 9.3.1 Channel Number */ - rsl_cch_push_hdr(msg, RSL_MT_CBCH_LOAD_IND, gsm_lchan2chan_nr(lchan)); + rsl_cch_push_hdr(msg, RSL_MT_CBCH_LOAD_IND, gsm_lchan2chan_nr_rsl(lchan)); /* 9.3.43 CBCH Load Information */ load_info = ((overflow & 1) << 7) | (amount & 0x0F); @@ -1810,7 +2795,9 @@ int rsl_tx_ipac_dlcx_ind(struct gsm_lchan *lchan, uint8_t cause) { struct msgb *nmsg; - LOGPLCHAN(lchan, DRSL, LOGL_NOTICE, "Sending RTP delete indication: cause = %s\n", + LOGPLCHAN(lchan, DRSL, + (cause == RSL_ERR_NORMAL_UNSPEC) ? LOGL_INFO : LOGL_NOTICE, + "Sending RTP delete indication: cause = %s\n", rsl_err_name(cause)); nmsg = rsl_msgb_alloc(sizeof(struct abis_rsl_dchan_hdr)); @@ -1820,7 +2807,7 @@ int rsl_tx_ipac_dlcx_ind(struct gsm_lchan *lchan, uint8_t cause) msgb_tv16_put(nmsg, RSL_IE_IPAC_CONN_ID, htons(lchan->abis_ip.conn_id)); rsl_add_rtp_stats(lchan, nmsg); msgb_tlv_put(nmsg, RSL_IE_CAUSE, 1, &cause); - rsl_ipa_push_hdr(nmsg, RSL_MT_IPAC_DLCX_IND, gsm_lchan2chan_nr(lchan)); + rsl_ipa_push_hdr(nmsg, RSL_MT_IPAC_DLCX_IND, gsm_lchan2chan_nr_rsl(lchan)); nmsg->trx = lchan->ts->trx; @@ -1832,7 +2819,7 @@ static int rsl_tx_ipac_XXcx_ack(struct gsm_lchan *lchan, int inc_pt2, uint8_t orig_msgt) { struct msgb *msg; - uint8_t chan_nr = gsm_lchan2chan_nr(lchan); + uint8_t chan_nr = gsm_lchan2chan_nr_rsl(lchan); const char *name; struct in_addr ia; @@ -1870,6 +2857,11 @@ static int rsl_tx_ipac_XXcx_ack(struct gsm_lchan *lchan, int inc_pt2, lchan->abis_ip.rtp_payload2); } + /* Osmocom Extension: Osmux CID */ + if (lchan->abis_ip.osmux.use) + msgb_tlv_put(msg, RSL_IE_OSMO_OSMUX_CID, 1, + &lchan->abis_ip.osmux.local_cid); + /* push the header in front */ rsl_ipa_push_hdr(msg, orig_msgt + 1, chan_nr); msg->trx = lchan->ts->trx; @@ -1880,7 +2872,7 @@ static int rsl_tx_ipac_XXcx_ack(struct gsm_lchan *lchan, int inc_pt2, static int rsl_tx_ipac_dlcx_ack(struct gsm_lchan *lchan, int inc_conn_id) { struct msgb *msg; - uint8_t chan_nr = gsm_lchan2chan_nr(lchan); + uint8_t chan_nr = gsm_lchan2chan_nr_rsl(lchan); LOGPLCHAN(lchan, DRSL, LOGL_INFO, "RSL Tx IPAC_DLCX_ACK\n"); @@ -1903,7 +2895,7 @@ static int rsl_tx_ipac_dlcx_nack(struct gsm_lchan *lchan, int inc_conn_id, uint8_t cause) { struct msgb *msg; - uint8_t chan_nr = gsm_lchan2chan_nr(lchan); + uint8_t chan_nr = gsm_lchan2chan_nr_rsl(lchan); LOGPLCHAN(lchan, DRSL, LOGL_INFO, "RSL Tx IPAC_DLCX_NACK\n"); @@ -1924,15 +2916,15 @@ static int rsl_tx_ipac_dlcx_nack(struct gsm_lchan *lchan, int inc_conn_id, } -/* transmit an CRCX NACK for the lchan */ +/* Send an xxCX NACK for the given xxCX message type and lchan */ static int tx_ipac_XXcx_nack(struct gsm_lchan *lchan, uint8_t cause, int inc_ipport, uint8_t orig_msgtype) { struct msgb *msg; - uint8_t chan_nr = gsm_lchan2chan_nr(lchan); + uint8_t chan_nr = gsm_lchan2chan_nr_rsl(lchan); + uint8_t msg_type = orig_msgtype + 2; - /* FIXME: allocate new msgb and copy old over */ - LOGPLCHAN(lchan, DRSL, LOGL_NOTICE, "RSL Tx IPAC_BIND_NACK\n"); + LOGPLCHAN(lchan, DRSL, LOGL_NOTICE, "RSL Tx %s\n", rsl_ipac_msg_name(msg_type)); msg = rsl_msgb_alloc(sizeof(struct abis_rsl_dchan_hdr)); if (!msg) @@ -1952,7 +2944,7 @@ static int tx_ipac_XXcx_nack(struct gsm_lchan *lchan, uint8_t cause, msgb_tlv_put(msg, RSL_IE_CAUSE, 1, &cause); /* push the header in front */ - rsl_ipa_push_hdr(msg, orig_msgtype + 2, chan_nr); + rsl_ipa_push_hdr(msg, msg_type, chan_nr); msg->trx = lchan->ts->trx; return abis_bts_rsl_sendmsg(msg); @@ -1960,7 +2952,7 @@ static int tx_ipac_XXcx_nack(struct gsm_lchan *lchan, uint8_t cause, static char *get_rsl_local_ip(struct gsm_bts_trx *trx) { - struct e1inp_ts *ts = trx->rsl_link->ts; + struct e1inp_ts *ts = trx->bb_transc.rsl.link->ts; struct sockaddr_storage ss; socklen_t sa_len = sizeof(ss); static char hostbuf[256]; @@ -1980,42 +2972,21 @@ static char *get_rsl_local_ip(struct gsm_bts_trx *trx) return hostbuf; } -static int bind_rtp(struct gsm_bts *bts, struct osmo_rtp_socket *rs, const char *ip) -{ - int rc; - unsigned int i; - unsigned int tries; - - tries = (bts->rtp_port_range_end - bts->rtp_port_range_start) / 2; - for (i = 0; i < tries; i++) { - - if (bts->rtp_port_range_next >= bts->rtp_port_range_end) - bts->rtp_port_range_next = bts->rtp_port_range_start; - - rc = osmo_rtp_socket_bind(rs, ip, bts->rtp_port_range_next); - - bts->rtp_port_range_next += 2; - - if (rc == 0) - return 0; - } - - return -1; -} - static int rsl_rx_ipac_XXcx(struct msgb *msg) { struct abis_rsl_dchan_hdr *dch = msgb_l2(msg); struct tlv_parsed tp; struct gsm_lchan *lchan = msg->lchan; struct gsm_bts *bts = lchan->ts->trx->bts; - const uint8_t *payload_type, *speech_mode, *payload_type2; + const uint8_t *payload_type, *speech_mode, *payload_type2, *csd_fmt; + const uint8_t *osmux_cid = NULL, *rtp_extensions = NULL; uint32_t connect_ip = 0; uint16_t connect_port = 0; - int rc, inc_ip_port = 0, port; + int rc, inc_ip_port = 0; char *name; struct in_addr ia; - struct in_addr addr; + enum rsl_ipac_rtp_csd_format_d csd_fmt_d; + enum rsl_ipac_rtp_csd_format_ir csd_fmt_ir; if (dch->c.msg_type == RSL_MT_IPAC_CRCX) name = "CRCX"; @@ -2027,22 +2998,22 @@ static int rsl_rx_ipac_XXcx(struct msgb *msg) return tx_ipac_XXcx_nack(lchan, 0x52, 0, dch->c.msg_type); - rc = rsl_tlv_parse(&tp, msgb_l3(msg), msgb_l3len(msg)); - if (rc < 0) - return tx_ipac_XXcx_nack(lchan, RSL_ERR_MAND_IE_ERROR, - 0, dch->c.msg_type); + if (rsl_tlv_parse(&tp, msgb_l3(msg), msgb_l3len(msg)) < 0) { + LOGPLCHAN(lchan, DRSL, LOGL_ERROR, "%s(): rsl_tlv_parse() failed\n", __func__); + return tx_ipac_XXcx_nack(lchan, RSL_ERR_PROTO, 0, dch->c.msg_type); + } LOGPLCHAN(lchan, DRSL, LOGL_DEBUG, "IPAC_%s: ", name); if (TLVP_PRES_LEN(&tp, RSL_IE_IPAC_REMOTE_IP, 4)) { + struct in_addr addr; connect_ip = tlvp_val32_unal(&tp, RSL_IE_IPAC_REMOTE_IP); addr.s_addr = connect_ip; LOGPC(DRSL, LOGL_DEBUG, "connect_ip=%s ", inet_ntoa(addr)); } if (TLVP_PRES_LEN(&tp, RSL_IE_IPAC_REMOTE_PORT, 2)) { - connect_port = tlvp_val16_unal(&tp, RSL_IE_IPAC_REMOTE_PORT); - LOGPC(DRSL, LOGL_DEBUG, "connect_port=%u ", - ntohs(connect_port)); + connect_port = tlvp_val16be(&tp, RSL_IE_IPAC_REMOTE_PORT); + LOGPC(DRSL, LOGL_DEBUG, "connect_port=%u ", connect_port); } speech_mode = TLVP_VAL(&tp, RSL_IE_IPAC_SPEECH_MODE); @@ -2056,6 +3027,20 @@ static int rsl_rx_ipac_XXcx(struct msgb *msg) LOGPC(DRSL, LOGL_DEBUG, "\n"); payload_type2 = TLVP_VAL(&tp, RSL_IE_IPAC_RTP_PAYLOAD2); + if (payload_type2) + LOGPC(DRSL, LOGL_DEBUG, "payload_type2=%u ", *payload_type2); + + /* this IE has TLV format when TV would have been good enough */ + if (TLVP_PRES_LEN(&tp, RSL_IE_OSMO_OSMUX_CID, 1)) + osmux_cid = TLVP_VAL(&tp, RSL_IE_OSMO_OSMUX_CID); + if (osmux_cid) + LOGPC(DRSL, LOGL_DEBUG, "osmux_cid=%u ", *osmux_cid); + + /* same here */ + if (TLVP_PRES_LEN(&tp, RSL_IE_OSMO_RTP_EXTENSIONS, 1)) + rtp_extensions = TLVP_VAL(&tp, RSL_IE_OSMO_RTP_EXTENSIONS); + if (rtp_extensions) + LOGPC(DRSL, LOGL_DEBUG, "rtp_extensions=%u ", *rtp_extensions); if (dch->c.msg_type == RSL_MT_IPAC_CRCX && connect_ip && connect_port) inc_ip_port = 1; @@ -2067,116 +3052,107 @@ static int rsl_rx_ipac_XXcx(struct msgb *msg) inc_ip_port, dch->c.msg_type); } - if (dch->c.msg_type == RSL_MT_IPAC_CRCX) { - char cname[32]; - char *ipstr = NULL; - if (lchan->abis_ip.rtp_socket) { - LOGPLCHAN(lchan, DRSL, LOGL_ERROR, "Rx RSL IPAC CRCX, " - "but we already have socket!\n"); - return tx_ipac_XXcx_nack(lchan, RSL_ERR_RES_UNAVAIL, - inc_ip_port, dch->c.msg_type); + if ((csd_fmt = TLVP_VAL(&tp, RSL_IE_IPAC_RTP_CSD_FMT))) { + csd_fmt_d = *csd_fmt & 0xf; + csd_fmt_ir = *csd_fmt >> 4; + LOGPC(DRSL, LOGL_DEBUG, "csd_fmt_d=%d csd_fmt_ir=%d ", csd_fmt_d, csd_fmt_ir); + if (csd_fmt_d != RSL_IPAC_RTP_CSD_TRAU_BTS) { + LOGPLCHAN(lchan, DRSL, LOGL_ERROR, "Rx RSL IPAC %s, csd_fmt_d=%d is not supported\n", + name, csd_fmt_d); + return tx_ipac_XXcx_nack(lchan, RSL_ERR_SERV_OPT_UNIMPL, inc_ip_port, dch->c.msg_type); } - /* FIXME: select default value depending on speech_mode */ - //if (!payload_type) - lchan->tch.last_fn = LCHAN_FN_DUMMY; - lchan->abis_ip.rtp_socket = osmo_rtp_socket_create(lchan->ts->trx, - OSMO_RTP_F_POLL); - if (!lchan->abis_ip.rtp_socket) { - LOGPLCHAN(lchan, DRTP, LOGL_ERROR, "IPAC Failed to create RTP/RTCP sockets\n"); - oml_tx_failure_event_rep(&lchan->ts->trx->mo, - NM_SEVER_MINOR, OSMO_EVT_CRIT_RTP_TOUT, - "%s IPAC Failed to create RTP/RTCP sockets", - gsm_lchan_name(lchan)); + } + + if (!osmux_cid) { /* Regular RTP */ + if (bts->osmux.use == OSMUX_USAGE_ONLY) { + LOGPLCHAN(lchan, DRSL, LOGL_ERROR, "Rx RSL IPAC XXcx without Osmux CID" + "goes against configured Osmux policy 'only'\n"); return tx_ipac_XXcx_nack(lchan, RSL_ERR_RES_UNAVAIL, inc_ip_port, dch->c.msg_type); } - rc = osmo_rtp_socket_set_param(lchan->abis_ip.rtp_socket, - bts->rtp_jitter_adaptive ? - OSMO_RTP_P_JIT_ADAP : - OSMO_RTP_P_JITBUF, - bts->rtp_jitter_buf_ms); - if (rc < 0) - LOGPLCHAN(lchan, DRTP, LOGL_ERROR, - "IPAC Failed to set RTP socket parameters: %s\n", strerror(-rc)); - else - LOGPLCHAN(lchan, DRTP, LOGL_INFO, "IPAC set RTP socket parameters: %d\n", rc); - lchan->abis_ip.rtp_socket->priv = lchan; - lchan->abis_ip.rtp_socket->rx_cb = &l1sap_rtp_rx_cb; - - if (connect_ip && connect_port) { - /* if CRCX specifies a remote IP, we can bind() - * here to 0.0.0.0 and wait for the connect() - * below, after which the kernel will have - * selected the local IP address. */ - ipstr = "0.0.0.0"; - } else { - /* if CRCX does not specify a remote IP, we will - * not do any connect() below, and thus the - * local socket will remain bound to 0.0.0.0 - - * which however we cannot legitimately report - * back to the BSC in the CRCX_ACK */ - ipstr = get_rsl_local_ip(lchan->ts->trx); + + if (dch->c.msg_type == RSL_MT_IPAC_CRCX) { /* CRCX */ + char *ipstr = NULL; + if (connect_ip && connect_port) { + /* if CRCX specifies a remote IP, we can bind() + * here to 0.0.0.0 and wait for the connect() + * below, after which the kernel will have + * selected the local IP address. */ + ipstr = "0.0.0.0"; + } else { + /* if CRCX does not specify a remote IP, we will + * not do any connect() below, and thus the + * local socket will remain bound to 0.0.0.0 - + * which however we cannot legitimately report + * back to the BSC in the CRCX_ACK */ + ipstr = get_rsl_local_ip(lchan->ts->trx); + } + rc = lchan_rtp_socket_create(lchan, ipstr); + if (rc < 0) + return tx_ipac_XXcx_nack(lchan, RSL_ERR_RES_UNAVAIL, + inc_ip_port, dch->c.msg_type); + } else { /* MDCX */ + if (!lchan->abis_ip.rtp_socket) { + LOGPLCHAN(lchan, DRSL, LOGL_ERROR, "Rx RSL IPAC MDCX, " + "but we have no RTP socket!\n"); + return tx_ipac_XXcx_nack(lchan, RSL_ERR_RES_UNAVAIL, + inc_ip_port, dch->c.msg_type); + } } - rc = bind_rtp(bts, lchan->abis_ip.rtp_socket, ipstr); + + /* Special rule: If connect_ip == 0.0.0.0, use RSL IP + * address */ + if (connect_ip == 0) { + struct e1inp_sign_link *sign_link = + lchan->ts->trx->bb_transc.rsl.link; + + ia.s_addr = htonl(get_signlink_remote_ip(sign_link)); + } else + ia.s_addr = connect_ip; + rc = lchan_rtp_socket_connect(lchan, &ia, connect_port); if (rc < 0) { - LOGPLCHAN(lchan, DRTP, LOGL_ERROR, "IPAC Failed to bind RTP/RTCP sockets\n"); - oml_tx_failure_event_rep(&lchan->ts->trx->mo, - NM_SEVER_MINOR, OSMO_EVT_CRIT_RTP_TOUT, - "%s IPAC Failed to bind RTP/RTCP sockets", - gsm_lchan_name(lchan)); - osmo_rtp_socket_free(lchan->abis_ip.rtp_socket); - lchan->abis_ip.rtp_socket = NULL; - msgb_queue_flush(&lchan->dl_tch_queue); + lchan_rtp_socket_free(lchan); return tx_ipac_XXcx_nack(lchan, RSL_ERR_RES_UNAVAIL, inc_ip_port, dch->c.msg_type); } - /* Ensure RTCP SDES contains some useful information */ - snprintf(cname, sizeof(cname), "bts@%s", ipstr); - osmo_rtp_set_source_desc(lchan->abis_ip.rtp_socket, cname, - gsm_lchan_name(lchan), NULL, NULL, - gsm_trx_unit_id(lchan->ts->trx), - "OsmoBTS-" PACKAGE_VERSION, NULL); - /* FIXME: multiplex connection, BSC proxy */ - } else { - /* MDCX */ - if (!lchan->abis_ip.rtp_socket) { - LOGPLCHAN(lchan, DRSL, LOGL_ERROR, "Rx RSL IPAC MDCX, " - "but we have no RTP socket!\n"); + + } else { /* Osmux */ + if (bts->osmux.use == OSMUX_USAGE_OFF) { + LOGPLCHAN(lchan, DRSL, LOGL_ERROR, "Rx RSL IPAC XXcx with Osmux CID" + "goes against configured Osmux policy 'off'\n"); return tx_ipac_XXcx_nack(lchan, RSL_ERR_RES_UNAVAIL, inc_ip_port, dch->c.msg_type); } - } + if (dch->c.msg_type == RSL_MT_IPAC_CRCX) { /* CRCX */ + rc = lchan_osmux_init(lchan, payload_type ? *payload_type : 0); + if (rc < 0) + return tx_ipac_XXcx_nack(lchan, RSL_ERR_RES_UNAVAIL, + inc_ip_port, dch->c.msg_type); + } else { /* MDCX */ + if (!lchan->abis_ip.osmux.use) { + LOGPLCHAN(lchan, DRSL, LOGL_ERROR, "Rx RSL IPAC MDCX with Osmux CID, " + "CRCX was configured as RTP!\n"); + return tx_ipac_XXcx_nack(lchan, RSL_ERR_RES_UNAVAIL, + inc_ip_port, dch->c.msg_type); + } + } - /* Special rule: If connect_ip == 0.0.0.0, use RSL IP - * address */ - if (connect_ip == 0) { - struct e1inp_sign_link *sign_link = - lchan->ts->trx->rsl_link; - - ia.s_addr = htonl(get_signlink_remote_ip(sign_link)); - } else - ia.s_addr = connect_ip; - rc = osmo_rtp_socket_connect(lchan->abis_ip.rtp_socket, - inet_ntoa(ia), ntohs(connect_port)); - if (rc < 0) { - LOGPLCHAN(lchan, DRTP, LOGL_ERROR, "Failed to connect RTP/RTCP sockets\n"); - osmo_rtp_socket_free(lchan->abis_ip.rtp_socket); - lchan->abis_ip.rtp_socket = NULL; - msgb_queue_flush(&lchan->dl_tch_queue); - return tx_ipac_XXcx_nack(lchan, RSL_ERR_RES_UNAVAIL, - inc_ip_port, dch->c.msg_type); + if (connect_ip != 0) + lchan->abis_ip.connect_ip = connect_ip; + if (connect_port != 0) + lchan->abis_ip.connect_port = connect_port; + lchan->abis_ip.osmux.remote_cid = *osmux_cid; + if (lchan->abis_ip.connect_ip && lchan->abis_ip.connect_port && + !lchan_osmux_connected(lchan)) { + rc = lchan_osmux_connect(lchan); + if (rc < 0) { + lchan_osmux_release(lchan); + return tx_ipac_XXcx_nack(lchan, RSL_ERR_RES_UNAVAIL, + inc_ip_port, dch->c.msg_type); + } + } } - /* save IP address and port number */ - lchan->abis_ip.connect_ip = ntohl(ia.s_addr); - lchan->abis_ip.connect_port = ntohs(connect_port); - - rc = osmo_rtp_get_bound_ip_port(lchan->abis_ip.rtp_socket, - &lchan->abis_ip.bound_ip, - &port); - if (rc < 0) - LOGPLCHAN(lchan, DRTP, LOGL_ERROR, "IPAC cannot obtain locally bound IP/port: %d\n", rc); - lchan->abis_ip.bound_port = port; /* Everything has succeeded, we can store new values in lchan */ if (payload_type) { @@ -2194,6 +3170,12 @@ static int rsl_rx_ipac_XXcx(struct msgb *msg) if (speech_mode) lchan->abis_ip.speech_mode = *speech_mode; + /* Configure non-standard RTP extensions */ + if (rtp_extensions) + lchan->abis_ip.rtp_extensions = *rtp_extensions; + else + lchan->abis_ip.rtp_extensions = 0; + /* FIXME: CSD, jitterbuffer, compression */ return rsl_tx_ipac_XXcx_ack(lchan, payload_type2 ? 1 : 0, @@ -2206,9 +3188,10 @@ static int rsl_rx_ipac_dlcx(struct msgb *msg) struct gsm_lchan *lchan = msg->lchan; int rc, inc_conn_id = 0; - rc = rsl_tlv_parse(&tp, msgb_l3(msg), msgb_l3len(msg)); - if (rc < 0) - return rsl_tx_ipac_dlcx_nack(lchan, 0, RSL_ERR_MAND_IE_ERROR); + if (rsl_tlv_parse(&tp, msgb_l3(msg), msgb_l3len(msg)) < 0) { + LOGPLCHAN(lchan, DRSL, LOGL_ERROR, "%s(): rsl_tlv_parse() failed\n", __func__); + return rsl_tx_ipac_dlcx_nack(lchan, 0, RSL_ERR_PROTO); + } if (TLVP_PRESENT(&tp, RSL_IE_IPAC_CONN_ID)) inc_conn_id = 1; @@ -2217,9 +3200,7 @@ static int rsl_rx_ipac_dlcx(struct msgb *msg) if (lchan->abis_ip.rtp_socket) { osmo_rtp_socket_log_stats(lchan->abis_ip.rtp_socket, DRTP, LOGL_INFO, "Closing RTP socket on DLCX "); - osmo_rtp_socket_free(lchan->abis_ip.rtp_socket); - lchan->abis_ip.rtp_socket = NULL; - msgb_queue_flush(&lchan->dl_tch_queue); + lchan_rtp_socket_free(lchan); } return rc; } @@ -2233,7 +3214,7 @@ static int rsl_rx_ipac_dlcx(struct msgb *msg) static int rsl_tx_dyn_pdch_ack(struct gsm_lchan *lchan, bool pdch_act) { struct gsm_time *gtime = get_time(lchan->ts->trx->bts); - uint8_t chan_nr = gsm_lchan2chan_nr(lchan); + uint8_t chan_nr = gsm_lchan2chan_nr_rsl(lchan); struct msgb *msg; uint8_t ie[2]; @@ -2262,7 +3243,7 @@ static int rsl_tx_dyn_pdch_nack(struct gsm_lchan *lchan, bool pdch_act, uint8_t cause) { struct msgb *msg; - uint8_t chan_nr = gsm_lchan2chan_nr(lchan); + uint8_t chan_nr = gsm_lchan2chan_nr_rsl(lchan); LOGPLCHAN(lchan, DRSL, LOGL_NOTICE, "Tx PDCH %s NACK (cause = 0x%02x)\n", pdch_act ? "ACT" : "DEACT", cause); @@ -2371,7 +3352,6 @@ static void rsl_rx_dyn_pdch(struct msgb *msg, bool pdch_act) if (pdch_act) { /* Clear TCH state. Only first lchan matters for PDCH */ clear_lchan_for_pdch_activ(ts->lchan); - /* First, disconnect the TCH channel, to connect PDTCH later */ rc = bts_model_ts_disconnect(ts); } else { @@ -2400,14 +3380,12 @@ static void ipacc_dyn_pdch_ts_disconnected(struct gsm_bts_trx_ts *ts) enum gsm_phys_chan_config as_pchan; if (ts->flags & TS_F_PDCH_DEACT_PENDING) { - LOGP(DRSL, LOGL_DEBUG, - "%s PDCH DEACT operation: channel disconnected, will reconnect as TCH\n", - gsm_lchan_name(ts->lchan)); + LOGPLCHAN(ts->lchan, DRSL, LOGL_DEBUG, + "PDCH DEACT operation: channel disconnected, will reconnect as TCH\n"); as_pchan = GSM_PCHAN_TCH_F; } else if (ts->flags & TS_F_PDCH_ACT_PENDING) { - LOGP(DRSL, LOGL_DEBUG, - "%s PDCH ACT operation: channel disconnected, will reconnect as PDTCH\n", - gsm_lchan_name(ts->lchan)); + LOGPLCHAN(ts->lchan, DRSL, LOGL_DEBUG, + "PDCH ACT operation: channel disconnected, will reconnect as PDTCH\n"); as_pchan = GSM_PCHAN_PDCH; } else /* No reconnect pending. */ @@ -2434,6 +3412,7 @@ static void osmo_dyn_ts_disconnected(struct gsm_bts_trx_ts *ts) switch (ts->dyn.pchan_want) { case GSM_PCHAN_TCH_F: case GSM_PCHAN_TCH_H: + case GSM_PCHAN_SDCCH8_SACCH8C: case GSM_PCHAN_PDCH: break; default: @@ -2457,7 +3436,7 @@ void cb_ts_disconnected(struct gsm_bts_trx_ts *ts) switch (ts->pchan) { case GSM_PCHAN_TCH_F_PDCH: return ipacc_dyn_pdch_ts_disconnected(ts); - case GSM_PCHAN_TCH_F_TCH_H_PDCH: + case GSM_PCHAN_OSMO_DYN: return osmo_dyn_ts_disconnected(ts); default: return; @@ -2467,39 +3446,31 @@ void cb_ts_disconnected(struct gsm_bts_trx_ts *ts) static void ipacc_dyn_pdch_ts_connected(struct gsm_bts_trx_ts *ts, int rc) { if (rc) { - LOGP(DRSL, LOGL_NOTICE, "%s PDCH ACT IPA operation failed (%d) in bts model\n", - gsm_lchan_name(ts->lchan), rc); + LOGPLCHAN(ts->lchan, DRSL, LOGL_NOTICE, "PDCH ACT IPA operation failed (%d) in bts model\n", rc); ipacc_dyn_pdch_complete(ts, rc); return; } if (ts->flags & TS_F_PDCH_DEACT_PENDING) { - if (ts->lchan[0].type != GSM_LCHAN_TCH_F) - LOGP(DRSL, LOGL_ERROR, "%s PDCH DEACT error:" - " timeslot connected, so expecting" - " lchan type TCH/F, but is %s\n", - gsm_lchan_name(ts->lchan), - gsm_lchant_name(ts->lchan[0].type)); + if (ts->lchan[0].type != GSM_LCHAN_TCH_F) { + LOGPLCHAN(ts->lchan, DRSL, LOGL_ERROR, "PDCH DEACT error: timeslot connected, so " + "expecting lchan type TCH/F, but is %s\n", gsm_lchant_name(ts->lchan[0].type)); + } - LOGP(DRSL, LOGL_DEBUG, "%s PDCH DEACT operation:" - " timeslot connected as TCH/F\n", - gsm_lchan_name(ts->lchan)); + LOGPLCHAN(ts->lchan, DRSL, LOGL_DEBUG, "PDCH DEACT operation: timeslot connected as TCH/F\n"); /* During PDCH DEACT, we're done right after the TCH/F came * back up. */ ipacc_dyn_pdch_complete(ts, 0); } else if (ts->flags & TS_F_PDCH_ACT_PENDING) { - if (ts->lchan[0].type != GSM_LCHAN_PDTCH) - LOGP(DRSL, LOGL_ERROR, "%s PDCH ACT error:" - " timeslot connected, so expecting" - " lchan type PDTCH, but is %s\n", - gsm_lchan_name(ts->lchan), + if (ts->lchan[0].type != GSM_LCHAN_PDTCH) { + LOGPLCHAN(ts->lchan, DRSL, LOGL_ERROR, "PDCH ACT error: timeslot connected, " + "so expecting lchan type PDTCH, but is %s\n", gsm_lchant_name(ts->lchan[0].type)); + } - LOGP(DRSL, LOGL_DEBUG, "%s PDCH ACT operation:" - " timeslot connected as PDTCH\n", - gsm_lchan_name(ts->lchan)); + LOGPLCHAN(ts->lchan, DRSL, LOGL_DEBUG, "PDCH ACT operation: timeslot connected as PDTCH\n"); /* The PDTCH is connected, now tell the PCU about it. Except * when the PCU is not connected (yet), then there's nothing @@ -2522,30 +3493,31 @@ static void ipacc_dyn_pdch_ts_connected(struct gsm_bts_trx_ts *ts, int rc) static void osmo_dyn_ts_connected(struct gsm_bts_trx_ts *ts, int rc) { - struct msgb *msg = ts->dyn.pending_chan_activ; - ts->dyn.pending_chan_activ = NULL; + unsigned int ln; if (rc) { - LOGP(DRSL, LOGL_NOTICE, "%s PDCH ACT OSMO operation failed (%d) in bts model\n", - gsm_lchan_name(ts->lchan), rc); + LOGPLCHAN(ts->lchan, DRSL, LOGL_NOTICE, "PDCH ACT OSMO operation failed (%d) in bts model\n", rc); ipacc_dyn_pdch_complete(ts, rc); return; } - if (!msg) { - LOGP(DRSL, LOGL_ERROR, - "%s TS re-connected, but no chan activ msg pending\n", - gsm_ts_and_pchan_name(ts)); - return; - } - ts->dyn.pchan_is = ts->dyn.pchan_want; DEBUGP(DRSL, "%s Connected\n", gsm_ts_and_pchan_name(ts)); - /* continue where we left off before re-connecting the TS. */ - rc = rsl_rx_chan_activ(msg); - if (rc != 1) - msgb_free(msg); + /* Handle postponed RSL CHANnel ACTIVation messages (if any) */ + for (ln = 0; ln < ARRAY_SIZE(ts->lchan); ln++) { + struct gsm_lchan *lchan = &ts->lchan[ln]; + + if (lchan->pending_chan_activ == NULL) + continue; + + struct msgb *msg = lchan->pending_chan_activ; + lchan->pending_chan_activ = NULL; + + /* Continue where we left off before re-connecting the TS */ + if (rsl_rx_chan_activ(msg) != 1) + msgb_free(msg); + } } void cb_ts_connected(struct gsm_bts_trx_ts *ts, int rc) @@ -2555,7 +3527,7 @@ void cb_ts_connected(struct gsm_bts_trx_ts *ts, int rc) switch (ts->pchan) { case GSM_PCHAN_TCH_F_PDCH: return ipacc_dyn_pdch_ts_connected(ts, rc); - case GSM_PCHAN_TCH_F_TCH_H_PDCH: + case GSM_PCHAN_OSMO_DYN: return osmo_dyn_ts_connected(ts, rc); default: return; @@ -2569,10 +3541,10 @@ void ipacc_dyn_pdch_complete(struct gsm_bts_trx_ts *ts, int rc) pdch_act = ts->flags & TS_F_PDCH_ACT_PENDING; - if ((ts->flags & TS_F_PDCH_PENDING_MASK) == TS_F_PDCH_PENDING_MASK) - LOGP(DRSL, LOGL_ERROR, - "%s Internal Error: both PDCH ACT and PDCH DEACT pending\n", - gsm_lchan_name(ts->lchan)); + if ((ts->flags & TS_F_PDCH_PENDING_MASK) == TS_F_PDCH_PENDING_MASK) { + LOGPLCHAN(ts->lchan, DRSL, LOGL_ERROR, + "Internal Error: both PDCH ACT and PDCH DEACT pending\n"); + } ts->flags &= ~TS_F_PDCH_PENDING_MASK; @@ -2588,9 +3560,8 @@ void ipacc_dyn_pdch_complete(struct gsm_bts_trx_ts *ts, int rc) ts->flags |= TS_F_PDCH_ACTIVE; else ts->flags &= ~TS_F_PDCH_ACTIVE; - DEBUGP(DRSL, "%s %s switched to %s mode (ts->flags == %x)\n", - gsm_lchan_name(ts->lchan), gsm_pchan_name(ts->pchan), - pdch_act? "PDCH" : "TCH/F", ts->flags); + LOGPLCHAN(ts->lchan, DRSL, LOGL_DEBUG, "%s switched to %s mode (ts->flags == %x)\n", + gsm_pchan_name(ts->pchan), pdch_act ? "PDCH" : "TCH/F", ts->flags); rc = rsl_tx_dyn_pdch_ack(ts->lchan, pdch_act); if (rc) @@ -2692,8 +3663,11 @@ static int rsl_rx_rll(struct gsm_bts_trx *trx, struct msgb *msg) return -1; } - DEBUGP(DRLL, "%s Rx RLL %s Abis -> LAPDm\n", gsm_lchan_name(lchan), - rsl_msg_name(rh->c.msg_type)); + /* VGCS Uplink is released by MSC using REL-REQ. */ + if (rh->c.msg_type == RSL_MT_REL_REQ) + vgcs_talker_reset(lchan, true); + + LOGPLCHAN(lchan, DRLL, LOGL_DEBUG, "Rx RLL %s Abis -> LAPDm\n", rsl_msg_name(rh->c.msg_type)); /* make copy of RLL header, as the message will be free'd in case of erroneous return */ rh2 = *rh; @@ -2788,15 +3762,15 @@ static int handle_gprs_susp_req(struct msgb *msg) int rc; if (!gh || msgb_l3len(msg) < sizeof(*gh)+sizeof(*gsr)) { - LOGP(DRSL, LOGL_NOTICE, "%s Short GPRS SUSPEND REQ received, ignoring\n", gsm_lchan_name(msg->lchan)); + LOGPLCHAN(msg->lchan, DRSL, LOGL_NOTICE, "Short GPRS SUSPEND REQ received, ignoring\n"); + msgb_free(msg); return -EINVAL; } gsr = (struct gsm48_gprs_susp_req *) gh->data; tlli = osmo_ntohl(gsr->tlli); - LOGP(DRSL, LOGL_INFO, "%s Fwd GPRS SUSPEND REQ for TLLI=0x%08x to PCU\n", - gsm_lchan_name(msg->lchan), tlli); + LOGPLCHAN(msg->lchan, DRSL, LOGL_INFO, "Fwd GPRS SUSPEND REQ for TLLI=0x%08x to PCU\n", tlli); rc = pcu_tx_susp_req(msg->lchan, tlli, gsr->ra_id, gsr->cause); msgb_free(msg); @@ -2804,16 +3778,6 @@ static int handle_gprs_susp_req(struct msgb *msg) return rc; } -static inline uint8_t ms_to2rsl(const struct gsm_lchan *lchan, const struct lapdm_entity *le) -{ - return (lchan->ms_t_offs >= 0) ? lchan->ms_t_offs : (lchan->p_offs - le->ta); -} - -static inline bool ms_to_valid(const struct gsm_lchan *lchan) -{ - return (lchan->ms_t_offs >= 0) || (lchan->p_offs >= 0); -} - struct osmo_bts_supp_meas_info { int16_t toa256_mean; int16_t toa256_min; @@ -2821,12 +3785,12 @@ struct osmo_bts_supp_meas_info { uint16_t toa256_std_dev; } __attribute__((packed)); -/* 8.4.8 MEASUREMENT RESult */ -static int rsl_tx_meas_res(struct gsm_lchan *lchan, uint8_t *l3, int l3_len, const struct lapdm_entity *le) +/* Compose and send 8.4.8 MEASUREMENT RESult via RSL. (timing_offset=-1 -> not present) */ +int rsl_tx_meas_res(struct gsm_lchan *lchan, const uint8_t *l3, unsigned int l3_len, int timing_offset) { struct msgb *msg; uint8_t meas_res[16]; - uint8_t chan_nr = gsm_lchan2chan_nr(lchan); + uint8_t chan_nr = gsm_lchan2chan_nr_rsl(lchan); int res_valid = lchan->meas.flags & LC_UL_M_F_RES_VALID; struct gsm_bts *bts = lchan->ts->trx->bts; @@ -2841,20 +3805,19 @@ static int rsl_tx_meas_res(struct gsm_lchan *lchan, uint8_t *l3, int l3_len, con return -ENOMEM; LOGPLCHAN(lchan, DRSL, LOGL_DEBUG, - "Send Meas RES: NUM:%u, RXLEV_FULL:%u, RXLEV_SUB:%u, RXQUAL_FULL:%u, RXQUAL_SUB:%u, MS_PWR:%u, UL_TA:%u, L3_LEN:%d, TimingOff:%u\n", + "Send Meas RES: NUM:%u, RXLEV_FULL:%u, RXLEV_SUB:%u, RXQUAL_FULL:%u, RXQUAL_SUB:%u, MS_PWR:%u, UL_TA:%u, L3_LEN:%u, TimingOff:%u\n", lchan->meas.res_nr, lchan->meas.ul_res.full.rx_lev, lchan->meas.ul_res.sub.rx_lev, lchan->meas.ul_res.full.rx_qual, lchan->meas.ul_res.sub.rx_qual, - lchan->meas.l1_info[0], - lchan->meas.l1_info[1], l3_len, ms_to2rsl(lchan, le) - MEAS_MAX_TIMING_ADVANCE); + lchan->meas.l1_info.ms_pwr, + lchan->meas.l1_info.ta, l3_len, timing_offset - MEAS_MAX_TIMING_ADVANCE); - msgb_tv_put(msg, RSL_IE_MEAS_RES_NR, lchan->meas.res_nr++); + msgb_tv_put(msg, RSL_IE_MEAS_RES_NR, lchan->meas.res_nr); size_t ie_len = gsm0858_rsl_ul_meas_enc(&lchan->meas.ul_res, lchan->tch.dtx.dl_active, meas_res); - lchan->tch.dtx.dl_active = false; if (ie_len >= 3) { if (bts->supp_meas_toa256 && lchan->meas.flags & LC_UL_M_F_OSMO_EXT_VALID) { struct osmo_bts_supp_meas_info *smi; @@ -2867,26 +3830,23 @@ static int rsl_tx_meas_res(struct gsm_lchan *lchan, uint8_t *l3, int l3_len, con * to know the total propagation time between MS and BTS, we need to add * the actual TA value applied by the MS plus the respective toa256 value in * 1/256 symbol periods. */ - int16_t ta256 = lchan_get_ta(lchan) * 256; + int16_t ta256 = lchan->meas.l1_info.ta * 256; smi->toa256_mean = htons(ta256 + lchan->meas.ms_toa256); smi->toa256_min = htons(ta256 + lchan->meas.ext.toa256_min); smi->toa256_max = htons(ta256 + lchan->meas.ext.toa256_max); smi->toa256_std_dev = htons(lchan->meas.ext.toa256_std_dev); - lchan->meas.flags &= ~LC_UL_M_F_OSMO_EXT_VALID; } msgb_tlv_put(msg, RSL_IE_UPLINK_MEAS, ie_len, meas_res); - lchan->meas.flags &= ~LC_UL_M_F_RES_VALID; } - msgb_tv_put(msg, RSL_IE_BS_POWER, lchan->meas.bts_tx_pwr); + msgb_tv_put(msg, RSL_IE_BS_POWER, lchan->bs_power_ctrl.current / 2); if (lchan->meas.flags & LC_UL_M_F_L1_VALID) { - msgb_tv_fixed_put(msg, RSL_IE_L1_INFO, 2, lchan->meas.l1_info); - lchan->meas.flags &= ~LC_UL_M_F_L1_VALID; + msgb_tv_fixed_put(msg, RSL_IE_L1_INFO, sizeof(lchan->meas.l1_info), (uint8_t*)&lchan->meas.l1_info); } - msgb_tl16v_put(msg, RSL_IE_L3_INFO, l3_len, l3); - if (ms_to_valid(lchan)) { - msgb_tv_put(msg, RSL_IE_MS_TIMING_OFFSET, ms_to2rsl(lchan, le)); - lchan->ms_t_offs = -1; - lchan->p_offs = -1; + + if (l3 && l3_len > 0) { + msgb_tl16v_put(msg, RSL_IE_L3_INFO, l3_len, l3); + if (timing_offset != -1) + msgb_tv_put(msg, RSL_IE_MS_TIMING_OFFSET, timing_offset); } rsl_dch_push_hdr(msg, RSL_MT_MEAS_RES, chan_nr); @@ -2914,22 +3874,42 @@ int lapdm_rll_tx_cb(struct msgb *msg, struct lapdm_entity *le, void *ctx) msg->trx = lchan->ts->trx; msg->lchan = lchan; - /* check if this is a measurement report from SACCH which needs special - * processing before forwarding */ - if (rslms_is_meas_rep(msg)) { - int rc; + /* If DL estabishment on main signaling link and SAPI 0 with L3 info is expected. */ + if (lchan->l3_info_estab && rh->msg_type == RSL_MT_EST_IND) { + struct abis_rsl_rll_hdr *rllh = msgb_l2(msg); + if ((rllh->link_id & 0xc7) == 0) { + /* Reject initial establishment without L3 info. */ + if (msgb_l2len(msg) == sizeof(struct abis_rsl_rll_hdr)) { + LOGPLCHAN(lchan, DRSL, LOGL_ERROR, "RLL EST IND without contention resolution.\n"); + /* Release normally, re-use the msgb. */ + rh->msg_type = RSL_MT_REL_REQ; + msgb_tv_put(msg, RSL_IE_RELEASE_MODE, RSL_REL_NORMAL); + return rsl_rx_rll(lchan->ts->trx, msg); + } + /* Re-estabishment without contention resoltuion is allowed. */ + lchan->l3_info_estab = false; + } + } - LOGPLCHAN(lchan, DRSL, LOGL_INFO, "Handing RLL msg %s from LAPDm to MEAS REP\n", + /* If this is a Measurement Report, then we simply ignore it, + * because it has already been processed in l1sap_ph_data_ind(). */ + if (rslms_is_meas_rep(msg)) { + msgb_free(msg); + return 0; + } else if (rslms_is_gprs_susp_req(msg)) { + return handle_gprs_susp_req(msg); + } else { + LOGPLCHAN(lchan, DRSL, LOGL_INFO, "Fwd RLL msg %s from LAPDm to A-bis\n", rsl_msg_name(rh->msg_type)); /* REL_IND handling */ - if (rh->msg_type == RSL_MT_REL_IND && - (lchan->type == GSM_LCHAN_TCH_F || lchan->type == GSM_LCHAN_TCH_H)) { + if (rh->msg_type == RSL_MT_REL_IND && lchan_is_tch(lchan)) { + vgcs_talker_reset(lchan, true); LOGPLCHAN(lchan, DRSL, LOGL_INFO, "Scheduling %s to L3 in next associated TCH-RTS.ind\n", rsl_msg_name(rh->msg_type)); - if(lchan->pending_rel_ind_msg) { + if (lchan->pending_rel_ind_msg) { LOGPLCHAN(lchan, DRSL, LOGL_INFO, "Dropping pending release indication message\n"); msgb_free(lchan->pending_rel_ind_msg); @@ -2939,15 +3919,6 @@ int lapdm_rll_tx_cb(struct msgb *msg, struct lapdm_entity *le, void *ctx) return 0; } - rc = rsl_tx_meas_res(lchan, msgb_l3(msg), msgb_l3len(msg), le); - msgb_free(msg); - return rc; - } else if (rslms_is_gprs_susp_req(msg)) { - return handle_gprs_susp_req(msg); - } else { - LOGPLCHAN(lchan, DRSL, LOGL_INFO, "Fwd RLL msg %s from LAPDm to A-bis\n", - rsl_msg_name(rh->msg_type)); - return abis_bts_rsl_sendmsg(msg); } } @@ -2994,8 +3965,10 @@ static int rsl_rx_cchan(struct gsm_bts_trx *trx, struct msgb *msg) case RSL_MT_SMS_BC_CMD: ret = rsl_rx_sms_bcast_cmd(trx, msg); break; - case RSL_MT_SMS_BC_REQ: case RSL_MT_NOT_CMD: + ret = rsl_rx_notification_cmd(trx, msg); + break; + case RSL_MT_SMS_BC_REQ: LOGPLCHAN(msg->lchan, DRSL, LOGL_NOTICE, "unimplemented RSL cchan msg_type %s\n", rsl_msg_name(cch->c.msg_type)); rsl_tx_error_report(trx, RSL_ERR_MSG_TYPE, &cch->chan_nr, NULL, msg); @@ -3003,6 +3976,10 @@ static int rsl_rx_cchan(struct gsm_bts_trx *trx, struct msgb *msg) case RSL_MT_OSMO_ETWS_CMD: ret = rsl_rx_osmo_etws_cmd(trx, msg); break; + /* Osmocom specific extension for BCCH carrier power reduction */ + case RSL_MT_BS_POWER_CONTROL: + ret = rsl_rx_bs_pwr_ctrl(msg); + break; default: LOGPLCHAN(msg->lchan, DRSL, LOGL_NOTICE, "undefined RSL cchan msg_type 0x%02x\n", cch->c.msg_type); @@ -3113,6 +4090,9 @@ static int rsl_rx_trx(struct gsm_bts_trx *trx, struct msgb *msg) case RSL_MT_SACCH_FILL: ret = rsl_rx_sacch_fill(trx, msg); break; + case RSL_MT_IPAC_MEAS_PREPROC_DFT: + ret = rsl_rx_meas_preproc_dft(trx, msg); + break; default: LOGP(DRSL, LOGL_NOTICE, "undefined RSL TRX msg_type 0x%02x\n", th->msg_type); @@ -3171,14 +4151,6 @@ static int rsl_rx_ipaccess(struct gsm_bts_trx *trx, struct msgb *msg) return ret; } -int lchan_deactivate(struct gsm_lchan *lchan) -{ - OSMO_ASSERT(lchan); - - lchan->ciph_state = 0; - return bts_model_lchan_deactivate(lchan); -} - int down_rsl(struct gsm_bts_trx *trx, struct msgb *msg) { struct abis_rsl_common_hdr *rslh; diff --git a/src/common/rtp_input_preen.c b/src/common/rtp_input_preen.c new file mode 100644 index 00000000..addd55bd --- /dev/null +++ b/src/common/rtp_input_preen.c @@ -0,0 +1,179 @@ +/* + * This module implements a helper function for the RTP input path: + * validates incoming RTP payloads, makes the accept-or-drop decision, + * and for some codecs signals additional required actions such as + * dropping one header octet. + * + * Author: Mychaela N. Falconia <falcon@freecalypso.org>, 2023 - however, + * Mother Mychaela's contributions are NOT subject to copyright. + * No rights reserved, all rights relinquished. + * + * 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 <stdint.h> + +#include <osmocom/core/logging.h> +#include <osmocom/core/utils.h> + +#include <osmocom/codec/codec.h> + +#include <osmo-bts/lchan.h> +#include <osmo-bts/logging.h> +#include <osmo-bts/rtp_input_preen.h> + +static bool amr_is_octet_aligned(const uint8_t *rtp_pl, unsigned rtp_pl_len) +{ + /* + * Logic: If 1st bit padding is not zero, packet is either: + * - bandwidth-efficient AMR payload. + * - malformed packet. + * However, Bandwidth-efficient AMR 4,75 frame last in payload(F=0, FT=0) + * with 4th,5ht,6th AMR payload to 0 matches padding==0. + * Furthermore, both AMR 4,75 bw-efficient and octet alignment are 14 bytes long (AMR 4,75 encodes 95b): + * bw-efficient: 95b, + 4b hdr + 6b ToC = 105b, + padding = 112b = 14B. + * octet-aligned: 1B hdr + 1B ToC + 95b = 111b, + padding = 112b = 14B. + * We cannot use other fields to match since they are inside the AMR + * payload bits which are unknown. + * As a result, this function may return false positive (true) for some AMR + * 4,75 AMR frames, but given the length, CMR and FT read is the same as a + * consequence, the damage in here is harmless other than being unable to + * decode the audio at the other side. + */ + #define AMR_PADDING1(rtp_pl) (rtp_pl[0] & 0x0f) + #define AMR_PADDING2(rtp_pl) (rtp_pl[1] & 0x03) + + if (rtp_pl_len < 2 || AMR_PADDING1(rtp_pl) || AMR_PADDING2(rtp_pl)) + return false; + + return true; +} + +static enum pl_input_decision +input_preen_fr(const uint8_t *rtp_pl, unsigned rtp_pl_len) +{ + switch (rtp_pl_len) { + case GSM_FR_BYTES: /* standard TS 101 318 or RFC 3551 format */ + /* magic must be correct */ + if ((rtp_pl[0] & 0xF0) != 0xD0) + return PL_DECISION_DROP; + return PL_DECISION_ACCEPT; + case GSM_FR_BYTES+1: /* Themyscira TW-TS-001 format */ + /* TEH octet must be correct, and not a BFI */ + if ((rtp_pl[0] & 0xF6) != 0xE0) + return PL_DECISION_DROP; + /* standard FR magic must be correct too */ + if ((rtp_pl[1] & 0xF0) != 0xD0) + return PL_DECISION_DROP; + /* Strip TEH octet, leaving only standard FR payload. */ + return PL_DECISION_STRIP_HDR_OCTET; + default: + /* invalid payload */ + return PL_DECISION_DROP; + } +} + +static enum pl_input_decision +input_preen_efr(const uint8_t *rtp_pl, unsigned rtp_pl_len) +{ + switch (rtp_pl_len) { + case GSM_EFR_BYTES: /* standard TS 101 318 or RFC 3551 format */ + /* magic must be correct */ + if ((rtp_pl[0] & 0xF0) != 0xC0) + return PL_DECISION_DROP; + return PL_DECISION_ACCEPT; + case GSM_EFR_BYTES+1: /* Themyscira TW-TS-001 format */ + /* TEH octet must be correct, and not a BFI */ + if ((rtp_pl[0] & 0xF6) != 0xE0) + return PL_DECISION_DROP; + /* standard EFR magic must be correct too */ + if ((rtp_pl[1] & 0xF0) != 0xC0) + return PL_DECISION_DROP; + /* Strip TEH octet, leaving only standard EFR payload. */ + return PL_DECISION_STRIP_HDR_OCTET; + default: + /* invalid payload */ + return PL_DECISION_DROP; + } +} + +static enum pl_input_decision +input_preen_hr(const uint8_t *rtp_pl, unsigned rtp_pl_len, + bool *rfc5993_sid_flag) +{ + switch (rtp_pl_len) { + case GSM_HR_BYTES: + /* RTP input matches our internal format - we are good */ + return PL_DECISION_ACCEPT; + case GSM_HR_BYTES_RTP_RFC5993: + /* Validate ToC octet: for payload of this length to be valid, + * the F bit must be 0 and the FT field must be either 0 (good + * speech) or 2 (good SID). */ + switch (rtp_pl[0] & 0xF0) { + case 0x00: + break; + case 0x20: + *rfc5993_sid_flag = true; + break; + default: + /* invalid payload */ + return PL_DECISION_DROP; + } + /* Strip ToC octet, leaving only "pure" TS 101 318 payload. */ + return PL_DECISION_STRIP_HDR_OCTET; + default: + /* invalid payload */ + return PL_DECISION_DROP; + } +} + +enum pl_input_decision +rtp_payload_input_preen(struct gsm_lchan *lchan, const uint8_t *rtp_pl, + unsigned rtp_pl_len, bool *rfc5993_sid_flag) +{ + /* If rtp continuous-streaming is enabled, we shall emit RTP packets + * with zero-length payloads as BFI markers. In a TrFO scenario such + * RTP packets sent by call leg A will be received by call leg B, + * hence we need to handle them gracefully. For the purposes of a BTS + * that runs on its own TDMA timing and does not need timing ticks from + * an incoming RTP stream, the correct action upon receiving such + * timing-tick-only RTP packets should be the same as when receiving + * no RTP packet at all. The simplest way to produce that behavior + * is to treat zero-length RTP payloads as invalid. */ + if (rtp_pl_len == 0) + return PL_DECISION_DROP; + + switch (lchan->tch_mode) { + case GSM48_CMODE_SPEECH_V1: + if (lchan->type == GSM_LCHAN_TCH_F) + return input_preen_fr(rtp_pl, rtp_pl_len); + else + return input_preen_hr(rtp_pl, rtp_pl_len, rfc5993_sid_flag); + case GSM48_CMODE_SPEECH_EFR: + return input_preen_efr(rtp_pl, rtp_pl_len); + case GSM48_CMODE_SPEECH_AMR: + /* Avoid forwarding bw-efficient AMR to lower layers, + * most bts models don't support it. */ + if (!amr_is_octet_aligned(rtp_pl, rtp_pl_len)) { + LOGPLCHAN(lchan, DL1P, LOGL_NOTICE, + "RTP->L1: Dropping unexpected AMR encoding (bw-efficient?) %s\n", + osmo_hexdump(rtp_pl, rtp_pl_len)); + return PL_DECISION_DROP; + } + return PL_DECISION_ACCEPT; + default: + return PL_DECISION_ACCEPT; + } +} diff --git a/src/common/scheduler.c b/src/common/scheduler.c index 95a1b00e..a449d167 100644 --- a/src/common/scheduler.c +++ b/src/common/scheduler.c @@ -3,6 +3,7 @@ /* (C) 2013 by Andreas Eversberg <jolly@eversberg.eu> * (C) 2015 by Alexander Chemeris <Alexander.Chemeris@fairwaves.co> * (C) 2015 by Harald Welte <laforge@gnumonks.org> + * Contributions by sysmocom - s.f.m.c. GmbH * * All Rights Reserved * @@ -14,7 +15,7 @@ * 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. + * 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/>. @@ -29,8 +30,12 @@ #include <osmocom/core/msgb.h> #include <osmocom/core/talloc.h> #include <osmocom/core/bits.h> +#include <osmocom/core/utils.h> +#include <osmocom/core/rate_ctr.h> +#include <osmocom/core/stats.h> #include <osmocom/gsm/protocol/gsm_08_58.h> +#include <osmocom/gsm/gsm0502.h> #include <osmocom/gsm/a5.h> #include <osmo-bts/gsm_data.h> @@ -39,17 +44,16 @@ #include <osmo-bts/l1sap.h> #include <osmo-bts/scheduler.h> #include <osmo-bts/scheduler_backend.h> +#include <osmo-bts/bts.h> extern void *tall_bts_ctx; -static int rts_data_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, - enum trx_chan_type chan); -static int rts_tchf_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, - enum trx_chan_type chan); -static int rts_tchh_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, - enum trx_chan_type chan); +static int rts_data_fn(const struct l1sched_ts *l1ts, const struct trx_dl_burst_req *br); +static int rts_tchf_fn(const struct l1sched_ts *l1ts, const struct trx_dl_burst_req *br); +static int rts_tchh_fn(const struct l1sched_ts *l1ts, const struct trx_dl_burst_req *br); + /*! \brief Dummy Burst (TS 05.02 Chapter 5.2.6) */ -static const ubit_t dummy_burst[GSM_BURST_LEN] = { +const ubit_t _sched_dummy_burst[GSM_BURST_LEN] = { 0,0,0, 1,1,1,1,1,0,1,1,0,1,1,1,0,1,1,0,0,0,0,0,1,0,1,0,0,1,0,0,1,1,1,0, 0,0,0,0,1,0,0,1,0,0,0,1,0,0,0,0,0,0,0,1,1,1,1,1,0,0,0,1,1,1,0,0, @@ -59,28 +63,51 @@ static const ubit_t dummy_burst[GSM_BURST_LEN] = { 0,0,0, }; -/*! \brief FCCH Burst (TS 05.02 Chapter 5.2.4) */ -const ubit_t _sched_fcch_burst[GSM_BURST_LEN] = { - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -}; - -/*! \brief Training Sequences (TS 05.02 Chapter 5.2.3) */ -const ubit_t _sched_tsc[8][26] = { - { 0,0,1,0,0,1,0,1,1,1,0,0,0,0,1,0,0,0,1,0,0,1,0,1,1,1, }, - { 0,0,1,0,1,1,0,1,1,1,0,1,1,1,1,0,0,0,1,0,1,1,0,1,1,1, }, - { 0,1,0,0,0,0,1,1,1,0,1,1,1,0,1,0,0,1,0,0,0,0,1,1,1,0, }, - { 0,1,0,0,0,1,1,1,1,0,1,1,0,1,0,0,0,1,0,0,0,1,1,1,1,0, }, - { 0,0,0,1,1,0,1,0,1,1,1,0,0,1,0,0,0,0,0,1,1,0,1,0,1,1, }, - { 0,1,0,0,1,1,1,0,1,0,1,1,0,0,0,0,0,1,0,0,1,1,1,0,1,0, }, - { 1,0,1,0,0,1,1,1,1,1,0,1,1,0,0,0,1,0,1,0,0,1,1,1,1,1, }, - { 1,1,1,0,1,1,1,1,0,0,0,1,0,0,1,0,1,1,1,0,1,1,1,1,0,0, }, +/*! Training Sequences for Normal Burst (see 3GPP TS 45.002, section 5.2.3) */ +const ubit_t _sched_train_seq_gmsk_nb[4][8][26] = { + { /* TSC set 1, table 5.2.3a */ + { 0,0,1,0,0,1,0,1,1,1,0,0,0,0,1,0,0,0,1,0,0,1,0,1,1,1 }, + { 0,0,1,0,1,1,0,1,1,1,0,1,1,1,1,0,0,0,1,0,1,1,0,1,1,1 }, + { 0,1,0,0,0,0,1,1,1,0,1,1,1,0,1,0,0,1,0,0,0,0,1,1,1,0 }, + { 0,1,0,0,0,1,1,1,1,0,1,1,0,1,0,0,0,1,0,0,0,1,1,1,1,0 }, + { 0,0,0,1,1,0,1,0,1,1,1,0,0,1,0,0,0,0,0,1,1,0,1,0,1,1 }, + { 0,1,0,0,1,1,1,0,1,0,1,1,0,0,0,0,0,1,0,0,1,1,1,0,1,0 }, + { 1,0,1,0,0,1,1,1,1,1,0,1,1,0,0,0,1,0,1,0,0,1,1,1,1,1 }, + { 1,1,1,0,1,1,1,1,0,0,0,1,0,0,1,0,1,1,1,0,1,1,1,1,0,0 }, + }, + { /* TSC set 2, table 5.2.3b */ + { 0,1,1,0,0,0,1,0,0,0,1,0,0,1,0,0,1,1,1,1,0,1,0,1,1,1 }, + { 0,1,0,1,1,1,1,0,1,0,0,1,1,0,1,1,1,0,1,1,1,0,0,0,0,1 }, + { 0,1,0,0,0,0,0,1,0,1,1,0,0,0,1,1,1,0,1,1,1,0,1,1,0,0 }, + { 0,0,1,0,1,1,0,1,1,1,0,1,1,1,0,0,1,1,1,1,0,1,0,0,0,0 }, + { 0,1,1,1,0,1,0,0,1,1,1,1,0,1,0,0,1,1,1,0,1,1,1,1,1,0 }, + { 0,1,0,0,0,0,0,1,0,0,1,1,0,1,0,1,0,0,1,1,1,1,0,0,1,1 }, + { 0,0,0,1,0,0,0,0,1,1,0,1,0,0,0,0,1,1,0,1,1,1,0,1,0,1 }, + { 0,1,0,0,0,1,0,1,1,1,0,0,1,1,1,1,1,1,0,0,1,0,1,0,0,1 }, + }, + { /* TSC set 3, table 5.2.3c */ + { 1,1,0,0,0,0,1,0,0,1,0,0,0,1,1,1,1,0,1,0,1,0,0,0,1,0 }, + { 0,0,1,0,1,1,1,1,1,0,0,0,1,0,0,1,0,1,0,0,0,0,1,0,0,0 }, + { 1,1,0,0,1,0,0,0,1,1,1,1,1,0,1,1,1,0,1,0,1,1,0,1,1,0 }, + { 0,0,1,1,0,0,0,0,1,0,1,0,0,1,1,0,0,0,0,0,1,0,1,1,0,0 }, + { 0,0,0,1,1,1,1,0,1,0,1,1,1,0,1,0,0,0,0,1,0,0,0,1,1,0 }, + { 1,1,0,0,1,1,1,1,0,1,0,1,0,1,1,1,1,0,0,1,0,0,0,0,0,0 }, + { 1,0,1,1,1,0,0,1,1,0,1,0,1,1,1,1,1,1,0,0,0,1,0,0,0,0 }, + { 1,1,1,0,0,1,0,1,1,1,1,0,1,1,1,0,0,0,0,0,1,0,0,1,0,0 }, + }, + { /* TSC set 4, table 5.2.3d */ + { 1,1,0,0,1,1,1,0,1,0,0,0,0,0,1,0,0,0,1,1,0,1,0,0,0,0 }, + { 0,1,1,0,0,0,1,0,0,0,0,1,0,1,0,0,0,1,0,1,1,1,0,0,0,0 }, + { 1,1,1,0,0,1,0,0,0,0,0,1,0,1,0,1,0,0,1,1,1,0,0,0,0,0 }, + { 0,1,1,0,1,1,0,0,1,1,1,1,1,0,1,0,1,0,0,0,0,1,1,0,0,0 }, + { 1,1,0,1,1,0,0,0,0,1,0,0,0,0,1,0,0,0,1,0,1,1,0,0,0,0 }, + { 1,1,0,1,0,0,1,1,1,1,1,1,1,0,1,0,0,0,1,1,0,1,0,1,1,0 }, + { 0,0,1,0,0,1,1,1,1,1,1,1,0,0,1,0,1,0,1,0,1,1,0,0,0,0 }, + { 0,1,0,1,1,1,0,0,0,0,0,0,1,0,1,0,0,1,1,0,0,0,1,1,1,0 }, + }, }; -const ubit_t _sched_egprs_tsc[8][78] = { +const ubit_t _sched_train_seq_8psk_nb[8][78] = { { 1,1,1,1,1,1,0,0,1,1,1,1,1,1,1,0,0,1,1,1,1,0,0,1,0,0, 1,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,1,1,1,1,1,1,1,1, 1,1,0,0,1,1,1,1,1,1,1,0,0,1,1,1,1,0,0,1,0,0,1,0,0,1, }, @@ -108,7 +135,7 @@ const ubit_t _sched_egprs_tsc[8][78] = { }; /*! \brief SCH training sequence (TS 05.02 Chapter 5.2.5) */ -const ubit_t _sched_sch_train[64] = { +const ubit_t _sched_train_seq_gmsk_sb[64] = { 1,0,1,1,1,0,0,1,0,1,1,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,1,1,1,1, 0,0,1,0,1,1,0,1,0,1,0,0,0,1,0,1,0,1,1,1,0,1,1,0,0,0,0,1,1,0,1,1, }; @@ -118,18 +145,12 @@ const struct trx_chan_desc trx_chan_desc[_TRX_CHAN_MAX] = { [TRXC_IDLE] = { .name = "IDLE", .desc = "Idle channel", - - /* On C0, BTS needs to ensure discontinuous burst transmission. - * Therefore we need to send dummy bursts on IDLE slots. */ - .flags = TRX_CHAN_FLAG_AUTO_ACTIVE, - .dl_fn = tx_idle_fn, }, [TRXC_FCCH] = { .name = "FCCH", /* 3GPP TS 05.02, section 3.3.2.1 */ .desc = "Frequency correction channel", /* Tx only, frequency correction bursts */ - .flags = TRX_CHAN_FLAG_AUTO_ACTIVE, .dl_fn = tx_fcch_fn, }, [TRXC_SCH] = { @@ -137,7 +158,6 @@ const struct trx_chan_desc trx_chan_desc[_TRX_CHAN_MAX] = { .desc = "Synchronization channel", /* Tx only, synchronization bursts */ - .flags = TRX_CHAN_FLAG_AUTO_ACTIVE, .dl_fn = tx_sch_fn, }, [TRXC_BCCH] = { @@ -148,7 +168,6 @@ const struct trx_chan_desc trx_chan_desc[_TRX_CHAN_MAX] = { /* Tx only, xCCH convolutional coding (3GPP TS 05.03, section 4.4), * regular interleaving (3GPP TS 05.02, clause 7, table 3): * a L2 frame is interleaved over 4 consecutive bursts. */ - .flags = TRX_CHAN_FLAG_AUTO_ACTIVE, .rts_fn = rts_data_fn, .dl_fn = tx_data_fn, }, @@ -158,7 +177,6 @@ const struct trx_chan_desc trx_chan_desc[_TRX_CHAN_MAX] = { .chan_nr = RSL_CHAN_RACH, /* Rx only, RACH convolutional coding (3GPP TS 05.03, section 4.6). */ - .flags = TRX_CHAN_FLAG_AUTO_ACTIVE, .ul_fn = rx_rach_fn, }, [TRXC_CCCH] = { @@ -169,7 +187,6 @@ const struct trx_chan_desc trx_chan_desc[_TRX_CHAN_MAX] = { /* Tx only, xCCH convolutional coding (3GPP TS 05.03, section 4.4), * regular interleaving (3GPP TS 05.02, clause 7, table 3): * a L2 frame is interleaved over 4 consecutive bursts. */ - .flags = TRX_CHAN_FLAG_AUTO_ACTIVE, .rts_fn = rts_data_fn, .dl_fn = tx_data_fn, }, @@ -200,13 +217,14 @@ const struct trx_chan_desc trx_chan_desc[_TRX_CHAN_MAX] = { /* Rx and Tx, multiple convolutional coding types (3GPP TS 05.03, * chapter 3), block diagonal interleaving (3GPP TS 05.02, clause 7): * - * - a traffic frame is interleaved over 6 consecutive bursts + * - a traffic frame is interleaved over 4 consecutive bursts * using the even numbered bits of the first 2 bursts, - * all bits of the middle two 2 bursts, * and odd numbered bits of the last 2 bursts; * - a FACCH/H frame 'steals' (replaces) two traffic frames, - * interleaving is done over 4 consecutive bursts, - * the same as given for a TCH/FS. */ + * interleaving is done over 6 consecutive bursts, + * using the even numbered bits of the first 2 bursts, + * all bits of the middle two 2 bursts, + * and odd numbered bits of the last 2 bursts. */ .rts_fn = rts_tchh_fn, .dl_fn = tx_tchh_fn, .ul_fn = rx_tchh_fn, @@ -525,10 +543,7 @@ const struct trx_chan_desc trx_chan_desc[_TRX_CHAN_MAX] = { .chan_nr = RSL_CHAN_OSMO_PDCH, /* Rx and Tx, multiple coding schemes: CS-2..4 and MCS-1..9 (3GPP TS - * 05.03, chapter 5), regular interleaving as specified for xCCH. - * NOTE: the burst buffer is three times bigger because the - * payload of EDGE bursts is three times longer. */ - .flags = TRX_CHAN_FLAG_PDCH, + * 05.03, chapter 5), regular interleaving as specified for xCCH. */ .rts_fn = rts_data_fn, .dl_fn = tx_pdtch_fn, .ul_fn = rx_pdtch_fn, @@ -538,11 +553,14 @@ const struct trx_chan_desc trx_chan_desc[_TRX_CHAN_MAX] = { .desc = "Packet Timing advance control channel", .chan_nr = RSL_CHAN_OSMO_PDCH, - /* Same as for TRXC_BCCH (xCCH), see above. */ - .flags = TRX_CHAN_FLAG_PDCH, + /* On the Uplink, mobile stations transmit random Access Bursts + * to allow estimation of the timing advance for one MS in packet + * transfer mode. On Downlink, the network sends timing advance + * updates for several mobile stations. The coding scheme used + * for PTCCH/D messages is the same as for PDTCH CS-1. */ .rts_fn = rts_data_fn, - .dl_fn = tx_data_fn, - .ul_fn = rx_data_fn, + .dl_fn = tx_pdtch_fn, + .ul_fn = rx_rach_fn, }, [TRXC_CBCH] = { /* TODO: distinguish CBCH on SDCCH/4 and SDCCH/8 */ @@ -551,163 +569,203 @@ const struct trx_chan_desc trx_chan_desc[_TRX_CHAN_MAX] = { .chan_nr = RSL_CHAN_OSMO_CBCH4, /* Tx only, same as for TRXC_BCCH (xCCH), see above. */ - .flags = TRX_CHAN_FLAG_AUTO_ACTIVE, .rts_fn = rts_data_fn, .dl_fn = tx_data_fn, }, }; +enum { + L1SCHED_TS_CTR_DL_LATE, + L1SCHED_TS_CTR_DL_NOT_FOUND, +}; + +static const struct rate_ctr_desc l1sched_ts_ctr_desc[] = { + [L1SCHED_TS_CTR_DL_LATE] = {"l1sched_ts:dl_late", "Downlink frames arrived too late to submit to lower layers"}, + [L1SCHED_TS_CTR_DL_NOT_FOUND] = {"l1sched_ts:dl_not_found", "Downlink frames not found while scheduling"}, +}; +static const struct rate_ctr_group_desc l1sched_ts_ctrg_desc = { + "l1sched_ts", + "L1 scheduler timeslot", + OSMO_STATS_CLASS_GLOBAL, + ARRAY_SIZE(l1sched_ts_ctr_desc), + l1sched_ts_ctr_desc +}; + /* * init / exit */ -int trx_sched_init(struct l1sched_trx *l1t, struct gsm_bts_trx *trx) +static void trx_sched_init_ts(struct gsm_bts_trx_ts *ts, + const unsigned int rate_ctr_idx) { - uint8_t tn; + struct l1sched_ts *l1ts; unsigned int i; + char name[128]; - if (!trx) - return -EINVAL; + l1ts = talloc_zero(ts->trx, struct l1sched_ts); + OSMO_ASSERT(l1ts != NULL); - l1t->trx = trx; + /* Link both structures */ + ts->priv = l1ts; + l1ts->ts = ts; - LOGP(DL1C, LOGL_NOTICE, "Init scheduler for trx=%u\n", l1t->trx->nr); + l1ts->ctrs = rate_ctr_group_alloc(ts->trx, + &l1sched_ts_ctrg_desc, + rate_ctr_idx); + snprintf(name, sizeof(name), "bts%u-trx%u-ts%u%s", + ts->trx->bts->nr, ts->trx->nr, ts->nr, + ts->vamos.is_shadow ? "-shadow" : ""); + rate_ctr_group_set_name(l1ts->ctrs, name); - for (tn = 0; tn < ARRAY_SIZE(l1t->ts); tn++) { - struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn); + INIT_LLIST_HEAD(&l1ts->dl_prims); - l1ts->mf_index = 0; - INIT_LLIST_HEAD(&l1ts->dl_prims); - for (i = 0; i < ARRAY_SIZE(l1ts->chan_state); i++) { - struct l1sched_chan_state *chan_state; - chan_state = &l1ts->chan_state[i]; - chan_state->active = 0; - } + for (i = 0; i < ARRAY_SIZE(l1ts->chan_state); i++) { + struct l1sched_chan_state *chan_state; + chan_state = &l1ts->chan_state[i]; + chan_state->active = false; } - - return 0; } -void trx_sched_exit(struct l1sched_trx *l1t) +void trx_sched_init(struct gsm_bts_trx *trx) { - struct gsm_bts_trx_ts *ts; - uint8_t tn; - int i; + unsigned int tn; - LOGP(DL1C, LOGL_NOTICE, "Exit scheduler for trx=%u\n", l1t->trx->nr); - - for (tn = 0; tn < ARRAY_SIZE(l1t->ts); tn++) { - struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn); - msgb_queue_flush(&l1ts->dl_prims); - for (i = 0; i < _TRX_CHAN_MAX; i++) { - struct l1sched_chan_state *chan_state; - chan_state = &l1ts->chan_state[i]; - if (chan_state->dl_bursts) { - talloc_free(chan_state->dl_bursts); - chan_state->dl_bursts = NULL; - } - if (chan_state->ul_bursts) { - talloc_free(chan_state->ul_bursts); - chan_state->ul_bursts = NULL; - } - } - /* clear lchan channel states */ - ts = &l1t->trx->ts[tn]; - for (i = 0; i < ARRAY_SIZE(ts->lchan); i++) - lchan_set_state(&ts->lchan[i], LCHAN_S_NONE); + OSMO_ASSERT(trx != NULL); + + LOGPTRX(trx, DL1C, LOGL_DEBUG, "Init scheduler structures\n"); + + /* Allocate shadow timeslots */ + gsm_bts_trx_init_shadow_ts(trx); + + for (tn = 0; tn < ARRAY_SIZE(trx->ts); tn++) { + unsigned int rate_ctr_idx = trx->nr * 100 + tn; + struct gsm_bts_trx_ts *ts = &trx->ts[tn]; + + /* Init primary and shadow timeslots */ + trx_sched_init_ts(ts, rate_ctr_idx); + trx_sched_init_ts(ts->vamos.peer, rate_ctr_idx + 10); } } -/* close all logical channels and reset timeslots */ -void trx_sched_reset(struct l1sched_trx *l1t) +static void trx_sched_clean_ts(struct gsm_bts_trx_ts *ts) { - trx_sched_exit(l1t); - trx_sched_init(l1t, l1t->trx); + struct l1sched_ts *l1ts = ts->priv; + unsigned int i; + + msgb_queue_free(&l1ts->dl_prims); + rate_ctr_group_free(l1ts->ctrs); + l1ts->ctrs = NULL; + + /* clear lchan channel states */ + for (i = 0; i < ARRAY_SIZE(ts->lchan); i++) + lchan_set_state(&ts->lchan[i], LCHAN_S_NONE); + + talloc_free(l1ts); + ts->priv = NULL; +} + +void trx_sched_clean(struct gsm_bts_trx *trx) +{ + unsigned int tn; + + LOGPTRX(trx, DL1C, LOGL_DEBUG, "Clean scheduler structures\n"); + + for (tn = 0; tn < ARRAY_SIZE(trx->ts); tn++) { + struct gsm_bts_trx_ts *ts = &trx->ts[tn]; + + /* Clean primary and shadow timeslots */ + trx_sched_clean_ts(ts); + trx_sched_clean_ts(ts->vamos.peer); + } + + /* Free previously allocated shadow timeslots */ + gsm_bts_trx_free_shadow_ts(trx); } -struct msgb *_sched_dequeue_prim(struct l1sched_trx *l1t, int8_t tn, uint32_t fn, - enum trx_chan_type chan) +struct msgb *_sched_dequeue_prim(struct l1sched_ts *l1ts, const struct trx_dl_burst_req *br) { struct msgb *msg, *msg2; - struct osmo_phsap_prim *l1sap; - uint32_t prim_fn; + uint32_t prim_fn, l1sap_fn; uint8_t chan_nr, link_id; - struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn); /* get prim of current fn from queue */ llist_for_each_entry_safe(msg, msg2, &l1ts->dl_prims, list) { - l1sap = msgb_l1sap_prim(msg); - if (l1sap->oph.operation != PRIM_OP_REQUEST) { -wrong_type: - LOGL1S(DL1P, LOGL_ERROR, l1t, tn, chan, fn, "Prim has wrong type.\n"); -free_msg: - /* unlink and free message */ - llist_del(&msg->list); - msgb_free(msg); - return NULL; - } + struct osmo_phsap_prim *l1sap = msgb_l1sap_prim(msg); switch (l1sap->oph.primitive) { case PRIM_PH_DATA: chan_nr = l1sap->u.data.chan_nr; link_id = l1sap->u.data.link_id; - prim_fn = ((l1sap->u.data.fn + GSM_HYPERFRAME - fn) % GSM_HYPERFRAME); + l1sap_fn = l1sap->u.data.fn; break; case PRIM_TCH: chan_nr = l1sap->u.tch.chan_nr; link_id = 0; - prim_fn = ((l1sap->u.tch.fn + GSM_HYPERFRAME - fn) % GSM_HYPERFRAME); + l1sap_fn = l1sap->u.tch.fn; break; default: - goto wrong_type; + LOGL1SB(DL1P, LOGL_ERROR, l1ts, br, "Prim has wrong type.\n"); + goto free_msg; } - if (prim_fn > 100) { - LOGL1S(DL1P, LOGL_NOTICE, l1t, tn, chan, fn, - "Prim %u is out of range (100), or channel %s with " + prim_fn = GSM_TDMA_FN_SUB(l1sap_fn, br->fn); + if (prim_fn > 100) { /* l1sap_fn < fn */ + LOGL1SB(DL1P, LOGL_NOTICE, l1ts, br, + "Prim %u is out of range (%u vs exp %u), or channel %s with " "type %s is already disabled. If this happens in " "conjunction with PCU, increase 'rts-advance' by 5.\n", - prim_fn, get_lchan_by_chan_nr(l1t->trx, chan_nr)->name, - trx_chan_desc[chan].name); + prim_fn, l1sap_fn, br->fn, + get_lchan_by_chan_nr(l1ts->ts->trx, chan_nr)->name, + trx_chan_desc[br->chan].name); + rate_ctr_inc2(l1ts->ctrs, L1SCHED_TS_CTR_DL_LATE); /* unlink and free message */ llist_del(&msg->list); msgb_free(msg); continue; } - if (prim_fn > 0) - continue; + if (prim_fn > 0) /* l1sap_fn > fn */ + break; - goto found_msg; + /* l1sap_fn == fn */ + if ((chan_nr ^ (trx_chan_desc[br->chan].chan_nr | br->tn)) + || ((link_id & 0xc0) ^ trx_chan_desc[br->chan].link_id)) { + LOGL1SB(DL1P, LOGL_ERROR, l1ts, br, "Prim has wrong chan_nr=0x%02x link_id=%02x, " + "expecting chan_nr=0x%02x link_id=%02x.\n", chan_nr, link_id, + trx_chan_desc[br->chan].chan_nr | br->tn, trx_chan_desc[br->chan].link_id); + goto free_msg; + } + + /* unlink and return message */ + llist_del(&msg->list); + return msg; } + /* Queue was traversed with no candidate, no prim is available for current FN: */ + rate_ctr_inc2(l1ts->ctrs, L1SCHED_TS_CTR_DL_NOT_FOUND); return NULL; -found_msg: - if ((chan_nr ^ (trx_chan_desc[chan].chan_nr | tn)) - || ((link_id & 0xc0) ^ trx_chan_desc[chan].link_id)) { - LOGL1S(DL1P, LOGL_ERROR, l1t, tn, chan, fn, "Prim has wrong chan_nr=0x%02x link_id=%02x, " - "expecting chan_nr=0x%02x link_id=%02x.\n", chan_nr, link_id, - trx_chan_desc[chan].chan_nr | tn, trx_chan_desc[chan].link_id); - goto free_msg; - } - - /* unlink and return message */ +free_msg: + /* unlink and free message */ llist_del(&msg->list); - return msg; + msgb_free(msg); + return NULL; } -int _sched_compose_ph_data_ind(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, - enum trx_chan_type chan, uint8_t *l2, - uint8_t l2_len, float rssi, +int _sched_compose_ph_data_ind(struct l1sched_ts *l1ts, uint32_t fn, + enum trx_chan_type chan, + const uint8_t *data, size_t data_len, + uint16_t ber10k, float rssi, int16_t ta_offs_256bits, int16_t link_qual_cb, - uint16_t ber10k, enum osmo_ph_pres_info_type presence_info) { struct msgb *msg; struct osmo_phsap_prim *l1sap; - uint8_t chan_nr = trx_chan_desc[chan].chan_nr | tn; - struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn); + uint8_t chan_nr = trx_chan_desc[chan].chan_nr | l1ts->ts->nr; + + /* VAMOS: use Osmocom specific channel number */ + if (l1ts->ts->vamos.is_shadow) + chan_nr |= RSL_CHAN_OSMO_VAMOS_MASK; /* compose primitive */ - msg = l1sap_msgb_alloc(l2_len); + msg = l1sap_msgb_alloc(data_len); l1sap = msgb_l1sap_prim(msg); osmo_prim_init(&l1sap->oph, SAP_GSM_PH, PRIM_PH_DATA, PRIM_OP_INDICATION, msg); @@ -719,48 +777,53 @@ int _sched_compose_ph_data_ind(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, l1sap->u.data.ta_offs_256bits = ta_offs_256bits; l1sap->u.data.lqual_cb = link_qual_cb; l1sap->u.data.pdch_presence_info = presence_info; - msg->l2h = msgb_put(msg, l2_len); - if (l2_len) - memcpy(msg->l2h, l2, l2_len); - - if (L1SAP_IS_LINK_SACCH(trx_chan_desc[chan].link_id)) - l1ts->chan_state[chan].lost_frames = 0; + msg->l2h = msgb_put(msg, data_len); + if (data_len) + memcpy(msg->l2h, data, data_len); /* forward primitive */ - l1sap_up(l1t->trx, l1sap); + l1sap_up(l1ts->ts->trx, l1sap); return 0; } -int _sched_compose_tch_ind(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, - enum trx_chan_type chan, uint8_t *tch, uint8_t tch_len) +int _sched_compose_tch_ind(struct l1sched_ts *l1ts, uint32_t fn, + enum trx_chan_type chan, + const uint8_t *data, size_t data_len, + uint16_t ber10k, float rssi, + int16_t ta_offs_256bits, int16_t link_qual_cb, + uint8_t is_sub) { struct msgb *msg; struct osmo_phsap_prim *l1sap; - struct gsm_bts_trx *trx = l1t->trx; - struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn); - uint8_t chan_nr = trx_chan_desc[chan].chan_nr | tn; - struct gsm_lchan *lchan = &trx->ts[L1SAP_CHAN2TS(chan_nr)].lchan[l1sap_chan2ss(chan_nr)]; + uint8_t chan_nr = trx_chan_desc[chan].chan_nr | l1ts->ts->nr; + struct gsm_lchan *lchan = &l1ts->ts->lchan[l1sap_chan2ss(chan_nr)]; + + /* VAMOS: use Osmocom specific channel number */ + if (l1ts->ts->vamos.is_shadow) + chan_nr |= RSL_CHAN_OSMO_VAMOS_MASK; /* compose primitive */ - msg = l1sap_msgb_alloc(tch_len); + msg = l1sap_msgb_alloc(data_len); l1sap = msgb_l1sap_prim(msg); osmo_prim_init(&l1sap->oph, SAP_GSM_PH, PRIM_TCH, PRIM_OP_INDICATION, msg); l1sap->u.tch.chan_nr = chan_nr; l1sap->u.tch.fn = fn; - msg->l2h = msgb_put(msg, tch_len); - if (tch_len) - memcpy(msg->l2h, tch, tch_len); - - if (l1ts->chan_state[chan].lost_frames) - l1ts->chan_state[chan].lost_frames--; - - LOGL1S(DL1P, LOGL_DEBUG, l1t, tn, -1, l1sap->u.data.fn, - "%s Rx -> RTP: %s\n", - gsm_lchan_name(lchan), osmo_hexdump(msgb_l2(msg), msgb_l2len(msg))); + l1sap->u.tch.rssi = (int8_t) (rssi); + l1sap->u.tch.ber10k = ber10k; + l1sap->u.tch.ta_offs_256bits = ta_offs_256bits; + l1sap->u.tch.lqual_cb = link_qual_cb; + l1sap->u.tch.is_sub = is_sub & 1; + + msg->l2h = msgb_put(msg, data_len); + if (data_len) + memcpy(msg->l2h, data, data_len); + + LOGL1S(DL1P, LOGL_DEBUG, l1ts, chan, l1sap->u.tch.fn, "%s Rx -> RTP: %s\n", + gsm_lchan_name(lchan), msgb_hexdump_l2(msg)); /* forward primitive */ - l1sap_up(l1t->trx, l1sap); + l1sap_up(l1ts->ts->trx, l1sap); return 0; } @@ -771,39 +834,37 @@ int _sched_compose_tch_ind(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, * data request (from upper layer) */ -int trx_sched_ph_data_req(struct l1sched_trx *l1t, struct osmo_phsap_prim *l1sap) +int trx_sched_ph_data_req(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap) { - uint8_t tn = l1sap->u.data.chan_nr & 7; - struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn); + uint8_t tn = L1SAP_CHAN2TS(l1sap->u.data.chan_nr); + struct l1sched_ts *l1ts = trx->ts[tn].priv; - LOGL1S(DL1P, LOGL_INFO, l1t, tn, -1, l1sap->u.data.fn, + LOGL1S(DL1P, LOGL_DEBUG, l1ts, -1, l1sap->u.data.fn, "PH-DATA.req: chan_nr=0x%02x link_id=0x%02x\n", l1sap->u.data.chan_nr, l1sap->u.data.link_id); - if (!l1sap->oph.msg) - abort(); - /* ignore empty frame */ - if (!msgb_l2len(l1sap->oph.msg)) { + if (!l1sap->oph.msg->l2h || msgb_l2len(l1sap->oph.msg) == 0) { msgb_free(l1sap->oph.msg); return 0; } + /* VAMOS: convert Osmocom specific channel number to a generic one */ + if (trx->ts[tn].vamos.is_shadow) + l1sap->u.data.chan_nr &= ~RSL_CHAN_OSMO_VAMOS_MASK; + msgb_enqueue(&l1ts->dl_prims, l1sap->oph.msg); return 0; } -int trx_sched_tch_req(struct l1sched_trx *l1t, struct osmo_phsap_prim *l1sap) +int trx_sched_tch_req(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap) { - uint8_t tn = l1sap->u.tch.chan_nr & 7; - struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn); + uint8_t tn = L1SAP_CHAN2TS(l1sap->u.tch.chan_nr); + struct l1sched_ts *l1ts = trx->ts[tn].priv; - LOGL1S(DL1P, LOGL_INFO, l1t, tn, -1, l1sap->u.tch.fn, "TCH.req: chan_nr=0x%02x\n", - l1sap->u.tch.chan_nr); - - if (!l1sap->oph.msg) - abort(); + LOGL1S(DL1P, LOGL_DEBUG, l1ts, -1, l1sap->u.tch.fn, + "TCH.req: chan_nr=0x%02x\n", l1sap->u.tch.chan_nr); /* ignore empty frame */ if (!msgb_l2len(l1sap->oph.msg)) { @@ -811,36 +872,43 @@ int trx_sched_tch_req(struct l1sched_trx *l1t, struct osmo_phsap_prim *l1sap) return 0; } + /* VAMOS: convert Osmocom specific channel number to a generic one */ + if (trx->ts[tn].vamos.is_shadow) + l1sap->u.tch.chan_nr &= ~RSL_CHAN_OSMO_VAMOS_MASK; + msgb_enqueue(&l1ts->dl_prims, l1sap->oph.msg); return 0; } -/* +/* * ready-to-send indication (to upper layer) */ /* RTS for data frame */ -static int rts_data_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, - enum trx_chan_type chan) +static int rts_data_fn(const struct l1sched_ts *l1ts, const struct trx_dl_burst_req *br) { uint8_t chan_nr, link_id; struct msgb *msg; struct osmo_phsap_prim *l1sap; /* get data for RTS indication */ - chan_nr = trx_chan_desc[chan].chan_nr | tn; - link_id = trx_chan_desc[chan].link_id; + chan_nr = trx_chan_desc[br->chan].chan_nr | br->tn; + link_id = trx_chan_desc[br->chan].link_id; - if (!chan_nr) { - LOGL1S(DL1P, LOGL_FATAL, l1t, tn, chan, fn, - "RTS func with non-existing chan_nr %d\n", chan_nr); - return -ENODEV; - } - LOGL1S(DL1P, LOGL_INFO, l1t, tn, chan, fn, - "PH-RTS.ind: chan_nr=0x%02x link_id=0x%02x\n", chan_nr, link_id); + /* VAMOS: use Osmocom specific channel number */ + if (l1ts->ts->vamos.is_shadow) + chan_nr |= RSL_CHAN_OSMO_VAMOS_MASK; + + /* For handover detection, there are cases where the SACCH should remain inactive until the first RACH + * indicating the TA is received. */ + if (L1SAP_IS_LINK_SACCH(link_id) + && !l1ts->chan_state[br->chan].lchan->want_dl_sacch_active) + return 0; + + LOGL1SB(DL1P, LOGL_DEBUG, l1ts, br, "PH-RTS.ind: chan_nr=0x%02x link_id=0x%02x\n", chan_nr, link_id); /* generate prim */ msg = l1sap_msgb_alloc(200); @@ -851,31 +919,30 @@ static int rts_data_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, PRIM_OP_INDICATION, msg); l1sap->u.data.chan_nr = chan_nr; l1sap->u.data.link_id = link_id; - l1sap->u.data.fn = fn; + l1sap->u.data.fn = br->fn; - return l1sap_up(l1t->trx, l1sap); + return l1sap_up(l1ts->ts->trx, l1sap); } -static int rts_tch_common(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, - enum trx_chan_type chan, int facch) +static int rts_tch_common(const struct l1sched_ts *l1ts, + const struct trx_dl_burst_req *br, + bool facch) { uint8_t chan_nr, link_id; struct msgb *msg; struct osmo_phsap_prim *l1sap; - struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn); int rc = 0; /* get data for RTS indication */ - chan_nr = trx_chan_desc[chan].chan_nr | tn; - link_id = trx_chan_desc[chan].link_id; + chan_nr = trx_chan_desc[br->chan].chan_nr | br->tn; + link_id = trx_chan_desc[br->chan].link_id; - if (!chan_nr) { - LOGL1S(DL1P, LOGL_FATAL, l1t, tn, chan, fn, - "RTS func with non-existing chan_nr %d\n", chan_nr); - return -ENODEV; - } - LOGL1S(DL1P, LOGL_INFO, l1t, tn, chan, fn, "TCH RTS.ind: chan_nr=0x%02x\n", chan_nr); + /* VAMOS: use Osmocom specific channel number */ + if (l1ts->ts->vamos.is_shadow) + chan_nr |= RSL_CHAN_OSMO_VAMOS_MASK; + + LOGL1SB(DL1P, LOGL_DEBUG, l1ts, br, "TCH RTS.ind: chan_nr=0x%02x\n", chan_nr); /* only send, if FACCH is selected */ if (facch) { @@ -888,13 +955,13 @@ static int rts_tch_common(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, PRIM_OP_INDICATION, msg); l1sap->u.data.chan_nr = chan_nr; l1sap->u.data.link_id = link_id; - l1sap->u.data.fn = fn; + l1sap->u.data.fn = br->fn; - rc = l1sap_up(l1t->trx, l1sap); + rc = l1sap_up(l1ts->ts->trx, l1sap); } - /* dont send, if TCH is in signalling only mode */ - if (l1ts->chan_state[chan].rsl_cmode != RSL_CMOD_SPD_SIGN) { + /* don't send, if TCH is in signalling only mode */ + if (l1ts->chan_state[br->chan].rsl_cmode != RSL_CMOD_SPD_SIGN) { /* generate prim */ msg = l1sap_msgb_alloc(200); if (!msg) @@ -903,128 +970,259 @@ static int rts_tch_common(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, osmo_prim_init(&l1sap->oph, SAP_GSM_PH, PRIM_TCH_RTS, PRIM_OP_INDICATION, msg); l1sap->u.tch.chan_nr = chan_nr; - l1sap->u.tch.fn = fn; + l1sap->u.tch.fn = br->fn; - return l1sap_up(l1t->trx, l1sap); + return l1sap_up(l1ts->ts->trx, l1sap); } return rc; } /* RTS for full rate traffic frame */ -static int rts_tchf_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, - enum trx_chan_type chan) +static int rts_tchf_fn(const struct l1sched_ts *l1ts, const struct trx_dl_burst_req *br) { /* TCH/F may include FACCH on every 4th burst */ - return rts_tch_common(l1t, tn, fn, chan, 1); + return rts_tch_common(l1ts, br, true); } +/* FACCH/H channel mapping for Downlink (see 3GPP TS 45.002, table 1). + * This mapping is valid for both FACCH/H(0) and FACCH/H(1). */ +const uint8_t sched_tchh_dl_facch_map[26] = { + [4] = 1, /* FACCH/H(0): B0(4,6,8,10,13,15) */ + [5] = 1, /* FACCH/H(1): B0(5,7,9,11,14,16) */ + [13] = 1, /* FACCH/H(0): B1(13,15,17,19,21,23) */ + [14] = 1, /* FACCH/H(1): B1(14,16,18,20,22,24) */ + [21] = 1, /* FACCH/H(0): B2(21,23,0,2,4,6) */ + [22] = 1, /* FACCH/H(1): B2(22,24,1,3,5,7) */ +}; /* RTS for half rate traffic frame */ -static int rts_tchh_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, - enum trx_chan_type chan) +static int rts_tchh_fn(const struct l1sched_ts *l1ts, const struct trx_dl_burst_req *br) { - /* the FN 4/5, 13/14, 21/22 defines that FACCH may be included. */ - return rts_tch_common(l1t, tn, fn, chan, ((fn % 26) >> 2) & 1); + return rts_tch_common(l1ts, br, sched_tchh_dl_facch_map[br->fn % 26]); } /* set multiframe scheduler to given pchan */ -int trx_sched_set_pchan(struct l1sched_trx *l1t, uint8_t tn, - enum gsm_phys_chan_config pchan) +int trx_sched_set_pchan(struct gsm_bts_trx_ts *ts, enum gsm_phys_chan_config pchan) { - struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn); - int i; - - i = find_sched_mframe_idx(pchan, tn); + struct l1sched_ts *l1ts = ts->priv; + int i = find_sched_mframe_idx(pchan, ts->nr); if (i < 0) { - LOGP(DL1C, LOGL_NOTICE, "Failed to configure multiframe " - "trx=%d ts=%d\n", l1t->trx->nr, tn); + LOGP(DL1C, LOGL_NOTICE, "%s Failed to configure multiframe (pchan=0x%02x)\n", + gsm_ts_name(ts), pchan); return -ENOTSUP; } l1ts->mf_index = i; l1ts->mf_period = trx_sched_multiframes[i].period; l1ts->mf_frames = trx_sched_multiframes[i].frames; - LOGP(DL1C, LOGL_NOTICE, "Configuring multiframe with %s trx=%d ts=%d\n", - trx_sched_multiframes[i].name, l1t->trx->nr, tn); + if (ts->vamos.peer != NULL) { + l1ts = ts->vamos.peer->priv; + l1ts->mf_index = i; + l1ts->mf_period = trx_sched_multiframes[i].period; + l1ts->mf_frames = trx_sched_multiframes[i].frames; + } + LOGP(DL1C, LOGL_NOTICE, "%s Configured multiframe with '%s'\n", + gsm_ts_name(ts), trx_sched_multiframes[i].name); return 0; } +/* Remove all matching (by chan_nr & link_id) primitives from the given queue */ +static void trx_sched_queue_filter(struct llist_head *q, uint8_t chan_nr, uint8_t link_id) +{ + struct msgb *msg, *_msg; + + llist_for_each_entry_safe(msg, _msg, q, list) { + struct osmo_phsap_prim *l1sap = msgb_l1sap_prim(msg); + switch (l1sap->oph.primitive) { + case PRIM_PH_DATA: + if (l1sap->u.data.chan_nr != chan_nr) + continue; + if (l1sap->u.data.link_id != link_id) + continue; + break; + case PRIM_TCH: + if (l1sap->u.tch.chan_nr != chan_nr) + continue; + if (link_id != 0x00) + continue; + break; + default: + /* Shall not happen */ + OSMO_ASSERT(0); + } + + /* Unlink and free() */ + llist_del(&msg->list); + talloc_free(msg); + } +} + +static void _trx_sched_set_lchan(struct gsm_lchan *lchan, + enum trx_chan_type chan, + bool active) +{ + struct l1sched_ts *l1ts = lchan->ts->priv; + struct l1sched_chan_state *chan_state; + + OSMO_ASSERT(l1ts != NULL); + chan_state = &l1ts->chan_state[chan]; + + LOGPLCHAN(lchan, DL1C, LOGL_INFO, "%s %s\n", + (active) ? "Activating" : "Deactivating", + trx_chan_desc[chan].name); + + if (active) { + /* Clean up everything */ + memset(chan_state, 0, sizeof(*chan_state)); + + /* Bind to generic 'struct gsm_lchan' */ + chan_state->lchan = lchan; + + /* Allocate memory for Rx/Tx burst buffers. Use the maximim size + * of 24 * (2 * 58) bytes, which is sufficient to store up to 24 GMSK + * modulated bursts for CSD or up to 8 8PSK modulated bursts for EGPRS. */ + const size_t buf_size = 24 * GSM_NBITS_NB_GMSK_PAYLOAD; + if (trx_chan_desc[chan].dl_fn != NULL) + chan_state->dl_bursts = talloc_zero_size(l1ts, buf_size); + if (trx_chan_desc[chan].ul_fn != NULL) + chan_state->ul_bursts = talloc_zero_size(l1ts, buf_size); + } else { + chan_state->ho_rach_detect = 0; + + /* Remove pending Tx prims belonging to this lchan */ + trx_sched_queue_filter(&l1ts->dl_prims, + trx_chan_desc[chan].chan_nr, + trx_chan_desc[chan].link_id); + + /* Release memory used by Rx/Tx burst buffers */ + TALLOC_FREE(chan_state->dl_bursts); + TALLOC_FREE(chan_state->ul_bursts); + } + + chan_state->active = active; +} + /* setting all logical channels given attributes to active/inactive */ -int trx_sched_set_lchan(struct l1sched_trx *l1t, uint8_t chan_nr, uint8_t link_id, - int active) +int trx_sched_set_lchan(struct gsm_lchan *lchan, uint8_t chan_nr, uint8_t link_id, bool active) { + struct l1sched_ts *l1ts = lchan->ts->priv; uint8_t tn = L1SAP_CHAN2TS(chan_nr); - struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn); uint8_t ss = l1sap_chan2ss(chan_nr); - int i; - int rc = -EINVAL; + bool found = false; + + if (!l1ts) { + LOGPLCHAN(lchan, DL1C, LOGL_ERROR, "%s lchan with uninitialized scheduler structure\n", + (active) ? "Activating" : "Deactivating"); + return -EINVAL; + } + + /* VAMOS: convert Osmocom specific channel number to a generic one, + * otherwise we won't match anything in trx_chan_desc[]. */ + if (lchan->ts->vamos.is_shadow) + chan_nr &= ~RSL_CHAN_OSMO_VAMOS_MASK; /* look for all matching chan_nr/link_id */ - for (i = 0; i < _TRX_CHAN_MAX; i++) { - struct l1sched_chan_state *chan_state; - chan_state = &l1ts->chan_state[i]; - /* Skip if pchan type does not match pdch flag. - * FIXME: Is it possible at all? Clarify if so. */ - if ((trx_sched_multiframes[l1ts->mf_index].pchan == GSM_PCHAN_PDCH) - && !(trx_chan_desc[i].flags & TRX_CHAN_FLAG_PDCH)) + for (enum trx_chan_type chan = 0; chan < _TRX_CHAN_MAX; chan++) { + if (trx_chan_desc[chan].chan_nr != (chan_nr & RSL_CHAN_NR_MASK)) continue; - if (trx_chan_desc[i].chan_nr == (chan_nr & 0xf8) - && trx_chan_desc[i].link_id == link_id) { - rc = 0; - if (chan_state->active == active) - continue; - LOGP(DL1C, LOGL_NOTICE, "%s %s on trx=%d ts=%d\n", - (active) ? "Activating" : "Deactivating", - trx_chan_desc[i].name, l1t->trx->nr, tn); - if (active) - memset(chan_state, 0, sizeof(*chan_state)); - chan_state->active = active; - /* free burst memory, to cleanly start with burst 0 */ - if (chan_state->dl_bursts) { - talloc_free(chan_state->dl_bursts); - chan_state->dl_bursts = NULL; - } - if (chan_state->ul_bursts) { - talloc_free(chan_state->ul_bursts); - chan_state->ul_bursts = NULL; - } - if (!active) - chan_state->ho_rach_detect = 0; - } + if (trx_chan_desc[chan].link_id != link_id) + continue; + if (l1ts->chan_state[chan].active == active) + continue; + found = true; + _trx_sched_set_lchan(lchan, chan, active); } /* disable handover detection (on deactivation) */ if (!active) - _sched_act_rach_det(l1t, tn, ss, 0); + _sched_act_rach_det(lchan->ts->trx, tn, ss, 0); - return rc; + return found ? 0 : -EINVAL; +} + +int trx_sched_set_ul_access(struct gsm_lchan *lchan, uint8_t chan_nr, bool active) +{ + struct l1sched_ts *l1ts = lchan->ts->priv; + uint8_t tn = L1SAP_CHAN2TS(chan_nr); + uint8_t ss = l1sap_chan2ss(chan_nr); + int i; + + if (!l1ts) { + LOGPLCHAN(lchan, DL1C, LOGL_ERROR, "%s UL access on lchan with uninitialized scheduler structure.\n", + (active) ? "Activating" : "Deactivating"); + return -EINVAL; + } + + /* look for all matching chan_nr */ + for (i = 0; i < _TRX_CHAN_MAX; i++) { + if (trx_chan_desc[i].chan_nr == (chan_nr & RSL_CHAN_NR_MASK)) { + struct l1sched_chan_state *l1cs = &l1ts->chan_state[i]; + + l1cs->ho_rach_detect = active; + } + } + + _sched_act_rach_det(lchan->ts->trx, tn, ss, active); + + return 0; +} + +int trx_sched_set_bcch_ccch(struct gsm_lchan *lchan, bool active) +{ + struct l1sched_ts *l1ts = lchan->ts->priv; + static const enum trx_chan_type chans[] = { + TRXC_FCCH, + TRXC_SCH, + TRXC_BCCH, + TRXC_RACH, + TRXC_CCCH, + }; + + if (!l1ts) + return -EINVAL; + + for (unsigned int i = 0; i < ARRAY_SIZE(chans); i++) { + enum trx_chan_type chan = chans[i]; + + if (l1ts->chan_state[chan].active == active) + continue; + _trx_sched_set_lchan(lchan, chan, active); + } + + return 0; } /* setting all logical channels given attributes to active/inactive */ -int trx_sched_set_mode(struct l1sched_trx *l1t, uint8_t chan_nr, uint8_t rsl_cmode, +int trx_sched_set_mode(struct gsm_bts_trx_ts *ts, uint8_t chan_nr, uint8_t rsl_cmode, uint8_t tch_mode, int codecs, uint8_t codec0, uint8_t codec1, uint8_t codec2, uint8_t codec3, uint8_t initial_id, uint8_t handover) { + struct l1sched_ts *l1ts = ts->priv; uint8_t tn = L1SAP_CHAN2TS(chan_nr); - struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn); uint8_t ss = l1sap_chan2ss(chan_nr); int i; int rc = -EINVAL; - struct l1sched_chan_state *chan_state; /* no mode for PDCH */ - if (trx_sched_multiframes[l1ts->mf_index].pchan == GSM_PCHAN_PDCH) + if (ts->pchan == GSM_PCHAN_PDCH) return 0; + /* VAMOS: convert Osmocom specific channel number to a generic one, + * otherwise we won't match anything in trx_chan_desc[]. */ + if (ts->vamos.is_shadow) + chan_nr &= ~RSL_CHAN_OSMO_VAMOS_MASK; + /* look for all matching chan_nr/link_id */ for (i = 0; i < _TRX_CHAN_MAX; i++) { if (trx_chan_desc[i].chan_nr == (chan_nr & 0xf8) && trx_chan_desc[i].link_id == 0x00) { - chan_state = &l1ts->chan_state[i]; - LOGP(DL1C, LOGL_NOTICE, "Set mode %u, %u, handover %u " - "on %s of trx=%d ts=%d\n", rsl_cmode, tch_mode, - handover, trx_chan_desc[i].name, l1t->trx->nr, - tn); + struct l1sched_chan_state *chan_state = &l1ts->chan_state[i]; + + LOGP(DL1C, LOGL_INFO, + "%s Set mode for %s (rsl_cmode=%u, tch_mode=%u, handover=%u)\n", + gsm_ts_name(ts), trx_chan_desc[i].name, + rsl_cmode, tch_mode, handover); + chan_state->rsl_cmode = rsl_cmode; chan_state->tch_mode = tch_mode; chan_state->ho_rach_detect = handover; @@ -1039,8 +1237,8 @@ int trx_sched_set_mode(struct l1sched_trx *l1t, uint8_t chan_nr, uint8_t rsl_cmo chan_state->dl_ft = initial_id; chan_state->ul_cmr = initial_id; chan_state->dl_cmr = initial_id; - chan_state->ber_sum = 0; - chan_state->ber_num = 0; + chan_state->lqual_cb_sum = 0; + chan_state->lqual_cb_num = 0; } rc = 0; } @@ -1051,53 +1249,54 @@ int trx_sched_set_mode(struct l1sched_trx *l1t, uint8_t chan_nr, uint8_t rsl_cmo * of transceiver link). * disable handover, if state is still set, since we might not know * the actual state of transceiver (due to loss of link) */ - _sched_act_rach_det(l1t, tn, ss, handover); + _sched_act_rach_det(ts->trx, tn, ss, handover); return rc; } /* setting cipher on logical channels */ -int trx_sched_set_cipher(struct l1sched_trx *l1t, uint8_t chan_nr, int downlink, - int algo, uint8_t *key, int key_len) +int trx_sched_set_cipher(struct gsm_lchan *lchan, uint8_t chan_nr, bool downlink) { - uint8_t tn = L1SAP_CHAN2TS(chan_nr); - struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn); - int i; - int rc = -EINVAL; - struct l1sched_chan_state *chan_state; + int algo = lchan->encr.alg_id - 1; + int i, rc = -EINVAL; /* no cipher for PDCH */ - if (trx_sched_multiframes[l1ts->mf_index].pchan == GSM_PCHAN_PDCH) + if (ts_pchan(lchan->ts) == GSM_PCHAN_PDCH) return 0; + /* VAMOS: convert Osmocom specific channel number to a generic one, + * otherwise we won't match anything in trx_chan_desc[]. */ + if (lchan->ts->vamos.is_shadow) + chan_nr &= ~RSL_CHAN_OSMO_VAMOS_MASK; + /* no algorithm given means a5/0 */ if (algo <= 0) algo = 0; - else if (key_len != 8) { - LOGP(DL1C, LOGL_ERROR, "Algo A5/%d not supported with given " - "key len=%d\n", algo, key_len); + else if (lchan->encr.key_len != 8 && lchan->encr.key_len != 16) { + LOGPLCHAN(lchan, DL1C, LOGL_ERROR, + "Algo A5/%d not supported with given key_len=%u\n", + algo, lchan->encr.key_len); return -ENOTSUP; } /* look for all matching chan_nr */ for (i = 0; i < _TRX_CHAN_MAX; i++) { - /* skip if pchan type */ - if (trx_chan_desc[i].flags & TRX_CHAN_FLAG_PDCH) - continue; - if (trx_chan_desc[i].chan_nr == (chan_nr & 0xf8)) { - chan_state = &l1ts->chan_state[i]; - LOGP(DL1C, LOGL_NOTICE, "Set a5/%d %s for %s on trx=%d " - "ts=%d\n", algo, - (downlink) ? "downlink" : "uplink", - trx_chan_desc[i].name, l1t->trx->nr, tn); + if (trx_chan_desc[i].chan_nr == (chan_nr & RSL_CHAN_NR_MASK)) { + struct l1sched_ts *l1ts = lchan->ts->priv; + struct l1sched_chan_state *l1cs = &l1ts->chan_state[i]; + + LOGPLCHAN(lchan, DL1C, LOGL_INFO, "Set A5/%d %s for %s\n", + algo, (downlink) ? "downlink" : "uplink", + trx_chan_desc[i].name); + if (downlink) { - chan_state->dl_encr_algo = algo; - memcpy(chan_state->dl_encr_key, key, key_len); - chan_state->dl_encr_key_len = key_len; + l1cs->dl_encr_algo = algo; + memcpy(l1cs->dl_encr_key, lchan->encr.key, lchan->encr.key_len); + l1cs->dl_encr_key_len = lchan->encr.key_len; } else { - chan_state->ul_encr_algo = algo; - memcpy(chan_state->ul_encr_key, key, key_len); - chan_state->ul_encr_key_len = key_len; + l1cs->ul_encr_algo = algo; + memcpy(l1cs->ul_encr_key, lchan->encr.key, lchan->encr.key_len); + l1cs->ul_encr_key_len = lchan->encr.key_len; } rc = 0; } @@ -1107,9 +1306,8 @@ int trx_sched_set_cipher(struct l1sched_trx *l1t, uint8_t chan_nr, int downlink, } /* process ready-to-send */ -int _sched_rts(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn) +int _sched_rts(const struct l1sched_ts *l1ts, uint32_t fn) { - struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn); const struct trx_sched_frame *frame; uint8_t offset, period, bid; trx_sched_rts_func *func; @@ -1137,87 +1335,99 @@ int _sched_rts(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn) return 0; /* check if channel is active */ - if (!TRX_CHAN_IS_ACTIVE(&l1ts->chan_state[chan], chan)) + if (!l1ts->chan_state[chan].active) return -EINVAL; - return func(l1t, tn, fn, frame->dl_chan); + /* There is no burst, just for logging */ + struct trx_dl_burst_req dbr = { + .fn = fn, + .tn = l1ts->ts->nr, + .bid = bid, + .chan = chan, + }; + + return func(l1ts, &dbr); +} + +static void trx_sched_apply_att(const struct gsm_lchan *lchan, + struct trx_dl_burst_req *br) +{ + const struct trx_chan_desc *desc = &trx_chan_desc[br->chan]; + + /* Current BS power reduction value in dB */ + br->att = lchan->bs_power_ctrl.current; + + /* Temporary Overpower for SACCH/FACCH bursts */ + if (!lchan->top_acch_active) + return; + if ((lchan->top_acch_cap.sacch_enable && desc->link_id == LID_SACCH) || + (lchan->top_acch_cap.facch_enable && br->flags & TRX_BR_F_FACCH)) { + if (br->att > lchan->top_acch_cap.overpower_db) + br->att -= lchan->top_acch_cap.overpower_db; + else + br->att = 0; + } } /* process downlink burst */ -const ubit_t *_sched_dl_burst(struct l1sched_trx *l1t, uint8_t tn, - uint32_t fn, uint16_t *nbits) +void _sched_dl_burst(struct l1sched_ts *l1ts, struct trx_dl_burst_req *br) { - struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn); - struct l1sched_chan_state *l1cs; + const struct l1sched_chan_state *l1cs; const struct trx_sched_frame *frame; - uint8_t offset, period, bid; + uint8_t offset, period; trx_sched_dl_func *func; - enum trx_chan_type chan; - ubit_t *bits = NULL; if (!l1ts->mf_index) - goto no_data; + return; /* get frame from multiframe */ period = l1ts->mf_period; - offset = fn % period; + offset = br->fn % period; frame = l1ts->mf_frames + offset; - chan = frame->dl_chan; - bid = frame->dl_bid; - func = trx_chan_desc[chan].dl_fn; + br->chan = frame->dl_chan; + br->bid = frame->dl_bid; + func = trx_chan_desc[br->chan].dl_fn; - l1cs = &l1ts->chan_state[chan]; + l1cs = &l1ts->chan_state[br->chan]; /* check if channel is active */ - if (!TRX_CHAN_IS_ACTIVE(l1cs, chan)) { - if (nbits) - *nbits = GSM_BURST_LEN; - goto no_data; - } + if (!l1cs->active) + return; + + /* Training Sequence Code and Set */ + br->tsc_set = l1ts->ts->tsc_set; + br->tsc = l1ts->ts->tsc; /* get burst from function */ - bits = func(l1t, tn, fn, chan, bid, nbits); + if (func(l1ts, br) != 0) + return; + + /* Modulation is indicated by func() */ + br->mod = l1cs->dl_mod_type; + + /* BS Power reduction (in dB) per logical channel */ + if (l1cs->lchan != NULL) + trx_sched_apply_att(l1cs->lchan, br); /* encrypt */ - if (bits && l1cs->dl_encr_algo) { + if (br->burst_len && l1cs->dl_encr_algo) { ubit_t ks[114]; int i; - osmo_a5(l1cs->dl_encr_algo, l1cs->dl_encr_key, fn, ks, NULL); + osmo_a5(l1cs->dl_encr_algo, l1cs->dl_encr_key, br->fn, ks, NULL); for (i = 0; i < 57; i++) { - bits[i + 3] ^= ks[i]; - bits[i + 88] ^= ks[i + 57]; + br->burst[i + 3] ^= ks[i]; + br->burst[i + 88] ^= ks[i + 57]; } } - -no_data: - /* in case of C0, we need a dummy burst to maintain RF power */ - if (bits == NULL && l1t->trx == l1t->trx->bts->c0) { -#if 0 - if (chan != TRXC_IDLE) // hack - LOGP(DL1C, LOGL_DEBUG, "No burst data for %s fn=%u ts=%u " - "burst=%d on C0, so filling with dummy burst\n", - trx_chan_desc[chan].name, fn, tn, bid); -#endif - bits = (ubit_t *) dummy_burst; - } - - return bits; } -#define TDMA_FN_SUM(a, b) \ - ((a + GSM_HYPERFRAME + b) % GSM_HYPERFRAME) - -#define TDMA_FN_SUB(a, b) \ - ((a + GSM_HYPERFRAME - b) % GSM_HYPERFRAME) - -static int trx_sched_calc_frame_loss(struct l1sched_trx *l1t, - struct l1sched_chan_state *l1cs, uint8_t tn, uint32_t fn) +static int trx_sched_calc_frame_loss(struct l1sched_ts *l1ts, + struct l1sched_chan_state *l1cs, + const struct trx_ul_burst_ind *bi) { - const struct trx_sched_frame *frame_head; const struct trx_sched_frame *frame; - struct l1sched_ts *l1ts; uint32_t elapsed_fs; uint8_t offset, i; uint32_t fn_i; @@ -1230,13 +1440,8 @@ static int trx_sched_calc_frame_loss(struct l1sched_trx *l1t, if (l1cs->proc_tdma_fs == 0) return 0; - /* Get current TDMA frame info */ - l1ts = l1sched_trx_get_ts(l1t, tn); - offset = fn % l1ts->mf_period; - frame_head = l1ts->mf_frames + offset; - /* Not applicable for some logical channels */ - switch (frame_head->ul_chan) { + switch (bi->chan) { case TRXC_IDLE: case TRXC_RACH: case TRXC_PDTCH: @@ -1249,9 +1454,9 @@ static int trx_sched_calc_frame_loss(struct l1sched_trx *l1t, } /* How many frames elapsed since the last one? */ - elapsed_fs = TDMA_FN_SUB(fn, l1cs->last_tdma_fn); + elapsed_fs = GSM_TDMA_FN_SUB(bi->fn, l1cs->last_tdma_fn); if (elapsed_fs > l1ts->mf_period) { /* Too many! */ - LOGL1S(DL1P, LOGL_ERROR, l1t, tn, frame_head->ul_chan, fn, + LOGL1SB(DL1P, LOGL_ERROR, l1ts, bi, "Too many (>%u) contiguous TDMA frames=%u elapsed " "since the last processed fn=%u\n", l1ts->mf_period, elapsed_fs, l1cs->last_tdma_fn); @@ -1263,21 +1468,21 @@ static int trx_sched_calc_frame_loss(struct l1sched_trx *l1t, * There are several TDMA frames between the last processed * frame and currently received one. Let's walk through this * path and count potentially lost frames, i.e. for which - * we didn't receive the corresponsing UL bursts. + * we didn't receive the corresponding UL bursts. * * Start counting from the last_fn + 1. */ for (i = 1; i < elapsed_fs; i++) { - fn_i = TDMA_FN_SUM(l1cs->last_tdma_fn, i); + fn_i = GSM_TDMA_FN_SUM(l1cs->last_tdma_fn, i); offset = fn_i % l1ts->mf_period; frame = l1ts->mf_frames + offset; - if (frame->ul_chan == frame_head->ul_chan) + if (frame->ul_chan == bi->chan) l1cs->lost_tdma_fs++; } if (l1cs->lost_tdma_fs > 0) { - LOGL1S(DL1P, LOGL_NOTICE, l1t, tn, frame_head->ul_chan, fn, + LOGL1SB(DL1P, LOGL_NOTICE, l1ts, bi, "At least %u TDMA frames were lost since the last " "processed fn=%u\n", l1cs->lost_tdma_fs, l1cs->last_tdma_fn); @@ -1290,31 +1495,33 @@ static int trx_sched_calc_frame_loss(struct l1sched_trx *l1t, trx_sched_ul_func *func; /* Prepare dummy burst indication */ - struct trx_ul_burst_ind bi = { + struct trx_ul_burst_ind dbi = { .flags = TRX_BI_F_NOPE_IND, .burst_len = GSM_BURST_LEN, .burst = { 0 }, .rssi = -128, .toa256 = 0, + .chan = bi->chan, /* TDMA FN is set below */ - .tn = tn, + .tn = bi->tn, }; for (i = 1; i < elapsed_fs; i++) { - fn_i = TDMA_FN_SUM(l1cs->last_tdma_fn, i); + fn_i = GSM_TDMA_FN_SUM(l1cs->last_tdma_fn, i); offset = fn_i % l1ts->mf_period; frame = l1ts->mf_frames + offset; func = trx_chan_desc[frame->ul_chan].ul_fn; - if (frame->ul_chan != frame_head->ul_chan) + if (frame->ul_chan != bi->chan) continue; - LOGL1S(DL1P, LOGL_NOTICE, l1t, tn, frame->ul_chan, fn, - "Substituting lost TDMA frame=%u by all-zero " - "dummy burst\n", fn_i); + dbi.bid = frame->ul_bid; + dbi.fn = fn_i; - bi.fn = fn_i; - func(l1t, frame->ul_chan, frame->ul_bid, &bi); + LOGL1SB(DL1P, LOGL_NOTICE, l1ts, &dbi, + "Substituting lost burst with NOPE.ind\n"); + + func(l1ts, &dbi); l1cs->lost_tdma_fs--; } @@ -1323,15 +1530,42 @@ static int trx_sched_calc_frame_loss(struct l1sched_trx *l1t, return 0; } +/* Process a single noise measurement for an inactive timeslot. */ +static void trx_sched_noise_meas(struct l1sched_chan_state *l1cs, + const struct trx_ul_burst_ind *bi) +{ + int *Avg = &l1cs->meas.interf_avg; + + /* EWMA (Exponentially Weighted Moving Average): + * + * Avg[n] = a * Val[n] + (1 - a) * Avg[n - 1] + * + * Implemented using the '+=' operator: + * + * Avg += a * Val - a * Avg + * Avg += a * (Val - Avg) + * + * We use constant 'a' = 0.5, what is equal to: + * + * Avg += (Val - Avg) / 2 + * + * We don't really need precisity here, so no scaling. + */ + + *Avg += (bi->rssi - *Avg) / 2; +} + /* Process an Uplink burst indication */ -int trx_sched_ul_burst(struct l1sched_trx *l1t, struct trx_ul_burst_ind *bi) +int trx_sched_ul_burst(struct l1sched_ts *l1ts, struct trx_ul_burst_ind *bi) { - struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, bi->tn); struct l1sched_chan_state *l1cs; const struct trx_sched_frame *frame; - uint8_t offset, period, bid; + uint8_t offset, period; trx_sched_ul_func *func; - enum trx_chan_type chan; + + /* VAMOS: redirect to the shadow timeslot */ + if (bi->flags & TRX_BI_F_SHADOW_IND) + l1ts = l1ts->ts->vamos.peer->priv; if (!l1ts->mf_index) return -EINVAL; @@ -1341,26 +1575,37 @@ int trx_sched_ul_burst(struct l1sched_trx *l1t, struct trx_ul_burst_ind *bi) offset = bi->fn % period; frame = l1ts->mf_frames + offset; - chan = frame->ul_chan; - bid = frame->ul_bid; - l1cs = &l1ts->chan_state[chan]; - func = trx_chan_desc[chan].ul_fn; + bi->chan = frame->ul_chan; + bi->bid = frame->ul_bid; + l1cs = &l1ts->chan_state[bi->chan]; + func = trx_chan_desc[bi->chan].ul_fn; /* check if channel is active */ - if (!TRX_CHAN_IS_ACTIVE(l1cs, chan)) - return -EINVAL; + if (!l1cs->active) { + /* handle noise measurements on dedicated and idle channels */ + if (TRX_CHAN_IS_DEDIC(bi->chan) || bi->chan == TRXC_IDLE) + trx_sched_noise_meas(l1cs, bi); + return 0; + } /* omit bursts which have no handler, like IDLE bursts */ if (!func) return -EINVAL; /* calculate how many TDMA frames were potentially lost */ - trx_sched_calc_frame_loss(l1t, l1cs, bi->tn, bi->fn); + trx_sched_calc_frame_loss(l1ts, l1cs, bi); /* update TDMA frame counters */ l1cs->last_tdma_fn = bi->fn; l1cs->proc_tdma_fs++; + /* handle NOPE indications */ + if (bi->flags & TRX_BI_F_NOPE_IND) { + /* NOTE: Uplink burst handler must check bi->burst_len before + * accessing bi->burst to avoid uninitialized memory access. */ + return func(l1ts, bi); + } + /* decrypt */ if (bi->burst_len && l1cs->ul_encr_algo) { ubit_t ks[114]; @@ -1376,13 +1621,7 @@ int trx_sched_ul_burst(struct l1sched_trx *l1t, struct trx_ul_burst_ind *bi) } /* Invoke the logical channel handler */ - func(l1t, chan, bid, bi); + func(l1ts, bi); return 0; } - -struct l1sched_ts *l1sched_trx_get_ts(struct l1sched_trx *l1t, uint8_t tn) -{ - OSMO_ASSERT(tn < ARRAY_SIZE(l1t->ts)); - return &l1t->ts[tn]; -} diff --git a/src/common/scheduler_mframe.c b/src/common/scheduler_mframe.c index b969407c..60b63531 100644 --- a/src/common/scheduler_mframe.c +++ b/src/common/scheduler_mframe.c @@ -14,7 +14,7 @@ * 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. + * 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/>. @@ -1000,14 +1000,14 @@ int find_sched_mframe_idx(enum gsm_phys_chan_config pchan, uint8_t tn) } /* Determine if given frame number contains SACCH (true) or other (false) burst */ -bool trx_sched_is_sacch_fn(struct gsm_bts_trx_ts *ts, uint32_t fn, bool uplink) +bool trx_sched_is_sacch_fn(const struct gsm_bts_trx_ts *ts, uint32_t fn, bool uplink) { int i; const struct trx_sched_multiframe *sched; const struct trx_sched_frame *frame; enum trx_chan_type ch_type; - i = find_sched_mframe_idx(ts->pchan, ts->nr); + i = find_sched_mframe_idx(ts_pchan(ts), ts->nr); if (i < 0) return -EINVAL; sched = &trx_sched_multiframes[i]; diff --git a/src/common/sysinfo.c b/src/common/sysinfo.c index c41f9d6e..48b1a19e 100644 --- a/src/common/sysinfo.c +++ b/src/common/sysinfo.c @@ -1,4 +1,4 @@ -/* (C) 2011-2019 by Harald Welte <laforge@gnumonks.org> +/* (C) 2011-2020 by Harald Welte <laforge@gnumonks.org> * * All Rights Reserved * @@ -10,7 +10,7 @@ * 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. + * 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/>. @@ -18,6 +18,7 @@ */ #include <stdint.h> +#include <errno.h> #include <osmocom/gsm/gsm_utils.h> #include <osmocom/gsm/sysinfo.h> @@ -25,6 +26,8 @@ #include <osmo-bts/logging.h> #include <osmo-bts/gsm_data.h> #include <osmo-bts/pcu_if.h> +#include <osmo-bts/bts.h> +#include <osmo-bts/bts_trx.h> /* properly increment SI2q index and return SI2q data for scheduling */ static inline uint8_t *get_si2q_inc_index(struct gsm_bts *bts) @@ -96,7 +99,7 @@ uint8_t *bts_sysinfo_get(struct gsm_bts *bts, const struct gsm_time *g_time) tc4_sub[tc4_cnt] = SYSINFO_TYPE_2quater; tc4_cnt += 1; } - if (GSM_BTS_HAS_SI(bts, SYSINFO_TYPE_13)) { + if (GSM_BTS_HAS_SI(bts, SYSINFO_TYPE_13) && pcu_connected()) { tc4_sub[tc4_cnt] = SYSINFO_TYPE_13; tc4_cnt += 1; } @@ -148,33 +151,37 @@ uint8_t *bts_sysinfo_get(struct gsm_bts *bts, const struct gsm_time *g_time) return 0; } -uint8_t num_agch(struct gsm_bts_trx *trx, const char * arg) +uint8_t num_agch(const struct gsm_bts_trx *trx, const char * arg) { - struct gsm_bts *b = trx->bts; - struct gsm48_system_information_type_3 *si3; + const struct gsm_bts *b = trx->bts; + const struct gsm48_system_information_type_3 *si3; if (GSM_BTS_HAS_SI(b, SYSINFO_TYPE_3)) { si3 = GSM_BTS_SI(b, SYSINFO_TYPE_3); return si3->control_channel_desc.bs_ag_blks_res; } - LOGP(DL1P, LOGL_ERROR, "%s: Unable to determine actual BS_AG_BLKS_RES " + LOGP(DL1P, LOGL_NOTICE, "%s: Unable to determine actual BS_AG_BLKS_RES " "value as SI3 is not available yet, fallback to 1\n", arg); return 1; } -/* obtain the next to-be transmitted dowlink SACCH frame (L2 hdr + L3); returns pointer to lchan->si buffer */ -uint8_t *lchan_sacch_get(struct gsm_lchan *lchan) +/* Returns position of the NCH accroding to SI1 rest octets. See Table 10.5.2.32.1 of TS 44.018. + * Returns < 0, if not present. */ +int pos_nch(const struct gsm_bts_trx *trx, const char *arg) { - uint32_t tmp, i; - - for (i = 0; i < _MAX_SYSINFO_TYPE; i++) { - tmp = (lchan->si.last + 1 + i) % _MAX_SYSINFO_TYPE; - if (!(lchan->si.valid & (1 << tmp))) - continue; - lchan->si.last = tmp; - return GSM_LCHAN_SI(lchan, tmp); + const struct gsm_bts *b = trx->bts; + const struct gsm48_system_information_type_1 *si1; + + if (GSM_BTS_HAS_SI(b, SYSINFO_TYPE_1)) { + si1 = GSM_BTS_SI(b, SYSINFO_TYPE_1); + if (si1->rest_octets[0] & 0x80) { + /* H <NCH Position : bit (5)> */ + return (si1->rest_octets[0] >> 2) & 0x1f; + } + return -ENOTSUP; } - LOGPLCHAN(lchan, DL1P, LOGL_NOTICE, "SACCH no SI available\n"); - return NULL; + LOGP(DL1P, LOGL_NOTICE, "%s: Unable to determine actual NCH Position " + "value as SI1 is not available yet.\n", arg); + return -EINVAL; } /* re-generate SI3 restoctets with GPRS indicator depending on the PCU socket connection state */ @@ -195,17 +202,79 @@ void regenerate_si3_restoctets(struct gsm_bts *bts) /* Create a temporary copy and patch that, if no PCU is around */ si3ro_tmp = bts->si3_ro_decoded; if (!pcu_connected()) { - if (!bts->si3_gprs_ind_disabled) - LOGP(DPCU, LOGL_NOTICE, "Disabling GPRS Indicator in SI3 (No PCU connected)\n"); - bts->si3_gprs_ind_disabled = true; + if (!bts->si_gprs_ind_disabled) + LOGP(DPCU, LOGL_NOTICE, "Disabling GPRS Indicator in SI (No PCU connected)\n"); + bts->si_gprs_ind_disabled = true; si3ro_tmp.gprs_ind.present = 0; } else { - if (bts->si3_gprs_ind_disabled) - LOGP(DPCU, LOGL_NOTICE, "Enabling GPRS Indicator in SI3 (PCU connected)\n"); - bts->si3_gprs_ind_disabled = false; + if (bts->si_gprs_ind_disabled) + LOGP(DPCU, LOGL_NOTICE, "Enabling GPRS Indicator in SI (PCU connected)\n"); + bts->si_gprs_ind_disabled = false; si3ro_tmp.gprs_ind.present = 1; /* is a no-op as we copy from bts->si3_ro_decoded */ } /* re-generate the binary SI3 rest octets */ osmo_gsm48_rest_octets_si3_encode(si3_buf + si3_size, &si3ro_tmp); } + +/* get the offset of the SI4 rest octets */ +int get_si4_ro_offset(const uint8_t *si4_buf) +{ + const struct gsm48_system_information_type_4 *si4 = + (const struct gsm48_system_information_type_4 *) si4_buf; + int si4_size; + + /* start with the length of the mandatory part */ + si4_size = offsetof(struct gsm48_system_information_type_4, data); + /* then add optional parts, if any */ + if (si4->data[0] == GSM48_IE_CBCH_CHAN_DESC) { + /* fixed 4-byte TV IE, see Table 9.1.36.1 of TS 44.018 */ + si4_size += 4; + if (si4->data[4] == GSM48_IE_CBCH_MOB_AL) + si4_size += TLV_GROSS_LEN(si4->data[5]); + } + + if (si4_size >= GSM_MACBLOCK_LEN) + return -EINVAL; + + return si4_size; +} + +/* re-generate SI4 restoctets with GPRS indicator depending on the PCU socket connection state */ +void regenerate_si4_restoctets(struct gsm_bts *bts) +{ + uint8_t *si4_buf = GSM_BTS_SI(bts, SYSINFO_TYPE_4); + struct osmo_gsm48_si_ro_info si4ro_tmp; + int si4_size; + + /* If BSC has never set SI4, there's nothing to patch */ + if (!GSM_BTS_HAS_SI(bts, SYSINFO_TYPE_4)) + return; + + /* If SI4 from BSC doesn't have a GPRS indicator, we won't have anything to patch */ + if (!bts->si4_ro_decoded.gprs_ind.present) + return; + + si4_size = get_si4_ro_offset(si4_buf); + if (si4_size < 0) { + LOGP(DPCU, LOGL_ERROR, "Cannot parse SI4, hence not patching GPRS indicator\n"); + return; + } + + /* Create a temporary copy and patch that, if no PCU is around */ + si4ro_tmp = bts->si4_ro_decoded; + if (!pcu_connected()) { + if (!bts->si_gprs_ind_disabled) + LOGP(DPCU, LOGL_NOTICE, "Disabling GPRS Indicator in SI (No PCU connected)\n"); + bts->si_gprs_ind_disabled = true; + si4ro_tmp.gprs_ind.present = 0; + } else { + if (bts->si_gprs_ind_disabled) + LOGP(DPCU, LOGL_NOTICE, "Enabling GPRS Indicator in SI (PCU connected)\n"); + bts->si_gprs_ind_disabled = false; + si4ro_tmp.gprs_ind.present = 1; /* is a no-op as we copy from bts->si4_ro_decoded */ + } + + /* re-generate the binary SI4 rest octets */ + osmo_gsm48_rest_octets_si4_encode(si4_buf + si4_size, &si4ro_tmp, GSM_MACBLOCK_LEN - si4_size); +} diff --git a/src/common/ta_control.c b/src/common/ta_control.c new file mode 100644 index 00000000..6fe409a2 --- /dev/null +++ b/src/common/ta_control.c @@ -0,0 +1,102 @@ +/* Loop control for Timing Advance */ + +/* (C) 2013 by Andreas Eversberg <jolly@eversberg.eu> + * (C) 2021 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/>. + * + */ + +/* Related specs: 3GPP TS 45.010 sections 5.5, 5.6 */ + +#include <osmo-bts/gsm_data.h> +#include <osmo-bts/bts_trx.h> +#include <osmo-bts/logging.h> + +/* 3GPP TS 45.010 sec 5.6.3 Delay assessment error: + * 75% of one bit duration in 1/256 symbols: 256*0.75 */ +#define TOA256_THRESH 192 + +/* rqd_ta value range */ +#define TA_MIN 0 +#define TA_MAX 63 + +/* TODO: make configurable over osmo-bts VTY? Pass it BSC->BTS? */ +#define TA_MAX_INC_STEP 2 +#define TA_MAX_DEC_STEP 2 + + +/*! compute the new "Ordered Timing Advance" communicated to the MS and store it in lchan. + * \param lchan logical channel for which to compute (and in which to store) new power value. + * \param[in] ms_tx_ta The TA used by the MS and reported in L1SACCH, see struct gsm_sacch_l1_hdr field "ta". + * \param[in] toa256 Time of Arrival (in 1/256th bits) computed at Rx side + */ +void lchan_ms_ta_ctrl(struct gsm_lchan *lchan, uint8_t ms_tx_ta, int16_t toa256) +{ + int16_t new_ta; + /* Shall we skip current block based on configured interval? */ + + /* TA control interval: how many blocks do we skip? */ + if (lchan->ta_ctrl.skip_block_num-- > 0) + return; + + /* Reset the number of SACCH blocks to be skipped: + * ctrl_interval=0 => 0 blocks to skip, + * ctrl_interval=1 => 1 blocks to skip, + * ctrl_interval=2 => 3 blocks to skip, + * so basically ctrl_interval * 2 - 1. */ + lchan->ta_ctrl.skip_block_num = lchan->ts->trx->ta_ctrl_interval * 2 - 1; + + int16_t delta_ta = toa256/256; + if (toa256 >= 0) { + if ((toa256 - (256 * delta_ta)) > TOA256_THRESH) + delta_ta++; + if (delta_ta > TA_MAX_INC_STEP) + delta_ta = TA_MAX_INC_STEP; + } else { + if ((toa256 - (256 * delta_ta)) < -TOA256_THRESH) + delta_ta--; + if (delta_ta < -TA_MAX_DEC_STEP) + delta_ta = -TA_MAX_DEC_STEP; + } + + new_ta = ms_tx_ta + delta_ta; + + /* Make sure new_ta is never negative: */ + if (new_ta < TA_MIN) + new_ta = TA_MIN; + + /* Don't ask for out of range TA: */ + if (new_ta > TA_MAX) + new_ta = TA_MAX; + + if (lchan->ta_ctrl.current == (uint8_t)new_ta) { + LOGPLCHAN(lchan, DLOOP, LOGL_DEBUG, + "Keeping current TA at %u: TOA was %d\n", + lchan->ta_ctrl.current, toa256); + return; + } + + LOGPLCHAN(lchan, DLOOP, LOGL_INFO, + "%s TA %u => %u: TOA was too %s (%d)\n", + (uint8_t)new_ta > lchan->ta_ctrl.current ? "Raising" : "Lowering", + lchan->ta_ctrl.current, (uint8_t)new_ta, + (uint8_t)new_ta > lchan->ta_ctrl.current ? "late" : "early", + toa256); + + /* store the resulting new TA in the lchan */ + lchan->ta_ctrl.current = (uint8_t)new_ta; +} diff --git a/src/common/tx_power.c b/src/common/tx_power.c index e418cec5..83cdb629 100644 --- a/src/common/tx_power.c +++ b/src/common/tx_power.c @@ -12,7 +12,7 @@ * 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. + * 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/>. @@ -29,6 +29,7 @@ #include <osmo-bts/gsm_data.h> #include <osmo-bts/bts_model.h> #include <osmo-bts/tx_power.h> +#include <osmo-bts/bts_trx.h> static int get_pa_drive_level_mdBm(const struct power_amp *pa, int desired_p_out_mdBm, unsigned int arfcn) @@ -42,16 +43,16 @@ static int get_pa_drive_level_mdBm(const struct power_amp *pa, } /* maximum output power of the system */ -int get_p_max_out_mdBm(struct gsm_bts_trx *trx) +int get_p_max_out_mdBm(const struct gsm_bts_trx *trx) { - struct trx_power_params *tpp = &trx->power_params; + const struct trx_power_params *tpp = &trx->power_params; /* Add user gain, internal and external PA gain to TRX output power */ return tpp->trx_p_max_out_mdBm + tpp->user_gain_mdB + tpp->pa.nominal_gain_mdB + tpp->user_pa.nominal_gain_mdB; } /* nominal output power, i.e. OML-reduced maximum output power */ -int get_p_nominal_mdBm(struct gsm_bts_trx *trx) +int get_p_nominal_mdBm(const struct gsm_bts_trx *trx) { /* P_max_out subtracted by OML maximum power reduction IE */ return get_p_max_out_mdBm(trx) - to_mdB(trx->max_power_red); @@ -60,21 +61,21 @@ int get_p_nominal_mdBm(struct gsm_bts_trx *trx) /* calculate the target total output power required, reduced by both * OML and RSL, but ignoring the attenuation required for power ramping and * thermal management */ -int get_p_target_mdBm(struct gsm_bts_trx *trx, uint8_t bs_power_ie) +int get_p_target_mdBm(const struct gsm_bts_trx *trx, uint8_t bs_power_red) { - /* Pn subtracted by RSL BS Power IE (in 2 dB steps) */ - return get_p_nominal_mdBm(trx) - to_mdB(bs_power_ie * 2); + /* Pn subtracted by RSL BS Power Recudtion (in 1 dB steps) */ + return get_p_nominal_mdBm(trx) - to_mdB(bs_power_red); } -int get_p_target_mdBm_lchan(struct gsm_lchan *lchan) +int get_p_target_mdBm_lchan(const struct gsm_lchan *lchan) { - return get_p_target_mdBm(lchan->ts->trx, lchan->bs_power); + return get_p_target_mdBm(lchan->ts->trx, lchan->bs_power_ctrl.current); } /* calculate the actual total output power required, taking into account the * attenuation required for power ramping but not thermal management */ -int get_p_actual_mdBm(struct gsm_bts_trx *trx, int p_target_mdBm) +int get_p_actual_mdBm(const struct gsm_bts_trx *trx, int p_target_mdBm) { - struct trx_power_params *tpp = &trx->power_params; + const struct trx_power_params *tpp = &trx->power_params; /* P_target subtracted by ramp attenuation */ return p_target_mdBm - tpp->ramp.attenuation_mdB; @@ -82,9 +83,9 @@ int get_p_actual_mdBm(struct gsm_bts_trx *trx, int p_target_mdBm) /* calculate the effective total output power required, taking into account the * attenuation required for power ramping and thermal management */ -int get_p_eff_mdBm(struct gsm_bts_trx *trx, int p_target_mdBm) +int get_p_eff_mdBm(const struct gsm_bts_trx *trx, int p_target_mdBm) { - struct trx_power_params *tpp = &trx->power_params; + const struct trx_power_params *tpp = &trx->power_params; /* P_target subtracted by ramp attenuation */ return p_target_mdBm - tpp->ramp.attenuation_mdB - tpp->thermal_attenuation_mdB; @@ -92,9 +93,9 @@ int get_p_eff_mdBm(struct gsm_bts_trx *trx, int p_target_mdBm) /* calculate effect TRX output power required, taking into account the * attenuations required for power ramping and thermal management */ -int get_p_trxout_eff_mdBm(struct gsm_bts_trx *trx, int p_target_mdBm) +int get_p_trxout_eff_mdBm(const struct gsm_bts_trx *trx, int p_target_mdBm) { - struct trx_power_params *tpp = &trx->power_params; + const struct trx_power_params *tpp = &trx->power_params; int p_actual_mdBm, user_pa_drvlvl_mdBm, pa_drvlvl_mdBm; unsigned int arfcn = trx->arfcn; @@ -113,14 +114,14 @@ int get_p_trxout_eff_mdBm(struct gsm_bts_trx *trx, int p_target_mdBm) /* calculate target TRX output power required, ignoring the * attenuations required for power ramping but not thermal management */ -int get_p_trxout_target_mdBm(struct gsm_bts_trx *trx, uint8_t bs_power_ie) +int get_p_trxout_target_mdBm(const struct gsm_bts_trx *trx, uint8_t bs_power_red) { - struct trx_power_params *tpp = &trx->power_params; + const struct trx_power_params *tpp = &trx->power_params; int p_target_mdBm, user_pa_drvlvl_mdBm, pa_drvlvl_mdBm; unsigned int arfcn = trx->arfcn; /* P_target subtracted by any bulk gain added by the user */ - p_target_mdBm = get_p_target_mdBm(trx, bs_power_ie) - tpp->user_gain_mdB; + p_target_mdBm = get_p_target_mdBm(trx, bs_power_red) - tpp->user_gain_mdB; /* determine input drive level required at input to user PA */ user_pa_drvlvl_mdBm = get_pa_drive_level_mdBm(&tpp->user_pa, p_target_mdBm, arfcn); @@ -131,9 +132,9 @@ int get_p_trxout_target_mdBm(struct gsm_bts_trx *trx, uint8_t bs_power_ie) /* internal PA input drive level is TRX output power */ return pa_drvlvl_mdBm; } -int get_p_trxout_target_mdBm_lchan(struct gsm_lchan *lchan) +int get_p_trxout_target_mdBm_lchan(const struct gsm_lchan *lchan) { - return get_p_trxout_target_mdBm(lchan->ts->trx, lchan->bs_power); + return get_p_trxout_target_mdBm(lchan->ts->trx, lchan->bs_power_ctrl.current); } @@ -146,9 +147,9 @@ int get_p_trxout_target_mdBm_lchan(struct gsm_lchan *lchan) * attempting to register at the same time. Rather, grow the cell slowly in * radius than start with the full radius at once. */ -static int we_are_ramping_up(struct gsm_bts_trx *trx) +static int we_are_ramping_up(const struct gsm_bts_trx *trx) { - struct trx_power_params *tpp = &trx->power_params; + const struct trx_power_params *tpp = &trx->power_params; if (tpp->p_total_tgt_mdBm > tpp->p_total_cur_mdBm) return 1; @@ -171,13 +172,13 @@ static void power_ramp_timer_cb(void *_trx) /* compute new effective (= minus ramp and thermal attenuation) TRX output required */ p_trxout_eff_mdBm = get_p_trxout_eff_mdBm(trx, tpp->p_total_tgt_mdBm); - LOGP(DL1C, LOGL_DEBUG, "ramp_timer_cb(cur_pout=%d, tgt_pout=%d, " + LOGPTRX(trx, DL1C, LOGL_DEBUG, "ramp_timer_cb(cur_pout=%d, tgt_pout=%d, " "ramp_att=%d, therm_att=%d, user_gain=%d)\n", tpp->p_total_cur_mdBm, tpp->p_total_tgt_mdBm, tpp->ramp.attenuation_mdB, tpp->thermal_attenuation_mdB, tpp->user_gain_mdB); - LOGP(DL1C, LOGL_INFO, + LOGPTRX(trx, DL1C, LOGL_INFO, "ramping TRX board output power to %d mdBm.\n", p_trxout_eff_mdBm); /* Instruct L1 to apply new effective TRX output power required */ @@ -196,9 +197,9 @@ void power_trx_change_compl(struct gsm_bts_trx *trx, int p_trxout_cur_mdBm) /* for now we simply write an error message, but in the future * we might use the value (again) as part of our math? */ if (p_trxout_cur_mdBm != p_trxout_should_mdBm) { - LOGP(DL1C, LOGL_ERROR, "bts_model notifies us of %u mdBm TRX " - "output power. However, it should be %u mdBm!\n", - p_trxout_cur_mdBm, p_trxout_should_mdBm); + LOGPTRX(trx, DL1C, LOGL_ERROR, "bts_model notifies us of %d mdBm TRX " + "output power. However, it should be %d mdBm!\n", + p_trxout_cur_mdBm, p_trxout_should_mdBm); } /* and do another step... */ @@ -210,8 +211,11 @@ static void power_ramp_do_step(struct gsm_bts_trx *trx, int first) struct trx_power_params *tpp = &trx->power_params; /* we had finished in last loop iteration */ - if (!first && tpp->ramp.attenuation_mdB == 0) + if (!first && tpp->ramp.attenuation_mdB == 0) { + if (tpp->ramp.compl_cb) + tpp->ramp.compl_cb(trx); return; + } if (we_are_ramping_up(trx)) { /* ramp up power -> ramp down attenuation */ @@ -235,8 +239,7 @@ static void power_ramp_do_step(struct gsm_bts_trx *trx, int first) osmo_timer_schedule(&tpp->ramp.step_timer, tpp->ramp.step_interval_sec, 0); } - -int power_ramp_start(struct gsm_bts_trx *trx, int p_total_tgt_mdBm, int bypass) +int _power_ramp_start(struct gsm_bts_trx *trx, int p_total_tgt_mdBm, int bypass, ramp_compl_cb_t ramp_compl_cb, bool skip_ramping) { struct trx_power_params *tpp = &trx->power_params; @@ -244,35 +247,43 @@ int power_ramp_start(struct gsm_bts_trx *trx, int p_total_tgt_mdBm, int bypass) * the maximum total system power subtracted by OML as well as RSL * reductions */ - LOGP(DL1C, LOGL_INFO, "power_ramp_start(cur=%d, tgt=%d)\n", - tpp->p_total_cur_mdBm, p_total_tgt_mdBm); + LOGPTRX(trx, DL1C, LOGL_INFO, "power_ramp_start(cur=%d, tgt=%d%s)\n", + tpp->p_total_cur_mdBm, p_total_tgt_mdBm, bypass ? ", bypass" : ""); if (!bypass && (p_total_tgt_mdBm > get_p_nominal_mdBm(trx))) { - LOGP(DL1C, LOGL_ERROR, "Asked to ramp power up to " - "%d mdBm, which exceeds P_max_out (%d)\n", - p_total_tgt_mdBm, get_p_nominal_mdBm(trx)); + LOGPTRX(trx, DL1C, LOGL_ERROR, "Asked to ramp power up to " + "%d mdBm, which exceeds P_max_out (%d)\n", + p_total_tgt_mdBm, get_p_nominal_mdBm(trx)); return -ERANGE; } /* Cancel any pending request */ - osmo_timer_del(&tpp->ramp.step_timer); + power_ramp_abort(trx); /* set the new target */ tpp->p_total_tgt_mdBm = p_total_tgt_mdBm; - - if (we_are_ramping_up(trx)) { + tpp->ramp.compl_cb = ramp_compl_cb; + + if (skip_ramping) { + /* Jump straight to the target power */ + tpp->p_total_cur_mdBm = p_total_tgt_mdBm; + tpp->ramp.attenuation_mdB = 0; + power_ramp_timer_cb(trx); + } else if (we_are_ramping_up(trx)) { if (tpp->p_total_tgt_mdBm <= tpp->ramp.max_initial_pout_mdBm) { - LOGP(DL1C, LOGL_INFO, - "target_power(%d) is below max.initial power\n", - tpp->p_total_tgt_mdBm); + LOGPTRX(trx, DL1C, LOGL_INFO, + "target_power (%d mdBm) is below or equal to 'power ramp max-initial' power (%d mdBm)\n", + tpp->p_total_tgt_mdBm, tpp->ramp.max_initial_pout_mdBm); /* new setting is below the maximum initial output * power, so we can directly jump to this level */ tpp->p_total_cur_mdBm = tpp->p_total_tgt_mdBm; tpp->ramp.attenuation_mdB = 0; power_ramp_timer_cb(trx); } else { - /* We need to step it up. Start from the current value */ + /* We need to step it up. Start from the current value, shortcutting to max-initial. */ /* Set attenuation to cause no power change right now */ + if (tpp->p_total_cur_mdBm + (int)tpp->ramp.step_size_mdB < tpp->ramp.max_initial_pout_mdBm) + tpp->p_total_cur_mdBm = tpp->ramp.max_initial_pout_mdBm - tpp->ramp.step_size_mdB; tpp->ramp.attenuation_mdB = tpp->p_total_tgt_mdBm - tpp->p_total_cur_mdBm; /* start with the first step */ @@ -290,10 +301,16 @@ int power_ramp_start(struct gsm_bts_trx *trx, int p_total_tgt_mdBm, int bypass) return 0; } +/* Cancel any pending request */ +void power_ramp_abort(struct gsm_bts_trx *trx) +{ + osmo_timer_del(&trx->power_params.ramp.step_timer); +} + /* determine the initial transceiver output power at start-up time */ -int power_ramp_initial_power_mdBm(struct gsm_bts_trx *trx) +int power_ramp_initial_power_mdBm(const struct gsm_bts_trx *trx) { - struct trx_power_params *tpp = &trx->power_params; + const struct trx_power_params *tpp = &trx->power_params; int pout_mdBm; /* this is the maximum initial output on the antenna connector diff --git a/src/common/vty.c b/src/common/vty.c index 801f34c0..c0008a85 100644 --- a/src/common/vty.c +++ b/src/common/vty.c @@ -12,7 +12,7 @@ * 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. + * 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/>. @@ -22,10 +22,12 @@ #include "btsconfig.h" #include <inttypes.h> +#include <limits.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <ctype.h> +#include <errno.h> #include <osmocom/core/talloc.h> #include <osmocom/gsm/abis_nm.h> @@ -36,16 +38,20 @@ #include <osmocom/vty/logging.h> #include <osmocom/vty/misc.h> #include <osmocom/vty/ports.h> +#include <osmocom/vty/tdef_vty.h> #include <osmocom/core/gsmtap.h> #include <osmocom/core/utils.h> +#include <osmocom/core/sockaddr_str.h> #include <osmocom/trau/osmo_ortp.h> - +#include <osmocom/core/fsm.h> +#include <osmocom/codec/codec.h> #include <osmo-bts/logging.h> #include <osmo-bts/gsm_data.h> #include <osmo-bts/phy_link.h> #include <osmo-bts/abis.h> #include <osmo-bts/bts.h> +#include <osmo-bts/bts_sm.h> #include <osmo-bts/rsl.h> #include <osmo-bts/oml.h> #include <osmo-bts/signal.h> @@ -54,6 +60,7 @@ #include <osmo-bts/measurement.h> #include <osmo-bts/vty.h> #include <osmo-bts/l1sap.h> +#include <osmo-bts/osmux.h> #define VTY_STR "Configure the VTY\n" @@ -64,23 +71,45 @@ #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 +/* INT32_MAX, because osmo_wqueue_init takes int as an argument + * and INT_MAX can't be stringified as a decimal */ +#define BTS_CFG_PCU_SOCK_WQUEUE_LEN_MAX_MAX 2147483647 + +#define X(x) (1 << x) int g_vty_port_num = OSMO_VTY_PORT_BTS; +static const struct value_string gsmtap_sapi_names[] = { + { GSMTAP_CHANNEL_BCCH, "BCCH" }, + { GSMTAP_CHANNEL_CCCH, "CCCH" }, + { GSMTAP_CHANNEL_RACH, "RACH" }, + { GSMTAP_CHANNEL_AGCH, "AGCH" }, + { GSMTAP_CHANNEL_PCH, "PCH" }, + { GSMTAP_CHANNEL_SDCCH, "SDCCH" }, + { GSMTAP_CHANNEL_TCH_F, "TCH/F" }, + { GSMTAP_CHANNEL_TCH_H, "TCH/H" }, + { GSMTAP_CHANNEL_PACCH, "PACCH" }, + { GSMTAP_CHANNEL_PDCH, "PDTCH" }, + { GSMTAP_CHANNEL_PTCCH, "PTCCH" }, + { GSMTAP_CHANNEL_CBCH51,"CBCH" }, + { GSMTAP_CHANNEL_ACCH, "SACCH" }, + { 0, NULL } +}; + struct phy_instance *vty_get_phy_instance(struct vty *vty, int phy_nr, int inst_nr) { struct phy_link *plink = phy_link_by_num(phy_nr); struct phy_instance *pinst; if (!plink) { - vty_out(vty, "Cannot find PHY link number %d%s", + vty_out(vty, "%% Cannot find PHY link number %d%s", phy_nr, VTY_NEWLINE); return NULL; } pinst = phy_instance_by_num(plink, inst_nr); if (!pinst) { - vty_out(vty, "Cannot find PHY instance number %d%s", + vty_out(vty, "%% Cannot find PHY instance number %d%s", inst_nr, VTY_NEWLINE); return NULL; } @@ -111,63 +140,9 @@ int bts_vty_go_parent(struct vty *vty) return vty->node; } -int bts_vty_is_config_node(struct vty *vty, int node) -{ - switch (node) { - case TRX_NODE: - case BTS_NODE: - case PHY_NODE: - case PHY_INST_NODE: - return 1; - default: - return 0; - } -} - -gDEFUN(ournode_exit, ournode_exit_cmd, "exit", - "Exit current node, go down to provious node") -{ - switch (vty->node) { - case PHY_INST_NODE: - vty->node = PHY_NODE; - { - struct phy_instance *pinst = vty->index; - vty->index = pinst->phy_link; - } - break; - case PHY_NODE: - vty->node = CONFIG_NODE; - vty->index = NULL; - break; - case TRX_NODE: - vty->node = BTS_NODE; - { - struct gsm_bts_trx *trx = vty->index; - vty->index = trx->bts; - } - break; - default: - break; - } - return CMD_SUCCESS; -} - -gDEFUN(ournode_end, ournode_end_cmd, "end", - "End current mode and change to enable mode") -{ - switch (vty->node) { - default: - vty_config_unlock(vty); - vty->node = ENABLE_NODE; - vty->index = NULL; - vty->index_sub = NULL; - break; - } - return CMD_SUCCESS; -} - static const char osmobts_copyright[] = - "Copyright (C) 2010, 2011 by Harald Welte, Andreas Eversberg and On-Waves\r\n" + "Copyright (C) 2010-2011 by Harald Welte, Andreas Eversberg and On-Waves\r\n" + "Copyright (C) 2011-2022 by sysmocom - s.f.m.c. GmbH\r\n" "License AGPLv3+: GNU AGPL version 3 or later <http://gnu.org/licenses/agpl-3.0.html>\r\n" "This is free software: you are free to change and redistribute it.\r\n" "There is NO WARRANTY, to the extent permitted by law.\r\n"; @@ -177,16 +152,18 @@ struct vty_app_info bts_vty_info = { .version = PACKAGE_VERSION, .copyright = osmobts_copyright, .go_parent_cb = bts_vty_go_parent, - .is_config_node = bts_vty_is_config_node, + .usr_attr_desc = { + [BTS_VTY_ATTR_NEW_LCHAN] = \ + "This command applies for newly created lchans", + [BTS_VTY_TRX_POWERCYCLE] = \ + "This command applies when the TRX powercycles or restarts", + }, + .usr_attr_letters = { + [BTS_VTY_ATTR_NEW_LCHAN] = 'l', + [BTS_VTY_TRX_POWERCYCLE] = 'p', + }, }; -extern struct gsm_network bts_gsmnet; - -struct gsm_network *gsmnet_from_vty(struct vty *v) -{ - return &bts_gsmnet; -} - static struct cmd_node bts_node = { BTS_NODE, "%s(bts)# ", @@ -199,6 +176,12 @@ static struct cmd_node trx_node = { 1, }; +static struct cmd_node osmux_node = { + OSMUX_NODE, + "%s(osmux)# ", + 1, +}; + gDEFUN(cfg_bts_auto_band, cfg_bts_auto_band_cmd, "auto-band", "Automatically select band for ARFCN based on configured band\n") @@ -219,10 +202,94 @@ gDEFUN(cfg_bts_no_auto_band, cfg_bts_no_auto_band_cmd, return CMD_SUCCESS; } +DEFUN_ATTR(cfg_bts_osmux, cfg_bts_osmux_cmd, + "osmux", + "Configure Osmux\n", + CMD_ATTR_IMMEDIATE) +{ + vty->node = OSMUX_NODE; + return CMD_SUCCESS; +} + +DEFUN_ATTR(cfg_bts_osmux_use, cfg_bts_osmux_use_cmd, + "use (off|on|only)", + "Configure Osmux usage\n" + "Never use Osmux\n" + "Use Osmux if requested by BSC (default)\n" + "Always use Osmux, reject non-Osmux BSC requests\n", + CMD_ATTR_IMMEDIATE) +{ + struct gsm_bts *bts = vty->index; + if (strcmp(argv[0], "off") == 0) + bts->osmux.use = OSMUX_USAGE_OFF; + else if (strcmp(argv[0], "on") == 0) + bts->osmux.use = OSMUX_USAGE_ON; + else if (strcmp(argv[0], "only") == 0) + bts->osmux.use = OSMUX_USAGE_ONLY; + return CMD_SUCCESS; +} -DEFUN(cfg_bts_trx, cfg_bts_trx_cmd, - "trx <0-254>", - "Select a TRX to configure\n" "TRX number\n") +DEFUN(cfg_bts_osmux_ip, + cfg_bts_osmux_ip_cmd, + "local-ip " VTY_IPV46_CMD, + IP_STR + "IPv4 Address to bind to\n" + "IPv6 Address to bind to\n") +{ + struct gsm_bts *bts = vty->index; + osmo_talloc_replace_string(bts, &bts->osmux.local_addr, argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_osmux_port, + cfg_bts_osmux_port_cmd, + "local-port <1-65535>", + "Osmux port\n" "UDP port\n") +{ + struct gsm_bts *bts = vty->index; + bts->osmux.local_port = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_osmux_batch_factor, + cfg_bts_osmux_batch_factor_cmd, + "batch-factor <1-8>", + "Batching factor\n" "Number of messages in the batch\n") +{ + struct gsm_bts *bts = vty->index; + bts->osmux.batch_factor = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_osmux_batch_size, + cfg_bts_osmux_batch_size_cmd, + "batch-size <1-65535>", + "Batch size\n" "Batch size in bytes\n") +{ + struct gsm_bts *bts = vty->index; + bts->osmux.batch_size = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_osmux_dummy_padding, + cfg_bts_osmux_dummy_padding_cmd, + "dummy-padding (on|off)", + "Dummy padding\n" + "Enable dummy padding\n" + "Disable dummy padding (default)\n") +{ + struct gsm_bts *bts = vty->index; + if (strcmp(argv[0], "on") == 0) + bts->osmux.dummy_padding = true; + else if (strcmp(argv[0], "off") == 0) + bts->osmux.dummy_padding = false; + return CMD_SUCCESS; +} + +DEFUN_ATTR(cfg_bts_trx, cfg_bts_trx_cmd, + "trx <0-254>", + "Select a TRX to configure\n" "TRX number\n", + CMD_ATTR_IMMEDIATE) { int trx_nr = atoi(argv[0]); struct gsm_bts *bts = vty->index; @@ -240,7 +307,7 @@ DEFUN(cfg_bts_trx, cfg_bts_trx_cmd, */ trx = gsm_bts_trx_alloc(bts); if (trx) - bts_trx_init(trx); + bts_model_trx_init(trx); } else trx = gsm_bts_trx_num(bts, trx_nr); @@ -257,11 +324,66 @@ DEFUN(cfg_bts_trx, cfg_bts_trx_cmd, return CMD_SUCCESS; } -static void config_write_bts_single(struct vty *vty, struct gsm_bts *bts) +static void config_write_dpc_params(struct vty *vty, const char *prefix, + const struct gsm_power_ctrl_params *params) { - struct gsm_bts_trx *trx; + const struct gsm_power_ctrl_meas_params *mp = ¶ms->rxlev_meas; + + if (mp->lower_thresh != power_ctrl_params_def.rxlev_meas.lower_thresh || + mp->upper_thresh != power_ctrl_params_def.rxlev_meas.upper_thresh) { + int target = (mp->lower_thresh + mp->upper_thresh) / 2; + int hyst = (mp->upper_thresh - mp->lower_thresh) / 2; + + vty_out(vty, " %s-power-target %d", prefix, rxlev2dbm(target)); + if (hyst > 0) + vty_out(vty, " hysteresis %d", hyst); + vty_out(vty, "%s", VTY_NEWLINE); + } + + /* MS Tx power filtering algorithm and parameters */ + switch (mp->algo) { + case GSM_PWR_CTRL_MEAS_AVG_ALGO_OSMO_EWMA: /* EWMA is the default */ + if (mp->ewma.alpha != power_ctrl_params_def.rxlev_meas.ewma.alpha) + vty_out(vty, " %s-power-filtering algo ewma beta %u%s", + prefix, 100 - mp->ewma.alpha, VTY_NEWLINE); + break; + /* Other algorithm cannot be set via the VTY */ + case BTS_PF_ALGO_NONE: + default: + vty_out(vty, " no %s-power-filtering%s", prefix, VTY_NEWLINE); + break; + } +} + +static void config_write_osmux(struct vty *vty, const char *prefix, const struct gsm_bts *bts) +{ + vty_out(vty, "%sosmux%s", prefix, VTY_NEWLINE); + vty_out(vty, "%s use ", prefix); + switch (bts->osmux.use) { + case OSMUX_USAGE_ON: + vty_out(vty, "on%s", VTY_NEWLINE); + break; + case OSMUX_USAGE_ONLY: + vty_out(vty, "only%s", VTY_NEWLINE); + break; + case OSMUX_USAGE_OFF: + default: + vty_out(vty, "off%s", VTY_NEWLINE); + break; + } + vty_out(vty, "%s local-ip %s%s", prefix, bts->osmux.local_addr, VTY_NEWLINE); + vty_out(vty, "%s local-port %u%s", prefix, bts->osmux.local_port, VTY_NEWLINE); + vty_out(vty, "%s batch-factor %d%s", prefix, bts->osmux.batch_factor, VTY_NEWLINE); + vty_out(vty, "%s batch-size %u%s", prefix, bts->osmux.batch_size, VTY_NEWLINE); + vty_out(vty, "%s dummy-padding %s%s", prefix, bts->osmux.dummy_padding ? "on" : "off", VTY_NEWLINE); +} + +static void config_write_bts_single(struct vty *vty, const struct gsm_bts *bts) +{ + const struct gsm_bts_trx *trx; const char *sapi_buf; int i; + struct bsc_oml_host *bsc_oml_host; vty_out(vty, "bts %u%s", bts->nr, VTY_NEWLINE); if (bts->description) @@ -271,18 +393,32 @@ static void config_write_bts_single(struct vty *vty, struct gsm_bts *bts) vty_out(vty, " auto-band%s", VTY_NEWLINE); vty_out(vty, " ipa unit-id %u %u%s", bts->ip_access.site_id, bts->ip_access.bts_id, VTY_NEWLINE); - vty_out(vty, " oml remote-ip %s%s", bts->bsc_oml_host, VTY_NEWLINE); + llist_for_each_entry(bsc_oml_host, &bts->bsc_oml_hosts, list) + vty_out(vty, " oml remote-ip %s%s", bsc_oml_host->addr, VTY_NEWLINE); vty_out(vty, " rtp jitter-buffer %u", bts->rtp_jitter_buf_ms); if (bts->rtp_jitter_adaptive) vty_out(vty, " adaptive"); vty_out(vty, "%s", VTY_NEWLINE); vty_out(vty, " rtp port-range %u %u%s", bts->rtp_port_range_start, bts->rtp_port_range_end, VTY_NEWLINE); + if (bts->rtp_ip_dscp != -1) + vty_out(vty, " rtp ip-dscp %d%s", bts->rtp_ip_dscp, VTY_NEWLINE); + if (bts->rtp_priority != -1) + vty_out(vty, " rtp socket-priority %d%s", bts->rtp_priority, VTY_NEWLINE); + if (bts->rtp_nogaps_mode) + vty_out(vty, " rtp continuous-streaming%s", VTY_NEWLINE); + vty_out(vty, " %srtp internal-uplink-ecu%s", + bts->use_ul_ecu ? "" : "no ", VTY_NEWLINE); + vty_out(vty, " rtp hr-format %s%s", + bts->emit_hr_rfc5993 ? "rfc5993" : "ts101318", VTY_NEWLINE); vty_out(vty, " paging queue-size %u%s", paging_get_queue_max(bts->paging_state), VTY_NEWLINE); vty_out(vty, " paging lifetime %u%s", paging_get_lifetime(bts->paging_state), VTY_NEWLINE); - vty_out(vty, " uplink-power-target %d%s", bts->ul_power_target, VTY_NEWLINE); + + /* Fall-back MS Power Control parameters may be changed by the user */ + config_write_dpc_params(vty, "uplink", &bts->ms_dpc_params); + if (bts->agch_queue.thresh_level != GSM_BTS_AGCH_QUEUE_THRESH_LEVEL_DEFAULT || bts->agch_queue.low_level != GSM_BTS_AGCH_QUEUE_LOW_LEVEL_DEFAULT || bts->agch_queue.high_level != GSM_BTS_AGCH_QUEUE_HIGH_LEVEL_DEFAULT) @@ -290,16 +426,33 @@ static void config_write_bts_single(struct vty *vty, struct gsm_bts *bts) bts->agch_queue.thresh_level, bts->agch_queue.low_level, bts->agch_queue.high_level, VTY_NEWLINE); - for (i = 0; i < 32; i++) { - if (gsmtap_sapi_mask & (1 << i)) { - sapi_buf = osmo_str_tolower(get_value_string(gsmtap_sapi_names, i)); + if (bts->gsmtap.remote_host != NULL) + vty_out(vty, " gsmtap-remote-host %s%s", + bts->gsmtap.remote_host, + VTY_NEWLINE); + if (bts->gsmtap.local_host != NULL) + vty_out(vty, " gsmtap-local-host %s%s", + bts->gsmtap.local_host, + VTY_NEWLINE); + for (i = 0; i < sizeof(uint32_t) * 8; i++) { + if (bts->gsmtap.sapi_mask & ((uint32_t) 1 << i)) { + sapi_buf = get_value_string_or_null(gsmtap_sapi_names, i); + if (sapi_buf == NULL) + continue; + sapi_buf = osmo_str_tolower(sapi_buf); vty_out(vty, " gsmtap-sapi %s%s", sapi_buf, VTY_NEWLINE); } } - if (gsmtap_sapi_acch) { + if (bts->gsmtap.sapi_acch) { sapi_buf = osmo_str_tolower(get_value_string(gsmtap_sapi_names, GSMTAP_CHANNEL_ACCH)); vty_out(vty, " gsmtap-sapi %s%s", sapi_buf, VTY_NEWLINE); } + if (bts->gsmtap.rlp) { + if (bts->gsmtap.rlp_skip_null) + vty_out(vty, " gsmtap-rlp skip-null%s", VTY_NEWLINE); + else + vty_out(vty, " gsmtap-rlp%s", VTY_NEWLINE); + } vty_out(vty, " min-qual-rach %d%s", bts->min_qual_rach, VTY_NEWLINE); vty_out(vty, " min-qual-norm %d%s", bts->min_qual_norm, @@ -308,20 +461,24 @@ static void config_write_bts_single(struct vty *vty, struct gsm_bts *bts) VTY_NEWLINE); if (strcmp(bts->pcu.sock_path, PCU_SOCK_DEFAULT)) vty_out(vty, " pcu-socket %s%s", bts->pcu.sock_path, VTY_NEWLINE); + if (bts->pcu.sock_wqueue_len_max != BTS_CFG_PCU_SOCK_WQUEUE_LEN_MAX_MAX) + vty_out(vty, " pcu-socket-wqueue-length %u%s", bts->pcu.sock_wqueue_len_max, VTY_NEWLINE); if (bts->supp_meas_toa256) vty_out(vty, " supp-meas-info toa256%s", VTY_NEWLINE); vty_out(vty, " smscb queue-max-length %d%s", bts->smscb_queue_max_len, VTY_NEWLINE); vty_out(vty, " smscb queue-target-length %d%s", bts->smscb_queue_tgt_len, VTY_NEWLINE); vty_out(vty, " smscb queue-hysteresis %d%s", bts->smscb_queue_hyst, VTY_NEWLINE); + config_write_osmux(vty, " ", bts); + bts_model_config_write_bts(vty, bts); llist_for_each_entry(trx, &bts->trx_list, list) { - struct trx_power_params *tpp = &trx->power_params; - struct phy_instance *pinst = trx_phy_instance(trx); + const struct trx_power_params *tpp = &trx->power_params; + const struct phy_instance *pinst = trx_phy_instance(trx); vty_out(vty, " trx %u%s", trx->nr, VTY_NEWLINE); - if (trx->power_params.user_gain_mdB) + if (tpp->user_gain_mdB) vty_out(vty, " user-gain %u mdB%s", tpp->user_gain_mdB, VTY_NEWLINE); vty_out(vty, " power-ramp max-initial %d mdBm%s", @@ -331,8 +488,10 @@ static void config_write_bts_single(struct vty *vty, struct gsm_bts *bts) vty_out(vty, " power-ramp step-interval %d%s", tpp->ramp.step_interval_sec, VTY_NEWLINE); vty_out(vty, " ms-power-control %s%s", - trx->ms_power_control == 0 ? "dsp" : "osmo", + trx->ms_pwr_ctl_soft ? "osmo" : "dsp", VTY_NEWLINE); + vty_out(vty, " ta-control interval %u%s", + trx->ta_ctrl_interval, VTY_NEWLINE); vty_out(vty, " phy %u instance %u%s", pinst->phy_link->num, pinst->num, VTY_NEWLINE); @@ -342,16 +501,16 @@ static void config_write_bts_single(struct vty *vty, struct gsm_bts *bts) static int config_write_bts(struct vty *vty) { - struct gsm_network *net = gsmnet_from_vty(vty); - struct gsm_bts *bts; + const struct gsm_bts *bts; - llist_for_each_entry(bts, &net->bts_list, list) + llist_for_each_entry(bts, &g_bts_sm->bts_list, list) config_write_bts_single(vty, bts); + osmo_tdef_vty_groups_write(vty, ""); return CMD_SUCCESS; } -static void config_write_phy_single(struct vty *vty, struct phy_link *plink) +static void config_write_phy_single(struct vty *vty, const struct phy_link *plink) { int i; @@ -359,7 +518,7 @@ static void config_write_phy_single(struct vty *vty, struct phy_link *plink) bts_model_config_write_phy(vty, plink); for (i = 0; i < 255; i++) { - struct phy_instance *pinst = phy_instance_by_num(plink, i); + const struct phy_instance *pinst = phy_instance_by_num(plink, i); if (!pinst) break; vty_out(vty, " instance %u%s", pinst->num, VTY_NEWLINE); @@ -372,7 +531,7 @@ static int config_write_phy(struct vty *vty) int i; for (i = 0; i < 255; i++) { - struct phy_link *plink = phy_link_by_num(i); + const struct phy_link *plink = phy_link_by_num(i); if (!plink) break; config_write_phy_single(vty, plink); @@ -396,22 +555,22 @@ DEFUN(cfg_vty_telnet_port, cfg_vty_telnet_port_cmd, } /* per-BTS configuration */ -DEFUN(cfg_bts, - cfg_bts_cmd, - "bts BTS_NR", - "Select a BTS to configure\n" - "BTS Number\n") +DEFUN_ATTR(cfg_bts, + cfg_bts_cmd, + "bts BTS_NR", + "Select a BTS to configure\n" + "BTS Number\n", + CMD_ATTR_IMMEDIATE) { - struct gsm_network *gsmnet = gsmnet_from_vty(vty); int bts_nr = atoi(argv[0]); struct gsm_bts *bts; - if (bts_nr >= gsmnet->num_bts) { + if (bts_nr >= g_bts_sm->num_bts) { vty_out(vty, "%% Unknown BTS number %u (num %u)%s", - bts_nr, gsmnet->num_bts, VTY_NEWLINE); + bts_nr, g_bts_sm->num_bts, VTY_NEWLINE); return CMD_WARNING; } else - bts = gsm_bts_num(gsmnet, bts_nr); + bts = gsm_bts_num(g_bts_sm, bts_nr); vty->index = bts; vty->index_sub = &bts->description; @@ -453,9 +612,11 @@ DEFUN(cfg_bts_band, struct gsm_bts *bts = vty->index; int band = gsm_band_parse(argv[0]); + /* This should not happen with the recent versions of libosmovty, + * but old versions may pass incomplete choice values like 'GSM9'. */ if (band < 0) { - vty_out(vty, "%% BAND %d is not a valid GSM band%s", - band, VTY_NEWLINE); + vty_out(vty, "%% BAND '%s' is not a valid GSM band%s", + argv[0], VTY_NEWLINE); return CMD_WARNING; } @@ -470,11 +631,51 @@ DEFUN(cfg_bts_oml_ip, "OML Parameters\n" "OML IP Address\n" "OML IP Address\n") { struct gsm_bts *bts = vty->index; + struct bsc_oml_host *bsc_oml_host; + + /* stop when the address is already in the list */ + llist_for_each_entry(bsc_oml_host, &bts->bsc_oml_hosts, list) { + if (strcmp(argv[0], bsc_oml_host->addr) == 0) { + vty_out(vty, "%% duplicate BSC (A-Bis/OML) ip address configured ('%s')%s", + argv[0], VTY_NEWLINE); + return CMD_WARNING; + } + } + + bsc_oml_host = talloc_zero(bts, struct bsc_oml_host); + OSMO_ASSERT(bsc_oml_host); + bsc_oml_host->addr = talloc_strdup(bsc_oml_host, argv[0]); + OSMO_ASSERT(bsc_oml_host->addr); + llist_add_tail(&bsc_oml_host->list, &bts->bsc_oml_hosts); - if (bts->bsc_oml_host) - talloc_free(bts->bsc_oml_host); + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_no_oml_ip, + cfg_bts_no_oml_ip_cmd, + "no oml remote-ip A.B.C.D", + NO_STR "OML Parameters\n" "OML IP Address\n" "OML IP Address\n") +{ + struct gsm_bts *bts = vty->index; + struct bsc_oml_host *bsc_oml_host; + struct bsc_oml_host *bsc_oml_host_del = NULL; + + llist_for_each_entry(bsc_oml_host, &bts->bsc_oml_hosts, list) { + if (strcmp(argv[0], bsc_oml_host->addr) == 0) + bsc_oml_host_del = bsc_oml_host; + } - bts->bsc_oml_host = talloc_strdup(bts, argv[0]); + if (bsc_oml_host_del) { + if (bts->abis_link_fi) { + osmo_fsm_inst_dispatch(bts->abis_link_fi, ABIS_LINK_EV_VTY_RM_ADDR, bsc_oml_host_del); + llist_del(&bsc_oml_host_del->list); + talloc_free(bsc_oml_host_del); + } + } else { + vty_out(vty, "%% no such BSC (A-Bis/OML) ip address configured ('%s')%s", + argv[0], VTY_NEWLINE); + return CMD_WARNING; + } return CMD_SUCCESS; } @@ -491,10 +692,13 @@ DEFUN_DEPRECATED(cfg_bts_rtp_bind_ip, return CMD_WARNING; } -DEFUN(cfg_bts_rtp_jitbuf, - cfg_bts_rtp_jitbuf_cmd, - "rtp jitter-buffer <0-10000> [adaptive]", - RTP_STR "RTP jitter buffer\n" "jitter buffer in ms\n") +DEFUN_USRATTR(cfg_bts_rtp_jitbuf, + cfg_bts_rtp_jitbuf_cmd, + X(BTS_VTY_ATTR_NEW_LCHAN), + "rtp jitter-buffer <0-10000> [adaptive]", + RTP_STR "RTP jitter buffer\n" + "Jitter buffer in ms\n" + "Enable adaptive RTP jitter buffering\n") { struct gsm_bts *bts = vty->index; @@ -506,9 +710,10 @@ DEFUN(cfg_bts_rtp_jitbuf, } DEFUN(cfg_bts_rtp_port_range, - cfg_bts_rtp_port_range_cmd, - "rtp port-range <1-65534> <1-65534>", - RTP_STR "Range of local ports to use for RTP/RTCP traffic\n") + cfg_bts_rtp_port_range_cmd, + "rtp port-range <1-65534> <1-65534>", + RTP_STR "Range of local ports to use for RTP/RTCP traffic\n" + "Port range start (inclusive)\n" "Port range end (inclusive)\n") { struct gsm_bts *bts = vty->index; unsigned int start; @@ -518,19 +723,19 @@ DEFUN(cfg_bts_rtp_port_range, end = atoi(argv[1]); if (end < start) { - vty_out(vty, "range end port (%u) must be greater than the range start port (%u)!%s", + vty_out(vty, "%% Range end port (%u) must be greater than the range start port (%u)!%s", end, start, VTY_NEWLINE); return CMD_WARNING; } if (start & 1) { - vty_out(vty, "range must begin at an even port number! (%u not even)%s", + vty_out(vty, "%% Range must begin at an even port number! (%u is odd)%s", start, VTY_NEWLINE); return CMD_WARNING; } if ((end & 1) == 0) { - vty_out(vty, "range must end at an odd port number! (%u not odd)%s", + vty_out(vty, "%% Range must end at an odd port number! (%u is even)%s", end, VTY_NEWLINE); return CMD_WARNING; } @@ -542,13 +747,99 @@ DEFUN(cfg_bts_rtp_port_range, return CMD_SUCCESS; } +DEFUN_USRATTR(cfg_bts_rtp_ip_dscp, + cfg_bts_rtp_ip_dscp_cmd, + X(BTS_VTY_ATTR_NEW_LCHAN), + "rtp ip-dscp <0-63>", + RTP_STR "Specify DSCP for RTP/IP packets\n" "The DSCP value (upper 6 bits of TOS)\n") +{ + struct gsm_bts *bts = vty->index; + int dscp = atoi(argv[0]); + + bts->rtp_ip_dscp = dscp; + + return CMD_SUCCESS; +} + +DEFUN_USRATTR(cfg_bts_rtp_priority, + cfg_bts_rtp_priority_cmd, + X(BTS_VTY_ATTR_NEW_LCHAN), + "rtp socket-priority <0-255>", + RTP_STR "Specify socket priority for RTP/IP packets\n" + "The socket priority value (> 6 requires CAP_NET_ADMIN)\n") +{ + struct gsm_bts *bts = vty->index; + int prio = atoi(argv[0]); + + bts->rtp_priority = prio; + + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_rtp_cont_stream, + cfg_bts_rtp_cont_stream_cmd, + "rtp continuous-streaming", + RTP_STR "Always emit an RTP packet every 20 ms\n") +{ + struct gsm_bts *bts = vty->index; + + bts->rtp_nogaps_mode = true; + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_no_rtp_cont_stream, + cfg_bts_no_rtp_cont_stream_cmd, + "no rtp continuous-streaming", + NO_STR RTP_STR "Always emit an RTP packet every 20 ms\n") +{ + struct gsm_bts *bts = vty->index; + + bts->rtp_nogaps_mode = false; + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_rtp_int_ul_ecu, + cfg_bts_rtp_int_ul_ecu_cmd, + "rtp internal-uplink-ecu", + RTP_STR "Apply a BTS-internal ECU to the uplink traffic frame stream\n") +{ + struct gsm_bts *bts = vty->index; + + bts->use_ul_ecu = true; + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_no_rtp_int_ul_ecu, + cfg_bts_no_rtp_int_ul_ecu_cmd, + "no rtp internal-uplink-ecu", + NO_STR RTP_STR "Apply a BTS-internal ECU to the uplink traffic frame stream\n") +{ + struct gsm_bts *bts = vty->index; + + bts->use_ul_ecu = false; + return CMD_SUCCESS; +} + +DEFUN_ATTR(cfg_bts_rtp_hr_format, + cfg_bts_rtp_hr_format_cmd, + "rtp hr-format (rfc5993|ts101318)", + RTP_STR "HRv1 codec output format\n" "RFC 5993\n" "TS 101 318\n", + CMD_ATTR_IMMEDIATE) +{ + struct gsm_bts *bts = vty->index; + + bts->emit_hr_rfc5993 = !strcmp(argv[0], "rfc5993"); + return CMD_SUCCESS; +} + #define PAG_STR "Paging related parameters\n" -DEFUN(cfg_bts_paging_queue_size, - cfg_bts_paging_queue_size_cmd, - "paging queue-size <1-1024>", - PAG_STR "Maximum length of BTS-internal paging queue\n" - "Maximum length of BTS-internal paging queue\n") +DEFUN_ATTR(cfg_bts_paging_queue_size, + cfg_bts_paging_queue_size_cmd, + "paging queue-size <1-1024>", + PAG_STR "Maximum length of BTS-internal paging queue\n" + "Maximum length of BTS-internal paging queue\n", + CMD_ATTR_IMMEDIATE) { struct gsm_bts *bts = vty->index; @@ -557,11 +848,12 @@ DEFUN(cfg_bts_paging_queue_size, return CMD_SUCCESS; } -DEFUN(cfg_bts_paging_lifetime, - cfg_bts_paging_lifetime_cmd, - "paging lifetime <0-60>", - PAG_STR "Maximum lifetime of a paging record\n" - "Maximum lifetime of a paging record (secods)\n") +DEFUN_ATTR(cfg_bts_paging_lifetime, + cfg_bts_paging_lifetime_cmd, + "paging lifetime <0-60>", + PAG_STR "Maximum lifetime of a paging record\n" + "Maximum lifetime of a paging record (secods)\n", + CMD_ATTR_IMMEDIATE) { struct gsm_bts *bts = vty->index; @@ -572,13 +864,14 @@ DEFUN(cfg_bts_paging_lifetime, #define AGCH_QUEUE_STR "AGCH queue mgmt\n" -DEFUN(cfg_bts_agch_queue_mgmt_params, - cfg_bts_agch_queue_mgmt_params_cmd, - "agch-queue-mgmt threshold <0-100> low <0-100> high <0-100000>", - AGCH_QUEUE_STR - "Threshold to start cleanup\nin %% of the maximum queue length\n" - "Low water mark for cleanup\nin %% of the maximum queue length\n" - "High water mark for cleanup\nin %% of the maximum queue length\n") +DEFUN_ATTR(cfg_bts_agch_queue_mgmt_params, + cfg_bts_agch_queue_mgmt_params_cmd, + "agch-queue-mgmt threshold <0-100> low <0-100> high <0-100000>", + AGCH_QUEUE_STR + "Threshold to start cleanup\nin % of the maximum queue length\n" + "Low water mark for cleanup\nin % of the maximum queue length\n" + "High water mark for cleanup\nin % of the maximum queue length\n", + CMD_ATTR_IMMEDIATE) { struct gsm_bts *bts = vty->index; @@ -589,11 +882,12 @@ DEFUN(cfg_bts_agch_queue_mgmt_params, return CMD_SUCCESS; } -DEFUN(cfg_bts_agch_queue_mgmt_default, - cfg_bts_agch_queue_mgmt_default_cmd, - "agch-queue-mgmt default", - AGCH_QUEUE_STR - "Reset clean parameters to default values\n") +DEFUN_ATTR(cfg_bts_agch_queue_mgmt_default, + cfg_bts_agch_queue_mgmt_default_cmd, + "agch-queue-mgmt default", + AGCH_QUEUE_STR + "Reset clean parameters to default values\n", + CMD_ATTR_IMMEDIATE) { struct gsm_bts *bts = vty->index; @@ -604,22 +898,94 @@ DEFUN(cfg_bts_agch_queue_mgmt_default, return CMD_SUCCESS; } -DEFUN(cfg_bts_ul_power_target, cfg_bts_ul_power_target_cmd, - "uplink-power-target <-110-0>", - "Set the nominal target Rx Level for uplink power control loop\n" - "Target uplink Rx level in dBm\n") +#define UL_POWER_TARGET_CMD \ + "uplink-power-target <-110-0>" +#define UL_POWER_TARGET_CMD_DESC \ + "Set the nominal target Rx Level for uplink power control loop\n" \ + "Target uplink Rx level in dBm\n" + +#define UL_POWER_DEPR_MSG(fmt, args...) \ + vty_out(vty, "%% Command '%s' has been deprecated.%s" \ + "%% MS/BS Power control parameters should be configured in osmo-bsc: " \ + fmt, self->string, VTY_NEWLINE, ##args) + +DEFUN_ATTR(cfg_bts_ul_power_target, cfg_bts_ul_power_target_cmd, + UL_POWER_TARGET_CMD, UL_POWER_TARGET_CMD_DESC, + CMD_ATTR_DEPRECATED) +{ + struct gsm_power_ctrl_meas_params *mp; + struct gsm_bts *bts = vty->index; + int rxlev_dbm = atoi(argv[0]); + int hyst = 0; + + if (argc > 1) /* optional argument */ + hyst = atoi(argv[1]); + + mp = &bts->ms_dpc_params.rxlev_meas; + mp->lower_thresh = dbm2rxlev(rxlev_dbm - hyst); + mp->upper_thresh = dbm2rxlev(rxlev_dbm + hyst); + + UL_POWER_DEPR_MSG("use 'rxlev-thresh lower %u upper %u'.%s", + mp->lower_thresh, mp->upper_thresh, + VTY_NEWLINE); + + return CMD_SUCCESS; +} + +/* FIXME: libosmovty is unable to handle 'foo <-110-0> [bar <1-25>]' */ +DEFUN_CMD_ELEMENT(cfg_bts_ul_power_target, + cfg_bts_ul_power_target_hysteresis_cmd, + UL_POWER_TARGET_CMD " hysteresis <1-25>", + UL_POWER_TARGET_CMD_DESC + "Target Rx Level hysteresis\n" + "Tolerable deviation in dBm\n", + CMD_ATTR_DEPRECATED, 0); + +DEFUN_ATTR(cfg_no_bts_ul_power_filter, + cfg_bts_no_ul_power_filter_cmd, + "no uplink-power-filtering", + NO_STR "Disable filtering for uplink power control loop\n", + CMD_ATTR_DEPRECATED) { + struct gsm_power_ctrl_meas_params *mp; struct gsm_bts *bts = vty->index; - bts->ul_power_target = atoi(argv[0]); + mp = &bts->ms_dpc_params.rxlev_meas; + mp->algo = GSM_PWR_CTRL_MEAS_AVG_ALGO_NONE; + + UL_POWER_DEPR_MSG("use 'no rxlev-avg'.%s", VTY_NEWLINE); return CMD_SUCCESS; } -DEFUN(cfg_bts_min_qual_rach, cfg_bts_min_qual_rach_cmd, - "min-qual-rach <-100-100>", - "Set the minimum link quality level of Access Bursts to be accepted\n" - "C/I (Carrier-to-Interference) ratio in centiBels (10e-2 B or 10e-1 dB)\n") +DEFUN_ATTR(cfg_bts_ul_power_filter_ewma, + cfg_bts_ul_power_filter_ewma_cmd, + "uplink-power-filtering algo ewma beta <1-99>", + "Configure filtering for uplink power control loop\n" + "Select the filtering algorithm\n" + "Exponentially Weighted Moving Average (EWMA)\n" + "Smoothing factor (in %): beta = (100 - alpha)\n" + "1% - lowest smoothing, 99% - highest smoothing\n", + CMD_ATTR_DEPRECATED) +{ + struct gsm_power_ctrl_meas_params *mp; + struct gsm_bts *bts = vty->index; + + mp = &bts->ms_dpc_params.rxlev_meas; + mp->algo = GSM_PWR_CTRL_MEAS_AVG_ALGO_OSMO_EWMA; + mp->ewma.alpha = 100 - atoi(argv[0]); + + UL_POWER_DEPR_MSG("use 'rxlev-avg algo osmo-ewma beta %s'.%s", + argv[0], VTY_NEWLINE); + + return CMD_SUCCESS; +} + +DEFUN_ATTR(cfg_bts_min_qual_rach, cfg_bts_min_qual_rach_cmd, + "min-qual-rach <-100-100>", + "Set the minimum link quality level of Access Bursts to be accepted\n" + "C/I (Carrier-to-Interference) ratio in centiBels (10e-2 B or 10e-1 dB)\n", + CMD_ATTR_IMMEDIATE) { struct gsm_bts *bts = vty->index; @@ -628,10 +994,11 @@ DEFUN(cfg_bts_min_qual_rach, cfg_bts_min_qual_rach_cmd, return CMD_SUCCESS; } -DEFUN(cfg_bts_min_qual_norm, cfg_bts_min_qual_norm_cmd, - "min-qual-norm <-100-100>", - "Set the minimum link quality level of Normal Bursts to be accepted\n" - "C/I (Carrier-to-Interference) ratio in centiBels (10e-2 B or 10e-1 dB)\n") +DEFUN_ATTR(cfg_bts_min_qual_norm, cfg_bts_min_qual_norm_cmd, + "min-qual-norm <-100-100>", + "Set the minimum link quality level of Normal Bursts to be accepted\n" + "C/I (Carrier-to-Interference) ratio in centiBels (10e-2 B or 10e-1 dB)\n", + CMD_ATTR_IMMEDIATE) { struct gsm_bts *bts = vty->index; @@ -640,10 +1007,11 @@ DEFUN(cfg_bts_min_qual_norm, cfg_bts_min_qual_norm_cmd, return CMD_SUCCESS; } -DEFUN(cfg_bts_max_ber_rach, cfg_bts_max_ber_rach_cmd, - "max-ber10k-rach <0-10000>", - "Set the maximum BER for valid RACH requests\n" - "BER in 1/10000 units (0=no BER; 100=1% BER)\n") +DEFUN_ATTR(cfg_bts_max_ber_rach, cfg_bts_max_ber_rach_cmd, + "max-ber10k-rach <0-10000>", + "Set the maximum BER for valid RACH requests\n" + "BER in 1/10000 units (0=no BER; 100=1% BER)\n", + CMD_ATTR_IMMEDIATE) { struct gsm_bts *bts = vty->index; @@ -652,26 +1020,34 @@ DEFUN(cfg_bts_max_ber_rach, cfg_bts_max_ber_rach_cmd, return CMD_SUCCESS; } -DEFUN(cfg_bts_pcu_sock, cfg_bts_pcu_sock_cmd, +DEFUN(cfg_bts_pcu_sock_path, cfg_bts_pcu_sock_path_cmd, "pcu-socket PATH", - "Configure the PCU socket file/path name\n") + "Configure the PCU socket file/path name\n" + "UNIX socket path\n") { struct gsm_bts *bts = vty->index; - if (bts->pcu.sock_path) { - /* FIXME: close the interface? */ - talloc_free(bts->pcu.sock_path); - } - bts->pcu.sock_path = talloc_strdup(bts, argv[0]); + osmo_talloc_replace_string(bts, &bts->pcu.sock_path, argv[0]); + /* FIXME: re-open the interface? */ + return CMD_SUCCESS; +} +DEFUN(cfg_bts_pcu_sock_ql, cfg_bts_pcu_sock_ql_cmd, + "pcu-socket-wqueue-length <1-" OSMO_STRINGIFY_VAL(BTS_CFG_PCU_SOCK_WQUEUE_LEN_MAX_MAX) ">", + "Configure the PCU socket queue length\n" + "Queue length\n") +{ + struct gsm_bts *bts = vty->index; + bts->pcu.sock_wqueue_len_max = atoi(argv[0]); return CMD_SUCCESS; } -DEFUN(cfg_bts_supp_meas_toa256, cfg_bts_supp_meas_toa256_cmd, - "supp-meas-info toa256", - "Configure the RSL Supplementary Measurement Info\n" - "Report the TOA in 1/256th symbol periods\n") +DEFUN_ATTR(cfg_bts_supp_meas_toa256, cfg_bts_supp_meas_toa256_cmd, + "supp-meas-info toa256", + "Configure the RSL Supplementary Measurement Info\n" + "Report the TOA in 1/256th symbol periods\n", + CMD_ATTR_IMMEDIATE) { struct gsm_bts *bts = vty->index; @@ -679,10 +1055,11 @@ DEFUN(cfg_bts_supp_meas_toa256, cfg_bts_supp_meas_toa256_cmd, return CMD_SUCCESS; } -DEFUN(cfg_bts_no_supp_meas_toa256, cfg_bts_no_supp_meas_toa256_cmd, - "no supp-meas-info toa256", - NO_STR "Configure the RSL Supplementary Measurement Info\n" - "Report the TOA in 1/256th symbol periods\n") +DEFUN_ATTR(cfg_bts_no_supp_meas_toa256, cfg_bts_no_supp_meas_toa256_cmd, + "no supp-meas-info toa256", + NO_STR "Configure the RSL Supplementary Measurement Info\n" + "Report the TOA in 1/256th symbol periods\n", + CMD_ATTR_IMMEDIATE) { struct gsm_bts *bts = vty->index; @@ -690,27 +1067,36 @@ DEFUN(cfg_bts_no_supp_meas_toa256, cfg_bts_no_supp_meas_toa256_cmd, return CMD_SUCCESS; } -DEFUN(cfg_bts_smscb_max_qlen, cfg_bts_smscb_max_qlen_cmd, - "smscb queue-max-length <1-60>", - "Maximum queue length for SMSCB (CBCH) queue. In count of messages/pages (Default: 15)") +#define SMSCB_STR \ + "SMSCB (SMS Cell Broadcast) / CBCH configuration\n" + +DEFUN_ATTR(cfg_bts_smscb_max_qlen, cfg_bts_smscb_max_qlen_cmd, + "smscb queue-max-length <1-60>", + SMSCB_STR "Maximum length of the SMSCB (CBCH) queue\n" + "Length in count of messages/pages (default: 15)\n", + CMD_ATTR_IMMEDIATE) { struct gsm_bts *bts = vty->index; bts->smscb_queue_max_len = atoi(argv[0]); return CMD_SUCCESS; } -DEFUN(cfg_bts_smscb_tgt_qlen, cfg_bts_smscb_tgt_qlen_cmd, - "smscb queue-target-length <1-30>", - "Target queue length for SMSCB (CBCH) queue. In count of messages/pages (Default: 2)") +DEFUN_ATTR(cfg_bts_smscb_tgt_qlen, cfg_bts_smscb_tgt_qlen_cmd, + "smscb queue-target-length <1-30>", + SMSCB_STR "Target length of the SMSCB (CBCH) queue\n" + "Length in count of messages/pages (default: 2)\n", + CMD_ATTR_IMMEDIATE) { struct gsm_bts *bts = vty->index; bts->smscb_queue_tgt_len = atoi(argv[0]); return CMD_SUCCESS; } -DEFUN(cfg_bts_smscb_qhyst, cfg_bts_smscb_qhyst_cmd, - "smscb queue-hysteresis <0-30>", - "Hysteresis for SMSCB (CBCH) queue. In count of messages/pages (Default: 2)") +DEFUN_ATTR(cfg_bts_smscb_qhyst, cfg_bts_smscb_qhyst_cmd, + "smscb queue-hysteresis <0-30>", + SMSCB_STR "Hysteresis of the SMSCB (CBCH) queue\n" + "In count of messages/pages (default: 2)\n", + CMD_ATTR_IMMEDIATE) { struct gsm_bts *bts = vty->index; bts->smscb_queue_hyst = atoi(argv[0]); @@ -718,7 +1104,7 @@ DEFUN(cfg_bts_smscb_qhyst, cfg_bts_smscb_qhyst_cmd, } -#define DB_DBM_STR \ +#define DB_MDB_STR \ "Unit is dB (decibels)\n" \ "Unit is mdB (milli-decibels, or rather 1/10000 bel)\n" @@ -732,11 +1118,12 @@ static int parse_mdbm(const char *valstr, const char *unit) return val; } -DEFUN(cfg_trx_user_gain, - cfg_trx_user_gain_cmd, - "user-gain <-100000-100000> (dB|mdB)", - "Inform BTS about additional, user-provided gain or attenuation at TRX output\n" - "Value of user-provided external gain(+)/attenuation(-)\n" DB_DBM_STR) +DEFUN_ATTR(cfg_trx_user_gain, + cfg_trx_user_gain_cmd, + "user-gain <-100000-100000> (dB|mdB)", + "Inform BTS about additional, user-provided gain or attenuation at TRX output\n" + "Value of user-provided external gain(+)/attenuation(-)\n" DB_MDB_STR, + CMD_ATTR_IMMEDIATE) { struct gsm_bts_trx *trx = vty->index; @@ -745,11 +1132,11 @@ DEFUN(cfg_trx_user_gain, return CMD_SUCCESS; } -#define PR_STR "Power-Ramp settings" +#define PR_STR "Power-Ramp settings\n" DEFUN(cfg_trx_pr_max_initial, cfg_trx_pr_max_initial_cmd, - "power-ramp max-initial <0-100000> (dBm|mdBm)", + "power-ramp max-initial <-10000-100000> (dBm|mdBm)", PR_STR "Maximum initial power\n" - "Value\n" DB_DBM_STR) + "Value\n" DB_MDB_STR) { struct gsm_bts_trx *trx = vty->index; @@ -762,7 +1149,7 @@ DEFUN(cfg_trx_pr_max_initial, cfg_trx_pr_max_initial_cmd, DEFUN(cfg_trx_pr_step_size, cfg_trx_pr_step_size_cmd, "power-ramp step-size <1-100000> (dB|mdB)", PR_STR "Power increase by step\n" - "Step size\n" DB_DBM_STR) + "Step size\n" DB_MDB_STR) { struct gsm_bts_trx *trx = vty->index; @@ -784,12 +1171,40 @@ DEFUN(cfg_trx_pr_step_interval, cfg_trx_pr_step_interval_cmd, DEFUN(cfg_trx_ms_power_control, cfg_trx_ms_power_control_cmd, "ms-power-control (dsp|osmo)", - "Mobile Station Power Level Control (change requires restart)\n" + "Mobile Station Power Level Control\n" "Handled by DSP\n" "Handled by OsmoBTS\n") { struct gsm_bts_trx *trx = vty->index; + bool soft = !strcmp(argv[0], "osmo"); + + if (!soft && !bts_internal_flag_get(trx->bts, BTS_INTERNAL_FLAG_MS_PWR_CTRL_DSP)) { + /* NOTE: osmo-bts-trx used to have its own (low-level) MS Power Control loop, which + * has been ripped out in favour of the common implementation. Configuration files + * may still contain 'dsp', so let's be tolerant and override 'dsp' by 'osmo'. */ + if (trx->bts->variant == BTS_OSMO_TRX && vty->type == VTY_FILE) { + vty_out(vty, "%% BTS model 'osmo-bts-trx' has no DSP/HW MS Power Control support, " + "consider updating your configuration file!%s", VTY_NEWLINE); + soft = true; /* override */ + } else { + vty_out(vty, "%% This BTS model has no DSP/HW MS Power Control support%s", VTY_NEWLINE); + return CMD_WARNING; + } + } + + trx->ms_pwr_ctl_soft = soft; + return CMD_SUCCESS; +} + +DEFUN(cfg_ta_ctrl_interval, cfg_ta_ctrl_interval_cmd, + "ta-control interval <0-31>", + "Timing Advance Control Parameters\n" + "Set TA control loop interval\n" + "As in P_CON_INTERVAL, in units of 2 SACCH periods (0.96 seconds) (default=0, every SACCH block)\n") +{ + struct gsm_bts_trx *trx = vty->index; + + trx->ta_ctrl_interval = atoi(argv[0]); - trx->ms_power_control = argv[0][0] == 'd' ? 0 : 1; return CMD_SUCCESS; } @@ -803,20 +1218,25 @@ DEFUN(cfg_trx_phy, cfg_trx_phy_cmd, struct phy_instance *pinst; if (!plink) { - vty_out(vty, "phy%s does not exist%s", + vty_out(vty, "%% phy%s does not exist%s", argv[0], VTY_NEWLINE); return CMD_WARNING; } pinst = phy_instance_by_num(plink, atoi(argv[1])); if (!pinst) { - vty_out(vty, "phy%s instance %s does not exit%s", + vty_out(vty, "%% phy%s instance %s does not exit%s", argv[0], argv[1], VTY_NEWLINE); return CMD_WARNING; } - trx->role_bts.l1h = pinst; - pinst->trx = trx; + if (pinst->trx != NULL) { + vty_out(vty, "%% phy%s instance %s is already bound to %s%s", + argv[0], argv[1], gsm_trx_name(pinst->trx), VTY_NEWLINE); + return CMD_WARNING; + } + + phy_instance_link_to_trx(pinst, trx); return CMD_SUCCESS; } @@ -825,7 +1245,7 @@ DEFUN(cfg_trx_phy, cfg_trx_phy_cmd, * SHOW * ======================================================================*/ -static void net_dump_nmstate(struct vty *vty, struct gsm_nm_state *nms) +static void net_dump_nmstate(struct vty *vty, const struct gsm_nm_state *nms) { vty_out(vty,"Oper '%s', Admin '%s', Avail '%s'%s", abis_nm_opstate_name(nms->operational), @@ -833,16 +1253,30 @@ static void net_dump_nmstate(struct vty *vty, struct gsm_nm_state *nms) abis_nm_avail_name(nms->availability), VTY_NEWLINE); } -static void bts_dump_vty_features(struct vty *vty, struct gsm_bts *bts) +static void bts_dump_vty_features(struct vty *vty, const struct gsm_bts *bts) { unsigned int i; - bool no_features = true; + bool no_features; + vty_out(vty, " Features:%s", VTY_NEWLINE); - for (i = 0; i < _NUM_BTS_FEAT; i++) { - if (gsm_bts_has_feature(bts, i)) { + for (i = 0, no_features = true; i < _NUM_BTS_FEAT; i++) { + if (osmo_bts_has_feature(bts->features, i)) { vty_out(vty, " %03u ", i); - vty_out(vty, "%-40s%s", get_value_string(gsm_bts_features_descs, i), VTY_NEWLINE); + vty_out(vty, "%-40s%s", osmo_bts_features_desc(i), VTY_NEWLINE); + no_features = false; + } + } + + if (no_features) + vty_out(vty, " (not available)%s", VTY_NEWLINE); + + vty_out(vty, " BTS model specific (internal) flags:%s", VTY_NEWLINE); + + for (i = 0, no_features = true; i < _BTS_INTERNAL_FLAG_NUM; i++) { + if (bts_internal_flag_get(bts, i)) { + vty_out(vty, " %03u ", i); + vty_out(vty, "%-40s%s", get_value_string(bts_impl_flag_desc, i), VTY_NEWLINE); no_features = false; } } @@ -851,13 +1285,24 @@ static void bts_dump_vty_features(struct vty *vty, struct gsm_bts *bts) vty_out(vty, " (not available)%s", VTY_NEWLINE); } -static void bts_dump_vty(struct vty *vty, struct gsm_bts *bts) +static const char *stringify_radio_link_timeout(int val) { - struct gsm_bts_trx *trx; + static char buf[32]; + if (val == -1) + snprintf(buf, sizeof(buf), "%s", "infinite"); + else + snprintf(buf, sizeof(buf), "%d SACCH blocks", val); + return buf; +} + +static void bts_dump_vty(struct vty *vty, const struct gsm_bts *bts) +{ + const struct gsm_bts_trx *trx; - vty_out(vty, "BTS %u is of %s type in band %s, has CI %u LAC %u, " + vty_out(vty, "BTS %u is of type '%s', in band %s, has CI %u LAC %u, " "BSIC %u and %u TRX%s", - bts->nr, "FIXME", gsm_band_name(bts->band), + bts->nr, btsvariant2str(bts->variant), + gsm_band_name(bts->band), bts->cell_identity, bts->location_area_code, bts->bsic, bts->num_trx, VTY_NEWLINE); @@ -865,11 +1310,12 @@ static void bts_dump_vty(struct vty *vty, struct gsm_bts *bts) bts->description ? bts->description : "(null)", VTY_NEWLINE); 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); + bts->oml_link ? bts->oml_link->tei : 0x00, + 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); + net_dump_nmstate(vty, &g_bts_sm->mo.nm_state); if (strnlen(bts->pcu_version, MAX_VERSION_LENGTH)) vty_out(vty, " PCU version %s connected%s", bts->pcu_version, VTY_NEWLINE); @@ -897,9 +1343,19 @@ static void bts_dump_vty(struct vty *vty, struct gsm_bts *bts) bts->oml_link ? "connected" : "disconnected", VTY_NEWLINE); vty_out(vty, " PH-RTS.ind FN advance average: %d, min: %d, max: %d%s", bts_get_avg_fn_advance(bts), bts->fn_stats.min, bts->fn_stats.max, VTY_NEWLINE); + vty_out(vty, " Radio Link Timeout (OML): %s%s", + stringify_radio_link_timeout(bts->radio_link_timeout.oml), VTY_NEWLINE); + if (bts->radio_link_timeout.vty_override) { + vty_out(vty, " Radio Link Timeout (OVERRIDE): %s%s", + stringify_radio_link_timeout(bts->radio_link_timeout.current), VTY_NEWLINE); + } + if (bts->c0_power_red_db > 0) { + vty_out(vty, " BCCH carrier power reduction: %u dB%s", + bts->c0_power_red_db, VTY_NEWLINE); + } llist_for_each_entry(trx, &bts->trx_list, list) { - struct phy_instance *pinst = trx_phy_instance(trx); + const struct phy_instance *pinst = trx_phy_instance(trx); vty_out(vty, " TRX %u%s", trx->nr, VTY_NEWLINE); if (pinst) { vty_out(vty, " phy %d %s", pinst->num, pinst->version); @@ -914,32 +1370,262 @@ static void bts_dump_vty(struct vty *vty, struct gsm_bts *bts) } -DEFUN(show_bts, show_bts_cmd, "show bts <0-255>", - SHOW_STR "Display information about a BTS\n" - BTS_NR_STR) +DEFUN(show_bts, show_bts_cmd, "show bts [<0-255>]", + SHOW_STR "Display information about a BTS\n" + BTS_NR_STR) { - 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) { + if (bts_nr >= g_bts_sm->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)); + bts_dump_vty(vty, gsm_bts_num(g_bts_sm, 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)); + for (bts_nr = 0; bts_nr < g_bts_sm->num_bts; bts_nr++) + bts_dump_vty(vty, gsm_bts_num(g_bts_sm, bts_nr)); + + return CMD_SUCCESS; +} + +static void gprs_dump_vty(struct vty *vty, const struct gsm_bts *bts) +{ + unsigned int i; + const struct gsm_gprs_nse *nse = &bts->site_mgr->gprs.nse; + + /* GPRS parameters received from the BSC */ + vty_out(vty, "BTS %u, RAC %u, NSEI %u, BVCI %u%s", + bts->nr, bts->gprs.rac, + nse->nsei, + bts->gprs.cell.bvci, + VTY_NEWLINE); + + vty_out(vty, " Cell NM state: "); + net_dump_nmstate(vty, &bts->gprs.cell.mo.nm_state); + vty_out(vty, " NSE NM state: "); + net_dump_nmstate(vty, &nse->mo.nm_state); + + for (i = 0; i < ARRAY_SIZE(nse->nsvc); i++) { + const struct gsm_gprs_nsvc *nsvc = &nse->nsvc[i]; + + vty_out(vty, " NSVC%u (NSVCI %u) NM state: ", i, nsvc->nsvci); + net_dump_nmstate(vty, &nsvc->mo.nm_state); + + if (nsvc->mo.nm_state.operational == NM_OPSTATE_ENABLED) { + struct osmo_sockaddr_str remote; + struct osmo_sockaddr_str local; + + if (osmo_sockaddr_str_from_sockaddr(&remote, &nsvc->remote.u.sas) != 0) + remote = (struct osmo_sockaddr_str) { .ip = "<INVALID>" }; + if (osmo_sockaddr_str_from_sockaddr(&local, &nsvc->local.u.sas) != 0) + local = (struct osmo_sockaddr_str) { .ip = "<INVALID>" }; + + /* Work around for over-defensiveness of OSMO_SOCKADDR_STR_FMT_ARGS(): + * error: the address of ‘remote’ will always evaluate as ‘true’ + * error: the address of ‘local’ will always evaluate as ‘true’ */ + const struct osmo_sockaddr_str *r = &remote; + const struct osmo_sockaddr_str *l = &local; + + /* Getting remote/local address info has never been so easy, right? */ + vty_out(vty, " Address: r=" OSMO_SOCKADDR_STR_FMT + "<->l=" OSMO_SOCKADDR_STR_FMT "%s", + OSMO_SOCKADDR_STR_FMT_ARGS(r), + OSMO_SOCKADDR_STR_FMT_ARGS(l), + VTY_NEWLINE); + } + } +} + +DEFUN(show_bts_gprs, show_bts_gprs_cmd, + "show bts <0-255> gprs", + SHOW_STR "Display information about a BTS\n" + BTS_NR_STR "GPRS/EGPRS configuration\n") +{ + const struct gsm_bts *bts; + + bts = gsm_bts_num(g_bts_sm, atoi(argv[0])); + if (bts == NULL) { + vty_out(vty, "%% can't find BTS '%s'%s", + argv[0], VTY_NEWLINE); + return CMD_WARNING; + } + /* TODO: also print info about PCUIF connection */ + gprs_dump_vty(vty, bts); return CMD_SUCCESS; } -static void trx_dump_vty(struct vty *vty, struct gsm_bts_trx *trx) +DEFUN(test_send_failure_event_report, test_send_failure_event_report_cmd, "test send-failure-event-report <0-255>", + "Various testing commands\n" + "Send a test OML failure event report to the BSC\n" BTS_NR_STR) +{ + int bts_nr = atoi(argv[0]); + const struct gsm_bts *bts; + + if (bts_nr >= g_bts_sm->num_bts) { + vty_out(vty, "%% can't find BTS '%s'%s", argv[0], VTY_NEWLINE); + return CMD_WARNING; + } + + bts = gsm_bts_num(g_bts_sm, bts_nr); + oml_tx_failure_event_rep(&bts->mo, NM_SEVER_MINOR, OSMO_EVT_WARN_SW_WARN, "test message sent from VTY"); + + return CMD_SUCCESS; +} + +DEFUN_HIDDEN(radio_link_timeout, radio_link_timeout_cmd, "bts <0-0> radio-link-timeout (oml|infinite|<4-64>)", + "BTS Specific Commands\n" BTS_NR_STR "Manually override Radio Link Timeout\n" + "Use value provided by BSC via A-bis OML (Connection Failure Criterion)\n" + "Use infinite timeout (DANGEROUS: only use during testing!)\n" + "Number of lost SACCH blocks\n") +{ + int bts_nr = atoi(argv[0]); + struct gsm_bts *bts = gsm_bts_num(g_bts_sm, bts_nr); + + if (!bts) { + vty_out(vty, "%% can't find BTS '%s'%s", argv[0], VTY_NEWLINE); + return CMD_WARNING; + } + + if (!strcmp(argv[1], "oml")) { + bts->radio_link_timeout.current = bts->radio_link_timeout.oml; + bts->radio_link_timeout.vty_override = false; + } else if (!strcmp(argv[1], "infinite")) { + bts->radio_link_timeout.current = -1; + bts->radio_link_timeout.vty_override = true; + vty_out(vty, "%% INFINITE RADIO LINK TIMEOUT, USE ONLY FOR BTS RF TESTING%s", VTY_NEWLINE); + } else { + bts->radio_link_timeout.current = atoi(argv[1]); + bts->radio_link_timeout.vty_override = true; + } + + return CMD_SUCCESS; +} + +DEFUN(bts_c0_power_red, + bts_c0_power_red_cmd, + "bts <0-0> c0-power-red <0-6>", + "BTS Specific Commands\n" BTS_NR_STR + "BCCH carrier power reduction operation\n" + "Power reduction value (in dB, even numbers only)\n") +{ + const int bts_nr = atoi(argv[0]); + const int red = atoi(argv[1]); + struct gsm_bts *bts; + + bts = gsm_bts_num(g_bts_sm, atoi(argv[0])); + if (bts == NULL) { + vty_out(vty, "%% No such BTS (%d)%s", bts_nr, VTY_NEWLINE); + return CMD_WARNING; + } + + if (red % 2 != 0) { + vty_out(vty, "%% Incorrect BCCH power reduction value, " + "an even number is expected%s", VTY_NEWLINE); + return CMD_WARNING; + } + + if (bts_set_c0_pwr_red(bts, red) != 0) { + vty_out(vty, "%% BCCH carrier power reduction operation mode " + "is not supported for BTS (%d)%s", bts_nr, VTY_NEWLINE); + return CMD_WARNING; + } + + return CMD_SUCCESS; +} + +/* TODO: generalize and move indention handling to libosmocore */ +#define cfg_out(vty, fmt, args...) \ + vty_out(vty, "%*s" fmt, indent, "", ##args) + +static void dump_dpc_meas_params(struct vty *vty, const unsigned int indent, + const struct gsm_power_ctrl_meas_params *mp, + const char *pname, const unsigned int pn) +{ + cfg_out(vty, "Lower threshold (L_%s_XX_P): %u%s", + pname, mp->lower_thresh, VTY_NEWLINE); + cfg_out(vty, "Upper threshold (U_%s_XX_P): %u%s", + pname, mp->upper_thresh, VTY_NEWLINE); + + cfg_out(vty, "Lower threshold comparators: P%u=%02u / N%u=%02u%s", + pn, mp->lower_cmp_p, pn, mp->lower_cmp_n, VTY_NEWLINE); + cfg_out(vty, "Upper threshold comparators: P%u=%02u / N%u=%02u%s", + pn + 1, mp->upper_cmp_p, pn + 1, mp->upper_cmp_n, VTY_NEWLINE); + + cfg_out(vty, "Pre-processing algorithm: "); + switch (mp->algo) { + case GSM_PWR_CTRL_MEAS_AVG_ALGO_UNWEIGHTED: + vty_out(vty, "unweighted average%s", VTY_NEWLINE); + break; + case GSM_PWR_CTRL_MEAS_AVG_ALGO_WEIGHTED: + vty_out(vty, "weighted average%s", VTY_NEWLINE); + break; + case GSM_PWR_CTRL_MEAS_AVG_ALGO_MOD_MEDIAN: + vty_out(vty, "modified median%s", VTY_NEWLINE); + break; + case GSM_PWR_CTRL_MEAS_AVG_ALGO_OSMO_EWMA: + vty_out(vty, "EWMA (alpha=%u)%s", + mp->ewma.alpha, VTY_NEWLINE); + break; + case GSM_PWR_CTRL_MEAS_AVG_ALGO_NONE: + vty_out(vty, "disabled (pass-through)%s", VTY_NEWLINE); + return; + default: + vty_out(vty, "unknown%s", VTY_NEWLINE); + return; + } + + cfg_out(vty, "Pre-processing parameters: Hreqave=%u / Hreqt=%u%s", + mp->h_reqave, mp->h_reqt, VTY_NEWLINE); +} + +static void dump_dpc_params(struct vty *vty, const unsigned int indent, + const struct gsm_power_ctrl_params *cp, bool uplink) +{ + cfg_out(vty, "Power control interval: %u ms (every %u SACCH block(s))%s", + cp->ctrl_interval ? cp->ctrl_interval * 2 * 480 : 480, + cp->ctrl_interval ? cp->ctrl_interval * 2 : 1, + VTY_NEWLINE); + + cfg_out(vty, "Power increase step size: %u%s", + cp->inc_step_size_db, VTY_NEWLINE); + cfg_out(vty, "Power reduce step size: %u%s", + cp->red_step_size_db, VTY_NEWLINE); + + cfg_out(vty, "RxLev measurement processing:%s", VTY_NEWLINE); + dump_dpc_meas_params(vty, indent + 2, &cp->rxlev_meas, "RXLEV", 1); + + cfg_out(vty, "RxQual measurement processing:%s", VTY_NEWLINE); + dump_dpc_meas_params(vty, indent + 2, &cp->rxqual_meas, "RXQUAL", 3); + + if (uplink) { + cfg_out(vty, "C/I measurement processing (FR/EFR):%s", VTY_NEWLINE); + dump_dpc_meas_params(vty, indent + 2, &cp->ci_fr_meas, "CI_FR", 0); + + cfg_out(vty, "C/I measurement processing (HR):%s", VTY_NEWLINE); + dump_dpc_meas_params(vty, indent + 2, &cp->ci_hr_meas, "CI_HR", 0); + + cfg_out(vty, "C/I measurement processing (AMR-FR):%s", VTY_NEWLINE); + dump_dpc_meas_params(vty, indent + 2, &cp->ci_amr_fr_meas, "CI_AMR_FR", 0); + + cfg_out(vty, "C/I measurement processing (AMR-HR):%s", VTY_NEWLINE); + dump_dpc_meas_params(vty, indent + 2, &cp->ci_amr_hr_meas, "CI_AMR_HR", 0); + + cfg_out(vty, "C/I measurement processing (SDCCH):%s", VTY_NEWLINE); + dump_dpc_meas_params(vty, indent + 2, &cp->ci_sdcch_meas, "CI_SDCCH", 0); + + cfg_out(vty, "C/I measurement processing (GPRS):%s", VTY_NEWLINE); + dump_dpc_meas_params(vty, indent + 2, &cp->ci_gprs_meas, "CI_GPRS", 0); + } +} + +static void trx_dump_vty(struct vty *vty, const 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); @@ -949,12 +1635,25 @@ static void trx_dump_vty(struct vty *vty, struct gsm_bts_trx *trx) "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, " BS Power control parameters (%s):%s", + trx->bs_dpc_params == &trx->bts->bs_dpc_params ? + "fall-back" : "from BSC", + VTY_NEWLINE); + dump_dpc_params(vty, 4, trx->bs_dpc_params, false); + + vty_out(vty, " MS Power control parameters (%s):%s", + trx->ms_dpc_params == &trx->bts->ms_dpc_params ? + "fall-back" : "from BSC", + VTY_NEWLINE); + dump_dpc_params(vty, 4, trx->ms_dpc_params, true); + 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, " RSL State: %s%s", trx->bb_transc.rsl.link ? "connected" : "disconnected", VTY_NEWLINE); vty_out(vty, " Baseband Transceiver NM State: "); net_dump_nmstate(vty, &trx->bb_transc.mo.nm_state); - vty_out(vty, " IPA stream ID: 0x%02x%s", trx->rsl_tei, VTY_NEWLINE); + vty_out(vty, " IPA stream ID: 0x%02x%s", trx->bb_transc.rsl.tei, VTY_NEWLINE); } static inline void print_all_trx(struct vty *vty, const struct gsm_bts *bts) @@ -970,19 +1669,18 @@ DEFUN(show_trx, SHOW_STR "Display information about a TRX\n" BTS_TRX_STR) { - struct gsm_network *net = gsmnet_from_vty(vty); - struct gsm_bts *bts = NULL; + const 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) { + if (bts_nr >= g_bts_sm->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 = gsm_bts_num(g_bts_sm, bts_nr); } if (argc >= 2) { trx_nr = atoi(argv[1]); @@ -1000,18 +1698,18 @@ DEFUN(show_trx, return CMD_SUCCESS; } - for (bts_nr = 0; bts_nr < net->num_bts; bts_nr++) - print_all_trx(vty, gsm_bts_num(net, bts_nr)); + for (bts_nr = 0; bts_nr < g_bts_sm->num_bts; bts_nr++) + print_all_trx(vty, gsm_bts_num(g_bts_sm, bts_nr)); return CMD_SUCCESS; } -static void ts_dump_vty(struct vty *vty, struct gsm_bts_trx_ts *ts) +static void ts_dump_vty(struct vty *vty, const 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)); + gsm_pchan_name(ts->pchan), ts->tsc); if (ts->pchan == GSM_PCHAN_TCH_F_PDCH) vty_out(vty, " (%s mode)", ts->flags & TS_F_PDCH_ACTIVE ? "PDCH" : "TCH/F"); @@ -1026,21 +1724,20 @@ DEFUN(show_ts, 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; + const struct gsm_bts *bts = NULL; + const struct gsm_bts_trx *trx = NULL; + const 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) { + if (bts_nr >= g_bts_sm->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 = gsm_bts_num(g_bts_sm, bts_nr); } if (argc >= 2) { trx_nr = atoi(argv[1]); @@ -1081,8 +1778,8 @@ DEFUN(show_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 (bts_nr = 0; bts_nr < g_bts_sm->num_bts; bts_nr++) { + bts = gsm_bts_num(g_bts_sm, 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++) { @@ -1096,25 +1793,12 @@ DEFUN(show_ts, return CMD_SUCCESS; } -/* 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) +static void vty_out_dyn_ts_status(struct vty *vty, const struct gsm_bts_trx_ts *ts) { switch (ts->pchan) { - case GSM_PCHAN_TCH_F_TCH_H_PDCH: + case GSM_PCHAN_OSMO_DYN: if (ts->dyn.pchan_is == ts->dyn.pchan_want) vty_out(vty, " as %s", gsm_pchan_name(ts->dyn.pchan_is)); @@ -1141,16 +1825,157 @@ static void vty_out_dyn_ts_status(struct vty *vty, struct gsm_bts_trx_ts *ts) } } -static void lchan_dump_full_vty(struct vty *vty, struct gsm_lchan *lchan) +static void lchan_bs_power_ctrl_state_dump(struct vty *vty, unsigned int indent, + const struct gsm_lchan *lchan) +{ + const struct lchan_power_ctrl_state *st = &lchan->bs_power_ctrl; + const struct gsm_bts_trx *trx = lchan->ts->trx; + + cfg_out(vty, "BS (Downlink) Power Control (%s mode):%s", + st->dpc_params ? "dynamic" : "static", VTY_NEWLINE); + indent += 2; + + cfg_out(vty, "Channel reduction: %u dB", st->current); + if (st->dpc_params != NULL) + vty_out(vty, " (max %u dB)", st->max); + vty_out(vty, "%s", VTY_NEWLINE); + + cfg_out(vty, "TRX reduction: %u dB%s", + trx->max_power_red, VTY_NEWLINE); + + int actual = trx->nominal_power - (trx->max_power_red + st->current); + cfg_out(vty, "Actual / Nominal power: %d dBm / %d dBm%s", + actual, trx->nominal_power, VTY_NEWLINE); + + if (st->dpc_params == NULL) + return; + + cfg_out(vty, "Power Control parameters:%s", VTY_NEWLINE); + dump_dpc_params(vty, indent + 2, st->dpc_params, false); +} + +static void lchan_ms_power_ctrl_state_dump(struct vty *vty, unsigned int indent, + const struct gsm_lchan *lchan) +{ + const struct lchan_power_ctrl_state *st = &lchan->ms_power_ctrl; + const struct gsm_bts_trx *trx = lchan->ts->trx; + + cfg_out(vty, "MS (Uplink) Power Control (%s):%s", + st->dpc_params ? "dynamic" : "static", VTY_NEWLINE); + indent += 2; + + int current_dbm = ms_pwr_dbm(trx->bts->band, st->current); + int max_dbm = ms_pwr_dbm(trx->bts->band, st->max); + + cfg_out(vty, "Current power level: %u, %d dBm", + st->current, current_dbm); + if (st->dpc_params != NULL) + vty_out(vty, " (max %u, %d dBm)", st->max, max_dbm); + vty_out(vty, "%s", VTY_NEWLINE); + + if (st->dpc_params == NULL) + return; + + cfg_out(vty, "Power Control parameters:%s", VTY_NEWLINE); + dump_dpc_params(vty, indent + 2, st->dpc_params, true); +} + +static void lchan_acch_rep_state_dump(struct vty *vty, unsigned int indent, + const struct gsm_lchan *lchan) +{ + cfg_out(vty, "ACCH repetition:%s", VTY_NEWLINE); + indent += 2; + if (lchan->rep_acch_cap.rxqual) + cfg_out(vty, "Enable RXQUAL threshold: %u%s", + lchan->rep_acch_cap.rxqual, VTY_NEWLINE); + else + cfg_out(vty, "Enable RXQUAL threshold: (none, alway on)%s", + VTY_NEWLINE); + + cfg_out(vty, "DL-FACCH:%s", VTY_NEWLINE); + indent += 2; + if (lchan->rep_acch_cap.dl_facch_all) + cfg_out(vty, "retramsit all LAPDM block types%s", VTY_NEWLINE); + else if (lchan->rep_acch_cap.dl_facch_cmd) + cfg_out(vty, "retramsit only LAPDM command blocks%s", + VTY_NEWLINE); + else + cfg_out(vty, "no retransmission (disabled)%s", VTY_NEWLINE); + if (lchan->rep_acch.dl_facch_active) + cfg_out(vty, "retransmission currently active%s", VTY_NEWLINE); + else + cfg_out(vty, "retransmission currently inactive%s", + VTY_NEWLINE); + indent -= 2; + + cfg_out(vty, "DL-SACCH:%s", VTY_NEWLINE); + indent += 2; + if (lchan->rep_acch_cap.ul_sacch) + cfg_out(vty, "retramsit all SACCH blocks for SAPI=0%s", + VTY_NEWLINE); + else + cfg_out(vty, "no retransmission (disabled)%s", VTY_NEWLINE); + if (lchan->rep_acch.dl_sacch_active) + cfg_out(vty, "retransmission currently active%s", VTY_NEWLINE); + else + cfg_out(vty, "retransmission currently inactive%s", + VTY_NEWLINE); + indent -= 2; + + cfg_out(vty, "UL-SACCH:%s", VTY_NEWLINE); + indent += 2; + if (lchan->rep_acch_cap.dl_sacch) + cfg_out(vty, "retramsit all SACCH blocks for SAPI=0%s", + VTY_NEWLINE); + else + cfg_out(vty, "no retransmission (disabled)%s", VTY_NEWLINE); + if (lchan->rep_acch.ul_sacch_active) + cfg_out(vty, "retransmission currently active%s", VTY_NEWLINE); + else + cfg_out(vty, "retransmission currently inactive%s", + VTY_NEWLINE); + indent -= 2; +} + +static void lchan_acch_top_state_dump(struct vty *vty, unsigned int indent, + const struct gsm_lchan *lchan) +{ + if (lchan->top_acch_cap.overpower_db == 0) + return; + + cfg_out(vty, "Temporary ACCH overpower:%s", VTY_NEWLINE); + indent += 2; + + cfg_out(vty, "Overpower value: %u dB%s", + lchan->top_acch_cap.overpower_db, VTY_NEWLINE); + + cfg_out(vty, "SACCH overpower: %sabled%s", + lchan->top_acch_cap.sacch_enable ? "en" : "dis", + VTY_NEWLINE); + cfg_out(vty, "FACCH overpower: %sabled%s", + lchan->top_acch_cap.facch_enable ? "en" : "dis", + VTY_NEWLINE); + + if (lchan->top_acch_cap.rxqual == 0) { + cfg_out(vty, "RxQual threshold: disabled " + "(overpower is always on)%s", VTY_NEWLINE); + } else { + cfg_out(vty, "RxQual threshold: %u%s", + lchan->top_acch_cap.rxqual, VTY_NEWLINE); + } +} + +static void lchan_dump_full_vty(struct vty *vty, const struct gsm_lchan *lchan) { struct in_addr ia; - vty_out(vty, "BTS %u, TRX %u, Timeslot %u, Lchan %u: Type %s%s", + vty_out(vty, "BTS %u, TRX %u, Timeslot %u (%s), Lchan %u: Type %s%s", lchan->ts->trx->bts->nr, lchan->ts->trx->nr, lchan->ts->nr, + lchan->ts->vamos.is_shadow ? "shadow" : "primary", 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: + case GSM_PCHAN_OSMO_DYN: vty_out(vty, " Osmocom Dyn TS:"); vty_out_dyn_ts_status(vty, lchan->ts); vty_out(vty, VTY_NEWLINE); @@ -1169,28 +1994,42 @@ static void lchan_dump_full_vty(struct vty *vty, struct gsm_lchan *lchan) lchan->state == LCHAN_S_BROKEN ? " Error reason: " : "", lchan->state == LCHAN_S_BROKEN ? lchan->broken_reason : "", VTY_NEWLINE); - vty_out(vty, " BS Power: %d 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), + gsm48_chan_mode_name(lchan->tch_mode), VTY_NEWLINE); + if (lchan->tch_mode == GSM48_CMODE_SPEECH_AMR) { + const struct amr_multirate_conf *amr_mrc = &lchan->tch.amr_mr; + const struct gsm48_multi_rate_conf *mr_conf = + (const struct gsm48_multi_rate_conf *) amr_mrc->gsm48_ie; + vty_out(vty, " AMR Multi-Rate Configuration: ICMI=%u, Start Mode=%u gsm48=%02x%02x%s", + mr_conf->icmi, mr_conf->smod, amr_mrc->gsm48_ie[0], amr_mrc->gsm48_ie[1], VTY_NEWLINE); + for (uint8_t i = 0; i < amr_mrc->num_modes; i++) { + const struct amr_mode *amode = &amr_mrc->mode[i]; + vty_out(vty, " AMR Mode %u (%s), threshold=%u, hysteresis=%u%s", + amode->mode, osmo_amr_type_name(amode->mode), + amode->threshold, amode->hysteresis, VTY_NEWLINE); + } + } 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", + vty_out(vty, " Bound IP: %s Port %u CONN_ID=%u", inet_ntoa(ia), lchan->abis_ip.bound_port, - lchan->abis_ip.rtp_payload2, lchan->abis_ip.conn_id, - VTY_NEWLINE); + lchan->abis_ip.conn_id); + if (lchan->abis_ip.osmux.use) + vty_out(vty, " Osmux_CID=%u%s", lchan->abis_ip.osmux.local_cid, VTY_NEWLINE); + else + vty_out(vty, " RTP_TYPE2=%u%s", lchan->abis_ip.rtp_payload2, 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%02u%s", + vty_out(vty, " Conn. IP: %s Port %u SPEECH_MODE=0x%02x", inet_ntoa(ia), lchan->abis_ip.connect_port, - lchan->abis_ip.rtp_payload, lchan->abis_ip.speech_mode, - VTY_NEWLINE); + lchan->abis_ip.speech_mode); + if (lchan->abis_ip.osmux.use) + vty_out(vty, " Osmux_CID=%u%s", lchan->abis_ip.osmux.remote_cid, VTY_NEWLINE); + else + vty_out(vty, " RTP_TYPE=%u%s", lchan->abis_ip.rtp_payload, VTY_NEWLINE); } #define LAPDM_ESTABLISHED(link, sapi_idx) \ (link).datalink[sapi_idx].dl.state == LAPD_STATE_MF_EST @@ -1215,15 +2054,31 @@ static void lchan_dump_full_vty(struct vty *vty, struct gsm_lchan *lchan) if (lchan->loopback) vty_out(vty, " RTP/PDCH Loopback Enabled%s", VTY_NEWLINE); vty_out(vty, " Radio Link Failure Counter 'S': %d%s", lchan->s, VTY_NEWLINE); - /* TODO: MS Power Control */ + + /* Interference levels */ + if (lchan->meas.interf_meas_avg_dbm != 0) { + vty_out(vty, " Interference: %d dBm (band %d)%s", + lchan->meas.interf_meas_avg_dbm, + lchan->meas.interf_band, + VTY_NEWLINE); + } + + /* BS/MS Power Control state and parameters */ + lchan_bs_power_ctrl_state_dump(vty, 2, lchan); + lchan_ms_power_ctrl_state_dump(vty, 2, lchan); + + /* ACCH repetition / overpower state */ + lchan_acch_rep_state_dump(vty, 2, lchan); + lchan_acch_top_state_dump(vty, 2, lchan); } -static void lchan_dump_short_vty(struct vty *vty, struct gsm_lchan *lchan) +static void lchan_dump_short_vty(struct vty *vty, const struct gsm_lchan *lchan) { const struct gsm_meas_rep_unidir *mru = &lchan->meas.ul_res; - vty_out(vty, "BTS %u, TRX %u, Timeslot %u %s", + vty_out(vty, "BTS %u, TRX %u, Timeslot %u (%s) %s", lchan->ts->trx->bts->nr, lchan->ts->trx->nr, lchan->ts->nr, + lchan->ts->vamos.is_shadow ? "shadow" : "primary", gsm_pchan_name(lchan->ts->pchan)); vty_out_dyn_ts_status(vty, lchan->ts); vty_out(vty, ", Lchan %u, Type %s, State %s - " @@ -1234,40 +2089,50 @@ static void lchan_dump_short_vty(struct vty *vty, struct gsm_lchan *lchan) 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 *)) +static int dump_lchan_trx_ts(const struct gsm_bts_trx_ts *ts, struct vty *vty, + void (*dump_cb)(struct vty *, const 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]; + const struct gsm_lchan *lchan = &ts->lchan[lchan_nr]; if (lchan->state == LCHAN_S_NONE) continue; - dump_cb(vty, lchan); + switch (lchan->type) { + case GSM_LCHAN_SDCCH: + case GSM_LCHAN_TCH_F: + case GSM_LCHAN_TCH_H: + dump_cb(vty, lchan); + break; + default: + continue; + } } return CMD_SUCCESS; } -static int dump_lchan_trx(struct gsm_bts_trx *trx, struct vty *vty, - void (*dump_cb)(struct vty *, struct gsm_lchan *)) +static int dump_lchan_trx(const struct gsm_bts_trx *trx, struct vty *vty, + void (*dump_cb)(struct vty *, const 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]; + const struct gsm_bts_trx_ts *ts = &trx->ts[ts_nr]; dump_lchan_trx_ts(ts, vty, dump_cb); + if (ts->vamos.peer != NULL) /* VAMOS: shadow timeslot */ + dump_lchan_trx_ts(ts->vamos.peer, 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 *)) +static int dump_lchan_bts(const struct gsm_bts *bts, struct vty *vty, + void (*dump_cb)(struct vty *, const 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); + const struct gsm_bts_trx *trx = gsm_bts_trx_num(bts, trx_nr); dump_lchan_trx(trx, vty, dump_cb); } @@ -1275,24 +2140,23 @@ static int dump_lchan_bts(struct gsm_bts *bts, struct vty *vty, } static int lchan_summary(struct vty *vty, int argc, const char **argv, - void (*dump_cb)(struct vty *, struct gsm_lchan *)) + void (*dump_cb)(struct vty *, const struct gsm_lchan *)) { - struct gsm_network *net = gsmnet_from_vty(vty); - struct gsm_bts *bts; - struct gsm_bts_trx *trx; - struct gsm_bts_trx_ts *ts; - struct gsm_lchan *lchan; + const struct gsm_bts *bts = NULL; /* initialize to avoid uninitialized false warnings on some gcc versions (11.1.0) */ + const struct gsm_bts_trx *trx = NULL; /* initialize to avoid uninitialized false warnings on some gcc versions (11.1.0) */ + const struct gsm_bts_trx_ts *ts = NULL; /* initialize to avoid uninitialized false warnings on some gcc versions (11.1.0) */ + const 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) { + if (bts_nr >= g_bts_sm->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 = gsm_bts_num(g_bts_sm, bts_nr); if (argc == 1) return dump_lchan_bts(bts, vty, dump_cb); @@ -1333,8 +2197,8 @@ static int lchan_summary(struct vty *vty, int argc, const char **argv, return CMD_SUCCESS; } - for (bts_nr = 0; bts_nr < net->num_bts; bts_nr++) { - bts = gsm_bts_num(net, bts_nr); + for (bts_nr = 0; bts_nr < g_bts_sm->num_bts; bts_nr++) { + bts = gsm_bts_num(g_bts_sm, bts_nr); dump_lchan_bts(bts, vty, dump_cb); } @@ -1360,18 +2224,18 @@ DEFUN(show_lchan_summary, return lchan_summary(vty, argc, argv, lchan_dump_short_vty); } -static struct gsm_lchan *resolve_lchan(struct gsm_network *net, - const char **argv, int idx) +static struct gsm_lchan *resolve_lchan(const char **argv, int idx) { int bts_nr = atoi(argv[idx+0]); int trx_nr = atoi(argv[idx+1]); int ts_nr = atoi(argv[idx+2]); - int lchan_nr = atoi(argv[idx+3]); + bool shadow = argv[idx+3][0] == 's'; + int lchan_nr = atoi(argv[idx+4]); struct gsm_bts *bts; struct gsm_bts_trx *trx; struct gsm_bts_trx_ts *ts; - bts = gsm_bts_num(net, bts_nr); + bts = gsm_bts_num(g_bts_sm, bts_nr); if (!bts) return NULL; @@ -1382,6 +2246,11 @@ static struct gsm_lchan *resolve_lchan(struct gsm_network *net, if (ts_nr >= ARRAY_SIZE(trx->ts)) return NULL; ts = &trx->ts[ts_nr]; + if (shadow) { /* VAMOS shadow */ + if (ts->vamos.peer == NULL) + return NULL; + ts = ts->vamos.peer; + } if (lchan_nr >= ARRAY_SIZE(ts->lchan)) return NULL; @@ -1389,6 +2258,8 @@ static struct gsm_lchan *resolve_lchan(struct gsm_network *net, return &ts->lchan[lchan_nr]; } +#define BTS_T_T_L_CMD \ + "bts <0-0> trx <0-255> ts <0-7> (lchan|shadow-lchan) <0-7>" #define BTS_T_T_L_STR \ "BTS related commands\n" \ "BTS number\n" \ @@ -1396,38 +2267,151 @@ static struct gsm_lchan *resolve_lchan(struct gsm_network *net, "TRX number\n" \ "timeslot related commands\n" \ "timeslot number\n" \ - "logical channel commands\n" \ + "Primary logical channel commands\n" \ + "Shadow logical channel commands\n" \ "logical channel number\n" -DEFUN(cfg_trx_gsmtap_sapi, cfg_trx_gsmtap_sapi_cmd, +DEFUN(cfg_bts_gsmtap_remote_host, + cfg_bts_gsmtap_remote_host_cmd, + "gsmtap-remote-host [HOSTNAME]", + "Enable GSMTAP Um logging (see also 'gsmtap-sapi')\n" + "Remote IP address or hostname ('localhost' if omitted)\n") +{ + struct gsm_bts *bts = vty->index; + + osmo_talloc_replace_string(bts, &bts->gsmtap.remote_host, + argc > 0 ? argv[0] : "localhost"); + + if (vty->type != VTY_FILE) + vty_out(vty, "%% This command requires restart%s", VTY_NEWLINE); + + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_gsmtap_local_host, + cfg_bts_gsmtap_local_host_cmd, + "gsmtap-local-host HOSTNAME", + "Enable local bind for GSMTAP Um logging (see also 'gsmtap-sapi')\n" + "Local IP address or hostname\n") +{ + struct gsm_bts *bts = vty->index; + + osmo_talloc_replace_string(bts, &bts->gsmtap.local_host, argv[0]); + + if (vty->type != VTY_FILE) + vty_out(vty, "%% This command requires restart%s", VTY_NEWLINE); + + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_no_gsmtap_remote_host, + cfg_bts_no_gsmtap_remote_host_cmd, + "no gsmtap-remote-host", + NO_STR "Disable GSMTAP Um logging\n") +{ + struct gsm_bts *bts = vty->index; + + if (bts->gsmtap.remote_host != NULL) + talloc_free(bts->gsmtap.remote_host); + bts->gsmtap.remote_host = NULL; + + if (vty->type != VTY_FILE) + vty_out(vty, "%% This command requires restart%s", VTY_NEWLINE); + + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_no_gsmtap_local_host, + cfg_bts_no_gsmtap_local_host_cmd, + "no gsmtap-local-host", + NO_STR "Disable local bind for GSMTAP Um logging\n") +{ + struct gsm_bts *bts = vty->index; + + if (bts->gsmtap.local_host != NULL) + talloc_free(bts->gsmtap.local_host); + + bts->gsmtap.local_host = NULL; + + if (vty->type != VTY_FILE) + vty_out(vty, "%% This command requires restart%s", VTY_NEWLINE); + + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_gsmtap_sapi_all, cfg_bts_gsmtap_sapi_all_cmd, + "gsmtap-sapi (enable-all|disable-all)", + "Enable/disable sending of UL/DL messages over GSMTAP\n" + "Enable all kinds of messages (all SAPI)\n" + "Disable all kinds of messages (all SAPI)\n") +{ + struct gsm_bts *bts = vty->index; + + if (argv[0][0] == 'e') { + bts->gsmtap.sapi_mask = UINT32_MAX; + bts->gsmtap.sapi_acch = 1; + } else { + bts->gsmtap.sapi_mask = 0x00; + bts->gsmtap.sapi_acch = 0; + } + + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_gsmtap_sapi, cfg_bts_gsmtap_sapi_cmd, "HIDDEN", "HIDDEN") { + struct gsm_bts *bts = vty->index; int sapi; sapi = get_string_value(gsmtap_sapi_names, argv[0]); OSMO_ASSERT(sapi >= 0); if (sapi == GSMTAP_CHANNEL_ACCH) - gsmtap_sapi_acch = 1; + bts->gsmtap.sapi_acch = 1; else - gsmtap_sapi_mask |= (1 << sapi); + bts->gsmtap.sapi_mask |= (1 << sapi); return CMD_SUCCESS; } -DEFUN(cfg_trx_no_gsmtap_sapi, cfg_trx_no_gsmtap_sapi_cmd, +DEFUN(cfg_bts_no_gsmtap_sapi, cfg_bts_no_gsmtap_sapi_cmd, "HIDDEN", "HIDDEN") { + struct gsm_bts *bts = vty->index; int sapi; sapi = get_string_value(gsmtap_sapi_names, argv[0]); OSMO_ASSERT(sapi >= 0); if (sapi == GSMTAP_CHANNEL_ACCH) - gsmtap_sapi_acch = 0; + bts->gsmtap.sapi_acch = 0; + else + bts->gsmtap.sapi_mask &= ~(1 << sapi); + + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_gsmtap_rlp, cfg_bts_gsmtap_rlp_cmd, + "gsmtap-rlp [skip-null]", + "Enable generation of GSMTAP frames for RLP (non-transparent CSD)\n" + "Skip the generation of GSMTAP for RLP NULL frames\n") +{ + struct gsm_bts *bts = vty->index; + bts->gsmtap.rlp = true; + if (argc >= 1 && !strcmp(argv[0], "skip-null")) + bts->gsmtap.rlp_skip_null = true; else - gsmtap_sapi_mask &= ~(1 << sapi); + bts->gsmtap.rlp_skip_null = false; + return CMD_SUCCESS; +} +DEFUN(cfg_bts_no_gsmtap_rlp, cfg_bts_no_gsmtap_rlp_cmd, + "no gsmtap-rlp", + NO_STR "Disable generation of GSMTAP frames for RLP (non-transparent CSD)\n") +{ + struct gsm_bts *bts = vty->index; + bts->gsmtap.rlp = false; return CMD_SUCCESS; } @@ -1443,9 +2427,10 @@ static struct cmd_node phy_inst_node = { 1, }; -DEFUN(cfg_phy, cfg_phy_cmd, - "phy <0-255>", - "Select a PHY to configure\n" "PHY number\n") +DEFUN_ATTR(cfg_phy, cfg_phy_cmd, + "phy <0-255>", + "Select a PHY to configure\n" "PHY number\n", + CMD_ATTR_IMMEDIATE) { int phy_nr = atoi(argv[0]); struct phy_link *plink; @@ -1463,9 +2448,10 @@ DEFUN(cfg_phy, cfg_phy_cmd, return CMD_SUCCESS; } -DEFUN(cfg_phy_inst, cfg_phy_inst_cmd, - "instance <0-255>", - "Select a PHY instance to configure\n" "PHY Instance number\n") +DEFUN_ATTR(cfg_phy_inst, cfg_phy_inst_cmd, + "instance <0-255>", + "Select a PHY instance to configure\n" "PHY Instance number\n", + CMD_ATTR_IMMEDIATE) { int inst_nr = atoi(argv[0]); struct phy_link *plink = vty->index; @@ -1475,7 +2461,7 @@ DEFUN(cfg_phy_inst, cfg_phy_inst_cmd, if (!pinst) { pinst = phy_instance_create(plink, inst_nr); if (!pinst) { - vty_out(vty, "Unable to create phy%u instance %u%s", + vty_out(vty, "%% Unable to create phy%u instance %u%s", plink->num, inst_nr, VTY_NEWLINE); return CMD_WARNING; } @@ -1498,7 +2484,7 @@ DEFUN(cfg_phy_no_inst, cfg_phy_no_inst_cmd, pinst = phy_instance_by_num(plink, inst_nr); if (!pinst) { - vty_out(vty, "No such instance %u%s", inst_nr, VTY_NEWLINE); + vty_out(vty, "%% No such instance %u%s", inst_nr, VTY_NEWLINE); return CMD_WARNING; } @@ -1518,7 +2504,7 @@ DEFUN(cfg_phy_type, cfg_phy_type_cmd, struct phy_link *plink = vty->index; if (plink->state != PHY_LINK_SHUTDOWN) { - vty_out(vty, "Cannot change type of active PHY%s", VTY_NEWLINE); + vty_out(vty, "%% Cannot change type of active PHY%s", VTY_NEWLINE); return CMD_WARNING; } @@ -1533,17 +2519,16 @@ DEFUN(cfg_phy_type, cfg_phy_type_cmd, DEFUN(bts_t_t_l_jitter_buf, bts_t_t_l_jitter_buf_cmd, - "bts <0-0> trx <0-0> ts <0-7> lchan <0-1> rtp jitter-buffer <0-10000>", + BTS_T_T_L_CMD " rtp jitter-buffer <0-10000>", BTS_T_T_L_STR "RTP settings\n" "Jitter buffer\n" "Size of jitter buffer in (ms)\n") { - struct gsm_network *net = gsmnet_from_vty(vty); struct gsm_lchan *lchan; int jitbuf_ms = atoi(argv[4]), rc; - lchan = resolve_lchan(net, argv, 0); + lchan = resolve_lchan(argv, 0); if (!lchan) { - vty_out(vty, "%% can't find BTS%s", VTY_NEWLINE); + vty_out(vty, "%% Could not resolve logical channel%s", VTY_NEWLINE); return CMD_WARNING; } if (!lchan->abis_ip.rtp_socket) { @@ -1564,17 +2549,17 @@ DEFUN(bts_t_t_l_jitter_buf, return CMD_SUCCESS; } -DEFUN(bts_t_t_l_loopback, - bts_t_t_l_loopback_cmd, - "bts <0-0> trx <0-0> ts <0-7> lchan <0-1> loopback", - BTS_T_T_L_STR "Set loopback\n") +DEFUN_ATTR(bts_t_t_l_loopback, + bts_t_t_l_loopback_cmd, + BTS_T_T_L_CMD " loopback", + BTS_T_T_L_STR "Set loopback\n", + CMD_ATTR_HIDDEN) { - struct gsm_network *net = gsmnet_from_vty(vty); struct gsm_lchan *lchan; - lchan = resolve_lchan(net, argv, 0); + lchan = resolve_lchan(argv, 0); if (!lchan) { - vty_out(vty, "%% can't find BTS%s", VTY_NEWLINE); + vty_out(vty, "%% Could not resolve logical channel%s", VTY_NEWLINE); return CMD_WARNING; } lchan->loopback = 1; @@ -1582,17 +2567,17 @@ DEFUN(bts_t_t_l_loopback, return CMD_SUCCESS; } -DEFUN(no_bts_t_t_l_loopback, - no_bts_t_t_l_loopback_cmd, - "no bts <0-0> trx <0-0> ts <0-7> lchan <0-1> loopback", - NO_STR BTS_T_T_L_STR "Set loopback\n") +DEFUN_ATTR(no_bts_t_t_l_loopback, + no_bts_t_t_l_loopback_cmd, + "no " BTS_T_T_L_CMD " loopback", + NO_STR BTS_T_T_L_STR "Set loopback\n", + CMD_ATTR_HIDDEN) { - struct gsm_network *net = gsmnet_from_vty(vty); struct gsm_lchan *lchan; - lchan = resolve_lchan(net, argv, 0); + lchan = resolve_lchan(argv, 0); if (!lchan) { - vty_out(vty, "%% can't find BTS%s", VTY_NEWLINE); + vty_out(vty, "%% Could not resolve logical channel%s", VTY_NEWLINE); return CMD_WARNING; } lchan->loopback = 0; @@ -1600,20 +2585,147 @@ DEFUN(no_bts_t_t_l_loopback, return CMD_SUCCESS; } -int bts_vty_init(struct gsm_bts *bts) +#define LCHAN_PWR_CTRL_CMD \ + BTS_T_T_L_CMD " (bs-power-ctrl|ms-power-ctrl)" +#define LCHAN_PWR_CTRL_STR \ + BTS_T_T_L_STR "BS power control\n" "MS power control\n" + +DEFUN_ATTR(bts_t_t_l_power_ctrl_mode, + bts_t_t_l_power_ctrl_mode_cmd, + LCHAN_PWR_CTRL_CMD " mode (static|dynamic)", + LCHAN_PWR_CTRL_STR "Change power control mode\n" + "Disable the power control loop\n" + "Enable the power control loop\n", + CMD_ATTR_HIDDEN) +{ + const struct gsm_power_ctrl_params *params; + struct lchan_power_ctrl_state *state; + const char **args = argv + 4; + struct gsm_lchan *lchan; + + lchan = resolve_lchan(argv, 0); + if (!lchan) { + vty_out(vty, "%% Could not resolve logical channel%s", VTY_NEWLINE); + return CMD_WARNING; + } + + if (strcmp(args[0], "bs-power-ctrl") == 0) { + params = &lchan->bs_dpc_params; + state = &lchan->bs_power_ctrl; + } else { /* ms-power-ctrl */ + params = &lchan->ms_dpc_params; + state = &lchan->ms_power_ctrl; + } + + if (strcmp(args[1], "dynamic") == 0) + state->dpc_params = params; + else + state->dpc_params = NULL; + + return CMD_SUCCESS; +} + +DEFUN_ATTR(bts_t_t_l_power_ctrl_current_max, + bts_t_t_l_power_ctrl_current_max_cmd, + LCHAN_PWR_CTRL_CMD " value (current|max) <0-255>", + LCHAN_PWR_CTRL_STR "Change current power value\n" + "Current value (for both dynamic and static modes)\n" + "Maximum value (for dynamic mode only)\n" + "BS power reduction (in dB) or MS power level\n", + CMD_ATTR_HIDDEN) { - cfg_trx_gsmtap_sapi_cmd.string = vty_cmd_string_from_valstr(bts, gsmtap_sapi_names, + struct lchan_power_ctrl_state *state; + const char **args = argv + 4; + struct gsm_lchan *lchan; + + lchan = resolve_lchan(argv, 0); + if (!lchan) { + vty_out(vty, "%% Could not resolve logical channel%s", VTY_NEWLINE); + return CMD_WARNING; + } + + if (strcmp(args[0], "bs-power-ctrl") == 0) + state = &lchan->bs_power_ctrl; + else /* ms-power-ctrl */ + state = &lchan->ms_power_ctrl; + + if (strcmp(args[1], "current") == 0) + state->current = atoi(args[2]); + else + state->max = atoi(args[2]); + + return CMD_SUCCESS; +} + +DEFUN(logging_fltr_l1_sapi, logging_fltr_l1_sapi_cmd, "HIDDEN", "HIDDEN") +{ + int sapi = get_string_value(l1sap_common_sapi_names, argv[0]); + struct log_target *tgt = osmo_log_vty2tgt(vty); + uint16_t **sapi_mask; + + OSMO_ASSERT(sapi >= 0); + if (!tgt) + return CMD_WARNING; + + sapi_mask = (uint16_t **)&tgt->filter_data[LOG_FLT_L1_SAPI]; + + if (!*sapi_mask) + *sapi_mask = talloc(tgt, uint16_t); + + OSMO_ASSERT(sapi <= 31); + **sapi_mask |= (1 << sapi); + tgt->filter_map |= (1 << LOG_FLT_L1_SAPI); + + return CMD_SUCCESS; +} + +DEFUN(no_logging_fltr_l1_sapi, no_logging_fltr_l1_sapi_cmd, "HIDDEN", "HIDDEN") +{ + int sapi = get_string_value(l1sap_common_sapi_names, argv[0]); + struct log_target *tgt = osmo_log_vty2tgt(vty); + uint16_t *sapi_mask; + + OSMO_ASSERT(sapi >= 0); + if (!tgt) + return CMD_WARNING; + if (!tgt->filter_data[LOG_FLT_L1_SAPI]) + return CMD_SUCCESS; + + OSMO_ASSERT(sapi <= 31); + sapi_mask = (uint16_t *)tgt->filter_data[LOG_FLT_L1_SAPI]; + *sapi_mask &= ~(1 << sapi); + + return CMD_SUCCESS; +} + +int bts_vty_init(void *ctx) +{ + cfg_bts_gsmtap_sapi_cmd.string = vty_cmd_string_from_valstr(ctx, gsmtap_sapi_names, "gsmtap-sapi (", "|",")", VTY_DO_LOWER); - cfg_trx_gsmtap_sapi_cmd.doc = vty_cmd_string_from_valstr(bts, gsmtap_sapi_names, - "GSMTAP SAPI\n", + cfg_bts_gsmtap_sapi_cmd.doc = vty_cmd_string_from_valstr(ctx, gsmtap_sapi_names, + "Enable sending of UL/DL messages over GSMTAP\n", "\n", "", 0); - cfg_trx_no_gsmtap_sapi_cmd.string = vty_cmd_string_from_valstr(bts, gsmtap_sapi_names, + cfg_bts_no_gsmtap_sapi_cmd.string = vty_cmd_string_from_valstr(ctx, gsmtap_sapi_names, "no gsmtap-sapi (", "|",")", VTY_DO_LOWER); - cfg_trx_no_gsmtap_sapi_cmd.doc = vty_cmd_string_from_valstr(bts, gsmtap_sapi_names, - NO_STR "GSMTAP SAPI\n", + cfg_bts_no_gsmtap_sapi_cmd.doc = vty_cmd_string_from_valstr(ctx, gsmtap_sapi_names, + NO_STR "Disable sending of UL/DL messages over GSMTAP\n", + "\n", "", 0); + + logging_fltr_l1_sapi_cmd.string = vty_cmd_string_from_valstr(ctx, l1sap_common_sapi_names, + "logging filter l1-sapi (", + "|", ")", VTY_DO_LOWER); + logging_fltr_l1_sapi_cmd.doc = vty_cmd_string_from_valstr(ctx, l1sap_common_sapi_names, + LOGGING_STR FILTER_STR "L1 SAPI\n", + "\n", "", 0); + + no_logging_fltr_l1_sapi_cmd.string = vty_cmd_string_from_valstr(ctx, l1sap_common_sapi_names, + "no logging filter l1-sapi (", + "|", ")", VTY_DO_LOWER); + no_logging_fltr_l1_sapi_cmd.doc = vty_cmd_string_from_valstr(ctx, l1sap_common_sapi_names, + NO_STR LOGGING_STR FILTER_STR "L1 SAPI\n", "\n", "", 0); install_element_ve(&show_bts_cmd); @@ -1621,19 +2733,30 @@ int bts_vty_init(struct gsm_bts *bts) install_element_ve(&show_ts_cmd); install_element_ve(&show_lchan_cmd); install_element_ve(&show_lchan_summary_cmd); + install_element_ve(&show_bts_gprs_cmd); - logging_vty_add_cmds(); - osmo_talloc_vty_add_cmds(); - osmo_stats_vty_add_cmds(); + install_element_ve(&logging_fltr_l1_sapi_cmd); + install_element_ve(&no_logging_fltr_l1_sapi_cmd); install_node(&bts_node, config_write_bts); install_element(CONFIG_NODE, &cfg_bts_cmd); install_element(CONFIG_NODE, &cfg_vty_telnet_port_cmd); + + osmo_tdef_vty_groups_init(CONFIG_NODE, bts_tdef_groups); + install_element(BTS_NODE, &cfg_bts_unit_id_cmd); install_element(BTS_NODE, &cfg_bts_oml_ip_cmd); + install_element(BTS_NODE, &cfg_bts_no_oml_ip_cmd); install_element(BTS_NODE, &cfg_bts_rtp_bind_ip_cmd); install_element(BTS_NODE, &cfg_bts_rtp_jitbuf_cmd); install_element(BTS_NODE, &cfg_bts_rtp_port_range_cmd); + install_element(BTS_NODE, &cfg_bts_rtp_ip_dscp_cmd); + install_element(BTS_NODE, &cfg_bts_rtp_priority_cmd); + install_element(BTS_NODE, &cfg_bts_rtp_cont_stream_cmd); + install_element(BTS_NODE, &cfg_bts_no_rtp_cont_stream_cmd); + install_element(BTS_NODE, &cfg_bts_rtp_int_ul_ecu_cmd); + install_element(BTS_NODE, &cfg_bts_no_rtp_int_ul_ecu_cmd); + install_element(BTS_NODE, &cfg_bts_rtp_hr_format_cmd); install_element(BTS_NODE, &cfg_bts_band_cmd); install_element(BTS_NODE, &cfg_description_cmd); install_element(BTS_NODE, &cfg_no_description_cmd); @@ -1642,18 +2765,40 @@ int bts_vty_init(struct gsm_bts *bts) install_element(BTS_NODE, &cfg_bts_agch_queue_mgmt_default_cmd); install_element(BTS_NODE, &cfg_bts_agch_queue_mgmt_params_cmd); install_element(BTS_NODE, &cfg_bts_ul_power_target_cmd); + install_element(BTS_NODE, &cfg_bts_ul_power_target_hysteresis_cmd); + install_element(BTS_NODE, &cfg_bts_no_ul_power_filter_cmd); + install_element(BTS_NODE, &cfg_bts_ul_power_filter_ewma_cmd); install_element(BTS_NODE, &cfg_bts_min_qual_rach_cmd); install_element(BTS_NODE, &cfg_bts_min_qual_norm_cmd); install_element(BTS_NODE, &cfg_bts_max_ber_rach_cmd); - install_element(BTS_NODE, &cfg_bts_pcu_sock_cmd); + install_element(BTS_NODE, &cfg_bts_pcu_sock_path_cmd); + install_element(BTS_NODE, &cfg_bts_pcu_sock_ql_cmd); install_element(BTS_NODE, &cfg_bts_supp_meas_toa256_cmd); install_element(BTS_NODE, &cfg_bts_no_supp_meas_toa256_cmd); install_element(BTS_NODE, &cfg_bts_smscb_max_qlen_cmd); install_element(BTS_NODE, &cfg_bts_smscb_tgt_qlen_cmd); install_element(BTS_NODE, &cfg_bts_smscb_qhyst_cmd); - install_element(BTS_NODE, &cfg_trx_gsmtap_sapi_cmd); - install_element(BTS_NODE, &cfg_trx_no_gsmtap_sapi_cmd); + install_element(BTS_NODE, &cfg_bts_gsmtap_remote_host_cmd); + install_element(BTS_NODE, &cfg_bts_no_gsmtap_remote_host_cmd); + install_element(BTS_NODE, &cfg_bts_gsmtap_local_host_cmd); + install_element(BTS_NODE, &cfg_bts_no_gsmtap_local_host_cmd); + install_element(BTS_NODE, &cfg_bts_gsmtap_sapi_all_cmd); + install_element(BTS_NODE, &cfg_bts_gsmtap_sapi_cmd); + install_element(BTS_NODE, &cfg_bts_no_gsmtap_sapi_cmd); + install_element(BTS_NODE, &cfg_bts_gsmtap_rlp_cmd); + install_element(BTS_NODE, &cfg_bts_no_gsmtap_rlp_cmd); + + /* Osmux Node */ + install_element(BTS_NODE, &cfg_bts_osmux_cmd); + install_node(&osmux_node, config_write_dummy); + + install_element(OSMUX_NODE, &cfg_bts_osmux_use_cmd); + install_element(OSMUX_NODE, &cfg_bts_osmux_ip_cmd); + install_element(OSMUX_NODE, &cfg_bts_osmux_port_cmd); + install_element(OSMUX_NODE, &cfg_bts_osmux_batch_factor_cmd); + install_element(OSMUX_NODE, &cfg_bts_osmux_batch_size_cmd); + install_element(OSMUX_NODE, &cfg_bts_osmux_dummy_padding_cmd); /* add and link to TRX config node */ install_element(BTS_NODE, &cfg_bts_trx_cmd); @@ -1664,11 +2809,17 @@ int bts_vty_init(struct gsm_bts *bts) install_element(TRX_NODE, &cfg_trx_pr_step_size_cmd); install_element(TRX_NODE, &cfg_trx_pr_step_interval_cmd); install_element(TRX_NODE, &cfg_trx_ms_power_control_cmd); + install_element(TRX_NODE, &cfg_ta_ctrl_interval_cmd); install_element(TRX_NODE, &cfg_trx_phy_cmd); install_element(ENABLE_NODE, &bts_t_t_l_jitter_buf_cmd); install_element(ENABLE_NODE, &bts_t_t_l_loopback_cmd); install_element(ENABLE_NODE, &no_bts_t_t_l_loopback_cmd); + install_element(ENABLE_NODE, &bts_t_t_l_power_ctrl_mode_cmd); + install_element(ENABLE_NODE, &bts_t_t_l_power_ctrl_current_max_cmd); + install_element(ENABLE_NODE, &test_send_failure_event_report_cmd); + install_element(ENABLE_NODE, &radio_link_timeout_cmd); + install_element(ENABLE_NODE, &bts_c0_power_red_cmd); install_element(CONFIG_NODE, &cfg_phy_cmd); install_node(&phy_node, config_write_phy); @@ -1677,5 +2828,6 @@ int bts_vty_init(struct gsm_bts *bts) install_node(&phy_inst_node, config_write_dummy); - return 0; + /* Install variant-specific VTY options */ + return bts_model_vty_init(ctx); } diff --git a/src/osmo-bts-lc15/Makefile.am b/src/osmo-bts-lc15/Makefile.am new file mode 100644 index 00000000..58284e95 --- /dev/null +++ b/src/osmo-bts-lc15/Makefile.am @@ -0,0 +1,102 @@ +AUTOMAKE_OPTIONS = subdir-objects + +AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include $(LITECELL15_INCDIR) + +AM_CFLAGS = \ + -Wall \ + $(LIBOSMOCORE_CFLAGS) \ + $(LIBOSMOCODEC_CFLAGS) \ + $(LIBOSMOGSM_CFLAGS) \ + $(LIBOSMOVTY_CFLAGS) \ + $(LIBOSMOCTRL_CFLAGS) \ + $(LIBOSMOABIS_CFLAGS) \ + $(LIBOSMOTRAU_CFLAGS) \ + $(LIBOSMONETIF_CFLAGS) \ + $(LIBGPS_CFLAGS) \ + $(LIBSYSTEMD_CFLAGS) \ + $(NULL) + +COMMON_LDADD = \ + $(LIBOSMOCORE_LIBS) \ + $(LIBOSMOCODEC_LIBS) \ + $(LIBOSMOGSM_LIBS) \ + $(LIBOSMOVTY_LIBS) \ + $(LIBOSMOCTRL_LIBS) \ + $(LIBOSMOABIS_LIBS) \ + $(LIBOSMOTRAU_LIBS) \ + $(LIBOSMONETIF_LIBS) \ + $(NULL) + +AM_CFLAGS += -DENABLE_LC15BTS + +EXTRA_DIST = \ + misc/lc15bts_mgr.h \ + misc/lc15bts_misc.h \ + misc/lc15bts_par.h \ + misc/lc15bts_led.h \ + misc/lc15bts_temp.h \ + misc/lc15bts_power.h \ + misc/lc15bts_clock.h \ + misc/lc15bts_bid.h \ + misc/lc15bts_nl.h \ + misc/lc15bts_bts.h \ + misc/lc15bts_swd.h \ + hw_misc.h \ + l1_if.h \ + l1_transp.h \ + lc15bts.h \ + utils.h \ + $(NULL) + +bin_PROGRAMS = osmo-bts-lc15 lc15bts-mgr lc15bts-util + +COMMON_SOURCES = \ + main.c \ + lc15bts.c \ + l1_if.c \ + oml.c \ + lc15bts_vty.c \ + tch.c \ + hw_misc.c \ + calib_file.c \ + utils.c \ + misc/lc15bts_par.c \ + misc/lc15bts_bid.c \ + $(NULL) + +osmo_bts_lc15_SOURCES = $(COMMON_SOURCES) l1_transp_hw.c +osmo_bts_lc15_LDADD = \ + $(top_builddir)/src/common/libbts.a \ + $(COMMON_LDADD) \ + $(NULL) + +lc15bts_mgr_SOURCES = \ + misc/lc15bts_mgr.c \ + misc/lc15bts_misc.c \ + misc/lc15bts_par.c \ + misc/lc15bts_nl.c \ + misc/lc15bts_temp.c \ + misc/lc15bts_power.c \ + misc/lc15bts_clock.c \ + misc/lc15bts_bid.c \ + misc/lc15bts_mgr_vty.c \ + misc/lc15bts_mgr_nl.c \ + misc/lc15bts_mgr_temp.c \ + misc/lc15bts_mgr_calib.c \ + misc/lc15bts_led.c \ + misc/lc15bts_bts.c \ + misc/lc15bts_swd.c \ + $(NULL) + +lc15bts_mgr_LDADD = \ + $(top_builddir)/src/common/libbts.a \ + $(COMMON_LDADD) \ + $(LIBGPS_LIBS) \ + $(LIBSYSTEMD_LIBS) \ + $(NULL) + +lc15bts_util_SOURCES = \ + misc/lc15bts_util.c \ + misc/lc15bts_par.c \ + $(NULL) +lc15bts_util_LDADD = $(LIBOSMOCORE_LIBS) diff --git a/src/osmo-bts-litecell15/calib_file.c b/src/osmo-bts-lc15/calib_file.c index b7049df1..543be90f 100644 --- a/src/osmo-bts-litecell15/calib_file.c +++ b/src/osmo-bts-lc15/calib_file.c @@ -16,7 +16,7 @@ * 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. + * 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/>. @@ -317,7 +317,7 @@ static int calib_verify(struct lc15l1_hdl *fl1h, const struct calib_file_desc *d fseek(st->fp, 0L, SEEK_END); sz = ftell(st->fp); - /* rewind read poiner */ + /* rewind read pointer */ fseek(st->fp, 0L, SEEK_SET); /* read file */ diff --git a/src/osmo-bts-litecell15/hw_misc.c b/src/osmo-bts-lc15/hw_misc.c index 9f070bba..97ed3b75 100644 --- a/src/osmo-bts-litecell15/hw_misc.c +++ b/src/osmo-bts-lc15/hw_misc.c @@ -15,7 +15,7 @@ * 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. + * 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-bts-litecell15/hw_misc.h b/src/osmo-bts-lc15/hw_misc.h index 59ed04b7..59ed04b7 100644 --- a/src/osmo-bts-litecell15/hw_misc.h +++ b/src/osmo-bts-lc15/hw_misc.h diff --git a/src/osmo-bts-litecell15/l1_if.c b/src/osmo-bts-lc15/l1_if.c index 2ac0b7ab..4ef90949 100644 --- a/src/osmo-bts-litecell15/l1_if.c +++ b/src/osmo-bts-lc15/l1_if.c @@ -17,7 +17,7 @@ * 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. + * 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/>. @@ -54,6 +54,7 @@ #include <osmo-bts/l1sap.h> #include <osmo-bts/msg_utils.h> #include <osmo-bts/dtx_dl_amr_fsm.h> +#include <osmo-bts/nm_common_fsm.h> #include <nrw/litecell15/litecell15.h> #include <nrw/litecell15/gsml1prim.h> @@ -346,7 +347,7 @@ static int ph_data_req(struct gsm_bts_trx *trx, struct msgb *msg, abort(); } - len = msgb_l2len(msg); + len = (msg->l2h) ? msgb_l2len(msg) : 0; chan_nr = l1sap->u.data.chan_nr; link_id = l1sap->u.data.link_id; @@ -394,9 +395,8 @@ static int ph_data_req(struct gsm_bts_trx *trx, struct msgb *msg, else sapi = GsmL1_Sapi_Agch; } else { - LOGPFN(DL1C, LOGL_NOTICE, u32Fn, "unknown prim %d op %d " - "chan_nr %d link_id %d\n", l1sap->oph.primitive, - l1sap->oph.operation, chan_nr, link_id); + LOGPLCFN(lchan, u32Fn, DL1C, LOGL_NOTICE, "unknown prim %d op %d chan_nr %d link_id %d\n", + l1sap->oph.primitive, l1sap->oph.operation, chan_nr, link_id); msgb_free(l1msg); return -EINVAL; } @@ -457,9 +457,8 @@ static int ph_data_req(struct gsm_bts_trx *trx, struct msgb *msg, memcpy(l1p->u.phDataReq.msgUnitParam.u8Buffer, msg->l2h, msgb_l2len(msg)); } - LOGPFN(DL1P, LOGL_DEBUG, u32Fn, "PH-DATA.req(%s)\n", - osmo_hexdump(l1p->u.phDataReq.msgUnitParam.u8Buffer, - l1p->u.phDataReq.msgUnitParam.u8Size)); + LOGPLCFN(lchan, u32Fn, DL1P, LOGL_DEBUG, "PH-DATA.req(%s)\n", + osmo_hexdump(l1p->u.phDataReq.msgUnitParam.u8Buffer, l1p->u.phDataReq.msgUnitParam.u8Size)); } else { /* empty frame */ GsmL1_Prim_t *l1p = msgb_l1prim(l1msg); @@ -469,7 +468,7 @@ static int ph_data_req(struct gsm_bts_trx *trx, struct msgb *msg, /* send message to DSP's queue */ if (osmo_wqueue_enqueue(&fl1->write_q[MQ_L1_WRITE], l1msg) != 0) { - LOGPFN(DL1P, LOGL_ERROR, u32Fn, "MQ_L1_WRITE queue full. Dropping msg.\n"); + LOGPLCFN(lchan, u32Fn, DL1P, LOGL_ERROR, "MQ_L1_WRITE queue full. Dropping msg.\n"); msgb_free(l1msg); } else dtx_int_signal(lchan); @@ -507,7 +506,6 @@ static int ph_tch_req(struct gsm_bts_trx *trx, struct msgb *msg, /* create new message and fill data */ if (msg) { - msgb_pull(msg, sizeof(*l1sap)); /* create new message */ nmsg = l1p_msgb_alloc(); if (!nmsg) @@ -516,7 +514,7 @@ static int ph_tch_req(struct gsm_bts_trx *trx, struct msgb *msg, rc = l1if_tch_encode(lchan, l1p->u.phDataReq.msgUnitParam.u8Buffer, &l1p->u.phDataReq.msgUnitParam.u8Size, - msg->data, msg->len, u32Fn, use_cache, + msgb_l2(msg), msgb_l2len(msg), u32Fn, use_cache, l1sap->u.tch.marker); if (rc < 0) { /* no data encoded for L1: smth will be generated below */ @@ -552,7 +550,12 @@ static int ph_tch_req(struct gsm_bts_trx *trx, struct msgb *msg, empty_req_from_l1sap(l1p, fl1, u8Tn, u32Fn, sapi, subCh, u8BlockNbr); } /* send message to DSP's queue */ - osmo_wqueue_enqueue(&fl1->write_q[MQ_L1_WRITE], nmsg); + if (osmo_wqueue_enqueue(&fl1->write_q[MQ_L1_WRITE], nmsg) < 0) { + LOGPLCFN(lchan, u32Fn, DL1P, LOGL_ERROR, "MQ_L1_WRITE queue full. Dropping msg.\n"); + msgb_free(nmsg); + return -ENOBUFS; + } + if (dtx_is_first_p1(lchan)) dtx_dispatch(lchan, E_FIRST); else @@ -681,7 +684,7 @@ static enum gsm_phys_chan_config pick_pchan(struct gsm_bts_trx_ts *ts) if (ts->flags & TS_F_PDCH_ACTIVE) return GSM_PCHAN_PDCH; return GSM_PCHAN_TCH_F; - case GSM_PCHAN_TCH_F_TCH_H_PDCH: + case GSM_PCHAN_OSMO_DYN: return ts->dyn.pchan_is; default: return ts->pchan; @@ -695,7 +698,7 @@ static uint8_t chan_nr_by_sapi(struct gsm_bts_trx_ts *ts, uint8_t cbits = 0; enum gsm_phys_chan_config pchan = pick_pchan(ts); OSMO_ASSERT(pchan != GSM_PCHAN_TCH_F_PDCH); - OSMO_ASSERT(pchan != GSM_PCHAN_TCH_F_TCH_H_PDCH); + OSMO_ASSERT(pchan != GSM_PCHAN_OSMO_DYN); switch (sapi) { case GsmL1_Sapi_Bcch: @@ -795,6 +798,45 @@ static uint8_t chan_nr_by_sapi(struct gsm_bts_trx_ts *ts, return (cbits << 3) | u8Tn; } +static const enum l1sap_common_sapi common_sapi_by_sapi_t[] = { + [GsmL1_Sapi_Idle] = L1SAP_COMMON_SAPI_IDLE, + [GsmL1_Sapi_Fcch] = L1SAP_COMMON_SAPI_FCCH, + [GsmL1_Sapi_Sch] = L1SAP_COMMON_SAPI_SCH, + [GsmL1_Sapi_Sacch] = L1SAP_COMMON_SAPI_SACCH, + [GsmL1_Sapi_Sdcch] = L1SAP_COMMON_SAPI_SDCCH, + [GsmL1_Sapi_Bcch] = L1SAP_COMMON_SAPI_BCCH, + [GsmL1_Sapi_Pch] = L1SAP_COMMON_SAPI_PCH, + [GsmL1_Sapi_Agch] = L1SAP_COMMON_SAPI_AGCH, + [GsmL1_Sapi_Cbch] = L1SAP_COMMON_SAPI_CBCH, + [GsmL1_Sapi_Rach] = L1SAP_COMMON_SAPI_RACH, + [GsmL1_Sapi_TchF] = L1SAP_COMMON_SAPI_TCH_F, + [GsmL1_Sapi_FacchF] = L1SAP_COMMON_SAPI_FACCH_F, + [GsmL1_Sapi_TchH] = L1SAP_COMMON_SAPI_TCH_H, + [GsmL1_Sapi_FacchH] = L1SAP_COMMON_SAPI_FACCH_H, + [GsmL1_Sapi_Nch] = L1SAP_COMMON_SAPI_NCH, + [GsmL1_Sapi_Pdtch] = L1SAP_COMMON_SAPI_PDTCH, + [GsmL1_Sapi_Pacch] = L1SAP_COMMON_SAPI_PACCH, + [GsmL1_Sapi_Pbcch] = L1SAP_COMMON_SAPI_PBCCH, + [GsmL1_Sapi_Pagch] = L1SAP_COMMON_SAPI_PAGCH, + [GsmL1_Sapi_Ppch] = L1SAP_COMMON_SAPI_PPCH, + [GsmL1_Sapi_Pnch] = L1SAP_COMMON_SAPI_PNCH, + [GsmL1_Sapi_Ptcch] = L1SAP_COMMON_SAPI_PTCCH, + [GsmL1_Sapi_Prach] = L1SAP_COMMON_SAPI_PRACH, +}; + +static enum l1sap_common_sapi get_common_sapi(GsmL1_Sapi_t sapi) +{ + if (sapi >= GsmL1_Sapi_NUM) + return L1SAP_COMMON_SAPI_UNKNOWN; + return common_sapi_by_sapi_t[sapi]; +} + +static void set_log_ctx_sapi(GsmL1_Sapi_t sapi) +{ + l1sap_log_ctx_sapi = get_common_sapi(sapi); + log_set_context(LOG_CTX_L1_SAPI, &l1sap_log_ctx_sapi); +} + static int handle_ph_readytosend_ind(struct lc15l1_hdl *fl1, GsmL1_PhReadyToSendInd_t *rts_ind, struct msgb *l1p_msg) @@ -811,6 +853,8 @@ static int handle_ph_readytosend_ind(struct lc15l1_hdl *fl1, uint8_t chan_nr, link_id; uint32_t fn; + set_log_ctx_sapi(rts_ind->sapi); + /* check if primitive should be handled by common part */ chan_nr = chan_nr_by_sapi(&trx->ts[rts_ind->u8Tn], rts_ind->sapi, rts_ind->subCh, rts_ind->u8Tn, rts_ind->u32Fn); @@ -895,12 +939,8 @@ empty_frame: goto tx; } -static void dump_meas_res(int ll, GsmL1_MeasParam_t *m) -{ - LOGPC(DL1C, ll, ", Meas: RSSI %-3.2f dBm, Qual %-3.2f dB, " - "BER %-3.2f, Timing %d\n", m->fRssi, m->fLinkQuality, - m->fBer, m->i16BurstTiming); -} +#define LOG_FMT_MEAS "Meas: RSSI %-3.2f dBm, Qual %-3.2f dB, BER %-3.2f, Timing %d" +#define LOG_PARAM_MEAS(meas_param) (meas_param)->fRssi, (meas_param)->fLinkQuality, (meas_param)->fBer, (meas_param)->i16BurstTiming static int process_meas_res(struct gsm_bts_trx *trx, uint8_t chan_nr, GsmL1_MeasParam_t *m, uint32_t fn) @@ -933,6 +973,8 @@ static int handle_ph_data_ind(struct lc15l1_hdl *fl1, GsmL1_PhDataInd_t *data_in int rc = 0; int8_t rssi; + set_log_ctx_sapi(data_ind->sapi); + chan_nr = chan_nr_by_sapi(&trx->ts[data_ind->u8Tn], data_ind->sapi, data_ind->subCh, data_ind->u8Tn, data_ind->u32Fn); fn = data_ind->u32Fn; @@ -949,10 +991,10 @@ static int handle_ph_data_ind(struct lc15l1_hdl *fl1, GsmL1_PhDataInd_t *data_in process_meas_res(trx, chan_nr, &data_ind->measParam, fn); - DEBUGPGT(DL1P, &g_time, "Rx PH-DATA.ind %s (hL2 %08x): %s\n", + DEBUGPGT(DL1P, &g_time, "Rx PH-DATA.ind %s (hL2 %08x): %s, " LOG_FMT_MEAS "\n", get_value_string(lc15bts_l1sapi_names, data_ind->sapi), (uint32_t)data_ind->hLayer2, - osmo_hexdump(data_ind->msgUnitParam.u8Buffer, data_ind->msgUnitParam.u8Size)); - dump_meas_res(LOGL_DEBUG, &data_ind->measParam); + osmo_hexdump(data_ind->msgUnitParam.u8Buffer, data_ind->msgUnitParam.u8Size), + LOG_PARAM_MEAS(&data_ind->measParam)); /* check for TCH */ if (data_ind->sapi == GsmL1_Sapi_TchF @@ -985,11 +1027,10 @@ static int handle_ph_data_ind(struct lc15l1_hdl *fl1, GsmL1_PhDataInd_t *data_in l1sap->u.data.chan_nr = chan_nr; l1sap->u.data.fn = fn; l1sap->u.data.rssi = rssi; - if (!pcu_direct) { - l1sap->u.data.ber10k = data_ind->measParam.fBer * 10000; - l1sap->u.data.ta_offs_256bits = data_ind->measParam.i16BurstTiming*64; - l1sap->u.data.lqual_cb = data_ind->measParam.fLinkQuality * 10; - } + l1sap->u.data.ber10k = data_ind->measParam.fBer * 10000; + l1sap->u.data.ta_offs_256bits = data_ind->measParam.i16BurstTiming*64; + l1sap->u.data.lqual_cb = data_ind->measParam.fLinkQuality * 10; + return l1sap_up(trx, l1sap); } @@ -1002,7 +1043,9 @@ static int handle_ph_ra_ind(struct lc15l1_hdl *fl1, GsmL1_PhRaInd_t *ra_ind, int rc; struct ph_rach_ind_param rach_ind_param; - dump_meas_res(LOGL_DEBUG, &ra_ind->measParam); + set_log_ctx_sapi(ra_ind->sapi); + LOGPFN(DL1C, LOGL_DEBUG, ra_ind->u32Fn, "Rx PH-RA.ind, " LOG_FMT_MEAS "\n", + LOG_PARAM_MEAS(&ra_ind->measParam)); if ((ra_ind->msgUnitParam.u8Size != 1) && (ra_ind->msgUnitParam.u8Size != 2)) { @@ -1204,7 +1247,6 @@ static int activate_rf_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp, Litecell15_Prim_t *sysp = msgb_sysprim(resp); GsmL1_Status_t status; int on = 0; - unsigned int i; if (sysp->id == Litecell15_PrimId_ActivateRfCnf) on = 1; @@ -1217,27 +1259,26 @@ static int activate_rf_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp, LOGP(DL1C, LOGL_INFO, "Rx RF-%sACT.conf (status=%s)\n", on ? "" : "DE", get_value_string(lc15bts_l1status_names, status)); + struct bts_lc15_priv *bts_lc15 = trx->bts->model_priv; if (on) { if (status != GsmL1_Status_Success) { LOGP(DL1C, LOGL_FATAL, "RF-ACT.conf with status %s\n", get_value_string(lc15bts_l1status_names, status)); bts_shutdown(trx->bts, "RF-ACT failure"); - } else - bts_update_status(BTS_STATUS_RF_ACTIVE, 1); + } else { + if (bts_lc15->led_ctrl_mode == LC15_LED_CONTROL_BTS) + bts_update_status(BTS_STATUS_RF_ACTIVE, 1); + } /* signal availability */ - oml_mo_state_chg(&trx->mo, NM_OPSTATE_DISABLED, NM_AVSTATE_OK); - oml_mo_tx_sw_act_rep(&trx->mo); - oml_mo_state_chg(&trx->bb_transc.mo, -1, NM_AVSTATE_OK); - oml_mo_tx_sw_act_rep(&trx->bb_transc.mo); - - for (i = 0; i < ARRAY_SIZE(trx->ts); i++) - oml_mo_state_chg(&trx->ts[i].mo, NM_OPSTATE_DISABLED, NM_AVSTATE_DEPENDENCY); + osmo_fsm_inst_dispatch(trx->mo.fi, NM_EV_SW_ACT, NULL); + osmo_fsm_inst_dispatch(trx->bb_transc.mo.fi, NM_EV_SW_ACT, NULL); } else { - bts_update_status(BTS_STATUS_RF_ACTIVE, 0); - oml_mo_state_chg(&trx->mo, NM_OPSTATE_DISABLED, NM_AVSTATE_OFF_LINE); - oml_mo_state_chg(&trx->bb_transc.mo, NM_OPSTATE_DISABLED, NM_AVSTATE_OFF_LINE); + if (bts_lc15->led_ctrl_mode == LC15_LED_CONTROL_BTS) + bts_update_status(BTS_STATUS_RF_ACTIVE, 0); + osmo_fsm_inst_dispatch(trx->mo.fi, NM_EV_DISABLE, NULL); + osmo_fsm_inst_dispatch(trx->bb_transc.mo.fi, NM_EV_DISABLE, NULL); } msgb_free(resp); @@ -1250,17 +1291,28 @@ int l1if_activate_rf(struct lc15l1_hdl *hdl, int on) { struct msgb *msg = sysp_msgb_alloc(); Litecell15_Prim_t *sysp = msgb_sysprim(msg); + struct phy_instance *pinst = hdl->phy_inst; if (on) { sysp->id = Litecell15_PrimId_ActivateRfReq; sysp->u.activateRfReq.msgq.u8UseTchMsgq = 0; sysp->u.activateRfReq.msgq.u8UsePdtchMsgq = pcu_direct; - sysp->u.activateRfReq.u8UnusedTsMode = 0; + sysp->u.activateRfReq.u8UnusedTsMode = pinst->u.lc15.pedestal_mode; + sysp->u.activateRfReq.u8McCorrMode = 0; + /* diversity mode: 0: SISO-A, 1: SISO-B, 2: MRC */ + sysp->u.activateRfReq.u8DiversityMode = pinst->u.lc15.diversity_mode; + /* maximum cell size in quarter-bits, 90 == 12.456 km */ - sysp->u.activateRfReq.u8MaxCellSize = 90; + sysp->u.activateRfReq.u8MaxCellSize = pinst->u.lc15.max_cell_size; + +#if LITECELL15_API_VERSION >= LITECELL15_API(2,1,7) + /* auto tx power adjustment mode 0:none, 1: automatic*/ + sysp->u.activateRfReq.u8EnAutoPowerAdjust = pinst->u.lc15.tx_pwr_adj_mode; +#endif + } else { sysp->id = Litecell15_PrimId_DeactivateRfReq; } @@ -1374,6 +1426,30 @@ static int info_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp, LOGP(DL1C, LOGL_FATAL, "BTS band %s not supported by hw\n", gsm_band_name(trx->bts->band)); + /* Frequency bands indicated to the BSC */ + switch (fl1h->hw_info.band_support) { + case GSM_BAND_450: + trx->support.freq_bands |= NM_IPAC_F_FREQ_BAND_450; + break; + case GSM_BAND_480: + trx->support.freq_bands |= NM_IPAC_F_FREQ_BAND_480; + break; + case GSM_BAND_850: + trx->support.freq_bands |= NM_IPAC_F_FREQ_BAND_850; + break; + case GSM_BAND_900: + trx->support.freq_bands |= NM_IPAC_F_FREQ_BAND_PGSM; + /* XXX: does GSM_BAND_900 include NM_IPAC_F_FREQ_BAND_EGSM? */ + /* XXX: does GSM_BAND_900 include NM_IPAC_F_FREQ_BAND_RGSM? */ + break; + case GSM_BAND_1800: + trx->support.freq_bands |= NM_IPAC_F_FREQ_BAND_DCS; + break; + case GSM_BAND_1900: + trx->support.freq_bands |= NM_IPAC_F_FREQ_BAND_PCS; + break; + } + /* Request the activation */ l1if_activate_rf(fl1h, 1); @@ -1549,6 +1625,57 @@ int l1if_close(struct lc15l1_hdl *fl1h) return 0; } +#if LITECELL15_API_VERSION >= LITECELL15_API(2,1,7) +static int dsp_alive_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp, void *data) +{ + Litecell15_Prim_t *sysp = msgb_sysprim(resp); + Litecell15_IsAliveCnf_t *sac = &sysp->u.isAliveCnf; + struct lc15l1_hdl *fl1h = trx_lc15l1_hdl(trx); + + fl1h->hw_alive.dsp_alive_cnt++; + LOGP(DL1C, LOGL_DEBUG, "Rx SYS prim %s, status=%d (%d)\n", + get_value_string(lc15bts_sysprim_names, sysp->id), sac->status, trx->nr); + + msgb_free(resp); + return 0; +} + +static void dsp_alive_timer_cb(void *data) +{ + struct lc15l1_hdl *fl1h = data; + struct gsm_bts_trx *trx = fl1h->phy_inst->trx; + struct msgb *msg = sysp_msgb_alloc(); + int rc; + + Litecell15_Prim_t *sys_prim = msgb_sysprim(msg); + sys_prim->id = Litecell15_PrimId_IsAliveReq; + + if (fl1h->hw_alive.dsp_alive_cnt == 0) { + LOGP(DL1C, LOGL_ERROR, "Timeout waiting for SYS prim %s primitive (%d)\n", + get_value_string(lc15bts_sysprim_names, sys_prim->id + 1), trx->nr); + + if( fl1h->phy_inst->trx ){ + fl1h->phy_inst->trx->mo.obj_inst.trx_nr = fl1h->phy_inst->trx->nr; + } + } + + LOGP(DL1C, LOGL_DEBUG, "Tx SYS prim %s (%d)\n", + get_value_string(lc15bts_sysprim_names, sys_prim->id), trx->nr); + + rc = l1if_req_compl(fl1h, msg, dsp_alive_compl_cb, NULL); + if (rc < 0) { + LOGP(DL1C, LOGL_FATAL, "Failed to send %s primitive\n", get_value_string(lc15bts_sysprim_names, sys_prim->id)); + return; + } + + /* restart timer */ + fl1h->hw_alive.dsp_alive_cnt = 0; + osmo_timer_schedule(&fl1h->hw_alive.dsp_alive_timer, fl1h->hw_alive.dsp_alive_period, 0); + + return; +} +#endif + int bts_model_phy_link_open(struct phy_link *plink) { struct phy_instance *pinst = phy_instance_by_num(plink, 0); @@ -1568,6 +1695,29 @@ int bts_model_phy_link_open(struct phy_link *plink) return -EIO; } + /* Set default PHY parameters */ + if (!pinst->u.lc15.max_cell_size) + pinst->u.lc15.max_cell_size = LC15_BTS_MAX_CELL_SIZE_DEFAULT; + + if (!pinst->u.lc15.diversity_mode) + pinst->u.lc15.diversity_mode = LC15_BTS_DIVERSITY_MODE_DEFAULT; + + if (!pinst->u.lc15.pedestal_mode) + pinst->u.lc15.pedestal_mode = LC15_BTS_PEDESTAL_MODE_DEFAULT; + +#if LITECELL15_API_VERSION >= LITECELL15_API(2,1,7) + if (!pinst->u.lc15.dsp_alive_period) + pinst->u.lc15.dsp_alive_period = LC15_BTS_DSP_ALIVE_TMR_DEFAULT; + + if (!pinst->u.lc15.tx_pwr_adj_mode) + pinst->u.lc15.tx_pwr_adj_mode = LC15_BTS_TX_PWR_ADJ_DEFAULT; + + if (!pinst->u.lc15.tx_pwr_red_8psk) + pinst->u.lc15.tx_pwr_red_8psk = LC15_BTS_TX_RED_PWR_8PSK_DEFAULT; + + if (!pinst->u.lc15.tx_c0_idle_pwr_red) + pinst->u.lc15.tx_c0_idle_pwr_red = LC15_BTS_TX_C0_IDLE_RED_PWR_DEFAULT; +#endif struct lc15l1_hdl *fl1h = pinst->u.lc15.hdl; fl1h->dsp_trace_f = dsp_trace; @@ -1576,5 +1726,26 @@ int bts_model_phy_link_open(struct phy_link *plink) phy_link_state_set(plink, PHY_LINK_CONNECTED); +#if LITECELL15_API_VERSION >= LITECELL15_API(2,1,7) + /* Send first IS_ALIVE primitive */ + struct msgb *msg = sysp_msgb_alloc(); + int rc; + + Litecell15_Prim_t *sys_prim = msgb_sysprim(msg); + sys_prim->id = Litecell15_PrimId_IsAliveReq; + + rc = l1if_req_compl(fl1h, msg, dsp_alive_compl_cb, NULL); + if (rc < 0) { + LOGP(DL1C, LOGL_FATAL, "Failed to send %s primitive\n", get_value_string(lc15bts_sysprim_names, sys_prim->id)); + return -EIO; + } + + /* initialize DSP heart beat alive timer */ + osmo_timer_setup(&fl1h->hw_alive.dsp_alive_timer, dsp_alive_timer_cb, fl1h); + fl1h->hw_alive.dsp_alive_cnt = 0; + fl1h->hw_alive.dsp_alive_period = pinst->u.lc15.dsp_alive_period; + osmo_timer_schedule(&fl1h->hw_alive.dsp_alive_timer, fl1h->hw_alive.dsp_alive_period, 0); +#endif + return 0; } diff --git a/src/osmo-bts-litecell15/l1_if.h b/src/osmo-bts-lc15/l1_if.h index aac26075..655e63fb 100644 --- a/src/osmo-bts-litecell15/l1_if.h +++ b/src/osmo-bts-lc15/l1_if.h @@ -30,6 +30,12 @@ enum { _NUM_MQ_WRITE }; +/* gsm_bts->model_priv, specific to Litecell 1.5 BTS */ +struct bts_lc15_priv { + uint8_t led_ctrl_mode; /* 0: control by BTS, 1: not control by BTS */ + unsigned int rtp_drift_thres_ms; /* RTP timestamp drift detection threshold */ +}; + struct calib_send_state { FILE *fp; const char *path; @@ -62,6 +68,15 @@ struct lc15l1_hdl { struct calib_send_state st; uint8_t last_rf_mute[8]; + +#if LITECELL15_API_VERSION >= LITECELL15_API(2,1,7) + struct { + struct osmo_timer_list dsp_alive_timer; + unsigned int dsp_alive_cnt; + uint8_t dsp_alive_period; + } hw_alive; +#endif + }; #define msgb_l1prim(msg) ((GsmL1_Prim_t *)(msg)->l1h) @@ -118,14 +133,14 @@ int bts_check_for_ciph_cmd(struct lc15l1_hdl *fl1h, int l1if_ms_pwr_ctrl(struct gsm_lchan *lchan, const int uplink_target, const uint8_t ms_power, const float rxLevel); -static inline struct lc15l1_hdl *trx_lc15l1_hdl(struct gsm_bts_trx *trx) +static inline struct lc15l1_hdl *trx_lc15l1_hdl(const struct gsm_bts_trx *trx) { - struct phy_instance *pinst = trx_phy_instance(trx); + const struct phy_instance *pinst = trx_phy_instance(trx); OSMO_ASSERT(pinst); return pinst->u.lc15.hdl; } -static inline struct gsm_bts_trx *lc15l1_hdl_trx(struct lc15l1_hdl *fl1h) +static inline struct gsm_bts_trx *lc15l1_hdl_trx(const struct lc15l1_hdl *fl1h) { OSMO_ASSERT(fl1h->phy_inst); return fl1h->phy_inst->trx; diff --git a/src/osmo-bts-litecell15/l1_transp.h b/src/osmo-bts-lc15/l1_transp.h index 7d6772e8..7d6772e8 100644 --- a/src/osmo-bts-litecell15/l1_transp.h +++ b/src/osmo-bts-lc15/l1_transp.h diff --git a/src/osmo-bts-litecell15/l1_transp_hw.c b/src/osmo-bts-lc15/l1_transp_hw.c index c8972be5..63b596b2 100644 --- a/src/osmo-bts-litecell15/l1_transp_hw.c +++ b/src/osmo-bts-lc15/l1_transp_hw.c @@ -15,7 +15,7 @@ * 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. + * 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/>. @@ -88,18 +88,18 @@ static int wqueue_vector_cb(struct osmo_fd *fd, unsigned int what) queue = container_of(fd, struct osmo_wqueue, bfd); - if (what & BSC_FD_READ) + if (what & OSMO_FD_READ) queue->read_cb(fd); - if (what & BSC_FD_EXCEPT) + if (what & OSMO_FD_EXCEPT) queue->except_cb(fd); - if (what & BSC_FD_WRITE) { + if (what & OSMO_FD_WRITE) { struct iovec iov[5]; struct msgb *msg, *tmp; int written, count = 0; - fd->when &= ~BSC_FD_WRITE; + osmo_fd_write_disable(fd); llist_for_each_entry(msg, &queue->msg_queue, list) { /* more writes than we have */ @@ -117,7 +117,7 @@ static int wqueue_vector_cb(struct osmo_fd *fd, unsigned int what) /* Nothing scheduled? This should not happen. */ if (count == 0) { if (!llist_empty(&queue->msg_queue)) - fd->when |= BSC_FD_WRITE; + osmo_fd_write_enable(fd); return 0; } @@ -125,7 +125,7 @@ static int wqueue_vector_cb(struct osmo_fd *fd, unsigned int what) if (written < 0) { /* nothing written?! */ if (!llist_empty(&queue->msg_queue)) - fd->when |= BSC_FD_WRITE; + osmo_fd_write_enable(fd); return 0; } @@ -144,7 +144,7 @@ static int wqueue_vector_cb(struct osmo_fd *fd, unsigned int what) } if (!llist_empty(&queue->msg_queue)) - fd->when |= BSC_FD_WRITE; + osmo_fd_write_enable(fd); } return 0; @@ -265,11 +265,7 @@ int l1if_transport_open(int q, struct lc15l1_hdl *hdl) buf, strerror(errno)); return rc; } - read_ofd->fd = rc; - read_ofd->priv_nr = q; - read_ofd->data = hdl; - read_ofd->cb = l1if_fd_cb; - read_ofd->when = BSC_FD_READ; + osmo_fd_setup(read_ofd, rc, OSMO_FD_READ, l1if_fd_cb, hdl, q); rc = osmo_fd_register(read_ofd); if (rc < 0) { close(read_ofd->fd); @@ -288,11 +284,7 @@ int l1if_transport_open(int q, struct lc15l1_hdl *hdl) } osmo_wqueue_init(wq, 10); wq->write_cb = l1fd_write_cb; - write_ofd->cb = wqueue_vector_cb; - write_ofd->fd = rc; - write_ofd->priv_nr = q; - write_ofd->data = hdl; - write_ofd->when = BSC_FD_WRITE; + osmo_fd_setup(write_ofd, rc, OSMO_FD_WRITE, wqueue_vector_cb, hdl, q); rc = osmo_fd_register(write_ofd); if (rc < 0) { close(write_ofd->fd); diff --git a/src/osmo-bts-litecell15/lc15bts.c b/src/osmo-bts-lc15/lc15bts.c index 172a7e45..40077902 100644 --- a/src/osmo-bts-litecell15/lc15bts.c +++ b/src/osmo-bts-lc15/lc15bts.c @@ -15,7 +15,7 @@ * 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. + * 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/>. @@ -121,6 +121,14 @@ enum l1prim_type lc15bts_get_sysprim_type(Litecell15_PrimId_t id) case Litecell15_PrimId_MuteRfCnf: return L1P_T_CONF; case Litecell15_PrimId_SetRxAttenReq: return L1P_T_REQ; case Litecell15_PrimId_SetRxAttenCnf: return L1P_T_CONF; +#if LITECELL15_API_VERSION >= LITECELL15_API(2,1,7) + case Litecell15_PrimId_IsAliveReq: return L1P_T_REQ; + case Litecell15_PrimId_IsAliveCnf: return L1P_T_CONF; + case Litecell15_PrimId_SetMaxCellSizeReq: return L1P_T_REQ; + case Litecell15_PrimId_SetMaxCellSizeCnf: return L1P_T_CONF; + case Litecell15_PrimId_SetC0IdleSlotPowerReductionReq: return L1P_T_REQ; + case Litecell15_PrimId_SetC0IdleSlotPowerReductionCnf: return L1P_T_CONF; +#endif default: return L1P_T_INVALID; } } @@ -142,6 +150,14 @@ const struct value_string lc15bts_sysprim_names[Litecell15_PrimId_NUM+1] = { { Litecell15_PrimId_MuteRfCnf, "MUTE-RF.cnf" }, { Litecell15_PrimId_SetRxAttenReq, "SET-RX-ATTEN.req" }, { Litecell15_PrimId_SetRxAttenCnf, "SET-RX-ATTEN-CNF.cnf" }, +#if LITECELL15_API_VERSION >= LITECELL15_API(2,1,7) + { Litecell15_PrimId_IsAliveReq, "IS-ALIVE.req" }, + { Litecell15_PrimId_IsAliveCnf, "IS-ALIVE-CNF.cnf" }, + { Litecell15_PrimId_SetMaxCellSizeReq, "SET-MAX-CELL-SIZE.req" }, + { Litecell15_PrimId_SetMaxCellSizeCnf, "SET-MAX-CELL-SIZE.cnf" }, + { Litecell15_PrimId_SetC0IdleSlotPowerReductionReq, "SET-C0-IDLE-PWR-RED.req" }, + { Litecell15_PrimId_SetC0IdleSlotPowerReductionCnf, "SET-C0-IDLE-PWR-RED.cnf" }, +#endif { 0, NULL } }; @@ -155,6 +171,11 @@ Litecell15_PrimId_t lc15bts_get_sysprim_conf(Litecell15_PrimId_t id) case Litecell15_PrimId_SetCalibTblReq: return Litecell15_PrimId_SetCalibTblCnf; case Litecell15_PrimId_MuteRfReq: return Litecell15_PrimId_MuteRfCnf; case Litecell15_PrimId_SetRxAttenReq: return Litecell15_PrimId_SetRxAttenCnf; +#if LITECELL15_API_VERSION >= LITECELL15_API(2,1,7) + case Litecell15_PrimId_IsAliveReq: return Litecell15_PrimId_IsAliveCnf; + case Litecell15_PrimId_SetMaxCellSizeReq: return Litecell15_PrimId_SetMaxCellSizeCnf; + case Litecell15_PrimId_SetC0IdleSlotPowerReductionReq: return Litecell15_PrimId_SetC0IdleSlotPowerReductionCnf; +#endif default: return -1; // Weak } } diff --git a/src/osmo-bts-lc15/lc15bts.h b/src/osmo-bts-lc15/lc15bts.h new file mode 100644 index 00000000..ecfeb3da --- /dev/null +++ b/src/osmo-bts-lc15/lc15bts.h @@ -0,0 +1,101 @@ +#ifndef LC15BTS_H +#define LC15BTS_H + +#include <stdlib.h> +#include <osmocom/core/utils.h> + +#include <nrw/litecell15/litecell15.h> +#include <nrw/litecell15/gsml1const.h> + +/* + * Depending on the firmware version either GsmL1_Prim_t or Litecell15_Prim_t + * is the bigger struct. For earlier firmware versions the GsmL1_Prim_t was the + * bigger struct. + */ +#define LC15BTS_PRIM_SIZE \ + (OSMO_MAX(sizeof(Litecell15_Prim_t), sizeof(GsmL1_Prim_t)) + 128) + +enum l1prim_type { + L1P_T_INVALID, /* this must be 0 to detect uninitialized elements */ + L1P_T_REQ, + L1P_T_CONF, + L1P_T_IND, +}; + + +enum lc15_diversity_mode{ + LC15_DIVERSITY_SISO_A = 0, + LC15_DIVERSITY_SISO_B, + LC15_DIVERSITY_MRC, +}; + +enum lc15_pedestal_mode{ + LC15_PEDESTAL_OFF = 0, + LC15_PEDESTAL_ON, +}; + +enum lc15_led_control_mode{ + LC15_LED_CONTROL_BTS = 0, + LC15_LED_CONTROL_EXT, +}; + +#if LITECELL15_API_VERSION >= LITECELL15_API(2,1,7) +enum lc15_auto_pwr_adjust_mode{ + LC15_TX_PWR_ADJ_NONE = 0, + LC15_TX_PWR_ADJ_AUTO, +}; +#endif + +enum l1prim_type lc15bts_get_l1prim_type(GsmL1_PrimId_t id); +extern const struct value_string lc15bts_l1prim_names[GsmL1_PrimId_NUM+1]; +GsmL1_PrimId_t lc15bts_get_l1prim_conf(GsmL1_PrimId_t id); + +enum l1prim_type lc15bts_get_sysprim_type(Litecell15_PrimId_t id); +extern const struct value_string lc15bts_sysprim_names[Litecell15_PrimId_NUM+1]; +Litecell15_PrimId_t lc15bts_get_sysprim_conf(Litecell15_PrimId_t id); + +extern const struct value_string lc15bts_l1sapi_names[GsmL1_Sapi_NUM+1]; +extern const struct value_string lc15bts_l1status_names[GSML1_STATUS_NUM+1]; + +extern const struct value_string lc15bts_tracef_names[29]; +extern const struct value_string lc15bts_tracef_docs[29]; + +extern const struct value_string lc15bts_tch_pl_names[15]; + +extern const struct value_string lc15bts_clksrc_names[10]; + +extern const struct value_string lc15bts_dir_names[6]; + +enum pdch_cs { + PDCH_CS_1, + PDCH_CS_2, + PDCH_CS_3, + PDCH_CS_4, + PDCH_MCS_1, + PDCH_MCS_2, + PDCH_MCS_3, + PDCH_MCS_4, + PDCH_MCS_5, + PDCH_MCS_6, + PDCH_MCS_7, + PDCH_MCS_8, + PDCH_MCS_9, + _NUM_PDCH_CS +}; + +extern const uint8_t pdch_msu_size[_NUM_PDCH_CS]; + +/* LC15 default parameters */ +#define LC15_BTS_MAX_CELL_SIZE_DEFAULT 166 /* 166 qbits is default value */ +#define LC15_BTS_DIVERSITY_MODE_DEFAULT 0 /* SISO-A is default mode */ +#define LC15_BTS_PEDESTAL_MODE_DEFAULT 0 /* Unused TS is off by default */ +#define LC15_BTS_LED_CTRL_MODE_DEFAULT 0 /* LED is controlled by BTS by default */ +#define LC15_BTS_RTP_DRIFT_THRES_DEFAULT 0 /* Default RTP drift threshold is 0 ms (disabled) */ +#if LITECELL15_API_VERSION >= LITECELL15_API(2,1,7) +#define LC15_BTS_DSP_ALIVE_TMR_DEFAULT 5 /* Default DSP alive timer is 5 seconds */ +#define LC15_BTS_TX_PWR_ADJ_DEFAULT 0 /* Default Tx power auto adjustment is none */ +#define LC15_BTS_TX_RED_PWR_8PSK_DEFAULT 0 /* Default 8-PSK maximum power level is 0 dB */ +#define LC15_BTS_TX_C0_IDLE_RED_PWR_DEFAULT 0 /* Default C0 idle slot reduction power level is 0 dB */ +#endif + +#endif /* LC15BTS_H */ diff --git a/src/osmo-bts-litecell15/lc15bts_vty.c b/src/osmo-bts-lc15/lc15bts_vty.c index d27ec281..5efbfcc5 100644 --- a/src/osmo-bts-litecell15/lc15bts_vty.c +++ b/src/osmo-bts-lc15/lc15bts_vty.c @@ -2,7 +2,7 @@ /* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com> * Copyright (C) 2016 by Harald Welte <laforge@gnumonks.org> - * + * * Based on sysmoBTS: * (C) 2011 by Harald Welte <laforge@gnumonks.org> * (C) 2012,2013 by Holger Hans Peter Freyther @@ -43,6 +43,11 @@ #include <osmocom/vty/command.h> #include <osmocom/vty/misc.h> +#include <osmocom/ctrl/control_cmd.h> +#include <osmo-bts/signal.h> +#include <osmo-bts/oml.h> +#include <osmo-bts/bts.h> + #include <osmo-bts/gsm_data.h> #include <osmo-bts/phy_link.h> #include <osmo-bts/logging.h> @@ -55,6 +60,7 @@ #include "utils.h" extern int lchan_activate(struct gsm_lchan *lchan); +extern int rsl_tx_preproc_meas_res(struct gsm_lchan *lchan); #define TRX_STR "Transceiver related commands\n" "TRX number\n" @@ -63,7 +69,32 @@ extern int lchan_activate(struct gsm_lchan *lchan); TRX_STR #define DSP_TRACE_F_STR "DSP Trace Flag\n" -static struct gsm_bts *vty_bts; +static const struct value_string lc15_diversity_mode_strs[] = { + { LC15_DIVERSITY_SISO_A, "siso-a" }, + { LC15_DIVERSITY_SISO_B, "siso-b" }, + { LC15_DIVERSITY_MRC, "mrc" }, + { 0, NULL } +}; + +static const struct value_string lc15_pedestal_mode_strs[] = { + { LC15_PEDESTAL_OFF, "off" }, + { LC15_PEDESTAL_ON, "on" }, + { 0, NULL } +}; + +static const struct value_string lc15_led_mode_strs[] = { + { LC15_LED_CONTROL_BTS, "bts" }, + { LC15_LED_CONTROL_EXT, "external" }, + { 0, NULL } +}; + +#if LITECELL15_API_VERSION >= LITECELL15_API(2,1,7) +static const struct value_string lc15_auto_adj_pwr_strs[] = { + { LC15_TX_PWR_ADJ_NONE, "none" }, + { LC15_TX_PWR_ADJ_AUTO, "auto" }, + { 0, NULL } +}; +#endif /* configuration */ @@ -87,7 +118,7 @@ DEFUN(cfg_phy_dsp_trace_f, cfg_phy_dsp_trace_f_cmd, struct phy_instance *pinst = vty->index; unsigned int flag; - flag = get_string_value(lc15bts_tracef_names, argv[1]); + flag = get_string_value(lc15bts_tracef_names, argv[0]); pinst->u.lc15.dsp_trace_f |= flag; return CMD_SUCCESS; @@ -99,7 +130,7 @@ DEFUN(cfg_phy_no_dsp_trace_f, cfg_phy_no_dsp_trace_f_cmd, struct phy_instance *pinst = vty->index; unsigned int flag; - flag = get_string_value(lc15bts_tracef_names, argv[1]); + flag = get_string_value(lc15bts_tracef_names, argv[0]); pinst->u.lc15.dsp_trace_f &= ~flag; return CMD_SUCCESS; @@ -109,11 +140,11 @@ DEFUN(cfg_phy_no_dsp_trace_f, cfg_phy_no_dsp_trace_f_cmd, /* runtime */ DEFUN(show_dsp_trace_f, show_dsp_trace_f_cmd, - "show trx <0-0> dsp-trace-flags", + "show dsp-trace-flags trx <0-0>", SHOW_TRX_STR "Display the current setting of the DSP trace flags") { int trx_nr = atoi(argv[0]); - struct gsm_bts_trx *trx = gsm_bts_trx_num(vty_bts, trx_nr); + struct gsm_bts_trx *trx = gsm_bts_trx_num(g_bts, trx_nr); struct lc15l1_hdl *fl1h; int i; @@ -235,7 +266,7 @@ DEFUN(activate_lchan, activate_lchan_cmd, int trx_nr = atoi(argv[0]); int ts_nr = atoi(argv[1]); int lchan_nr = atoi(argv[3]); - struct gsm_bts_trx *trx = gsm_bts_trx_num(vty_bts, trx_nr); + struct gsm_bts_trx *trx = gsm_bts_trx_num(g_bts, trx_nr); struct gsm_bts_trx_ts *ts = &trx->ts[ts_nr]; struct gsm_lchan *lchan = &ts->lchan[lchan_nr]; @@ -256,9 +287,9 @@ DEFUN(set_tx_power, set_tx_power_cmd, { int trx_nr = atoi(argv[0]); int power = atoi(argv[1]); - struct gsm_bts_trx *trx = gsm_bts_trx_num(vty_bts, trx_nr); + struct gsm_bts_trx *trx = gsm_bts_trx_num(g_bts, trx_nr); - power_ramp_start(trx, to_mdB(power), 1); + power_ramp_start(trx, to_mdB(power), 1, NULL); return CMD_SUCCESS; } @@ -273,7 +304,7 @@ DEFUN(loopback, loopback_cmd, int trx_nr = atoi(argv[0]); int ts_nr = atoi(argv[1]); int lchan_nr = atoi(argv[2]); - struct gsm_bts_trx *trx = gsm_bts_trx_num(vty_bts, trx_nr); + struct gsm_bts_trx *trx = gsm_bts_trx_num(g_bts, trx_nr); struct gsm_bts_trx_ts *ts = &trx->ts[ts_nr]; struct gsm_lchan *lchan = &ts->lchan[lchan_nr]; @@ -292,7 +323,7 @@ DEFUN(no_loopback, no_loopback_cmd, int trx_nr = atoi(argv[0]); int ts_nr = atoi(argv[1]); int lchan_nr = atoi(argv[2]); - struct gsm_bts_trx *trx = gsm_bts_trx_num(vty_bts, trx_nr); + struct gsm_bts_trx *trx = gsm_bts_trx_num(g_bts, trx_nr); struct gsm_bts_trx_ts *ts = &trx->ts[ts_nr]; struct gsm_lchan *lchan = &ts->lchan[lchan_nr]; @@ -302,39 +333,155 @@ DEFUN(no_loopback, no_loopback_cmd, } DEFUN(cfg_trx_nominal_power, cfg_trx_nominal_power_cmd, - "nominal-tx-power <0-40>", - "Set the nominal transmit output power in dBm\n" - "Nominal transmit output power level in dBm\n") + "nominal-tx-power <0-40>", + "Set the nominal transmit output power in dBm\n" + "Nominal transmit output power level in dBm\n") { int nominal_power = atoi(argv[0]); struct gsm_bts_trx *trx = vty->index; - if (( nominal_power > 40 ) || ( nominal_power < 0 )) { - vty_out(vty, "Nominal Tx power level must be between 0 and 40 dBm (%d) %s", - nominal_power, VTY_NEWLINE); - return CMD_WARNING; - } - trx->nominal_power = nominal_power; trx->power_params.trx_p_max_out_mdBm = to_mdB(nominal_power); return CMD_SUCCESS; } -void bts_model_config_write_bts(struct vty *vty, struct gsm_bts *bts) +DEFUN(cfg_phy_max_cell_size, cfg_phy_max_cell_size_cmd, + "max-cell-size <0-166>", + "Set the maximum cell size in qbits\n") +{ + struct phy_instance *pinst = vty->index; + int cell_size = (uint8_t)atoi(argv[0]); + + pinst->u.lc15.max_cell_size = (uint8_t)cell_size; + return CMD_SUCCESS; +} + +DEFUN(cfg_phy_diversity_mode, cfg_phy_diversity_mode_cmd, + "diversity-mode (siso-a|siso-b|mrc)", + "Set reception diversity mode \n" + "Reception diversity mode can be (siso-a, siso-b, mrc)\n") +{ + struct phy_instance *pinst = vty->index; + int val = get_string_value(lc15_diversity_mode_strs, argv[0]); + + OSMO_ASSERT(val != -EINVAL); + + pinst->u.lc15.diversity_mode = (uint8_t)val; + return CMD_SUCCESS; +} + +DEFUN(cfg_phy_pedestal_mode, cfg_phy_pedestal_mode_cmd, + "pedestal-mode (on|off)", + "Set unused time-slot transmission in pedestal mode\n" + "Transmission pedestal mode can be (off, on)\n") +{ + struct phy_instance *pinst = vty->index; + int val = get_string_value(lc15_pedestal_mode_strs, argv[0]); + + OSMO_ASSERT(val != -EINVAL); + + pinst->u.lc15.pedestal_mode = (uint8_t)val; + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_led_mode, cfg_bts_led_mode_cmd, + "led-control-mode (bts|external)", + "Set LED controlled by BTS or external software\n" + "LED can be controlled by (bts, external)\n") +{ + struct gsm_bts *bts = vty->index; + int val = get_string_value(lc15_led_mode_strs, argv[0]); + + OSMO_ASSERT(val != -EINVAL); + + struct bts_lc15_priv *bts_lc15 = bts->model_priv; + bts_lc15->led_ctrl_mode = (uint8_t)val; + return CMD_SUCCESS; +} + +#if LITECELL15_API_VERSION >= LITECELL15_API(2,1,7) +DEFUN(cfg_phy_dsp_alive_timer, cfg_phy_dsp_alive_timer_cmd, + "dsp-alive-period <0-60>", + "Set DSP alive timer period in second\n") +{ + struct phy_instance *pinst = vty->index; + uint8_t period = (uint8_t)atoi(argv[0]); + + pinst->u.lc15.dsp_alive_period = period; + return CMD_SUCCESS; +} + +DEFUN(cfg_phy_auto_tx_pwr_adj, cfg_phy_auto_tx_pwr_adj_cmd, + "pwr-adj-mode (none|auto)", + "Set output power adjustment mode\n") { + struct phy_instance *pinst = vty->index; + int val = get_string_value(lc15_auto_adj_pwr_strs, argv[0]); + + OSMO_ASSERT(val != -EINVAL); + + pinst->u.lc15.tx_pwr_adj_mode = (uint8_t)val; + return CMD_SUCCESS; } -void bts_model_config_write_trx(struct vty *vty, struct gsm_bts_trx *trx) +DEFUN(cfg_phy_tx_red_pwr_8psk, cfg_phy_tx_red_pwr_8psk_cmd, + "tx-red-pwr-8psk <0-40>", + "Set reduction output power for 8-PSK scheme in dB unit\n") +{ + struct phy_instance *pinst = vty->index; + int val = atoi(argv[0]); + + pinst->u.lc15.tx_pwr_red_8psk = (uint8_t)val; + return CMD_SUCCESS; +} + +DEFUN(cfg_phy_c0_idle_red_pwr, cfg_phy_c0_idle_red_pwr_cmd, + "c0-idle-red-pwr <0-40>", + "Set reduction output power for C0 idle slot in dB unit\n") +{ + struct phy_instance *pinst = vty->index; + int val = atoi(argv[0]); + + pinst->u.lc15.tx_c0_idle_pwr_red = (uint8_t)val; + return CMD_SUCCESS; +} +#endif + +DEFUN(cfg_bts_rtp_drift_threshold, cfg_bts_rtp_drift_threshold_cmd, + "rtp-drift-threshold <0-10000>", + "RTP parameters\n" + "RTP timestamp drift threshold in ms\n") +{ + struct gsm_bts *bts = vty->index; + + struct bts_lc15_priv *bts_lc15 = bts->model_priv; + bts_lc15->rtp_drift_thres_ms = (unsigned int) atoi(argv[0]); + + return CMD_SUCCESS; +} + +void bts_model_config_write_bts(struct vty *vty, const struct gsm_bts *bts) +{ + const struct bts_lc15_priv *bts_lc15 = bts->model_priv; + vty_out(vty, " led-control-mode %s%s", + get_value_string(lc15_led_mode_strs, bts_lc15->led_ctrl_mode), VTY_NEWLINE); + + vty_out(vty, " rtp-drift-threshold %d%s", + bts_lc15->rtp_drift_thres_ms, VTY_NEWLINE); + +} + +void bts_model_config_write_trx(struct vty *vty, const struct gsm_bts_trx *trx) { vty_out(vty, " nominal-tx-power %d%s", trx->nominal_power,VTY_NEWLINE); } -void bts_model_config_write_phy(struct vty *vty, struct phy_link *plink) +void bts_model_config_write_phy(struct vty *vty, const struct phy_link *plink) { } -void bts_model_config_write_phy_inst(struct vty *vty, struct phy_instance *pinst) +void bts_model_config_write_phy_inst(struct vty *vty, const struct phy_instance *pinst) { int i; @@ -349,41 +496,62 @@ void bts_model_config_write_phy_inst(struct vty *vty, struct phy_instance *pinst if (pinst->u.lc15.calib_path) vty_out(vty, " trx-calibration-path %s%s", pinst->u.lc15.calib_path, VTY_NEWLINE); + + vty_out(vty, " max-cell-size %d%s", + pinst->u.lc15.max_cell_size, VTY_NEWLINE); + + vty_out(vty, " diversity-mode %s%s", + get_value_string(lc15_diversity_mode_strs, pinst->u.lc15.diversity_mode), VTY_NEWLINE); + + vty_out(vty, " pedestal-mode %s%s", + get_value_string(lc15_pedestal_mode_strs, pinst->u.lc15.pedestal_mode) , VTY_NEWLINE); + +#if LITECELL15_API_VERSION >= LITECELL15_API(2,1,7) + vty_out(vty, " dsp-alive-period %d%s", + pinst->u.lc15.dsp_alive_period, VTY_NEWLINE); + + vty_out(vty, " pwr-adj-mode %s%s", + get_value_string(lc15_auto_adj_pwr_strs, pinst->u.lc15.tx_pwr_adj_mode), VTY_NEWLINE); + + vty_out(vty, " tx-red-pwr-8psk %d%s", + pinst->u.lc15.tx_pwr_red_8psk, VTY_NEWLINE); + + vty_out(vty, " c0-idle-red-pwr %d%s", + pinst->u.lc15.tx_c0_idle_pwr_red, VTY_NEWLINE); +#endif } -int bts_model_vty_init(struct gsm_bts *bts) +int bts_model_vty_init(void *ctx) { - vty_bts = bts; - /* runtime-patch the command strings with debug levels */ - dsp_trace_f_cmd.string = vty_cmd_string_from_valstr(bts, lc15bts_tracef_names, + dsp_trace_f_cmd.string = vty_cmd_string_from_valstr(ctx, lc15bts_tracef_names, "phy <0-0> dsp-trace-flag (", "|",")", VTY_DO_LOWER); - dsp_trace_f_cmd.doc = vty_cmd_string_from_valstr(bts, lc15bts_tracef_docs, + dsp_trace_f_cmd.doc = vty_cmd_string_from_valstr(ctx, lc15bts_tracef_docs, TRX_STR DSP_TRACE_F_STR, "\n", "", 0); - no_dsp_trace_f_cmd.string = vty_cmd_string_from_valstr(bts, lc15bts_tracef_names, + no_dsp_trace_f_cmd.string = vty_cmd_string_from_valstr(ctx, lc15bts_tracef_names, "no phy <0-0> dsp-trace-flag (", "|",")", VTY_DO_LOWER); - no_dsp_trace_f_cmd.doc = vty_cmd_string_from_valstr(bts, lc15bts_tracef_docs, + no_dsp_trace_f_cmd.doc = vty_cmd_string_from_valstr(ctx, lc15bts_tracef_docs, NO_STR TRX_STR DSP_TRACE_F_STR, "\n", "", 0); - cfg_phy_dsp_trace_f_cmd.string = vty_cmd_string_from_valstr(bts, + cfg_phy_dsp_trace_f_cmd.string = vty_cmd_string_from_valstr(ctx, lc15bts_tracef_names, "dsp-trace-flag (", "|",")", VTY_DO_LOWER); - cfg_phy_dsp_trace_f_cmd.doc = vty_cmd_string_from_valstr(bts, + cfg_phy_dsp_trace_f_cmd.doc = vty_cmd_string_from_valstr(ctx, lc15bts_tracef_docs, DSP_TRACE_F_STR, "\n", "", 0); - cfg_phy_no_dsp_trace_f_cmd.string = vty_cmd_string_from_valstr(bts, + cfg_phy_no_dsp_trace_f_cmd.string = vty_cmd_string_from_valstr(ctx, lc15bts_tracef_names, "no dsp-trace-flag (", "|",")", VTY_DO_LOWER); - cfg_phy_no_dsp_trace_f_cmd.doc = vty_cmd_string_from_valstr(bts, + cfg_phy_no_dsp_trace_f_cmd.doc = vty_cmd_string_from_valstr(ctx, lc15bts_tracef_docs, NO_STR DSP_TRACE_F_STR, "\n", "", 0); @@ -401,6 +569,8 @@ int bts_model_vty_init(struct gsm_bts *bts) install_element(BTS_NODE, &cfg_bts_auto_band_cmd); install_element(BTS_NODE, &cfg_bts_no_auto_band_cmd); + install_element(BTS_NODE, &cfg_bts_led_mode_cmd); + install_element(BTS_NODE, &cfg_bts_rtp_drift_threshold_cmd); install_element(TRX_NODE, &cfg_trx_nominal_power_cmd); @@ -408,6 +578,15 @@ int bts_model_vty_init(struct gsm_bts *bts) install_element(PHY_INST_NODE, &cfg_phy_no_dsp_trace_f_cmd); install_element(PHY_INST_NODE, &cfg_phy_cal_path_cmd); + install_element(PHY_INST_NODE, &cfg_phy_diversity_mode_cmd); + install_element(PHY_INST_NODE, &cfg_phy_pedestal_mode_cmd); + install_element(PHY_INST_NODE, &cfg_phy_max_cell_size_cmd); +#if LITECELL15_API_VERSION >= LITECELL15_API(2,1,7) + install_element(PHY_INST_NODE, &cfg_phy_dsp_alive_timer_cmd); + install_element(PHY_INST_NODE, &cfg_phy_auto_tx_pwr_adj_cmd); + install_element(PHY_INST_NODE, &cfg_phy_tx_red_pwr_8psk_cmd); + install_element(PHY_INST_NODE, &cfg_phy_c0_idle_red_pwr_cmd); +#endif return 0; } diff --git a/src/osmo-bts-litecell15/main.c b/src/osmo-bts-lc15/main.c index c47d76d0..23601299 100644 --- a/src/osmo-bts-litecell15/main.c +++ b/src/osmo-bts-lc15/main.c @@ -2,7 +2,7 @@ /* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com> * Copyright (C) 2016 by Harald Welte <laforge@gnumonks.org> - * + * * Based on sysmoBTS: * (C) 2011-2013 by Harald Welte <laforge@gnumonks.org> * @@ -16,7 +16,7 @@ * 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. + * 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/>. @@ -76,50 +76,69 @@ static int write_status_file(char *status_file, char *status_str) #include "utils.h" #include "l1_if.h" #include "hw_misc.h" -#include "oml_router.h" #include "misc/lc15bts_bid.h" unsigned int dsp_trace = 0x00000000; int bts_model_init(struct gsm_bts *bts) { - struct gsm_bts_trx *trx; struct stat st; - static struct osmo_fd accept_fd, read_fd; - int rc; + struct bts_lc15_priv *bts_lc15 = talloc(bts, struct bts_lc15_priv); + + bts->model_priv = bts_lc15; bts->variant = BTS_OSMO_LITECELL15; bts->support.ciphers = CIPHER_A5(1) | CIPHER_A5(2) | CIPHER_A5(3); + bts->gprs.cell.support.gprs_codings = NM_IPAC_MASK_GPRS_CODING_CS + | NM_IPAC_MASK_GPRS_CODING_MCS; - rc = oml_router_init(bts, OML_ROUTER_PATH, &accept_fd, &read_fd); - if (rc < 0) { - fprintf(stderr, "Error creating the OML router: %s rc=%d\n", - OML_ROUTER_PATH, rc); - exit(1); - } + /* specific default values for LC15 platform */ + bts_lc15->led_ctrl_mode = LC15_BTS_LED_CTRL_MODE_DEFAULT; + /* RTP drift threshold default */ + bts_lc15->rtp_drift_thres_ms = LC15_BTS_RTP_DRIFT_THRES_DEFAULT; if (stat(LC15BTS_RF_LOCK_PATH, &st) == 0) { LOGP(DL1C, LOGL_NOTICE, "Not starting BTS due to RF_LOCK file present\n"); exit(23); } - gsm_bts_set_feature(bts, BTS_FEAT_GPRS); - gsm_bts_set_feature(bts, BTS_FEAT_EGPRS); - gsm_bts_set_feature(bts, BTS_FEAT_OML_ALERTS); - gsm_bts_set_feature(bts, BTS_FEAT_AGCH_PCH_PROP); - gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_F_V1); - gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_H_V1); - gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_F_EFR); - gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_F_AMR); - gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_H_AMR); - - bts_model_vty_init(bts); + /* order alphabetically */ + osmo_bts_set_feature(bts->features, BTS_FEAT_AGCH_PCH_PROP); + osmo_bts_set_feature(bts->features, BTS_FEAT_CBCH); + osmo_bts_set_feature(bts->features, BTS_FEAT_EGPRS); + osmo_bts_set_feature(bts->features, BTS_FEAT_GPRS); + osmo_bts_set_feature(bts->features, BTS_FEAT_OML_ALERTS); + osmo_bts_set_feature(bts->features, BTS_FEAT_SPEECH_F_AMR); + osmo_bts_set_feature(bts->features, BTS_FEAT_SPEECH_F_EFR); + osmo_bts_set_feature(bts->features, BTS_FEAT_SPEECH_F_V1); + osmo_bts_set_feature(bts->features, BTS_FEAT_SPEECH_H_AMR); + osmo_bts_set_feature(bts->features, BTS_FEAT_SPEECH_H_V1); + + bts_internal_flag_set(bts, BTS_INTERNAL_FLAG_MS_PWR_CTRL_DSP); + bts_internal_flag_set(bts, BTS_INTERNAL_FLAG_NM_RCHANNEL_DEPENDS_RCARRIER); + + /* The default HR codec output format in the absence of saved + * vty config needs to match what was implemented previously, + * for the sake of existing deployments, i.e., to avoid + * a surprise functional change upon software update. */ + bts->emit_hr_rfc5993 = false; return 0; } int bts_model_trx_init(struct gsm_bts_trx *trx) { + /* Frequency bands indicated to the BSC */ + trx->support.freq_bands = 0x00; /* updated in info_compl_cb() */ + + /* Channel types and modes indicated to the BSC */ + trx->support.chan_types = NM_IPAC_MASK_CHANT_COMMON + | NM_IPAC_F_CHANT_BCCH_SDCCH4_CBCH + | NM_IPAC_F_CHANT_SDCCH8_CBCH + | NM_IPAC_F_CHANT_PDCHF + | NM_IPAC_F_CHANT_TCHF_PDCHF; + trx->support.chan_modes = NM_IPAC_MASK_CHANM_SPEECH; + trx->nominal_power = 40; trx->power_params.trx_p_max_out_mdBm = to_mdB(trx->bts->c0->nominal_power); return 0; @@ -159,9 +178,11 @@ void bts_update_status(enum bts_global_status which, int on) void bts_model_print_help() { - printf( " -w --hw-version Print the targeted HW Version\n" - " -M --pcu-direct Force PCU to access message queue for PDCH dchannel directly\n" - " -p --dsp-trace Set DSP trace flags\n" + printf( "\nModel specific options:\n" + " -w --hw-version Print the targeted HW Version\n" + " -M --pcu-direct Force PCU to access message queue for " + "PDCH dchannel directly\n" + " -p --dsp-trace Set DSP trace flags\n" ); } diff --git a/src/osmo-bts-litecell15/misc/lc15bts_bid.c b/src/osmo-bts-lc15/misc/lc15bts_bid.c index 9284b62e..9267e06f 100644 --- a/src/osmo-bts-litecell15/misc/lc15bts_bid.c +++ b/src/osmo-bts-lc15/misc/lc15bts_bid.c @@ -10,7 +10,7 @@ * 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. + * 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-bts-litecell15/misc/lc15bts_bid.h b/src/osmo-bts-lc15/misc/lc15bts_bid.h index a71fdd7e..a71fdd7e 100644 --- a/src/osmo-bts-litecell15/misc/lc15bts_bid.h +++ b/src/osmo-bts-lc15/misc/lc15bts_bid.h diff --git a/src/osmo-bts-litecell15/misc/lc15bts_bts.c b/src/osmo-bts-lc15/misc/lc15bts_bts.c index 0343e930..560320a0 100644 --- a/src/osmo-bts-litecell15/misc/lc15bts_bts.c +++ b/src/osmo-bts-lc15/misc/lc15bts_bts.c @@ -10,7 +10,7 @@ * 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. + * 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-bts-litecell15/misc/lc15bts_bts.h b/src/osmo-bts-lc15/misc/lc15bts_bts.h index 3918b870..3918b870 100644 --- a/src/osmo-bts-litecell15/misc/lc15bts_bts.h +++ b/src/osmo-bts-lc15/misc/lc15bts_bts.h diff --git a/src/osmo-bts-litecell15/misc/lc15bts_clock.c b/src/osmo-bts-lc15/misc/lc15bts_clock.c index 71701496..23f8f021 100644 --- a/src/osmo-bts-litecell15/misc/lc15bts_clock.c +++ b/src/osmo-bts-lc15/misc/lc15bts_clock.c @@ -10,7 +10,7 @@ * 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. + * 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-bts-litecell15/misc/lc15bts_clock.h b/src/osmo-bts-lc15/misc/lc15bts_clock.h index d9673598..d9673598 100644 --- a/src/osmo-bts-litecell15/misc/lc15bts_clock.h +++ b/src/osmo-bts-lc15/misc/lc15bts_clock.h diff --git a/src/osmo-bts-litecell15/misc/lc15bts_led.c b/src/osmo-bts-lc15/misc/lc15bts_led.c index a93d3fb0..88b12207 100644 --- a/src/osmo-bts-litecell15/misc/lc15bts_led.c +++ b/src/osmo-bts-lc15/misc/lc15bts_led.c @@ -10,7 +10,7 @@ * 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. + * 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-bts-litecell15/misc/lc15bts_led.h b/src/osmo-bts-lc15/misc/lc15bts_led.h index b6d9d28b..b6d9d28b 100644 --- a/src/osmo-bts-litecell15/misc/lc15bts_led.h +++ b/src/osmo-bts-lc15/misc/lc15bts_led.h diff --git a/src/osmo-bts-litecell15/misc/lc15bts_mgr.c b/src/osmo-bts-lc15/misc/lc15bts_mgr.c index ccacc1e5..fb4f5c40 100644 --- a/src/osmo-bts-litecell15/misc/lc15bts_mgr.c +++ b/src/osmo-bts-lc15/misc/lc15bts_mgr.c @@ -1,7 +1,7 @@ /* Main program for NuRAN Wireless Litecell 1.5 BTS management daemon */ /* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com> - * + * * Based on sysmoBTS: * sysmobts_mgr.c * (C) 2012 by Harald Welte <laforge@gnumonks.org> @@ -17,7 +17,7 @@ * 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. + * 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/>. @@ -220,11 +220,11 @@ static int parse_options(int argc, char **argv) return 0; } -static void signal_handler(int signal) +static void signal_handler(int signum) { - fprintf(stderr, "signal %u received\n", signal); + fprintf(stderr, "signal %u received\n", signum); - switch (signal) { + switch (signum) { case SIGINT: case SIGTERM: lc15bts_check_temp(no_rom_write); @@ -234,6 +234,16 @@ static void signal_handler(int signal) exit(0); break; case SIGABRT: + /* in case of abort, we want to obtain a talloc report and + * then run default SIGABRT handler, who will generate coredump + * and abort the process. abort() should do this for us after we + * return, but program wouldn't exit if an external SIGABRT is + * received. + */ + talloc_report_full(tall_mgr_ctx, stderr); + signal(SIGABRT, SIG_DFL); + raise(SIGABRT); + break; case SIGUSR1: case SIGUSR2: talloc_report_full(tall_mgr_ctx, stderr); @@ -248,31 +258,31 @@ static struct log_info_cat mgr_log_info_cat[] = { .name = "DTEMP", .description = "Temperature monitoring", .color = "\033[1;35m", - .enabled = 1, .loglevel = LOGL_INFO, + .enabled = 1, .loglevel = LOGL_NOTICE, }, [DFW] = { .name = "DFW", .description = "Firmware management", .color = "\033[1;36m", - .enabled = 1, .loglevel = LOGL_INFO, + .enabled = 1, .loglevel = LOGL_NOTICE, }, [DFIND] = { .name = "DFIND", .description = "ipaccess-find handling", .color = "\033[1;37m", - .enabled = 1, .loglevel = LOGL_INFO, + .enabled = 1, .loglevel = LOGL_NOTICE, }, [DCALIB] = { .name = "DCALIB", .description = "Calibration handling", .color = "\033[1;37m", - .enabled = 1, .loglevel = LOGL_INFO, + .enabled = 1, .loglevel = LOGL_NOTICE, }, [DSWD] = { .name = "DSWD", .description = "Software Watchdog", .color = "\033[1;37m", - .enabled = 1, .loglevel = LOGL_INFO, + .enabled = 1, .loglevel = LOGL_NOTICE, }, }; @@ -293,6 +303,7 @@ int main(int argc, char **argv) osmo_init_ignore_signals(); signal(SIGINT, &signal_handler); signal(SIGTERM, &signal_handler); + signal(SIGABRT, &signal_handler); signal(SIGUSR1, &signal_handler); signal(SIGUSR2, &signal_handler); @@ -308,7 +319,7 @@ int main(int argc, char **argv) exit(1); } - rc = telnet_init(tall_mgr_ctx, NULL, OSMO_VTY_PORT_BTSMGR); + rc = telnet_init_default(tall_mgr_ctx, NULL, OSMO_VTY_PORT_BTSMGR); if (rc < 0) { fprintf(stderr, "Error initializing telnet\n"); exit(1); diff --git a/src/osmo-bts-litecell15/misc/lc15bts_mgr.h b/src/osmo-bts-lc15/misc/lc15bts_mgr.h index 4bfbdbc9..4bfbdbc9 100644 --- a/src/osmo-bts-litecell15/misc/lc15bts_mgr.h +++ b/src/osmo-bts-lc15/misc/lc15bts_mgr.h diff --git a/src/osmo-bts-litecell15/misc/lc15bts_mgr_calib.c b/src/osmo-bts-lc15/misc/lc15bts_mgr_calib.c index badb5455..f25955f8 100644 --- a/src/osmo-bts-litecell15/misc/lc15bts_mgr_calib.c +++ b/src/osmo-bts-lc15/misc/lc15bts_mgr_calib.c @@ -17,7 +17,7 @@ * 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. + * 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-bts-litecell15/misc/lc15bts_mgr_nl.c b/src/osmo-bts-lc15/misc/lc15bts_mgr_nl.c index 3a617dd7..b34becf1 100644 --- a/src/osmo-bts-litecell15/misc/lc15bts_mgr_nl.c +++ b/src/osmo-bts-lc15/misc/lc15bts_mgr_nl.c @@ -16,7 +16,7 @@ * 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. + * 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-bts-litecell15/misc/lc15bts_mgr_temp.c b/src/osmo-bts-lc15/misc/lc15bts_mgr_temp.c index 9665e1db..abffce3b 100644 --- a/src/osmo-bts-litecell15/misc/lc15bts_mgr_temp.c +++ b/src/osmo-bts-lc15/misc/lc15bts_mgr_temp.c @@ -16,7 +16,7 @@ * 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. + * 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-bts-litecell15/misc/lc15bts_mgr_vty.c b/src/osmo-bts-lc15/misc/lc15bts_mgr_vty.c index 80751fb0..65c1fe72 100644 --- a/src/osmo-bts-litecell15/misc/lc15bts_mgr_vty.c +++ b/src/osmo-bts-lc15/misc/lc15bts_mgr_vty.c @@ -16,7 +16,7 @@ * 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. + * 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/>. @@ -89,39 +89,10 @@ static int go_to_parent(struct vty *vty) return vty->node; } -static int is_config_node(struct vty *vty, int node) -{ - switch (node) { - case MGR_NODE: - case ACT_NORM_NODE: - case ACT_WARN_NODE: - case ACT_CRIT_NODE: - case LIMIT_SUPPLY_TEMP_NODE: - case LIMIT_SOC_NODE: - case LIMIT_FPGA_NODE: - case LIMIT_RMSDET_NODE: - case LIMIT_OCXO_NODE: - case LIMIT_TX0_TEMP_NODE: - case LIMIT_TX1_TEMP_NODE: - case LIMIT_PA0_TEMP_NODE: - case LIMIT_PA1_TEMP_NODE: - case LIMIT_SUPPLY_VOLT_NODE: - case LIMIT_TX0_VSWR_NODE: - case LIMIT_TX1_VSWR_NODE: - case LIMIT_SUPPLY_PWR_NODE: - case LIMIT_PA0_PWR_NODE: - case LIMIT_PA1_PWR_NODE: - return 1; - default: - return 0; - } -} - static struct vty_app_info vty_info = { .name = "lc15bts-mgr", .version = PACKAGE_VERSION, .go_parent_cb = go_to_parent, - .is_config_node = is_config_node, .copyright = copyright, }; @@ -584,39 +555,39 @@ DEFUN(show_mgr, show_mgr_cmd, "show manager", lc15bts_mgr_sensor_get_state(s_mgr->state.state), VTY_NEWLINE); vty_out(vty, "Current Temperatures%s", VTY_NEWLINE); lc15bts_temp_get(LC15BTS_TEMP_SUPPLY, &temp); - vty_out(vty, " Main Supply : %4.2f Celcius%s", + vty_out(vty, " Main Supply : %4.2f Celsius%s", temp/ 1000.0f, VTY_NEWLINE); lc15bts_temp_get(LC15BTS_TEMP_SOC, &temp); - vty_out(vty, " SoC : %4.2f Celcius%s", + vty_out(vty, " SoC : %4.2f Celsius%s", temp / 1000.0f, VTY_NEWLINE); lc15bts_temp_get(LC15BTS_TEMP_FPGA, &temp); - vty_out(vty, " FPGA : %4.2f Celcius%s", + vty_out(vty, " FPGA : %4.2f Celsius%s", temp / 1000.0f, VTY_NEWLINE); lc15bts_temp_get(LC15BTS_TEMP_RMSDET, &temp); - vty_out(vty, " RMSDet : %4.2f Celcius%s", + vty_out(vty, " RMSDet : %4.2f Celsius%s", temp / 1000.0f, VTY_NEWLINE); lc15bts_temp_get(LC15BTS_TEMP_OCXO, &temp); - vty_out(vty, " OCXO : %4.2f Celcius%s", + vty_out(vty, " OCXO : %4.2f Celsius%s", temp / 1000.0f, VTY_NEWLINE); lc15bts_temp_get(LC15BTS_TEMP_TX0, &temp); - vty_out(vty, " TX 0 : %4.2f Celcius%s", + vty_out(vty, " TX 0 : %4.2f Celsius%s", temp / 1000.0f, VTY_NEWLINE); lc15bts_temp_get(LC15BTS_TEMP_TX1, &temp); - vty_out(vty, " TX 1 : %4.2f Celcius%s", + vty_out(vty, " TX 1 : %4.2f Celsius%s", temp / 1000.0f, VTY_NEWLINE); lc15bts_temp_get(LC15BTS_TEMP_PA0, &temp); - vty_out(vty, " Power Amp #0: %4.2f Celcius%s", + vty_out(vty, " Power Amp #0: %4.2f Celsius%s", temp / 1000.0f, VTY_NEWLINE); lc15bts_temp_get(LC15BTS_TEMP_PA1, &temp); - vty_out(vty, " Power Amp #1: %4.2f Celcius%s", + vty_out(vty, " Power Amp #1: %4.2f Celsius%s", temp / 1000.0f, VTY_NEWLINE); diff --git a/src/osmo-bts-litecell15/misc/lc15bts_misc.c b/src/osmo-bts-lc15/misc/lc15bts_misc.c index 2cedc5d8..bd801099 100644 --- a/src/osmo-bts-litecell15/misc/lc15bts_misc.c +++ b/src/osmo-bts-lc15/misc/lc15bts_misc.c @@ -14,7 +14,7 @@ * 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. + * 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/>. @@ -363,7 +363,7 @@ int lc15bts_firmware_reload(enum lc15bts_firmware_type type) case LC15BTS_FW_DSP1: fd = open(fw_sysfs[type], O_WRONLY); if (fd < 0) { - LOGP(DFW, LOGL_ERROR, "unable ot open firmware device %s: %s\n", + LOGP(DFW, LOGL_ERROR, "unable to open firmware device %s: %s\n", fw_sysfs[type], strerror(errno)); close(fd); return fd; diff --git a/src/osmo-bts-litecell15/misc/lc15bts_misc.h b/src/osmo-bts-lc15/misc/lc15bts_misc.h index 79e9e686..79e9e686 100644 --- a/src/osmo-bts-litecell15/misc/lc15bts_misc.h +++ b/src/osmo-bts-lc15/misc/lc15bts_misc.h diff --git a/src/osmo-bts-litecell15/misc/lc15bts_nl.c b/src/osmo-bts-lc15/misc/lc15bts_nl.c index 39f64aae..d1d1bd13 100644 --- a/src/osmo-bts-litecell15/misc/lc15bts_nl.c +++ b/src/osmo-bts-lc15/misc/lc15bts_nl.c @@ -16,7 +16,7 @@ * 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. + * 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-bts-litecell15/misc/lc15bts_nl.h b/src/osmo-bts-lc15/misc/lc15bts_nl.h index 340cf117..b5a15403 100644 --- a/src/osmo-bts-litecell15/misc/lc15bts_nl.h +++ b/src/osmo-bts-lc15/misc/lc15bts_nl.h @@ -14,7 +14,7 @@ * 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. + * 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-bts-litecell15/misc/lc15bts_par.c b/src/osmo-bts-lc15/misc/lc15bts_par.c index af9d030f..e93c45b6 100644 --- a/src/osmo-bts-litecell15/misc/lc15bts_par.c +++ b/src/osmo-bts-lc15/misc/lc15bts_par.c @@ -16,7 +16,7 @@ * 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. + * 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-bts-litecell15/misc/lc15bts_par.h b/src/osmo-bts-lc15/misc/lc15bts_par.h index 74295653..74295653 100644 --- a/src/osmo-bts-litecell15/misc/lc15bts_par.h +++ b/src/osmo-bts-lc15/misc/lc15bts_par.h diff --git a/src/osmo-bts-litecell15/misc/lc15bts_power.c b/src/osmo-bts-lc15/misc/lc15bts_power.c index 1a37d8e6..c28232b7 100644 --- a/src/osmo-bts-litecell15/misc/lc15bts_power.c +++ b/src/osmo-bts-lc15/misc/lc15bts_power.c @@ -10,7 +10,7 @@ * 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. + * 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-bts-litecell15/misc/lc15bts_power.h b/src/osmo-bts-lc15/misc/lc15bts_power.h index b48cfdcd..b48cfdcd 100644 --- a/src/osmo-bts-litecell15/misc/lc15bts_power.h +++ b/src/osmo-bts-lc15/misc/lc15bts_power.h diff --git a/src/osmo-bts-litecell15/misc/lc15bts_swd.c b/src/osmo-bts-lc15/misc/lc15bts_swd.c index 59c7b616..f0af6bca 100644 --- a/src/osmo-bts-litecell15/misc/lc15bts_swd.c +++ b/src/osmo-bts-lc15/misc/lc15bts_swd.c @@ -10,7 +10,7 @@ * 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. + * 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/>. @@ -161,7 +161,7 @@ int lc15bts_swd_init(struct lc15bts_mgr_instance *mgr, int swd_num_events) the value must be in the range of [0,'swd_num_events'[ (see lc15bts_swd_init). For example, if 'swd_num_events' was 64, 'swd_event' events are numbered 0 to 63. WARNING: if this function can be used from multiple threads at the same time, - it must be protected with a kind of mutex to avoid loosing event notification. + it must be protected with a kind of mutex to avoid losing event notification. */ int lc15bts_swd_event(struct lc15bts_mgr_instance *mgr, enum mgr_swd_events swd_event) { diff --git a/src/osmo-bts-litecell15/misc/lc15bts_swd.h b/src/osmo-bts-lc15/misc/lc15bts_swd.h index b78a2c2a..b78a2c2a 100644 --- a/src/osmo-bts-litecell15/misc/lc15bts_swd.h +++ b/src/osmo-bts-lc15/misc/lc15bts_swd.h diff --git a/src/osmo-bts-litecell15/misc/lc15bts_temp.c b/src/osmo-bts-lc15/misc/lc15bts_temp.c index 45602dcc..f69b9bb7 100644 --- a/src/osmo-bts-litecell15/misc/lc15bts_temp.c +++ b/src/osmo-bts-lc15/misc/lc15bts_temp.c @@ -10,7 +10,7 @@ * 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. + * 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-bts-litecell15/misc/lc15bts_temp.h b/src/osmo-bts-lc15/misc/lc15bts_temp.h index 35d81f1b..35d81f1b 100644 --- a/src/osmo-bts-litecell15/misc/lc15bts_temp.h +++ b/src/osmo-bts-lc15/misc/lc15bts_temp.h diff --git a/src/osmo-bts-litecell15/misc/lc15bts_util.c b/src/osmo-bts-lc15/misc/lc15bts_util.c index 430ce0f7..a5ab6027 100644 --- a/src/osmo-bts-litecell15/misc/lc15bts_util.c +++ b/src/osmo-bts-lc15/misc/lc15bts_util.c @@ -16,7 +16,7 @@ * 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. + * 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-bts-litecell15/oml.c b/src/osmo-bts-lc15/oml.c index f084f1bf..6f312e80 100644 --- a/src/osmo-bts-litecell15/oml.c +++ b/src/osmo-bts-lc15/oml.c @@ -14,7 +14,7 @@ * 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. + * 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/>. @@ -26,6 +26,7 @@ #include <osmocom/core/talloc.h> #include <osmocom/core/utils.h> +#include <osmocom/core/fsm.h> #include <nrw/litecell15/gsml1prim.h> #include <nrw/litecell15/gsml1const.h> @@ -42,11 +43,14 @@ #include <osmo-bts/phy_link.h> #include <osmo-bts/handover.h> #include <osmo-bts/l1sap.h> +#include <osmo-bts/nm_common_fsm.h> #include "l1_if.h" #include "lc15bts.h" #include "utils.h" +static void dump_lch_par(int logl, GsmL1_LogChParam_t *lch_par, GsmL1_Sapi_t sapi); + static int mph_info_chan_confirm(struct gsm_lchan *lchan, enum osmo_mph_info_type type, uint8_t cause) { @@ -91,7 +95,7 @@ static const enum GsmL1_LogChComb_t pchan_to_logChComb[_GSM_PCHAN_MAX] = { [GSM_PCHAN_PDCH] = GsmL1_LogChComb_XIII, [GSM_PCHAN_UNKNOWN] = GsmL1_LogChComb_0, /* - * GSM_PCHAN_TCH_F_PDCH and GSM_PCHAN_TCH_F_TCH_H_PDCH should not be + * GSM_PCHAN_TCH_F_PDCH and GSM_PCHAN_OSMO_DYN should not be * part of this, only "real" pchan values will be looked up here. * See the callers of ts_connect_as(). */ @@ -268,36 +272,48 @@ static int opstart_compl(struct gsm_abis_mo *mo, struct msgb *l1_msg) { GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg); GsmL1_Status_t status = prim_status(l1p); + struct gsm_bts_trx *trx = gsm_bts_trx_num(mo->bts, mo->obj_inst.trx_nr); if (status != GsmL1_Status_Success) { LOGP(DL1C, LOGL_ERROR, "Rx %s, status: %s\n", get_value_string(lc15bts_l1prim_names, l1p->id), get_value_string(lc15bts_l1status_names, status)); msgb_free(l1_msg); - return oml_mo_opstart_nack(mo, NM_NACK_CANT_PERFORM); + switch (mo->obj_class) { + case NM_OC_RADIO_CARRIER: + return osmo_fsm_inst_dispatch(trx->mo.fi, NM_EV_OPSTART_NACK, + (void*)(intptr_t)NM_NACK_CANT_PERFORM); + case NM_OC_CHANNEL: + return osmo_fsm_inst_dispatch(trx->ts[mo->obj_inst.ts_nr].mo.fi, NM_EV_OPSTART_NACK, + (void*)(intptr_t)NM_NACK_CANT_PERFORM); + default: + OSMO_ASSERT(0); + } } msgb_free(l1_msg); - - /* Set to Operational State: Enabled */ - oml_mo_state_chg(mo, NM_OPSTATE_ENABLED, NM_AVSTATE_OK); - - /* ugly hack to auto-activate all SAPIs for the BCCH/CCCH on TS0 */ - if (mo->obj_class == NM_OC_CHANNEL && mo->obj_inst.trx_nr == 0 && - mo->obj_inst.ts_nr == 0) { - struct gsm_lchan *cbch = gsm_bts_get_cbch(mo->bts); - DEBUGP(DL1C, "====> trying to activate lchans of BCCH\n"); - mo->bts->c0->ts[0].lchan[CCCH_LCHAN].rel_act_kind = - LCHAN_REL_ACT_OML; - lchan_activate(&mo->bts->c0->ts[0].lchan[CCCH_LCHAN]); - if (cbch) { - cbch->rel_act_kind = LCHAN_REL_ACT_OML; - lchan_activate(cbch); + switch (mo->obj_class) { + case NM_OC_RADIO_CARRIER: + return osmo_fsm_inst_dispatch(trx->mo.fi, NM_EV_OPSTART_ACK, NULL); + case NM_OC_CHANNEL: + /* ugly hack to auto-activate all SAPIs for the BCCH/CCCH on TS0 */ + if (mo->obj_inst.trx_nr == 0 && + mo->obj_inst.ts_nr == 0) { + struct gsm_lchan *cbch = gsm_bts_get_cbch(mo->bts); + DEBUGP(DL1C, "====> trying to activate lchans of BCCH\n"); + mo->bts->c0->ts[0].lchan[CCCH_LCHAN].rel_act_kind = + LCHAN_REL_ACT_OML; + lchan_activate(&mo->bts->c0->ts[0].lchan[CCCH_LCHAN]); + if (cbch) { + cbch->rel_act_kind = LCHAN_REL_ACT_OML; + lchan_activate(cbch); + } } + return osmo_fsm_inst_dispatch(trx->ts[mo->obj_inst.ts_nr].mo.fi, + NM_EV_OPSTART_ACK, NULL); + default: + OSMO_ASSERT(0); } - - /* Send OPSTART ack */ - return oml_mo_opstart_ack(mo); } static int opstart_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg, @@ -355,7 +371,7 @@ static int trx_init_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg, trx_rf_lock(trx, 1, trx_mute_on_init_cb); /* Begin to ramp up the power */ - power_ramp_start(trx, get_p_target_mdBm(trx, 0), 0); + power_ramp_start(trx, get_p_target_mdBm(trx, 0), 0, NULL); return opstart_compl(&trx->mo, l1_msg); } @@ -384,14 +400,24 @@ static int trx_init(struct gsm_bts_trx *trx) struct msgb *msg; GsmL1_MphInitReq_t *mi_req; GsmL1_DeviceParam_t *dev_par; - int lc15_band; + int rc, lc15_band; if (!gsm_abis_mo_check_attr(&trx->mo, trx_rqd_attr, ARRAY_SIZE(trx_rqd_attr))) { /* HACK: spec says we need to decline, but openbsc * doesn't deal with this very well */ - return oml_mo_opstart_ack(&trx->mo); - //return oml_mo_opstart_nack(&trx->mo, NM_NACK_CANT_PERFORM); + return osmo_fsm_inst_dispatch(trx->mo.fi, NM_EV_OPSTART_ACK, NULL); + //return osmo_fsm_inst_dispatch(trx->mo.fi, NM_EV_OPSTART_NACK, + // (void*)(intptr_t)NM_NACK_CANT_PERFORM); + } + + /* Update TRX band */ + rc = gsm_arfcn2band_rc(trx->arfcn, &trx->bts->band); + if (rc) { + /* FIXME: abort initialization? */ + LOGP(DL1C, LOGL_ERROR, "Could not pick GSM band " + "for ARFCN %u\n", trx->arfcn); + trx->bts->band = 0x00; } lc15_band = lc15bts_select_lc15_band(trx, trx->arfcn); @@ -408,9 +434,16 @@ static int trx_init(struct gsm_bts_trx *trx) dev_par->freqBand = lc15_band; dev_par->u16Arfcn = trx->arfcn; dev_par->u16BcchArfcn = trx->bts->c0->arfcn; - dev_par->u8NbTsc = trx->bts->bsic & 7; - dev_par->fRxPowerLevel = trx_ms_pwr_ctrl_is_osmo(trx) - ? 0.0 : trx->bts->ul_power_target; + dev_par->u8NbTsc = BTS_TSC(trx->bts); + + if (!trx_ms_pwr_ctrl_is_osmo(trx)) { + /* Target is in the middle between lower and upper RxLev thresholds */ + int lower_dbm = rxlev2dbm(trx->ms_dpc_params->rxlev_meas.lower_thresh); + int upper_dbm = rxlev2dbm(trx->ms_dpc_params->rxlev_meas.upper_thresh); + dev_par->fRxPowerLevel = (float) (lower_dbm + upper_dbm) / 2; + } else { + dev_par->fRxPowerLevel = 0.0; + } dev_par->fTxPowerLevel = 0.0; LOGP(DL1C, LOGL_NOTICE, "Init TRX (Band %d, ARFCN %u, TSC %u, RxPower % 2f dBm, " @@ -421,9 +454,9 @@ static int trx_init(struct gsm_bts_trx *trx) return l1if_gsm_req_compl(fl1h, msg, trx_init_compl_cb, NULL); } -uint32_t trx_get_hlayer1(struct gsm_bts_trx *trx) +uint32_t trx_get_hlayer1(const struct gsm_bts_trx *trx) { - struct lc15l1_hdl *fl1h = trx_lc15l1_hdl(trx); + const struct lc15l1_hdl *fl1h = trx_lc15l1_hdl(trx); return fl1h->hLayer1; } @@ -432,20 +465,24 @@ static int trx_close_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg, void *data) { msgb_free(l1_msg); + bts_model_trx_close_cb(trx, 0); return 0; } -int bts_model_trx_close(struct gsm_bts_trx *trx) +void bts_model_trx_close(struct gsm_bts_trx *trx) { struct lc15l1_hdl *fl1h = trx_lc15l1_hdl(trx); struct msgb *msg; + int rc; msg = l1p_msgb_alloc(); prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphCloseReq, fl1h, l1p_handle_for_trx(trx)); LOGP(DL1C, LOGL_NOTICE, "Close TRX %u\n", trx->nr); - return l1if_gsm_req_compl(fl1h, msg, trx_close_compl_cb, NULL); + rc = l1if_gsm_req_compl(fl1h, msg, trx_close_compl_cb, NULL); + if (rc < 0) + bts_model_trx_close_cb(trx, rc); } static int trx_rf_lock(struct gsm_bts_trx *trx, int locked, l1if_compl_cb *cb) @@ -490,7 +527,7 @@ static int ts_connect_as(struct gsm_bts_trx_ts *ts, GsmL1_MphConnectReq_t *cr; if (pchan == GSM_PCHAN_TCH_F_PDCH - || pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH) { + || pchan == GSM_PCHAN_OSMO_DYN) { LOGP(DL1C, LOGL_ERROR, "%s Requested TS connect as %s," " expected a specific pchan instead\n", @@ -510,7 +547,7 @@ static int ts_opstart(struct gsm_bts_trx_ts *ts) { enum gsm_phys_chan_config pchan = ts->pchan; switch (pchan) { - case GSM_PCHAN_TCH_F_TCH_H_PDCH: + case GSM_PCHAN_OSMO_DYN: ts->dyn.pchan_is = ts->dyn.pchan_want = GSM_PCHAN_NONE; /* First connect as NONE, until first RSL CHAN ACT. */ pchan = GSM_PCHAN_NONE; @@ -534,8 +571,7 @@ GsmL1_Sapi_t lchan_to_GsmL1_Sapi_t(const struct gsm_lchan *lchan) case GSM_LCHAN_TCH_H: return GsmL1_Sapi_TchH; default: - LOGP(DL1C, LOGL_NOTICE, "%s cannot determine L1 SAPI\n", - gsm_lchan_name(lchan)); + LOGPLCHAN(lchan, DL1C, LOGL_NOTICE, "cannot determine L1 SAPI\n"); break; } return GsmL1_Sapi_Idle; @@ -545,7 +581,7 @@ GsmL1_SubCh_t lchan_to_GsmL1_SubCh_t(const struct gsm_lchan *lchan) { enum gsm_phys_chan_config pchan = lchan->ts->pchan; - if (pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH) + if (pchan == GSM_PCHAN_OSMO_DYN) pchan = lchan->ts->dyn.pchan_want; switch (pchan) { @@ -564,7 +600,7 @@ GsmL1_SubCh_t lchan_to_GsmL1_SubCh_t(const struct gsm_lchan *lchan) case GSM_PCHAN_PDCH: case GSM_PCHAN_UNKNOWN: default: - /* case GSM_PCHAN_TCH_F_TCH_H_PDCH: is caught above */ + /* case GSM_PCHAN_OSMO_DYN: is caught above */ return GsmL1_SubCh_NA; } @@ -627,10 +663,6 @@ static const struct sapi_dir pdtch_sapis[] = { #endif }; -static const struct sapi_dir ho_sapis[] = { - { GsmL1_Sapi_Rach, GsmL1_Dir_RxUplink }, -}; - struct lchan_sapis { const struct sapi_dir *sapis; unsigned int num_sapis; @@ -663,11 +695,6 @@ static const struct lchan_sapis sapis_for_lchan[_GSM_LCHAN_MAX] = { }, }; -static const struct lchan_sapis sapis_for_ho = { - .sapis = ho_sapis, - .num_sapis = ARRAY_SIZE(ho_sapis), -}; - static int mph_send_activate_req(struct gsm_lchan *lchan, struct sapi_cmd *cmd); static int mph_send_deactivate_req(struct gsm_lchan *lchan, struct sapi_cmd *cmd); static int mph_send_config_ciphering(struct gsm_lchan *lchan, struct sapi_cmd *cmd); @@ -755,12 +782,8 @@ static void sapi_queue_dispatch(struct gsm_lchan *lchan, int status) talloc_free(cmd); if (end || llist_empty(&lchan->sapi_cmds)) { - LOGP(DL1C, LOGL_DEBUG, - "%s End of SAPI cmd queue encountered.%s\n", - gsm_lchan_name(lchan), - llist_empty(&lchan->sapi_cmds) - ? " Queue is now empty." - : " More pending."); + LOGPLCHAN(lchan, DL1C, LOGL_DEBUG, "End of SAPI cmd queue encountered.%s\n", + llist_empty(&lchan->sapi_cmds) ? " Queue is now empty." : " More pending."); return; } @@ -800,9 +823,8 @@ static int lchan_act_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg, goto err; } - LOGP(DL1C, LOGL_INFO, "%s MPH-ACTIVATE.conf (%s ", - gsm_lchan_name(lchan), - get_value_string(lc15bts_l1sapi_names, ic->sapi)); + LOGPLCHAN(lchan, DL1C, LOGL_INFO, "MPH-ACTIVATE.conf (%s ", + get_value_string(lc15bts_l1sapi_names, ic->sapi)); LOGPC(DL1C, LOGL_INFO, "%s)\n", get_value_string(lc15bts_dir_names, ic->dir)); @@ -823,19 +845,15 @@ static int lchan_act_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg, lchan->sapis_ul[ic->sapi] = status; if (llist_empty(&lchan->sapi_cmds)) { - LOGP(DL1C, LOGL_ERROR, - "%s Got activation confirmation with empty queue\n", - gsm_lchan_name(lchan)); + LOGPLCHAN(lchan, DL1C, LOGL_ERROR, "Got activation confirmation with empty queue\n"); goto err; } cmd = llist_entry(lchan->sapi_cmds.next, struct sapi_cmd, entry); if (cmd->sapi != ic->sapi || cmd->dir != ic->dir || cmd->type != SAPI_CMD_ACTIVATE) { - LOGP(DL1C, LOGL_ERROR, - "%s Confirmation mismatch (%d, %d) (%d, %d)\n", - gsm_lchan_name(lchan), cmd->sapi, cmd->dir, - ic->sapi, ic->dir); + LOGPLCHAN(lchan, DL1C, LOGL_ERROR, "Confirmation mismatch (%d, %d) (%d, %d)\n", + cmd->sapi, cmd->dir, ic->sapi, ic->dir); goto err; } @@ -909,15 +927,14 @@ static void set_payload_format(GsmL1_LogChParam_t *lch_par) lch_par->tch.tchPlFmt = GsmL1_TchPlFmt_Rtp; } -static void lchan2lch_par(GsmL1_LogChParam_t *lch_par, struct gsm_lchan *lchan) +static int lchan2lch_par(GsmL1_LogChParam_t *lch_par, struct gsm_lchan *lchan) { struct amr_multirate_conf *amr_mrc = &lchan->tch.amr_mr; struct gsm48_multi_rate_conf *mr_conf = (struct gsm48_multi_rate_conf *) amr_mrc->gsm48_ie; int j; - LOGP(DL1C, LOGL_INFO, "%s: %s tch_mode=0x%02x\n", - gsm_lchan_name(lchan), __FUNCTION__, lchan->tch_mode); + LOGPLCHAN(lchan, DL1C, LOGL_INFO, "%s tch_mode=0x%02x\n", __func__, lchan->tch_mode); switch (lchan->tch_mode) { case GSM48_CMODE_SIGN: @@ -945,7 +962,9 @@ static void lchan2lch_par(GsmL1_LogChParam_t *lch_par, struct gsm_lchan *lchan) case GSM48_CMODE_SPEECH_AMR: lch_par->tch.tchPlType = GsmL1_TchPlType_Amr; set_payload_format(lch_par); - lch_par->tch.amrCmiPhase = GsmL1_AmrCmiPhase_Odd; /* FIXME? */ + /* At call set-up, after every successful handover and after a channel mode modify, the + * default phase (odd) shall be used in downlink direction. */ + lch_par->tch.amrCmiPhase = GsmL1_AmrCmiPhase_Odd; lch_par->tch.amrInitCodecMode = amr_get_initial_mode(lchan); /* initialize to clean state */ @@ -994,10 +1013,13 @@ static void lchan2lch_par(GsmL1_LogChParam_t *lch_par, struct gsm_lchan *lchan) case GSM48_CMODE_DATA_12k0: case GSM48_CMODE_DATA_6k0: case GSM48_CMODE_DATA_3k6: - LOGP(DL1C, LOGL_ERROR, "%s: CSD not supported!\n", - gsm_lchan_name(lchan)); - break; + default: + LOGPLCHAN(lchan, DL1C, LOGL_ERROR, "Channel mode %s is not supported!\n", + gsm48_chan_mode_name(lchan->tch_mode)); + return -ENOTSUP; } + + return 0; } static int mph_send_activate_req(struct gsm_lchan *lchan, struct sapi_cmd *cmd) @@ -1006,6 +1028,7 @@ static int mph_send_activate_req(struct gsm_lchan *lchan, struct sapi_cmd *cmd) struct msgb *msg = l1p_msgb_alloc(); int sapi = cmd->sapi; int dir = cmd->dir; + int rc; GsmL1_MphActivateReq_t *act_req; GsmL1_LogChParam_t *lch_par; @@ -1028,7 +1051,10 @@ static int mph_send_activate_req(struct gsm_lchan *lchan, struct sapi_cmd *cmd) break; case GsmL1_Sapi_TchH: case GsmL1_Sapi_TchF: - lchan2lch_par(lch_par, lchan); + if ((rc = lchan2lch_par(lch_par, lchan)) != 0) { + talloc_free(msg); + return rc; + } /* * Be sure that every packet is received, even if it * fails. In this case the length might be lower or 0. @@ -1061,9 +1087,9 @@ static int mph_send_activate_req(struct gsm_lchan *lchan, struct sapi_cmd *cmd) break; } - LOGP(DL1C, LOGL_INFO, "%s MPH-ACTIVATE.req (hL2=0x%08x, %s ", - gsm_lchan_name(lchan), (uint32_t)act_req->hLayer2, - get_value_string(lc15bts_l1sapi_names, act_req->sapi)); + LOGPLCHAN(lchan, DL1C, LOGL_INFO, "MPH-ACTIVATE.req (hL2=0x%08x, %s ", + (uint32_t)act_req->hLayer2, get_value_string(lc15bts_l1sapi_names, act_req->sapi)); + dump_lch_par(LOGL_INFO, lch_par, act_req->sapi); LOGPC(DL1C, LOGL_INFO, "%s)\n", get_value_string(lc15bts_dir_names, act_req->dir)); @@ -1087,9 +1113,7 @@ static int sapi_activate_cb(struct gsm_lchan *lchan, int status) /* FIXME: Error handling */ if (status != GsmL1_Status_Success) { - LOGP(DL1C, LOGL_ERROR, - "%s act failed mark broken due status: %d\n", - gsm_lchan_name(lchan), status); + LOGPLCHAN(lchan, DL1C, LOGL_ERROR, "act failed mark broken due status: %d\n", status); lchan_set_state(lchan, LCHAN_S_BROKEN); sapi_clear_queue(&lchan->sapi_cmds); mph_info_chan_confirm(lchan, PRIM_INFO_ACTIVATE, RSL_ERR_PROCESSOR_OVERLOAD); @@ -1136,14 +1160,12 @@ int lchan_activate(struct gsm_lchan *lchan) lchan_set_state(lchan, LCHAN_S_ACT_REQ); if (!llist_empty(&lchan->sapi_cmds)) - LOGP(DL1C, LOGL_ERROR, - "%s Trying to activate lchan, but commands in queue\n", - gsm_lchan_name(lchan)); + LOGPLCHAN(lchan, DL1C, LOGL_ERROR, "Trying to activate lchan, but commands in queue\n"); - /* override the regular SAPIs if this is the first hand-over - * related activation of the LCHAN */ - if (lchan->ho.active == HANDOVER_ENABLED) - s4l = &sapis_for_ho; + /* For handover, always start the main channel immediately. lchan->want_dl_sacch_active indicates whether dl + * SACCH should be activated. Also, for HO, start the RACH SAPI. */ + if (lchan->ho.active == HANDOVER_ENABLED || rsl_chan_rt_is_asci(lchan->rsl_chan_rt)) + enqueue_sapi_act_cmd(lchan, GsmL1_Sapi_Rach, GsmL1_Dir_RxUplink); for (i = 0; i < s4l->num_sapis; i++) { int sapi = s4l->sapis[i].sapi; @@ -1156,12 +1178,14 @@ int lchan_activate(struct gsm_lchan *lchan) fl1h->alive_prim_cnt = 0; osmo_timer_schedule(&fl1h->alive_timer, 5, 0); } - enqueue_sapi_act_cmd(lchan, sapi, dir); - } -#warning "FIXME: Should this be in sapi_activate_cb?" - lchan_init_lapdm(lchan); + /* For handover, possibly postpone activating the dl SACCH until the HO RACH is received. */ + if (sapi == GsmL1_Sapi_Sacch && dir == GsmL1_Dir_TxDownlink + && !lchan->want_dl_sacch_active) + continue; + enqueue_sapi_act_cmd(lchan, sapi, dir); + } return 0; } @@ -1170,6 +1194,9 @@ const struct value_string lc15bts_l1cfgt_names[] = { { GsmL1_ConfigParamId_SetTxPowerLevel, "Set Tx power level" }, { GsmL1_ConfigParamId_SetLogChParams, "Set logical channel params" }, { GsmL1_ConfigParamId_SetCipheringParams,"Configure ciphering params" }, +#if LITECELL15_API_VERSION >= LITECELL15_API(2,1,7) + { GsmL1_ConfigParamId_Set8pskPowerReduction, "Set 8PSK Tx power reduction" }, +#endif { 0, NULL } }; @@ -1227,6 +1254,58 @@ static int chmod_txpower_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg, return 0; } +#if LITECELL15_API_VERSION >= LITECELL15_API(2,1,7) +static int chmod_txpower_backoff_8psk_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg, + void *data) +{ + GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg); + GsmL1_MphConfigCnf_t *cc = &l1p->u.mphConfigCnf; + + LOGP(DL1C, LOGL_INFO, "%s MPH-CONFIG.conf (%s) ", + gsm_trx_name(trx), + get_value_string(lc15bts_l1cfgt_names, cc->cfgParamId)); + + LOGPC(DL1C, LOGL_INFO, "Backoff %u dB\n", + cc->cfgParams.set8pskPowerReduction.u8PowerReduction); + + msgb_free(l1_msg); + + return 0; +} + +static int chmod_max_cell_size_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp, + void *data) +{ + Litecell15_Prim_t *sysp = msgb_sysprim(resp); + Litecell15_SetMaxCellSizeCnf_t *sac = &sysp->u.setMaxCellSizeCnf; + + LOGP(DL1C, LOGL_INFO, "%s Rx SYS prim %s -> %s\n", + gsm_trx_name(trx), + get_value_string(lc15bts_sysprim_names, sysp->id), + get_value_string(lc15bts_l1status_names, sac->status)); + + msgb_free(resp); + + return 0; +} + +static int chmod_c0_idle_pwr_red_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp, + void *data) +{ + Litecell15_Prim_t *sysp = msgb_sysprim(resp); + Litecell15_SetC0IdleSlotPowerReductionCnf_t *sac = &sysp->u.setC0IdleSlotPowerReductionCnf; + + LOGP(DL1C, LOGL_INFO, "%s Rx SYS prim %s -> %s\n", + gsm_trx_name(trx), + get_value_string(lc15bts_sysprim_names, sysp->id), + get_value_string(lc15bts_l1status_names, sac->status)); + + msgb_free(resp); + + return 0; +} +#endif + static int chmod_modif_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg, void *data) { @@ -1242,9 +1321,8 @@ static int chmod_modif_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg, goto err; } - LOGP(DL1C, LOGL_INFO, "%s MPH-CONFIG.conf (%s) ", - gsm_lchan_name(lchan), - get_value_string(lc15bts_l1cfgt_names, cc->cfgParamId)); + LOGPLCHAN(lchan, DL1C, LOGL_INFO, "MPH-CONFIG.conf (%s) ", + get_value_string(lc15bts_l1cfgt_names, cc->cfgParamId)); switch (cc->cfgParamId) { case GsmL1_ConfigParamId_SetLogChParams: @@ -1276,9 +1354,7 @@ static int chmod_modif_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg, break; } if (llist_empty(&lchan->sapi_cmds)) { - LOGP(DL1C, LOGL_ERROR, - "%s Got ciphering conf with empty queue\n", - gsm_lchan_name(lchan)); + LOGPLCHAN(lchan, DL1C, LOGL_ERROR, "Got ciphering conf with empty queue\n"); goto err; } @@ -1303,6 +1379,7 @@ static int mph_send_config_logchpar(struct gsm_lchan *lchan, struct sapi_cmd *cm struct msgb *msg = l1p_msgb_alloc(); GsmL1_MphConfigReq_t *conf_req; GsmL1_LogChParam_t *lch_par; + int rc; /* channel mode, encryption and/or multirate have changed */ @@ -1317,7 +1394,10 @@ static int mph_send_config_logchpar(struct gsm_lchan *lchan, struct sapi_cmd *cm conf_req->hLayer3 = (HANDLE)l1if_lchan_to_hLayer(lchan); lch_par = &conf_req->cfgParams.setLogChParams.logChParams; - lchan2lch_par(lch_par, lchan); + if ((rc = lchan2lch_par(lch_par, lchan)) != 0) { + talloc_free(msg); + return rc; + } /* Update the MS Power Level */ if (cmd->sapi == GsmL1_Sapi_Sacch && trx_ms_pwr_ctrl_is_osmo(trx)) @@ -1325,10 +1405,8 @@ static int mph_send_config_logchpar(struct gsm_lchan *lchan, struct sapi_cmd *cm /* FIXME: update encryption */ - LOGP(DL1C, LOGL_INFO, "%s MPH-CONFIG.req (%s) ", - gsm_lchan_name(lchan), - get_value_string(lc15bts_l1sapi_names, - conf_req->cfgParams.setLogChParams.sapi)); + LOGPLCHAN(lchan, DL1C, LOGL_INFO, "MPH-CONFIG.req (%s) ", + get_value_string(lc15bts_l1sapi_names, conf_req->cfgParams.setLogChParams.sapi)); LOGPC(DL1C, LOGL_INFO, "cfgParams Tn=%u, subCh=%u, dir=0x%x ", conf_req->cfgParams.setLogChParams.u8Tn, conf_req->cfgParams.setLogChParams.subCh, @@ -1368,6 +1446,49 @@ int l1if_set_txpower(struct lc15l1_hdl *fl1h, float tx_power) return l1if_gsm_req_compl(fl1h, msg, chmod_txpower_compl_cb, NULL); } +#if LITECELL15_API_VERSION >= LITECELL15_API(2,1,7) +int l1if_set_txpower_backoff_8psk(struct lc15l1_hdl *fl1h, uint8_t backoff) +{ + struct msgb *msg = l1p_msgb_alloc(); + GsmL1_MphConfigReq_t *conf_req; + + conf_req = prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphConfigReq, fl1h, 0); + conf_req->cfgParamId = GsmL1_ConfigParamId_Set8pskPowerReduction; + conf_req->cfgParams.set8pskPowerReduction.u8PowerReduction = backoff; + + return l1if_gsm_req_compl(fl1h, msg, chmod_txpower_backoff_8psk_compl_cb, NULL); +} + +int l1if_set_max_cell_size(struct lc15l1_hdl *fl1h, uint8_t cell_size) +{ + struct msgb *msg = sysp_msgb_alloc(); + Litecell15_Prim_t *sys_prim = msgb_sysprim(msg); + sys_prim->id = Litecell15_PrimId_SetMaxCellSizeReq; + sys_prim->u.setMaxCellSizeReq.u8MaxCellSize = cell_size; + + LOGP(DL1C, LOGL_INFO, "%s Set max cell size = %d qbits\n", + gsm_trx_name(fl1h->phy_inst->trx), + cell_size); + + return l1if_req_compl(fl1h, msg, chmod_max_cell_size_compl_cb, NULL); + +} + +int l1if_set_txpower_c0_idle_pwr_red(struct lc15l1_hdl *fl1h, uint8_t red) +{ + struct msgb *msg = sysp_msgb_alloc(); + Litecell15_Prim_t *sys_prim = msgb_sysprim(msg); + sys_prim->id = Litecell15_PrimId_SetC0IdleSlotPowerReductionReq; + sys_prim->u.setC0IdleSlotPowerReductionReq.u8PowerReduction = red; + + LOGP(DL1C, LOGL_INFO, "%s Set C0 idle slot power reduction = %d dB\n", + gsm_trx_name(fl1h->phy_inst->trx), + red); + + return l1if_req_compl(fl1h, msg, chmod_c0_idle_pwr_red_compl_cb, NULL); +} +#endif + const enum GsmL1_CipherId_t rsl2l1_ciph[] = { [0] = GsmL1_CipherId_A50, [1] = GsmL1_CipherId_A50, @@ -1395,11 +1516,9 @@ static int mph_send_config_ciphering(struct gsm_lchan *lchan, struct sapi_cmd *c return -EINVAL; cfgr->cfgParams.setCipheringParams.cipherId = rsl2l1_ciph[lchan->encr.alg_id]; - LOGP(DL1C, LOGL_NOTICE, "%s SET_CIPHERING (ALG=%u %s)\n", - gsm_lchan_name(lchan), + LOGPLCHAN(lchan, DL1C, LOGL_NOTICE, "SET_CIPHERING (ALG=%u %s)\n", cfgr->cfgParams.setCipheringParams.cipherId, - get_value_string(lc15bts_dir_names, - cfgr->cfgParams.setCipheringParams.dir)); + get_value_string(lc15bts_dir_names, cfgr->cfgParams.setCipheringParams.dir)); memcpy(cfgr->cfgParams.setCipheringParams.u8Kc, lchan->encr.key, lchan->encr.key_len); @@ -1477,9 +1596,8 @@ static int lchan_deact_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg, goto err; } - LOGP(DL1C, LOGL_INFO, "%s MPH-DEACTIVATE.conf (%s ", - gsm_lchan_name(lchan), - get_value_string(lc15bts_l1sapi_names, ic->sapi)); + LOGPLCHAN(lchan, DL1C, LOGL_INFO, "MPH-DEACTIVATE.conf (%s ", + get_value_string(lc15bts_l1sapi_names, ic->sapi)); LOGPC(DL1C, LOGL_INFO, "%s)\n", get_value_string(lc15bts_dir_names, ic->dir)); @@ -1501,19 +1619,15 @@ static int lchan_deact_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg, if (llist_empty(&lchan->sapi_cmds)) { - LOGP(DL1C, LOGL_ERROR, - "%s Got de-activation confirmation with empty queue\n", - gsm_lchan_name(lchan)); + LOGPLCHAN(lchan, DL1C, LOGL_ERROR, "Got de-activation confirmation with empty queue\n"); goto err; } cmd = llist_entry(lchan->sapi_cmds.next, struct sapi_cmd, entry); if (cmd->sapi != ic->sapi || cmd->dir != ic->dir || cmd->type != SAPI_CMD_DEACTIVATE) { - LOGP(DL1C, LOGL_ERROR, - "%s Confirmation mismatch (%d, %d) (%d, %d)\n", - gsm_lchan_name(lchan), cmd->sapi, cmd->dir, - ic->sapi, ic->dir); + LOGPLCHAN(lchan, DL1C, LOGL_ERROR, "Confirmation mismatch (%d, %d) (%d, %d)\n", + cmd->sapi, cmd->dir, ic->sapi, ic->dir); goto err; } @@ -1538,9 +1652,8 @@ static int mph_send_deactivate_req(struct gsm_lchan *lchan, struct sapi_cmd *cmd deact_req->sapi = cmd->sapi; deact_req->hLayer3 = (HANDLE)l1if_lchan_to_hLayer(lchan); - LOGP(DL1C, LOGL_INFO, "%s MPH-DEACTIVATE.req (%s ", - gsm_lchan_name(lchan), - get_value_string(lc15bts_l1sapi_names, deact_req->sapi)); + LOGPLCHAN(lchan, DL1C, LOGL_INFO, "MPH-DEACTIVATE.req (%s ", + get_value_string(lc15bts_l1sapi_names, deact_req->sapi)); LOGPC(DL1C, LOGL_INFO, "%s)\n", get_value_string(lc15bts_dir_names, deact_req->dir)); @@ -1552,8 +1665,7 @@ static int sapi_deactivate_cb(struct gsm_lchan *lchan, int status) { /* FIXME: Error handling. There is no NACK... */ if (status != GsmL1_Status_Success && lchan->state == LCHAN_S_REL_REQ) { - LOGP(DL1C, LOGL_ERROR, "%s is now broken. Stopping the release.\n", - gsm_lchan_name(lchan)); + LOGPLCHAN(lchan, DL1C, LOGL_ERROR, "is now broken. Stopping the release.\n"); lchan_set_state(lchan, LCHAN_S_BROKEN); sapi_clear_queue(&lchan->sapi_cmds); mph_info_chan_confirm(lchan, PRIM_INFO_DEACTIVATE, 0); @@ -1610,17 +1722,9 @@ static int check_sapi_release(struct gsm_lchan *lchan, int sapi, int dir) return enqueue_sapi_deact_cmd(lchan, sapi, dir); } -static int release_sapis_for_ho(struct gsm_lchan *lchan) +static int release_sapi_ul_rach(struct gsm_lchan *lchan) { - int res = 0; - int i; - - const struct lchan_sapis *s4l = &sapis_for_ho; - - for (i = s4l->num_sapis-1; i >= 0; i--) - res |= check_sapi_release(lchan, - s4l->sapis[i].sapi, s4l->sapis[i].dir); - return res; + return check_sapi_release(lchan, GsmL1_Sapi_Rach, GsmL1_Dir_RxUplink); } static int lchan_deactivate_sapis(struct gsm_lchan *lchan) @@ -1642,12 +1746,11 @@ static int lchan_deactivate_sapis(struct gsm_lchan *lchan) } /* always attempt to disable the RACH burst */ - res |= release_sapis_for_ho(lchan); + res |= release_sapi_ul_rach(lchan); /* nothing was queued */ if (res == 0) { - LOGP(DL1C, LOGL_ERROR, "%s all SAPIs already released?\n", - gsm_lchan_name(lchan)); + LOGPLCHAN(lchan, DL1C, LOGL_ERROR, "all SAPIs already released?\n"); lchan_set_state(lchan, LCHAN_S_BROKEN); mph_info_chan_confirm(lchan, PRIM_INFO_DEACTIVATE, 0); } @@ -1694,67 +1797,87 @@ int bts_model_check_oml(struct gsm_bts *bts, uint8_t msg_type, void *obj) { /* FIXME: more checks if the attributes are valid */ - - switch (msg_type) { - case NM_MT_SET_CHAN_ATTR: - /* our L1 only supports one global TSC for all channels - * one one TRX, so we need to make sure not to activate - * channels with a different TSC!! */ - if (TLVP_PRES_LEN(new_attr, NM_ATT_TSC, 1) && - *TLVP_VAL(new_attr, NM_ATT_TSC) != (bts->bsic & 7)) { - LOGP(DOML, LOGL_ERROR, "Channel TSC %u != BSIC-TSC %u\n", - *TLVP_VAL(new_attr, NM_ATT_TSC), bts->bsic & 7); - return -NM_NACK_PARAM_RANGE; - } - break; - } return 0; } /* callback from OML */ -int bts_model_apply_oml(struct gsm_bts *bts, struct msgb *msg, - struct tlv_parsed *new_attr, int kind, void *obj) -{ - if (kind == NM_OC_RADIO_CARRIER) { - struct gsm_bts_trx *trx = obj; - struct lc15l1_hdl *fl1h = trx_lc15l1_hdl(trx); +int bts_model_apply_oml(struct gsm_bts *bts, const struct msgb *msg, + struct gsm_abis_mo *mo, void *obj) +{ + struct abis_om_fom_hdr *foh = msgb_l3(msg); + struct gsm_bts_trx *trx; + struct lc15l1_hdl *fl1h; + + switch (foh->msg_type) { + case NM_MT_SET_RADIO_ATTR: + trx = obj; + fl1h = trx_lc15l1_hdl(trx); + +#if LITECELL15_API_VERSION >= LITECELL15_API(2,1,7) + { + /* convert max TA to max cell size in qbits */ + uint8_t cell_size = bts->max_ta << 2; + /* We do not need to check for L1 handle + * because the max cell size parameter can receive before MphInit */ + if (fl1h->phy_inst->u.lc15.max_cell_size != cell_size) { + /* instruct L1 to apply max cell size */ + l1if_set_max_cell_size(fl1h, cell_size); + /* update current max cell size */ + fl1h->phy_inst->u.lc15.max_cell_size = cell_size; + } + } +#endif /* Did we go through MphInit yet? If yes fire and forget */ - if (fl1h->hLayer1) - power_ramp_start(trx, get_p_target_mdBm(trx, 0), 0); + if (fl1h->hLayer1) { + power_ramp_start(trx, get_p_target_mdBm(trx, 0), 0, NULL); +#if LITECELL15_API_VERSION >= LITECELL15_API(2,1,7) + if (fl1h->phy_inst->u.lc15.tx_pwr_red_8psk != trx->max_power_backoff_8psk) { + /* update current Tx power backoff for 8-PSK */ + fl1h->phy_inst->u.lc15.tx_pwr_red_8psk = trx->max_power_backoff_8psk; + /* instruct L1 to apply Tx power backoff for 8 PSK */ + l1if_set_txpower_backoff_8psk(fl1h, fl1h->phy_inst->u.lc15.tx_pwr_red_8psk); + } + + if (fl1h->phy_inst->u.lc15.tx_c0_idle_pwr_red != trx->c0_idle_power_red) { + /* update current C0 idle slot Tx power reduction */ + fl1h->phy_inst->u.lc15.tx_c0_idle_pwr_red = trx->c0_idle_power_red; + /* instruct L1 to apply C0 idle slot power reduction */ + l1if_set_txpower_c0_idle_pwr_red(fl1h, fl1h->phy_inst->u.lc15.tx_c0_idle_pwr_red); + } +#endif + } + break; } - /* FIXME: we actaully need to send a ACK or NACK for the OML message */ - return oml_fom_ack_nack(msg, 0); + return 0; } /* callback from OML */ int bts_model_opstart(struct gsm_bts *bts, struct gsm_abis_mo *mo, void *obj) { + struct gsm_bts_bb_trx *bb_transc; + struct gsm_bts_trx *trx; + struct gsm_bts_trx_ts *ts; int rc; switch (mo->obj_class) { - case NM_OC_RADIO_CARRIER: - rc = trx_init(obj); - break; - case NM_OC_CHANNEL: - rc = ts_opstart(obj); - break; - case NM_OC_BTS: case NM_OC_SITE_MANAGER: + case NM_OC_BTS: case NM_OC_BASEB_TRANSC: case NM_OC_GPRS_NSE: case NM_OC_GPRS_CELL: case NM_OC_GPRS_NSVC: - oml_mo_state_chg(mo, NM_OPSTATE_ENABLED, -1); - rc = oml_mo_opstart_ack(mo); - if (mo->obj_class == NM_OC_BTS) { - oml_mo_state_chg(&bts->mo, -1, NM_AVSTATE_OK); - oml_mo_state_chg(&bts->gprs.nse.mo, -1, NM_AVSTATE_OK); - oml_mo_state_chg(&bts->gprs.cell.mo, -1, NM_AVSTATE_OK); - oml_mo_state_chg(&bts->gprs.nsvc[0].mo, -1, NM_AVSTATE_OK); - } + rc = osmo_fsm_inst_dispatch(mo->fi, NM_EV_OPSTART_ACK, NULL); + break; + case NM_OC_RADIO_CARRIER: + trx = (struct gsm_bts_trx *) obj; + rc = trx_init(trx); + break; + case NM_OC_CHANNEL: + ts = (struct gsm_bts_trx_ts*) obj; + rc = ts_opstart(ts); break; default: rc = oml_mo_opstart_nack(mo, NM_NACK_OBJCLASS_NOTSUPP); @@ -1825,24 +1948,17 @@ int l1if_rsl_chan_act(struct gsm_lchan *lchan) */ int l1if_rsl_chan_mod(struct gsm_lchan *lchan) { - const struct lchan_sapis *s4l = &sapis_for_lchan[lchan->type]; - unsigned int i; - if (lchan->ho.active == HANDOVER_NONE) return -1; - LOGP(DHO, LOGL_ERROR, "%s modifying channel for handover\n", - gsm_lchan_name(lchan)); + LOGPLCHAN(lchan, DHO, LOGL_ERROR, "modifying channel for handover\n"); /* Give up listening to RACH bursts */ - release_sapis_for_ho(lchan); + release_sapi_ul_rach(lchan); - /* Activate the normal SAPIs */ - for (i = 0; i < s4l->num_sapis; i++) { - int sapi = s4l->sapis[i].sapi; - int dir = s4l->sapis[i].dir; - enqueue_sapi_act_cmd(lchan, sapi, dir); - } + /* All the normal SAPIs have already been activated, only DL SACCH may still be missing. */ + if (lchan->want_dl_sacch_active) + enqueue_sapi_act_cmd(lchan, GsmL1_Sapi_Sacch, GsmL1_Dir_TxDownlink); return 0; } @@ -1851,8 +1967,7 @@ int l1if_rsl_chan_rel(struct gsm_lchan *lchan) { /* A duplicate RF Release Request, ignore it */ if (lchan->state == LCHAN_S_REL_REQ) { - LOGP(DL1C, LOGL_ERROR, "%s already in release request state.\n", - gsm_lchan_name(lchan)); + LOGPLCHAN(lchan, DL1C, LOGL_ERROR, "already in release request state.\n"); return 0; } @@ -1888,11 +2003,12 @@ static int ts_disconnect_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg, struct gsm_bts_trx_ts *ts = &trx->ts[cnf->u8Tn]; OSMO_ASSERT(cnf->u8Tn < TRX_NR_TS); - LOGP(DL1C, LOGL_DEBUG, "%s Rx mphDisconnectCnf\n", - gsm_lchan_name(ts->lchan)); + LOGPLCHAN(ts->lchan, DL1C, LOGL_DEBUG, "Rx mphDisconnectCnf\n"); cb_ts_disconnected(ts); + msgb_free(l1_msg); + return 0; } @@ -1902,7 +2018,7 @@ int bts_model_ts_disconnect(struct gsm_bts_trx_ts *ts) struct lc15l1_hdl *fl1h = trx_lc15l1_hdl(ts->trx); GsmL1_MphDisconnectReq_t *cr; - DEBUGP(DRSL, "%s TS disconnect\n", gsm_lchan_name(ts->lchan)); + LOGPLCHAN(ts->lchan, DRSL, LOGL_DEBUG, "TS disconnect\n"); cr = prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphDisconnectReq, fl1h, l1p_handle_for_ts(ts)); cr->u8Tn = ts->nr; @@ -1918,8 +2034,7 @@ static int ts_connect_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg, struct gsm_bts_trx_ts *ts = &trx->ts[cnf->u8Tn]; OSMO_ASSERT(cnf->u8Tn < TRX_NR_TS); - DEBUGP(DL1C, "%s %s Rx mphConnectCnf flags=%s%s%s\n", - gsm_lchan_name(ts->lchan), + LOGPLCHAN(ts->lchan, DL1C, LOGL_DEBUG, "%s Rx mphConnectCnf flags=%s%s%s\n", gsm_pchan_name(ts->pchan), ts->flags & TS_F_PDCH_ACTIVE ? "ACTIVE " : "", ts->flags & TS_F_PDCH_ACT_PENDING ? "ACT_PENDING " : "", @@ -1927,6 +2042,8 @@ static int ts_connect_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg, cb_ts_connected(ts, 0); + msgb_free(l1_msg); + return 0; } diff --git a/src/osmo-bts-litecell15/tch.c b/src/osmo-bts-lc15/tch.c index 5eae7538..afd5b53f 100644 --- a/src/osmo-bts-litecell15/tch.c +++ b/src/osmo-bts-lc15/tch.c @@ -15,7 +15,7 @@ * 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. + * 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/>. @@ -68,7 +68,7 @@ static struct msgb *l1_to_rtppayload_fr(uint8_t *l1_payload, uint8_t payload_len cur = msgb_put(msg, GSM_FR_BYTES); memcpy(cur, l1_payload, GSM_FR_BYTES); - lchan_set_marker(osmo_fr_check_sid(l1_payload, payload_len), lchan); + lchan_set_marker(osmo_fr_is_any_sid(l1_payload), lchan); return msg; } @@ -101,12 +101,8 @@ static struct msgb *l1_to_rtppayload_efr(uint8_t *l1_payload, /* new L1 can deliver bits like we need them */ cur = msgb_put(msg, GSM_EFR_BYTES); memcpy(cur, l1_payload, GSM_EFR_BYTES); - enum osmo_amr_type ft; - enum osmo_amr_quality bfi; - uint8_t cmr; - int8_t sti, cmi; - osmo_amr_rtp_dec(l1_payload, payload_len, &cmr, &cmi, &ft, &bfi, &sti); - lchan_set_marker(ft == AMR_GSM_EFR_SID, lchan); + + lchan_set_marker(osmo_efr_is_any_sid(l1_payload), lchan); return msg; } @@ -259,7 +255,10 @@ int l1if_tch_encode(struct gsm_lchan *lchan, uint8_t *data, uint8_t *len, *payload_type = GsmL1_TchPlType_Efr; rc = rtppayload_to_l1_efr(l1_payload, rtp_pl, rtp_pl_len); - /* FIXME: detect and save EFR SID */ + if (rc && lchan->ts->trx->bts->dtxd) + is_sid = osmo_efr_check_sid(rtp_pl, rtp_pl_len); + if (is_sid) + dtx_cache_payload(lchan, rtp_pl, rtp_pl_len, fn, -1); break; case GSM48_CMODE_SPEECH_AMR: if (use_cache) { @@ -360,7 +359,7 @@ int l1if_tch_rx(struct gsm_bts_trx *trx, uint8_t chan_nr, struct msgb *l1p_msg) { GsmL1_Prim_t *l1p = msgb_l1prim(l1p_msg); GsmL1_PhDataInd_t *data_ind = &l1p->u.phDataInd; - uint8_t *payload, payload_type, payload_len, sid_first[9] = { 0 }; + uint8_t *payload, payload_type, payload_len; struct msgb *rmsg = NULL; struct gsm_lchan *lchan = &trx->ts[L1SAP_CHAN2TS(chan_nr)].lchan[l1sap_chan2ss(chan_nr)]; @@ -368,12 +367,12 @@ int l1if_tch_rx(struct gsm_bts_trx *trx, uint8_t chan_nr, struct msgb *l1p_msg) return -EAGAIN; if (data_ind->msgUnitParam.u8Size < 1) { - LOGPFN(DL1P, LOGL_DEBUG, data_ind->u32Fn, "chan_nr %d Rx Payload size 0\n", chan_nr); + LOGPLCFN(lchan, data_ind->u32Fn, DL1P, LOGL_DEBUG, "chan_nr %d Rx Payload size 0\n", chan_nr); /* Push empty payload to upper layers */ rmsg = msgb_alloc_headroom(256, 128, "L1P-to-RTP"); return add_l1sap_header(trx, rmsg, lchan, chan_nr, data_ind->u32Fn, data_ind->measParam.fBer * 10000, - data_ind->measParam.fLinkQuality * 10); + data_ind->measParam.fLinkQuality * 10, 0, 0, 0); } payload_type = data_ind->msgUnitParam.u8Buffer[0]; @@ -399,6 +398,8 @@ int l1if_tch_rx(struct gsm_bts_trx *trx, uint8_t chan_nr, struct msgb *l1p_msg) if (lchan->type != GSM_LCHAN_TCH_H && lchan->type != GSM_LCHAN_TCH_F) goto err_payload_match; + LOGPLCFN(lchan, data_ind->u32Fn, DL1P, LOGL_DEBUG, "DTX: received ONSET from L1 " "(%d bytes)\n", + payload_len); /* according to 3GPP TS 26.093 ONSET frames precede the first speech frame of a speech burst - set the marker for next RTP frame */ @@ -407,33 +408,32 @@ int l1if_tch_rx(struct gsm_bts_trx *trx, uint8_t chan_nr, struct msgb *l1p_msg) case GsmL1_TchPlType_Amr_SidFirstP1: if (lchan->type != GSM_LCHAN_TCH_H) goto err_payload_match; - LOGPFN(DL1P, LOGL_DEBUG, data_ind->u32Fn, "DTX: received SID_FIRST_P1 from L1 " - "(%d bytes)\n", payload_len); + LOGPLCFN(lchan, data_ind->u32Fn, DL1P, LOGL_DEBUG, "DTX: received SID_FIRST_P1 from L1 " + "(%d bytes)\n", payload_len); break; case GsmL1_TchPlType_Amr_SidFirstP2: if (lchan->type != GSM_LCHAN_TCH_H) goto err_payload_match; - LOGPFN(DL1P, LOGL_DEBUG, data_ind->u32Fn, "DTX: received SID_FIRST_P2 from L1 " - "(%d bytes)\n", payload_len); + LOGPLCFN(lchan, data_ind->u32Fn, DL1P, LOGL_DEBUG, "DTX: received SID_FIRST_P2 from L1 " + "(%d bytes)\n", payload_len); break; case GsmL1_TchPlType_Amr_SidFirstInH: if (lchan->type != GSM_LCHAN_TCH_H) goto err_payload_match; lchan->rtp_tx_marker = true; - LOGPFN(DL1P, LOGL_DEBUG, data_ind->u32Fn, "DTX: received SID_FIRST_INH from L1 " - "(%d bytes)\n", payload_len); + LOGPLCFN(lchan, data_ind->u32Fn, DL1P, LOGL_DEBUG, "DTX: received SID_FIRST_INH from L1 " + "(%d bytes)\n", payload_len); break; case GsmL1_TchPlType_Amr_SidUpdateInH: if (lchan->type != GSM_LCHAN_TCH_H) goto err_payload_match; lchan->rtp_tx_marker = true; - LOGPFN(DL1P, LOGL_DEBUG, data_ind->u32Fn, "DTX: received SID_UPDATE_INH from L1 " - "(%d bytes)\n", payload_len); + LOGPLCFN(lchan, data_ind->u32Fn, DL1P, LOGL_DEBUG, "DTX: received SID_UPDATE_INH from L1 " + "(%d bytes)\n", payload_len); break; default: - LOGPFN(DL1P, LOGL_NOTICE, data_ind->u32Fn, "%s Rx Payload Type %s is unsupported\n", - gsm_lchan_name(lchan), - get_value_string(lc15bts_tch_pl_names, payload_type)); + LOGPLCFN(lchan, data_ind->u32Fn, DL1P, LOGL_NOTICE, "%s Rx Payload Type %s is unsupported\n", + gsm_lchan_name(lchan), get_value_string(lc15bts_tch_pl_names, payload_type)); break; } @@ -449,27 +449,21 @@ int l1if_tch_rx(struct gsm_bts_trx *trx, uint8_t chan_nr, struct msgb *l1p_msg) rmsg = l1_to_rtppayload_efr(payload, payload_len, lchan); break; case GsmL1_TchPlType_Amr: - rmsg = l1_to_rtppayload_amr(payload, payload_len, lchan); - break; case GsmL1_TchPlType_Amr_SidFirstP1: - memcpy(sid_first, payload, payload_len); - int len = osmo_amr_rtp_enc(sid_first, 0, AMR_SID, AMR_GOOD); - if (len < 0) - return 0; - rmsg = l1_to_rtppayload_amr(sid_first, len, lchan); + rmsg = l1_to_rtppayload_amr(payload, payload_len, lchan); break; } if (rmsg) return add_l1sap_header(trx, rmsg, lchan, chan_nr, data_ind->u32Fn, data_ind->measParam.fBer * 10000, - data_ind->measParam.fLinkQuality * 10); + data_ind->measParam.fLinkQuality * 10, 0, 0, 0); return 0; err_payload_match: - LOGPFN(DL1P, LOGL_ERROR, data_ind->u32Fn, "%s Rx Payload Type %s incompatible with lchan\n", - gsm_lchan_name(lchan), get_value_string(lc15bts_tch_pl_names, payload_type)); + LOGPLCFN(lchan, data_ind->u32Fn, DL1P, LOGL_ERROR, "%s Rx Payload Type %s incompatible with lchan\n", + gsm_lchan_name(lchan), get_value_string(lc15bts_tch_pl_names, payload_type)); return -EINVAL; } diff --git a/src/osmo-bts-litecell15/utils.c b/src/osmo-bts-lc15/utils.c index 8d980ba8..b1906d12 100644 --- a/src/osmo-bts-litecell15/utils.c +++ b/src/osmo-bts-lc15/utils.c @@ -16,7 +16,7 @@ * 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. + * 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-bts-litecell15/utils.h b/src/osmo-bts-lc15/utils.h index a2a22348..a2a22348 100644 --- a/src/osmo-bts-litecell15/utils.h +++ b/src/osmo-bts-lc15/utils.h diff --git a/src/osmo-bts-litecell15/Makefile.am b/src/osmo-bts-litecell15/Makefile.am deleted file mode 100644 index 0cc124ab..00000000 --- a/src/osmo-bts-litecell15/Makefile.am +++ /dev/null @@ -1,38 +0,0 @@ -AUTOMAKE_OPTIONS = subdir-objects - -AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include $(LITECELL15_INCDIR) -AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOCODEC_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(LIBOSMOTRAU_CFLAGS) $(LIBOSMOABIS_CFLAGS) $(LIBOSMOCTRL_CFLAGS) $(LIBOSMOABIS_CFLAGS) $(LIBGPS_CFLAGS) $(LIBSYSTEMD_CFLAGS) -COMMON_LDADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOCODEC_LIBS) $(LIBOSMOGSM_LIBS) $(LIBOSMOVTY_LIBS) $(LIBOSMOTRAU_LIBS) $(LIBOSMOABIS_LIBS) $(LIBOSMOCTRL_LIBS) - -AM_CFLAGS += -DENABLE_LC15BTS - -EXTRA_DIST = misc/lc15bts_mgr.h misc/lc15bts_misc.h misc/lc15bts_par.h misc/lc15bts_led.h \ - misc/lc15bts_temp.h misc/lc15bts_power.h misc/lc15bts_clock.h \ - misc/lc15bts_bid.h misc/lc15bts_nl.h misc/lc15bts_bts.h misc/lc15bts_swd.h \ - hw_misc.h l1_if.h l1_transp.h lc15bts.h oml_router.h utils.h - -bin_PROGRAMS = osmo-bts-lc15 lc15bts-mgr lc15bts-util - -COMMON_SOURCES = main.c lc15bts.c l1_if.c oml.c lc15bts_vty.c tch.c hw_misc.c calib_file.c \ - utils.c misc/lc15bts_par.c misc/lc15bts_bid.c oml_router.c - -osmo_bts_lc15_SOURCES = $(COMMON_SOURCES) l1_transp_hw.c -osmo_bts_lc15_LDADD = $(top_builddir)/src/common/libbts.a $(COMMON_LDADD) - -lc15bts_mgr_SOURCES = \ - misc/lc15bts_mgr.c misc/lc15bts_misc.c \ - misc/lc15bts_par.c misc/lc15bts_nl.c \ - misc/lc15bts_temp.c misc/lc15bts_power.c \ - misc/lc15bts_clock.c misc/lc15bts_bid.c \ - misc/lc15bts_mgr_vty.c \ - misc/lc15bts_mgr_nl.c \ - misc/lc15bts_mgr_temp.c \ - misc/lc15bts_mgr_calib.c \ - misc/lc15bts_led.c \ - misc/lc15bts_bts.c \ - misc/lc15bts_swd.c - -lc15bts_mgr_LDADD = $(top_builddir)/src/common/libbts.a $(LIBGPS_LIBS) $(LIBOSMOCORE_LIBS) $(LIBOSMOVTY_LIBS) $(LIBOSMOABIS_LIBS) $(LIBOSMOGSM_LIBS) $(LIBOSMOCTRL_LIBS) $(LIBSYSTEMD_LIBS) $(COMMON_LDADD) - -lc15bts_util_SOURCES = misc/lc15bts_util.c misc/lc15bts_par.c -lc15bts_util_LDADD = $(LIBOSMOCORE_LIBS) diff --git a/src/osmo-bts-litecell15/lc15bts.h b/src/osmo-bts-litecell15/lc15bts.h deleted file mode 100644 index 4c40db0f..00000000 --- a/src/osmo-bts-litecell15/lc15bts.h +++ /dev/null @@ -1,64 +0,0 @@ -#ifndef LC15BTS_H -#define LC15BTS_H - -#include <stdlib.h> -#include <osmocom/core/utils.h> - -#include <nrw/litecell15/litecell15.h> -#include <nrw/litecell15/gsml1const.h> - -/* - * Depending on the firmware version either GsmL1_Prim_t or Litecell15_Prim_t - * is the bigger struct. For earlier firmware versions the GsmL1_Prim_t was the - * bigger struct. - */ -#define LC15BTS_PRIM_SIZE \ - (OSMO_MAX(sizeof(Litecell15_Prim_t), sizeof(GsmL1_Prim_t)) + 128) - -enum l1prim_type { - L1P_T_INVALID, /* this must be 0 to detect uninitialized elements */ - L1P_T_REQ, - L1P_T_CONF, - L1P_T_IND, -}; - -enum l1prim_type lc15bts_get_l1prim_type(GsmL1_PrimId_t id); -const struct value_string lc15bts_l1prim_names[GsmL1_PrimId_NUM+1]; -GsmL1_PrimId_t lc15bts_get_l1prim_conf(GsmL1_PrimId_t id); - -enum l1prim_type lc15bts_get_sysprim_type(Litecell15_PrimId_t id); -const struct value_string lc15bts_sysprim_names[Litecell15_PrimId_NUM+1]; -Litecell15_PrimId_t lc15bts_get_sysprim_conf(Litecell15_PrimId_t id); - -const struct value_string lc15bts_l1sapi_names[GsmL1_Sapi_NUM+1]; -const struct value_string lc15bts_l1status_names[GSML1_STATUS_NUM+1]; - -const struct value_string lc15bts_tracef_names[29]; -const struct value_string lc15bts_tracef_docs[29]; - -const struct value_string lc15bts_tch_pl_names[15]; - -const struct value_string lc15bts_clksrc_names[10]; - -const struct value_string lc15bts_dir_names[6]; - -enum pdch_cs { - PDCH_CS_1, - PDCH_CS_2, - PDCH_CS_3, - PDCH_CS_4, - PDCH_MCS_1, - PDCH_MCS_2, - PDCH_MCS_3, - PDCH_MCS_4, - PDCH_MCS_5, - PDCH_MCS_6, - PDCH_MCS_7, - PDCH_MCS_8, - PDCH_MCS_9, - _NUM_PDCH_CS -}; - -const uint8_t pdch_msu_size[_NUM_PDCH_CS]; - -#endif /* LC15BTS_H */ diff --git a/src/osmo-bts-litecell15/oml_router.c b/src/osmo-bts-litecell15/oml_router.c deleted file mode 100644 index 198d5e30..00000000 --- a/src/osmo-bts-litecell15/oml_router.c +++ /dev/null @@ -1,132 +0,0 @@ -/* Beginnings of an OML router */ - -/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com> - * - * Based on sysmoBTS: - * (C) 2014 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 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 "oml_router.h" - -#include <osmo-bts/bts.h> -#include <osmo-bts/logging.h> -#include <osmo-bts/oml.h> -#include <osmo-bts/msg_utils.h> - -#include <osmocom/core/socket.h> -#include <osmocom/core/select.h> - -#include <errno.h> -#include <string.h> -#include <unistd.h> - -static int oml_router_read_cb(struct osmo_fd *fd, unsigned int what) -{ - struct msgb *msg; - int rc; - - msg = oml_msgb_alloc(); - if (!msg) { - LOGP(DL1C, LOGL_ERROR, "Failed to allocate oml msgb.\n"); - return -1; - } - - rc = recv(fd->fd, msg->tail, msg->data_len, 0); - if (rc <= 0) { - close(fd->fd); - osmo_fd_unregister(fd); - fd->fd = -1; - goto err; - } - - msg->l1h = msgb_put(msg, rc); - rc = msg_verify_ipa_structure(msg); - if (rc < 0) { - LOGP(DL1C, LOGL_ERROR, - "OML Router: Invalid IPA message rc(%d)\n", rc); - goto err; - } - - rc = msg_verify_oml_structure(msg); - if (rc < 0) { - LOGP(DL1C, LOGL_ERROR, - "OML Router: Invalid OML message rc(%d)\n", rc); - goto err; - } - - /* todo dispatch message */ - -err: - msgb_free(msg); - return -1; -} - -static int oml_router_accept_cb(struct osmo_fd *accept_fd, unsigned int what) -{ - int fd; - struct osmo_fd *read_fd = (struct osmo_fd *) accept_fd->data; - - /* Accept only one connection at a time. De-register it */ - if (read_fd->fd > -1) { - LOGP(DL1C, LOGL_NOTICE, - "New OML router connection. Closing old one.\n"); - close(read_fd->fd); - osmo_fd_unregister(read_fd); - read_fd->fd = -1; - } - - fd = accept(accept_fd->fd, NULL, NULL); - if (fd < 0) { - LOGP(DL1C, LOGL_ERROR, "Failed to accept. errno: %s.\n", - strerror(errno)); - return -1; - } - - read_fd->fd = fd; - if (osmo_fd_register(read_fd) != 0) { - LOGP(DL1C, LOGL_ERROR, "Registering the read fd failed.\n"); - close(fd); - read_fd->fd = -1; - return -1; - } - - return 0; -} - -int oml_router_init(struct gsm_bts *bts, const char *path, - struct osmo_fd *accept_fd, struct osmo_fd *read_fd) -{ - int rc; - - memset(accept_fd, 0, sizeof(*accept_fd)); - memset(read_fd, 0, sizeof(*read_fd)); - - accept_fd->cb = oml_router_accept_cb; - accept_fd->data = read_fd; - - read_fd->cb = oml_router_read_cb; - read_fd->data = bts; - read_fd->when = BSC_FD_READ; - read_fd->fd = -1; - - rc = osmo_sock_unix_init_ofd(accept_fd, SOCK_SEQPACKET, 0, - path, - OSMO_SOCK_F_BIND | OSMO_SOCK_F_NONBLOCK); - return rc; -} diff --git a/src/osmo-bts-litecell15/oml_router.h b/src/osmo-bts-litecell15/oml_router.h deleted file mode 100644 index 8c08baaa..00000000 --- a/src/osmo-bts-litecell15/oml_router.h +++ /dev/null @@ -1,13 +0,0 @@ -#pragma once - -struct gsm_bts; -struct osmo_fd; - -/** - * The default path lc15bts will listen for incoming - * registrations for OML routing and sending. - */ -#define OML_ROUTER_PATH "/var/run/lc15bts_oml_router" - - -int oml_router_init(struct gsm_bts *bts, const char *path, struct osmo_fd *accept, struct osmo_fd *read); diff --git a/src/osmo-bts-oc2g/Makefile.am b/src/osmo-bts-oc2g/Makefile.am index 54a8afab..29374efd 100644 --- a/src/osmo-bts-oc2g/Makefile.am +++ b/src/osmo-bts-oc2g/Makefile.am @@ -1,38 +1,104 @@ -AUTOMAKE_OPTIONS = subdir-objects +AUTOMAKE_OPTIONS = subdir-objects AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include $(OC2G_INCDIR) -AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOCODEC_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(LIBOSMOTRAU_CFLAGS) $(LIBOSMOABIS_CFLAGS) $(LIBOSMOCTRL_CFLAGS) $(LIBOSMOABIS_CFLAGS) $(LIBGPS_CFLAGS) $(ORTP_CFLAGS) $(LIBSYSTEMD_CFLAGS) -COMMON_LDADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOCODEC_LIBS) $(LIBOSMOGSM_LIBS) $(LIBOSMOVTY_LIBS) $(LIBOSMOTRAU_LIBS) $(LIBOSMOABIS_LIBS) $(LIBOSMOCTRL_LIBS) $(ORTP_LIBS) + +AM_CFLAGS = \ + -Wall \ + $(LIBOSMOCORE_CFLAGS) \ + $(LIBOSMOCODEC_CFLAGS) \ + $(LIBOSMOGSM_CFLAGS) \ + $(LIBOSMOVTY_CFLAGS) \ + $(LIBOSMOCTRL_CFLAGS) \ + $(LIBOSMOABIS_CFLAGS) \ + $(LIBOSMOTRAU_CFLAGS) \ + $(LIBOSMONETIF_CFLAGS) \ + $(LIBGPS_CFLAGS) \ + $(LIBSYSTEMD_CFLAGS) \ + $(ORTP_CFLAGS) \ + $(NULL) + +COMMON_LDADD = \ + $(LIBOSMOCORE_LIBS) \ + $(LIBOSMOCODEC_LIBS) \ + $(LIBOSMOGSM_LIBS) \ + $(LIBOSMOVTY_LIBS) \ + $(LIBOSMOCTRL_LIBS) \ + $(LIBOSMOABIS_LIBS) \ + $(LIBOSMOTRAU_LIBS) \ + $(LIBOSMONETIF_LIBS) \ + $(ORTP_LIBS) \ + $(NULL) AM_CFLAGS += -DENABLE_OC2GBTS -EXTRA_DIST = misc/oc2gbts_mgr.h misc/oc2gbts_misc.h misc/oc2gbts_par.h misc/oc2gbts_led.h \ - misc/oc2gbts_temp.h misc/oc2gbts_power.h misc/oc2gbts_clock.h \ - misc/oc2gbts_bid.h misc/oc2gbts_bts.h misc/oc2gbts_nl.h misc/oc2gbts_swd.h \ - hw_misc.h l1_if.h l1_transp.h oc2gbts.h oml_router.h utils.h +EXTRA_DIST = \ + misc/oc2gbts_mgr.h \ + misc/oc2gbts_misc.h \ + misc/oc2gbts_par.h \ + misc/oc2gbts_led.h \ + misc/oc2gbts_temp.h \ + misc/oc2gbts_power.h \ + misc/oc2gbts_clock.h \ + misc/oc2gbts_bid.h \ + misc/oc2gbts_bts.h \ + misc/oc2gbts_nl.h \ + misc/oc2gbts_swd.h \ + hw_misc.h \ + l1_if.h \ + l1_transp.h \ + oc2gbts.h \ + utils.h \ + $(NULL) bin_PROGRAMS = osmo-bts-oc2g oc2gbts-mgr oc2gbts-util -COMMON_SOURCES = main.c oc2gbts.c l1_if.c oml.c oc2gbts_vty.c tch.c hw_misc.c calib_file.c \ - utils.c misc/oc2gbts_par.c misc/oc2gbts_bid.c oml_router.c +COMMON_SOURCES = \ + main.c \ + oc2gbts.c \ + l1_if.c \ + oml.c \ + oc2gbts_vty.c \ + tch.c \ + hw_misc.c \ + calib_file.c \ + utils.c \ + misc/oc2gbts_par.c \ + misc/oc2gbts_bid.c \ + $(NULL) osmo_bts_oc2g_SOURCES = $(COMMON_SOURCES) l1_transp_hw.c -osmo_bts_oc2g_LDADD = $(top_builddir)/src/common/libbts.a $(COMMON_LDADD) +osmo_bts_oc2g_LDADD = \ + $(top_builddir)/src/common/libbts.a \ + $(COMMON_LDADD) \ + $(NULL) oc2gbts_mgr_SOURCES = \ - misc/oc2gbts_mgr.c misc/oc2gbts_misc.c \ - misc/oc2gbts_par.c misc/oc2gbts_nl.c \ - misc/oc2gbts_temp.c misc/oc2gbts_power.c \ - misc/oc2gbts_clock.c misc/oc2gbts_bid.c \ + misc/oc2gbts_mgr.c \ + misc/oc2gbts_misc.c \ + misc/oc2gbts_par.c \ + misc/oc2gbts_nl.c \ + misc/oc2gbts_temp.c \ + misc/oc2gbts_power.c \ + misc/oc2gbts_clock.c \ + misc/oc2gbts_bid.c \ misc/oc2gbts_mgr_vty.c \ misc/oc2gbts_mgr_nl.c \ misc/oc2gbts_mgr_temp.c \ misc/oc2gbts_mgr_calib.c \ misc/oc2gbts_led.c \ misc/oc2gbts_bts.c \ - misc/oc2gbts_swd.c + misc/oc2gbts_swd.c \ + $(NULL) -oc2gbts_mgr_LDADD = $(top_builddir)/src/common/libbts.a $(LIBGPS_LIBS) $(LIBOSMOCORE_LIBS) $(LIBOSMOVTY_LIBS) $(LIBOSMOABIS_LIBS) $(LIBOSMOGSM_LIBS) $(LIBOSMOCTRL_LIBS) $(LIBSYSTEMD_LIBS) $(COMMON_LDADD) +oc2gbts_mgr_LDADD = \ + $(top_builddir)/src/common/libbts.a \ + $(COMMON_LDADD) \ + $(LIBGPS_LIBS) \ + $(LIBSYSTEMD_LIBS) \ + $(NULL) -oc2gbts_util_SOURCES = misc/oc2gbts_util.c misc/oc2gbts_par.c +oc2gbts_util_SOURCES = \ + misc/oc2gbts_util.c \ + misc/oc2gbts_par.c \ + $(NULL) oc2gbts_util_LDADD = $(LIBOSMOCORE_LIBS) diff --git a/src/osmo-bts-oc2g/calib_file.c b/src/osmo-bts-oc2g/calib_file.c index 6d2d5610..df15ee99 100644 --- a/src/osmo-bts-oc2g/calib_file.c +++ b/src/osmo-bts-oc2g/calib_file.c @@ -16,7 +16,7 @@ * 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. + * 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/>. @@ -307,7 +307,7 @@ static int calib_verify(struct oc2gl1_hdl *fl1h, const struct calib_file_desc *d fseek(st->fp, 0L, SEEK_END); sz = ftell(st->fp); - /* rewind read poiner */ + /* rewind read pointer */ fseek(st->fp, 0L, SEEK_SET); /* read file */ diff --git a/src/osmo-bts-oc2g/hw_misc.c b/src/osmo-bts-oc2g/hw_misc.c index 31daf078..d886effd 100644 --- a/src/osmo-bts-oc2g/hw_misc.c +++ b/src/osmo-bts-oc2g/hw_misc.c @@ -15,7 +15,7 @@ * 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. + * 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-bts-oc2g/l1_if.c b/src/osmo-bts-oc2g/l1_if.c index d987bb52..d43f31b7 100644 --- a/src/osmo-bts-oc2g/l1_if.c +++ b/src/osmo-bts-oc2g/l1_if.c @@ -17,7 +17,7 @@ * 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. + * 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/>. @@ -37,6 +37,7 @@ #include <osmocom/core/select.h> #include <osmocom/core/timer.h> #include <osmocom/core/write_queue.h> +#include <osmocom/core/fsm.h> #include <osmocom/gsm/gsm_utils.h> #include <osmocom/gsm/lapdm.h> @@ -55,6 +56,7 @@ #include <osmo-bts/msg_utils.h> #include <osmo-bts/dtx_dl_amr_fsm.h> #include <osmo-bts/cbch.h> +#include <osmo-bts/nm_common_fsm.h> #include <nrw/oc2g/oc2g.h> #include <nrw/oc2g/gsml1prim.h> @@ -390,7 +392,7 @@ static int ph_data_req(struct gsm_bts_trx *trx, struct msgb *msg, abort(); } - len = msgb_l2len(msg); + len = (msg->l2h) ? msgb_l2len(msg) : 0; chan_nr = l1sap->u.data.chan_nr; link_id = l1sap->u.data.link_id; @@ -438,9 +440,8 @@ static int ph_data_req(struct gsm_bts_trx *trx, struct msgb *msg, else sapi = GsmL1_Sapi_Agch; } else { - LOGPFN(DL1C, LOGL_NOTICE, u32Fn, "unknown prim %d op %d " - "chan_nr %d link_id %d\n", l1sap->oph.primitive, - l1sap->oph.operation, chan_nr, link_id); + LOGPLCFN(lchan, u32Fn, DL1C, LOGL_NOTICE, "unknown prim %d op %d " "chan_nr %d link_id %d\n", + l1sap->oph.primitive, l1sap->oph.operation, chan_nr, link_id); msgb_free(l1msg); return -EINVAL; } @@ -501,9 +502,8 @@ static int ph_data_req(struct gsm_bts_trx *trx, struct msgb *msg, memcpy(l1p->u.phDataReq.msgUnitParam.u8Buffer, msg->l2h, msgb_l2len(msg)); } - LOGPFN(DL1P, LOGL_DEBUG, u32Fn, "PH-DATA.req(%s)\n", - osmo_hexdump(l1p->u.phDataReq.msgUnitParam.u8Buffer, - l1p->u.phDataReq.msgUnitParam.u8Size)); + LOGPLCFN(lchan, u32Fn, DL1P, LOGL_DEBUG, "PH-DATA.req(%s)\n", + osmo_hexdump(l1p->u.phDataReq.msgUnitParam.u8Buffer, l1p->u.phDataReq.msgUnitParam.u8Size)); } else { GsmL1_Prim_t *l1p = msgb_l1prim(l1msg); if (lchan->rsl_cmode == RSL_CMOD_SPD_SIGN) @@ -521,7 +521,7 @@ static int ph_data_req(struct gsm_bts_trx *trx, struct msgb *msg, /* send message to DSP's queue */ if (osmo_wqueue_enqueue(&fl1->write_q[MQ_L1_WRITE], l1msg) != 0) { - LOGPFN(DL1P, LOGL_ERROR, u32Fn, "MQ_L1_WRITE queue full. Dropping msg.\n"); + LOGPLCFN(lchan, u32Fn, DL1P, LOGL_ERROR, "MQ_L1_WRITE queue full. Dropping msg.\n"); msgb_free(l1msg); } else dtx_int_signal(lchan); @@ -559,7 +559,6 @@ static int ph_tch_req(struct gsm_bts_trx *trx, struct msgb *msg, /* create new message and fill data */ if (msg) { - msgb_pull(msg, sizeof(*l1sap)); /* create new message */ nmsg = l1p_msgb_alloc(); if (!nmsg) @@ -568,7 +567,7 @@ static int ph_tch_req(struct gsm_bts_trx *trx, struct msgb *msg, rc = l1if_tch_encode(lchan, l1p->u.phDataReq.msgUnitParam.u8Buffer, &l1p->u.phDataReq.msgUnitParam.u8Size, - msg->data, msg->len, u32Fn, use_cache, + msgb_l2(msg), msgb_l2len(msg), u32Fn, use_cache, l1sap->u.tch.marker); if (rc < 0) { /* no data encoded for L1: smth will be generated below */ @@ -604,7 +603,11 @@ static int ph_tch_req(struct gsm_bts_trx *trx, struct msgb *msg, empty_req_from_l1sap(l1p, fl1, u8Tn, u32Fn, sapi, subCh, u8BlockNbr); } /* send message to DSP's queue */ - osmo_wqueue_enqueue(&fl1->write_q[MQ_L1_WRITE], nmsg); + if (osmo_wqueue_enqueue(&fl1->write_q[MQ_L1_WRITE], nmsg) < 0) { + LOGPLCFN(lchan, u32Fn, DL1P, LOGL_ERROR, "MQ_L1_WRITE queue full. Dropping msg.\n"); + msgb_free(nmsg); + return -ENOBUFS; + } if (dtx_is_first_p1(lchan)) dtx_dispatch(lchan, E_FIRST); else @@ -733,7 +736,7 @@ static enum gsm_phys_chan_config pick_pchan(struct gsm_bts_trx_ts *ts) if (ts->flags & TS_F_PDCH_ACTIVE) return GSM_PCHAN_PDCH; return GSM_PCHAN_TCH_F; - case GSM_PCHAN_TCH_F_TCH_H_PDCH: + case GSM_PCHAN_OSMO_DYN: return ts->dyn.pchan_is; default: return ts->pchan; @@ -747,7 +750,7 @@ static uint8_t chan_nr_by_sapi(struct gsm_bts_trx_ts *ts, uint8_t cbits = 0; enum gsm_phys_chan_config pchan = pick_pchan(ts); OSMO_ASSERT(pchan != GSM_PCHAN_TCH_F_PDCH); - OSMO_ASSERT(pchan != GSM_PCHAN_TCH_F_TCH_H_PDCH); + OSMO_ASSERT(pchan != GSM_PCHAN_OSMO_DYN); switch (sapi) { case GsmL1_Sapi_Bcch: @@ -847,6 +850,45 @@ static uint8_t chan_nr_by_sapi(struct gsm_bts_trx_ts *ts, return (cbits << 3) | u8Tn; } +static const enum l1sap_common_sapi common_sapi_by_sapi_t[] = { + [GsmL1_Sapi_Idle] = L1SAP_COMMON_SAPI_IDLE, + [GsmL1_Sapi_Fcch] = L1SAP_COMMON_SAPI_FCCH, + [GsmL1_Sapi_Sch] = L1SAP_COMMON_SAPI_SCH, + [GsmL1_Sapi_Sacch] = L1SAP_COMMON_SAPI_SACCH, + [GsmL1_Sapi_Sdcch] = L1SAP_COMMON_SAPI_SDCCH, + [GsmL1_Sapi_Bcch] = L1SAP_COMMON_SAPI_BCCH, + [GsmL1_Sapi_Pch] = L1SAP_COMMON_SAPI_PCH, + [GsmL1_Sapi_Agch] = L1SAP_COMMON_SAPI_AGCH, + [GsmL1_Sapi_Cbch] = L1SAP_COMMON_SAPI_CBCH, + [GsmL1_Sapi_Rach] = L1SAP_COMMON_SAPI_RACH, + [GsmL1_Sapi_TchF] = L1SAP_COMMON_SAPI_TCH_F, + [GsmL1_Sapi_FacchF] = L1SAP_COMMON_SAPI_FACCH_F, + [GsmL1_Sapi_TchH] = L1SAP_COMMON_SAPI_TCH_H, + [GsmL1_Sapi_FacchH] = L1SAP_COMMON_SAPI_FACCH_H, + [GsmL1_Sapi_Nch] = L1SAP_COMMON_SAPI_NCH, + [GsmL1_Sapi_Pdtch] = L1SAP_COMMON_SAPI_PDTCH, + [GsmL1_Sapi_Pacch] = L1SAP_COMMON_SAPI_PACCH, + [GsmL1_Sapi_Pbcch] = L1SAP_COMMON_SAPI_PBCCH, + [GsmL1_Sapi_Pagch] = L1SAP_COMMON_SAPI_PAGCH, + [GsmL1_Sapi_Ppch] = L1SAP_COMMON_SAPI_PPCH, + [GsmL1_Sapi_Pnch] = L1SAP_COMMON_SAPI_PNCH, + [GsmL1_Sapi_Ptcch] = L1SAP_COMMON_SAPI_PTCCH, + [GsmL1_Sapi_Prach] = L1SAP_COMMON_SAPI_PRACH, +}; + +static enum l1sap_common_sapi get_common_sapi(GsmL1_Sapi_t sapi) +{ + if (sapi >= GsmL1_Sapi_NUM) + return L1SAP_COMMON_SAPI_UNKNOWN; + return common_sapi_by_sapi_t[sapi]; +} + +static void set_log_ctx_sapi(GsmL1_Sapi_t sapi) +{ + l1sap_log_ctx_sapi = get_common_sapi(sapi); + log_set_context(LOG_CTX_L1_SAPI, &l1sap_log_ctx_sapi); +} + static int handle_ph_readytosend_ind(struct oc2gl1_hdl *fl1, GsmL1_PhReadyToSendInd_t *rts_ind, struct msgb *l1p_msg) @@ -863,6 +905,8 @@ static int handle_ph_readytosend_ind(struct oc2gl1_hdl *fl1, uint8_t chan_nr, link_id; uint32_t fn; + set_log_ctx_sapi(rts_ind->sapi); + /* check if primitive should be handled by common part */ chan_nr = chan_nr_by_sapi(&trx->ts[rts_ind->u8Tn], rts_ind->sapi, rts_ind->subCh, rts_ind->u8Tn, rts_ind->u32Fn); @@ -951,12 +995,9 @@ empty_frame: goto tx; } -static void dump_meas_res(int ll, GsmL1_MeasParam_t *m) -{ - LOGPC(DL1C, ll, ", Meas: RSSI %-3.2f dBm, Qual %-3.2f dB, " - "BER %-3.2f, Timing %d\n", m->fRssi, m->fLinkQuality, - m->fBer, m->i16BurstTiming); -} + +#define LOG_FMT_MEAS "Meas: RSSI %-3.2f dBm, Qual %-3.2f dB, BER %-3.2f, Timing %d" +#define LOG_PARAM_MEAS(meas_param) (meas_param)->fRssi, (meas_param)->fLinkQuality, (meas_param)->fBer, (meas_param)->i16BurstTiming static int process_meas_res(struct gsm_bts_trx *trx, uint8_t chan_nr, GsmL1_MeasParam_t *m, uint32_t fn) @@ -989,6 +1030,8 @@ static int handle_ph_data_ind(struct oc2gl1_hdl *fl1, GsmL1_PhDataInd_t *data_in int rc = 0; int8_t rssi; + set_log_ctx_sapi(data_ind->sapi); + chan_nr = chan_nr_by_sapi(&trx->ts[data_ind->u8Tn], data_ind->sapi, data_ind->subCh, data_ind->u8Tn, data_ind->u32Fn); fn = data_ind->u32Fn; @@ -1005,10 +1048,10 @@ static int handle_ph_data_ind(struct oc2gl1_hdl *fl1, GsmL1_PhDataInd_t *data_in process_meas_res(trx, chan_nr, &data_ind->measParam, fn); - DEBUGPGT(DL1P, &g_time, "Rx PH-DATA.ind %s (hL2 %08x): %s\n", + DEBUGPGT(DL1P, &g_time, "Rx PH-DATA.ind %s (hL2 %08x): %s, " LOG_FMT_MEAS "\n", get_value_string(oc2gbts_l1sapi_names, data_ind->sapi), (uint32_t)data_ind->hLayer2, - osmo_hexdump(data_ind->msgUnitParam.u8Buffer, data_ind->msgUnitParam.u8Size)); - dump_meas_res(LOGL_DEBUG, &data_ind->measParam); + osmo_hexdump(data_ind->msgUnitParam.u8Buffer, data_ind->msgUnitParam.u8Size), + LOG_PARAM_MEAS(&data_ind->measParam)); /* check for TCH */ if (data_ind->sapi == GsmL1_Sapi_TchF @@ -1041,11 +1084,10 @@ static int handle_ph_data_ind(struct oc2gl1_hdl *fl1, GsmL1_PhDataInd_t *data_in l1sap->u.data.chan_nr = chan_nr; l1sap->u.data.fn = fn; l1sap->u.data.rssi = rssi; - if (!pcu_direct) { - l1sap->u.data.ber10k = data_ind->measParam.fBer * 10000; - l1sap->u.data.ta_offs_256bits = data_ind->measParam.i16BurstTiming*64; - l1sap->u.data.lqual_cb = data_ind->measParam.fLinkQuality * 10; - } + l1sap->u.data.ber10k = data_ind->measParam.fBer * 10000; + l1sap->u.data.ta_offs_256bits = data_ind->measParam.i16BurstTiming*64; + l1sap->u.data.lqual_cb = data_ind->measParam.fLinkQuality * 10; + return l1sap_up(trx, l1sap); } @@ -1058,7 +1100,9 @@ static int handle_ph_ra_ind(struct oc2gl1_hdl *fl1, GsmL1_PhRaInd_t *ra_ind, int rc; struct ph_rach_ind_param rach_ind_param; - dump_meas_res(LOGL_DEBUG, &ra_ind->measParam); + set_log_ctx_sapi(ra_ind->sapi); + LOGPFN(DL1C, LOGL_DEBUG, ra_ind->u32Fn, "Rx PH-RA.ind, " LOG_FMT_MEAS "\n", + LOG_PARAM_MEAS(&ra_ind->measParam)); if ((ra_ind->msgUnitParam.u8Size != 1) && (ra_ind->msgUnitParam.u8Size != 2)) { @@ -1283,17 +1327,12 @@ static int activate_rf_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp, bts_update_status(BTS_STATUS_RF_ACTIVE, 1); /* signal availability */ - oml_mo_state_chg(&trx->mo, NM_OPSTATE_DISABLED, NM_AVSTATE_OK); - oml_mo_tx_sw_act_rep(&trx->mo); - oml_mo_state_chg(&trx->bb_transc.mo, -1, NM_AVSTATE_OK); - oml_mo_tx_sw_act_rep(&trx->bb_transc.mo); - - for (i = 0; i < ARRAY_SIZE(trx->ts); i++) - oml_mo_state_chg(&trx->ts[i].mo, NM_OPSTATE_DISABLED, NM_AVSTATE_DEPENDENCY); + osmo_fsm_inst_dispatch(trx->mo.fi, NM_EV_SW_ACT, NULL); + osmo_fsm_inst_dispatch(trx->bb_transc.mo.fi, NM_EV_SW_ACT, NULL); } else { bts_update_status(BTS_STATUS_RF_ACTIVE, 0); - oml_mo_state_chg(&trx->mo, NM_OPSTATE_DISABLED, NM_AVSTATE_OFF_LINE); - oml_mo_state_chg(&trx->bb_transc.mo, NM_OPSTATE_DISABLED, NM_AVSTATE_OFF_LINE); + osmo_fsm_inst_dispatch(trx->mo.fi, NM_EV_DISABLE, NULL); + osmo_fsm_inst_dispatch(trx->bb_transc.mo.fi, NM_EV_DISABLE, NULL); } msgb_free(resp); @@ -1436,6 +1475,30 @@ static int info_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp, LOGP(DL1C, LOGL_FATAL, "BTS band %s not supported by hw\n", gsm_band_name(trx->bts->band)); + /* Frequency bands indicated to the BSC */ + switch (fl1h->hw_info.band_support) { + case GSM_BAND_450: + trx->support.freq_bands |= NM_IPAC_F_FREQ_BAND_450; + break; + case GSM_BAND_480: + trx->support.freq_bands |= NM_IPAC_F_FREQ_BAND_480; + break; + case GSM_BAND_850: + trx->support.freq_bands |= NM_IPAC_F_FREQ_BAND_850; + break; + case GSM_BAND_900: + trx->support.freq_bands |= NM_IPAC_F_FREQ_BAND_PGSM; + /* XXX: does GSM_BAND_900 include NM_IPAC_F_FREQ_BAND_EGSM? */ + /* XXX: does GSM_BAND_900 include NM_IPAC_F_FREQ_BAND_RGSM? */ + break; + case GSM_BAND_1800: + trx->support.freq_bands |= NM_IPAC_F_FREQ_BAND_DCS; + break; + case GSM_BAND_1900: + trx->support.freq_bands |= NM_IPAC_F_FREQ_BAND_PCS; + break; + } + /* Request the activation */ l1if_activate_rf(fl1h, 1); @@ -1489,7 +1552,7 @@ static int reset_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp, return 0; } -/* FIXME: This delays the TRX initalization by 5 sec in order to avoid the +/* FIXME: This delays the TRX initialization by 5 sec in order to avoid the * occurrence of a race condition in the OML bringup. This a work around and * should be fixed properly. See also OS#3782, OS#2470 and OS#2469 */ void l1if_reset_cb(void *arg) @@ -1617,7 +1680,7 @@ int l1if_close(struct oc2gl1_hdl *fl1h) /* TODO(oramadan) MERGE */ #ifdef MERGE_ME -static void dsp_alive_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp, void *data) +static int dsp_alive_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp, void *data) { Oc2g_Prim_t *sysp = msgb_sysprim(resp); Oc2g_IsAliveCnf_t *sac = &sysp->u.isAliveCnf; @@ -1628,9 +1691,10 @@ static void dsp_alive_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp, void get_value_string(oc2gbts_sysprim_names, sysp->id), sac->status, trx->nr); msgb_free(resp); + return 0; } -static int dsp_alive_timer_cb(void *data) +static void dsp_alive_timer_cb(void *data) { struct oc2gl1_hdl *fl1h = data; struct gsm_bts_trx *trx = fl1h->phy_inst->trx; @@ -1665,7 +1729,7 @@ static int dsp_alive_timer_cb(void *data) /* allocate new list of sent alarms */ alarm_sent = talloc_zero(fl1h, struct oml_alarm_list); if (!alarm_sent) - return -EIO; + return; alarm_sent->alarm_signal = S_NM_OML_BTS_DSP_ALIVE_ALARM; /* add alarm to sent list */ @@ -1681,14 +1745,14 @@ static int dsp_alive_timer_cb(void *data) rc = l1if_req_compl(fl1h, msg, dsp_alive_compl_cb, NULL); if (rc < 0) { LOGP(DL1C, LOGL_FATAL, "Failed to send %s primitive\n", get_value_string(oc2gbts_sysprim_names, sys_prim->id)); - return -EIO; + return; } /* restart timer */ fl1h->hw_alive.dsp_alive_cnt = 0; osmo_timer_schedule(&fl1h->hw_alive.dsp_alive_timer, fl1h->hw_alive.dsp_alive_period, 0); - return 0; + return; } #endif @@ -1752,8 +1816,7 @@ int bts_model_phy_link_open(struct phy_link *plink) } / * initialize DSP heart beat alive timer * / - fl1h->hw_alive.dsp_alive_timer.cb = dsp_alive_timer_cb; - fl1h->hw_alive.dsp_alive_timer.data = fl1h; + osmo_timer_setup(&fl1h->hw_alive.dsp_alive_timer, dsp_alive_timer_cb, fl1h); fl1h->hw_alive.dsp_alive_cnt = 0; fl1h->hw_alive.dsp_alive_period = pinst->u.oc2g.dsp_alive_period; osmo_timer_schedule(&fl1h->hw_alive.dsp_alive_timer, fl1h->hw_alive.dsp_alive_period, 0); */ diff --git a/src/osmo-bts-oc2g/l1_if.h b/src/osmo-bts-oc2g/l1_if.h index 38699e01..8e8a2edb 100644 --- a/src/osmo-bts-oc2g/l1_if.h +++ b/src/osmo-bts-oc2g/l1_if.h @@ -30,6 +30,13 @@ enum { _NUM_MQ_WRITE }; +/* gsm_bts->model_priv, specific to Open Cellular 2G BTS */ +struct bts_oc2g_priv { + uint8_t led_ctrl_mode; /* 0: control by BTS, 1: not control by BTS */ + struct llist_head ceased_alarm_list; /* ceased alarm list*/ + unsigned int rtp_drift_thres_ms; /* RTP timestamp drift detection threshold */ +}; + struct calib_send_state { FILE *fp; const char *path; @@ -129,9 +136,9 @@ int bts_check_for_ciph_cmd(struct oc2gl1_hdl *fl1h, int l1if_ms_pwr_ctrl(struct gsm_lchan *lchan, const int uplink_target, const uint8_t ms_power, const float rxLevel); -static inline struct oc2gl1_hdl *trx_oc2gl1_hdl(struct gsm_bts_trx *trx) +static inline struct oc2gl1_hdl *trx_oc2gl1_hdl(const struct gsm_bts_trx *trx) { - struct phy_instance *pinst = trx_phy_instance(trx); + const struct phy_instance *pinst = trx_phy_instance(trx); OSMO_ASSERT(pinst); return pinst->u.oc2g.hdl; } diff --git a/src/osmo-bts-oc2g/l1_transp_hw.c b/src/osmo-bts-oc2g/l1_transp_hw.c index e1d46581..5ffd6568 100644 --- a/src/osmo-bts-oc2g/l1_transp_hw.c +++ b/src/osmo-bts-oc2g/l1_transp_hw.c @@ -15,7 +15,7 @@ * 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. + * 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/>. @@ -88,18 +88,18 @@ static int wqueue_vector_cb(struct osmo_fd *fd, unsigned int what) queue = container_of(fd, struct osmo_wqueue, bfd); - if (what & BSC_FD_READ) + if (what & OSMO_FD_READ) queue->read_cb(fd); - if (what & BSC_FD_EXCEPT) + if (what & OSMO_FD_EXCEPT) queue->except_cb(fd); - if (what & BSC_FD_WRITE) { + if (what & OSMO_FD_WRITE) { struct iovec iov[5]; struct msgb *msg, *tmp; int written, count = 0; - fd->when &= ~BSC_FD_WRITE; + osmo_fd_write_disable(fd); llist_for_each_entry(msg, &queue->msg_queue, list) { /* more writes than we have */ @@ -117,7 +117,7 @@ static int wqueue_vector_cb(struct osmo_fd *fd, unsigned int what) /* Nothing scheduled? This should not happen. */ if (count == 0) { if (!llist_empty(&queue->msg_queue)) - fd->when |= BSC_FD_WRITE; + osmo_fd_write_enable(fd); return 0; } @@ -125,7 +125,7 @@ static int wqueue_vector_cb(struct osmo_fd *fd, unsigned int what) if (written < 0) { /* nothing written?! */ if (!llist_empty(&queue->msg_queue)) - fd->when |= BSC_FD_WRITE; + osmo_fd_write_enable(fd); return 0; } @@ -144,7 +144,7 @@ static int wqueue_vector_cb(struct osmo_fd *fd, unsigned int what) } if (!llist_empty(&queue->msg_queue)) - fd->when |= BSC_FD_WRITE; + osmo_fd_write_enable(fd); } return 0; @@ -265,11 +265,7 @@ int l1if_transport_open(int q, struct oc2gl1_hdl *hdl) buf, strerror(errno)); return rc; } - read_ofd->fd = rc; - read_ofd->priv_nr = q; - read_ofd->data = hdl; - read_ofd->cb = l1if_fd_cb; - read_ofd->when = BSC_FD_READ; + osmo_fd_setup(read_ofd, rc, OSMO_FD_READ, l1if_fd_cb, hdl, q); rc = osmo_fd_register(read_ofd); if (rc < 0) { close(read_ofd->fd); @@ -288,11 +284,7 @@ int l1if_transport_open(int q, struct oc2gl1_hdl *hdl) } osmo_wqueue_init(wq, 10); wq->write_cb = l1fd_write_cb; - write_ofd->cb = wqueue_vector_cb; - write_ofd->fd = rc; - write_ofd->priv_nr = q; - write_ofd->data = hdl; - write_ofd->when = BSC_FD_WRITE; + osmo_fd_setup(write_ofd, rc, OSMO_FD_WRITE, wqueue_vector_cb, hdl, q); rc = osmo_fd_register(write_ofd); if (rc < 0) { close(write_ofd->fd); diff --git a/src/osmo-bts-oc2g/main.c b/src/osmo-bts-oc2g/main.c index 5b66c6f3..75ad3149 100644 --- a/src/osmo-bts-oc2g/main.c +++ b/src/osmo-bts-oc2g/main.c @@ -2,7 +2,7 @@ /* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com> * Copyright (C) 2016 by Harald Welte <laforge@gnumonks.org> - * + * * Based on sysmoBTS: * (C) 2011-2013 by Harald Welte <laforge@gnumonks.org> * @@ -16,7 +16,7 @@ * 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. + * 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/>. @@ -76,7 +76,6 @@ static int write_status_file(char *status_file, char *status_str) #include "utils.h" #include "l1_if.h" #include "hw_misc.h" -#include "oml_router.h" #include "misc/oc2gbts_bid.h" unsigned int dsp_trace = 0x00000000; @@ -84,48 +83,63 @@ unsigned int dsp_trace = 0x00000000; int bts_model_init(struct gsm_bts *bts) { struct stat st; - static struct osmo_fd accept_fd, read_fd; - int rc; + struct bts_oc2g_priv *bts_oc2g = talloc(bts, struct bts_oc2g_priv); + bts->model_priv = bts_oc2g; bts->variant = BTS_OSMO_OC2G; bts->support.ciphers = CIPHER_A5(1) | CIPHER_A5(2) | CIPHER_A5(3); + bts->gprs.cell.support.gprs_codings = NM_IPAC_MASK_GPRS_CODING_CS + | NM_IPAC_MASK_GPRS_CODING_MCS; /* specific default values for OC2G platform */ /* TODO(oramadan) MERGE - bts->oc2g.led_ctrl_mode = OC2G_BTS_LED_CTRL_MODE_DEFAULT; + bts_oc2g->led_ctrl_mode = OC2G_BTS_LED_CTRL_MODE_DEFAULT; */ /* RTP drift threshold default */ - /* bts->oc2g.rtp_drift_thres_ms = OC2G_BTS_RTP_DRIFT_THRES_DEFAULT; */ - - rc = oml_router_init(bts, OML_ROUTER_PATH, &accept_fd, &read_fd); - if (rc < 0) { - fprintf(stderr, "Error creating the OML router: %s rc=%d\n", - OML_ROUTER_PATH, rc); - exit(1); - } + /* bts_oc2g->rtp_drift_thres_ms = OC2G_BTS_RTP_DRIFT_THRES_DEFAULT; */ if (stat(OC2GBTS_RF_LOCK_PATH, &st) == 0) { LOGP(DL1C, LOGL_NOTICE, "Not starting BTS due to RF_LOCK file present\n"); exit(23); } - gsm_bts_set_feature(bts, BTS_FEAT_GPRS); - gsm_bts_set_feature(bts, BTS_FEAT_EGPRS); - gsm_bts_set_feature(bts, BTS_FEAT_OML_ALERTS); - gsm_bts_set_feature(bts, BTS_FEAT_AGCH_PCH_PROP); - gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_F_V1); - gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_H_V1); - gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_F_EFR); - gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_F_AMR); - gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_H_AMR); - - bts_model_vty_init(bts); + /* order alphabetically */ + osmo_bts_set_feature(bts->features, BTS_FEAT_AGCH_PCH_PROP); + osmo_bts_set_feature(bts->features, BTS_FEAT_CBCH); + osmo_bts_set_feature(bts->features, BTS_FEAT_EGPRS); + osmo_bts_set_feature(bts->features, BTS_FEAT_GPRS); + osmo_bts_set_feature(bts->features, BTS_FEAT_OML_ALERTS); + osmo_bts_set_feature(bts->features, BTS_FEAT_SPEECH_F_AMR); + osmo_bts_set_feature(bts->features, BTS_FEAT_SPEECH_F_EFR); + osmo_bts_set_feature(bts->features, BTS_FEAT_SPEECH_F_V1); + osmo_bts_set_feature(bts->features, BTS_FEAT_SPEECH_H_AMR); + osmo_bts_set_feature(bts->features, BTS_FEAT_SPEECH_H_V1); + + bts_internal_flag_set(bts, BTS_INTERNAL_FLAG_MS_PWR_CTRL_DSP); + bts_internal_flag_set(bts, BTS_INTERNAL_FLAG_NM_RCHANNEL_DEPENDS_RCARRIER); + + /* The default HR codec output format in the absence of saved + * vty config needs to match what was implemented previously, + * for the sake of existing deployments, i.e., to avoid + * a surprise functional change upon software update. */ + bts->emit_hr_rfc5993 = false; return 0; } int bts_model_trx_init(struct gsm_bts_trx *trx) { + /* Frequency bands indicated to the BSC */ + trx->support.freq_bands = 0x00; /* updated in info_compl_cb() */ + + /* Channel types and modes indicated to the BSC */ + trx->support.chan_types = NM_IPAC_MASK_CHANT_COMMON + | NM_IPAC_F_CHANT_BCCH_SDCCH4_CBCH + | NM_IPAC_F_CHANT_SDCCH8_CBCH + | NM_IPAC_F_CHANT_PDCHF + | NM_IPAC_F_CHANT_TCHF_PDCHF; + trx->support.chan_modes = NM_IPAC_MASK_CHANM_SPEECH; + trx->nominal_power = 25; trx->power_params.trx_p_max_out_mdBm = to_mdB(trx->bts->c0->nominal_power); return 0; @@ -165,9 +179,11 @@ void bts_update_status(enum bts_global_status which, int on) void bts_model_print_help() { - printf( " -w --hw-version Print the targeted HW Version\n" - " -M --pcu-direct Force PCU to access message queue for PDCH dchannel directly\n" - " -p --dsp-trace Set DSP trace flags\n" + printf( "\nModel specific options:\n" + " -w --hw-version Print the targeted HW Version\n" + " -M --pcu-direct Force PCU to access message queue for " + "PDCH dchannel directly\n" + " -p --dsp-trace Set DSP trace flags\n" ); } diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_bid.c b/src/osmo-bts-oc2g/misc/oc2gbts_bid.c index c2cd483d..0589e3f0 100644 --- a/src/osmo-bts-oc2g/misc/oc2gbts_bid.c +++ b/src/osmo-bts-oc2g/misc/oc2gbts_bid.c @@ -10,7 +10,7 @@ * 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. + * 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-bts-oc2g/misc/oc2gbts_bts.c b/src/osmo-bts-oc2g/misc/oc2gbts_bts.c index b3dae76e..3e5db829 100644 --- a/src/osmo-bts-oc2g/misc/oc2gbts_bts.c +++ b/src/osmo-bts-oc2g/misc/oc2gbts_bts.c @@ -10,7 +10,7 @@ * 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. + * 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-bts-oc2g/misc/oc2gbts_clock.c b/src/osmo-bts-oc2g/misc/oc2gbts_clock.c index 5263e3ec..10b97a31 100644 --- a/src/osmo-bts-oc2g/misc/oc2gbts_clock.c +++ b/src/osmo-bts-oc2g/misc/oc2gbts_clock.c @@ -10,7 +10,7 @@ * 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. + * 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-bts-oc2g/misc/oc2gbts_led.c b/src/osmo-bts-oc2g/misc/oc2gbts_led.c index 40d4b722..c2c807b6 100644 --- a/src/osmo-bts-oc2g/misc/oc2gbts_led.c +++ b/src/osmo-bts-oc2g/misc/oc2gbts_led.c @@ -10,7 +10,7 @@ * 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. + * 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-bts-oc2g/misc/oc2gbts_mgr.c b/src/osmo-bts-oc2g/misc/oc2gbts_mgr.c index 25948b0e..843fbb38 100644 --- a/src/osmo-bts-oc2g/misc/oc2gbts_mgr.c +++ b/src/osmo-bts-oc2g/misc/oc2gbts_mgr.c @@ -1,7 +1,7 @@ /* Main program for NuRAN Wireless OC-2G BTS management daemon */ /* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com> - * + * * Based on sysmoBTS: * sysmobts_mgr.c * (C) 2012 by Harald Welte <laforge@gnumonks.org> @@ -17,7 +17,7 @@ * 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. + * 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/>. @@ -201,11 +201,11 @@ static int parse_options(int argc, char **argv) return 0; } -static void signal_handler(int signal) +static void signal_handler(int signum) { - fprintf(stderr, "signal %u received\n", signal); + fprintf(stderr, "signal %u received\n", signum); - switch (signal) { + switch (signum) { case SIGINT: oc2gbts_check_temp(no_rom_write); oc2gbts_check_power(no_rom_write); @@ -214,6 +214,16 @@ static void signal_handler(int signal) exit(0); break; case SIGABRT: + /* in case of abort, we want to obtain a talloc report and + * then run default SIGABRT handler, who will generate coredump + * and abort the process. abort() should do this for us after we + * return, but program wouldn't exit if an external SIGABRT is + * received. + */ + talloc_report_full(tall_mgr_ctx, stderr); + signal(SIGABRT, SIG_DFL); + raise(SIGABRT); + break; case SIGUSR1: case SIGUSR2: talloc_report_full(tall_mgr_ctx, stderr); @@ -228,31 +238,31 @@ static struct log_info_cat mgr_log_info_cat[] = { .name = "DTEMP", .description = "Temperature monitoring", .color = "\033[1;35m", - .enabled = 1, .loglevel = LOGL_INFO, + .enabled = 1, .loglevel = LOGL_NOTICE, }, [DFW] = { .name = "DFW", .description = "Firmware management", .color = "\033[1;36m", - .enabled = 1, .loglevel = LOGL_INFO, + .enabled = 1, .loglevel = LOGL_NOTICE, }, [DFIND] = { .name = "DFIND", .description = "ipaccess-find handling", .color = "\033[1;37m", - .enabled = 1, .loglevel = LOGL_INFO, + .enabled = 1, .loglevel = LOGL_NOTICE, }, [DCALIB] = { .name = "DCALIB", .description = "Calibration handling", .color = "\033[1;37m", - .enabled = 1, .loglevel = LOGL_INFO, + .enabled = 1, .loglevel = LOGL_NOTICE, }, [DSWD] = { .name = "DSWD", .description = "Software Watchdog", .color = "\033[1;37m", - .enabled = 1, .loglevel = LOGL_INFO, + .enabled = 1, .loglevel = LOGL_NOTICE, }, }; @@ -280,6 +290,7 @@ int main(int argc, char **argv) osmo_init_ignore_signals(); signal(SIGINT, &signal_handler); + signal(SIGABRT, &signal_handler); signal(SIGUSR1, &signal_handler); signal(SIGUSR2, &signal_handler); @@ -295,7 +306,7 @@ int main(int argc, char **argv) exit(1); } - rc = telnet_init(tall_mgr_ctx, NULL, OSMO_VTY_PORT_BTSMGR); + rc = telnet_init_default(tall_mgr_ctx, NULL, OSMO_VTY_PORT_BTSMGR); if (rc < 0) { fprintf(stderr, "Error initializing telnet\n"); exit(1); diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_mgr_calib.c b/src/osmo-bts-oc2g/misc/oc2gbts_mgr_calib.c index 3ddf0e8c..cf904aea 100644 --- a/src/osmo-bts-oc2g/misc/oc2gbts_mgr_calib.c +++ b/src/osmo-bts-oc2g/misc/oc2gbts_mgr_calib.c @@ -17,7 +17,7 @@ * 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. + * 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/>. @@ -171,7 +171,12 @@ static void mgr_gps_checkfix(struct oc2gbts_mgr_instance *mgr) return; } +#if GPSD_API_MAJOR_VERSION >= 9 + mgr->gps.gps_fix_now = data->fix.time.tv_sec; +#else mgr->gps.gps_fix_now = (time_t) data->fix.time; +#endif + LOGP(DCALIB, LOGL_INFO, "Got a GPS fix, satellites used: %d, timestamp: %ld\n", data->satellites_used, mgr->gps.gps_fix_now); osmo_timer_del(&mgr->gps.fix_timeout); @@ -220,10 +225,8 @@ static void mgr_gps_open(struct oc2gbts_mgr_instance *mgr) mgr->gps.gps_open = 1; gps_stream(&mgr->gps.gpsdata, WATCH_ENABLE, NULL); - mgr->gps.gpsfd.data = mgr; - mgr->gps.gpsfd.cb = mgr_gps_read; - mgr->gps.gpsfd.when = BSC_FD_READ | BSC_FD_EXCEPT; - mgr->gps.gpsfd.fd = mgr->gps.gpsdata.gps_fd; + osmo_fd_setup(&mgr->gps.gpsfd, mgr->gps.gpsdata.gps_fd, OSMO_FD_READ | OSMO_FD_EXCEPT, + mgr_gps_read, mgr, 0); if (osmo_fd_register(&mgr->gps.gpsfd) < 0) { LOGP(DCALIB, LOGL_ERROR, "Failed to register GPSD fd\n"); calib_state_reset(mgr, CALIB_FAIL_GPSFIX); @@ -607,7 +610,7 @@ static void bts_recon_timer_cb(void *data) select_led_pattern(mgr); /* The connection failures are to be expected during boot */ - mgr->oc2gbts_ctrl.bts_conn->ofd->when |= BSC_FD_WRITE; + osmo_fd_write_enable(mgr->oc2gbts_ctrl.bts_conn->ofd); rc = ipa_client_conn_open(mgr->oc2gbts_ctrl.bts_conn); if (rc < 0) { LOGP(DLCTRL, LOGL_NOTICE, "Failed to connect to BTS.\n"); diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_mgr_nl.c b/src/osmo-bts-oc2g/misc/oc2gbts_mgr_nl.c index db67caf2..1091f622 100644 --- a/src/osmo-bts-oc2g/misc/oc2gbts_mgr_nl.c +++ b/src/osmo-bts-oc2g/misc/oc2gbts_mgr_nl.c @@ -16,7 +16,7 @@ * 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. + * 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-bts-oc2g/misc/oc2gbts_mgr_temp.c b/src/osmo-bts-oc2g/misc/oc2gbts_mgr_temp.c index f9efd9cd..9a92a075 100644 --- a/src/osmo-bts-oc2g/misc/oc2gbts_mgr_temp.c +++ b/src/osmo-bts-oc2g/misc/oc2gbts_mgr_temp.c @@ -16,7 +16,7 @@ * 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. + * 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-bts-oc2g/misc/oc2gbts_mgr_vty.c b/src/osmo-bts-oc2g/misc/oc2gbts_mgr_vty.c index 7e80e030..10dda21f 100644 --- a/src/osmo-bts-oc2g/misc/oc2gbts_mgr_vty.c +++ b/src/osmo-bts-oc2g/misc/oc2gbts_mgr_vty.c @@ -1,5 +1,5 @@ /* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com> - * + * * Based on sysmoBTS: * sysmobts_mgr_vty.c * (C) 2014 by oc2gcom - s.f.m.c. GmbH @@ -16,7 +16,7 @@ * 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. + * 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/>. @@ -86,35 +86,10 @@ static int go_to_parent(struct vty *vty) return vty->node; } -static int is_config_node(struct vty *vty, int node) -{ - switch (node) { - case MGR_NODE: - case ACT_NORM_NODE: - case ACT_WARN_NODE: - case ACT_CRIT_NODE: - case LIMIT_SUPPLY_TEMP_NODE: - case LIMIT_SOC_NODE: - case LIMIT_FPGA_NODE: - case LIMIT_RMSDET_NODE: - case LIMIT_OCXO_NODE: - case LIMIT_TX_TEMP_NODE: - case LIMIT_PA_TEMP_NODE: - case LIMIT_SUPPLY_VOLT_NODE: - case LIMIT_VSWR_NODE: - case LIMIT_SUPPLY_PWR_NODE: - case LIMIT_PA_PWR_NODE: - return 1; - default: - return 0; - } -} - static struct vty_app_info vty_info = { .name = "oc2gbts-mgr", .version = PACKAGE_VERSION, .go_parent_cb = go_to_parent, - .is_config_node = is_config_node, .copyright = copyright, }; @@ -512,35 +487,35 @@ DEFUN(show_mgr, show_mgr_cmd, "show manager", oc2gbts_mgr_sensor_get_state(s_mgr->state.state), VTY_NEWLINE); vty_out(vty, "Current Temperatures%s", VTY_NEWLINE); oc2gbts_temp_get(OC2GBTS_TEMP_SUPPLY, &temp); - vty_out(vty, " Main Supply : %4.2f Celcius%s", + vty_out(vty, " Main Supply : %4.2f Celsius%s", temp/ 1000.0f, VTY_NEWLINE); oc2gbts_temp_get(OC2GBTS_TEMP_SOC, &temp); - vty_out(vty, " SoC : %4.2f Celcius%s", + vty_out(vty, " SoC : %4.2f Celsius%s", temp / 1000.0f, VTY_NEWLINE); oc2gbts_temp_get(OC2GBTS_TEMP_FPGA, &temp); - vty_out(vty, " FPGA : %4.2f Celcius%s", + vty_out(vty, " FPGA : %4.2f Celsius%s", temp / 1000.0f, VTY_NEWLINE); if (oc2gbts_option_get(OC2GBTS_OPTION_RMS_FWD) || oc2gbts_option_get(OC2GBTS_OPTION_RMS_REFL)) { oc2gbts_temp_get(OC2GBTS_TEMP_RMSDET, &temp); - vty_out(vty, " RMSDet : %4.2f Celcius%s", + vty_out(vty, " RMSDet : %4.2f Celsius%s", temp / 1000.0f, VTY_NEWLINE); } oc2gbts_temp_get(OC2GBTS_TEMP_OCXO, &temp); - vty_out(vty, " OCXO : %4.2f Celcius%s", + vty_out(vty, " OCXO : %4.2f Celsius%s", temp / 1000.0f, VTY_NEWLINE); oc2gbts_temp_get(OC2GBTS_TEMP_TX, &temp); - vty_out(vty, " TX : %4.2f Celcius%s", + vty_out(vty, " TX : %4.2f Celsius%s", temp / 1000.0f, VTY_NEWLINE); if (oc2gbts_option_get(OC2GBTS_OPTION_PA_TEMP)) { oc2gbts_temp_get(OC2GBTS_TEMP_PA, &temp); - vty_out(vty, " Power Amp : %4.2f Celcius%s", + vty_out(vty, " Power Amp : %4.2f Celsius%s", temp / 1000.0f, VTY_NEWLINE); } @@ -649,7 +624,7 @@ DEFUN(show_thresh, show_thresh_cmd, "show thresholds", DEFUN(calibrate_clock, calibrate_clock_cmd, "calibrate clock", - "Calibration commands\n" + "Calibration commands\n" "Calibrate clock against GPS PPS\n") { if (oc2gbts_mgr_calib_run(s_mgr) < 0) { diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_misc.c b/src/osmo-bts-oc2g/misc/oc2gbts_misc.c index bacf07bd..c3b91ec1 100644 --- a/src/osmo-bts-oc2g/misc/oc2gbts_misc.c +++ b/src/osmo-bts-oc2g/misc/oc2gbts_misc.c @@ -14,7 +14,7 @@ * 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. + * 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/>. @@ -361,7 +361,7 @@ int oc2gbts_firmware_reload(enum oc2gbts_firmware_type type) case OC2GBTS_FW_DSP: fd = open(fw_sysfs[type], O_WRONLY); if (fd < 0) { - LOGP(DFW, LOGL_ERROR, "unable ot open firmware device %s: %s\n", + LOGP(DFW, LOGL_ERROR, "unable to open firmware device %s: %s\n", fw_sysfs[type], strerror(errno)); close(fd); return fd; diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_nl.c b/src/osmo-bts-oc2g/misc/oc2gbts_nl.c index 39f64aae..d1d1bd13 100644 --- a/src/osmo-bts-oc2g/misc/oc2gbts_nl.c +++ b/src/osmo-bts-oc2g/misc/oc2gbts_nl.c @@ -16,7 +16,7 @@ * 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. + * 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-bts-oc2g/misc/oc2gbts_nl.h b/src/osmo-bts-oc2g/misc/oc2gbts_nl.h index 340cf117..b5a15403 100644 --- a/src/osmo-bts-oc2g/misc/oc2gbts_nl.h +++ b/src/osmo-bts-oc2g/misc/oc2gbts_nl.h @@ -14,7 +14,7 @@ * 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. + * 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-bts-oc2g/misc/oc2gbts_par.c b/src/osmo-bts-oc2g/misc/oc2gbts_par.c index 7dc77c90..fef350f2 100644 --- a/src/osmo-bts-oc2g/misc/oc2gbts_par.c +++ b/src/osmo-bts-oc2g/misc/oc2gbts_par.c @@ -16,7 +16,7 @@ * 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. + * 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-bts-oc2g/misc/oc2gbts_power.c b/src/osmo-bts-oc2g/misc/oc2gbts_power.c index 4e2fc95a..46b8fc03 100644 --- a/src/osmo-bts-oc2g/misc/oc2gbts_power.c +++ b/src/osmo-bts-oc2g/misc/oc2gbts_power.c @@ -10,7 +10,7 @@ * 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. + * 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-bts-oc2g/misc/oc2gbts_swd.c b/src/osmo-bts-oc2g/misc/oc2gbts_swd.c index 59b795ac..6358d4e2 100644 --- a/src/osmo-bts-oc2g/misc/oc2gbts_swd.c +++ b/src/osmo-bts-oc2g/misc/oc2gbts_swd.c @@ -10,7 +10,7 @@ * 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. + * 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/>. @@ -161,7 +161,7 @@ int oc2gbts_swd_init(struct oc2gbts_mgr_instance *mgr, int swd_num_events) the value must be in the range of [0,'swd_num_events'[ (see oc2gbts_swd_init). For example, if 'swd_num_events' was 64, 'swd_event' events are numbered 0 to 63. WARNING: if this function can be used from multiple threads at the same time, - it must be protected with a kind of mutex to avoid loosing event notification. + it must be protected with a kind of mutex to avoid losing event notification. */ int oc2gbts_swd_event(struct oc2gbts_mgr_instance *mgr, enum mgr_swd_events swd_event) { diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_temp.c b/src/osmo-bts-oc2g/misc/oc2gbts_temp.c index 8425dda3..d7afa4e6 100644 --- a/src/osmo-bts-oc2g/misc/oc2gbts_temp.c +++ b/src/osmo-bts-oc2g/misc/oc2gbts_temp.c @@ -10,7 +10,7 @@ * 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. + * 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-bts-oc2g/misc/oc2gbts_util.c b/src/osmo-bts-oc2g/misc/oc2gbts_util.c index b71f0383..0919da93 100644 --- a/src/osmo-bts-oc2g/misc/oc2gbts_util.c +++ b/src/osmo-bts-oc2g/misc/oc2gbts_util.c @@ -16,7 +16,7 @@ * 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. + * 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-bts-oc2g/oc2gbts.c b/src/osmo-bts-oc2g/oc2gbts.c index 012d705c..5860a566 100644 --- a/src/osmo-bts-oc2g/oc2gbts.c +++ b/src/osmo-bts-oc2g/oc2gbts.c @@ -15,7 +15,7 @@ * 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. + * 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-bts-oc2g/oc2gbts.h b/src/osmo-bts-oc2g/oc2gbts.h index 9eb87452..80fd1d59 100644 --- a/src/osmo-bts-oc2g/oc2gbts.h +++ b/src/osmo-bts-oc2g/oc2gbts.h @@ -39,26 +39,26 @@ enum oc2g_auto_pwr_adjust_mode{ }; enum l1prim_type oc2gbts_get_l1prim_type(GsmL1_PrimId_t id); -const struct value_string oc2gbts_l1prim_names[GsmL1_PrimId_NUM+1]; +extern const struct value_string oc2gbts_l1prim_names[GsmL1_PrimId_NUM+1]; GsmL1_PrimId_t oc2gbts_get_l1prim_conf(GsmL1_PrimId_t id); enum l1prim_type oc2gbts_get_sysprim_type(Oc2g_PrimId_t id); -const struct value_string oc2gbts_sysprim_names[Oc2g_PrimId_NUM+1]; +extern const struct value_string oc2gbts_sysprim_names[Oc2g_PrimId_NUM+1]; Oc2g_PrimId_t oc2gbts_get_sysprim_conf(Oc2g_PrimId_t id); -const struct value_string oc2gbts_l1sapi_names[GsmL1_Sapi_NUM+1]; -const struct value_string oc2gbts_l1status_names[GSML1_STATUS_NUM+1]; +extern const struct value_string oc2gbts_l1sapi_names[GsmL1_Sapi_NUM+1]; +extern const struct value_string oc2gbts_l1status_names[GSML1_STATUS_NUM+1]; -const struct value_string oc2gbts_tracef_names[29]; -const struct value_string oc2gbts_tracef_docs[29]; +extern const struct value_string oc2gbts_tracef_names[29]; +extern const struct value_string oc2gbts_tracef_docs[29]; -const struct value_string oc2gbts_tch_pl_names[15]; +extern const struct value_string oc2gbts_tch_pl_names[15]; -const struct value_string oc2gbts_clksrc_names[10]; +extern const struct value_string oc2gbts_clksrc_names[10]; -const struct value_string oc2gbts_dir_names[6]; +extern const struct value_string oc2gbts_dir_names[6]; -const struct value_string oc2gbts_rsl_ho_causes[IPAC_HO_RQD_CAUSE_MAX]; +extern const struct value_string oc2gbts_rsl_ho_causes[IPAC_HO_RQD_CAUSE_MAX]; enum pdch_cs { PDCH_CS_1, @@ -77,7 +77,7 @@ enum pdch_cs { _NUM_PDCH_CS }; -const uint8_t pdch_msu_size[_NUM_PDCH_CS]; +extern const uint8_t pdch_msu_size[_NUM_PDCH_CS]; /* OC2G default parameters */ #define OC2G_BTS_MAX_CELL_SIZE_DEFAULT 166 /* 166 qbits is default value */ diff --git a/src/osmo-bts-oc2g/oc2gbts_vty.c b/src/osmo-bts-oc2g/oc2gbts_vty.c index 1f092dde..051528ab 100644 --- a/src/osmo-bts-oc2g/oc2gbts_vty.c +++ b/src/osmo-bts-oc2g/oc2gbts_vty.c @@ -2,7 +2,7 @@ /* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com> * Copyright (C) 2016 by Harald Welte <laforge@gnumonks.org> - * + * * Based on sysmoBTS: * (C) 2011 by Harald Welte <laforge@gnumonks.org> * (C) 2012,2013 by Holger Hans Peter Freyther @@ -47,6 +47,7 @@ #include <osmo-bts/signal.h> #include <osmo-bts/oml.h> #include <osmo-bts/bts.h> +#include <osmo-bts/bts_sm.h> #include <osmo-bts/gsm_data.h> #include <osmo-bts/phy_link.h> @@ -69,8 +70,6 @@ extern int rsl_tx_preproc_meas_res(struct gsm_lchan *lchan); TRX_STR #define DSP_TRACE_F_STR "DSP Trace Flag\n" -static struct gsm_bts *vty_bts; - static const struct value_string oc2g_pedestal_mode_strs[] = { { OC2G_PEDESTAL_OFF, "off" }, { OC2G_PEDESTAL_ON, "on" }, @@ -113,7 +112,7 @@ DEFUN(cfg_phy_dsp_trace_f, cfg_phy_dsp_trace_f_cmd, struct phy_instance *pinst = vty->index; unsigned int flag; - flag = get_string_value(oc2gbts_tracef_names, argv[1]); + flag = get_string_value(oc2gbts_tracef_names, argv[0]); pinst->u.oc2g.dsp_trace_f |= flag; return CMD_SUCCESS; @@ -125,7 +124,7 @@ DEFUN(cfg_phy_no_dsp_trace_f, cfg_phy_no_dsp_trace_f_cmd, struct phy_instance *pinst = vty->index; unsigned int flag; - flag = get_string_value(oc2gbts_tracef_names, argv[1]); + flag = get_string_value(oc2gbts_tracef_names, argv[0]); pinst->u.oc2g.dsp_trace_f &= ~flag; return CMD_SUCCESS; @@ -135,11 +134,11 @@ DEFUN(cfg_phy_no_dsp_trace_f, cfg_phy_no_dsp_trace_f_cmd, /* runtime */ DEFUN(show_dsp_trace_f, show_dsp_trace_f_cmd, - "show trx <0-0> dsp-trace-flags", + "show dsp-trace-flags trx <0-0>", SHOW_TRX_STR "Display the current setting of the DSP trace flags") { int trx_nr = atoi(argv[0]); - struct gsm_bts_trx *trx = gsm_bts_trx_num(vty_bts, trx_nr); + struct gsm_bts_trx *trx = gsm_bts_trx_num(g_bts, trx_nr); struct oc2gl1_hdl *fl1h; int i; @@ -261,7 +260,7 @@ DEFUN(activate_lchan, activate_lchan_cmd, int trx_nr = atoi(argv[0]); int ts_nr = atoi(argv[1]); int lchan_nr = atoi(argv[3]); - struct gsm_bts_trx *trx = gsm_bts_trx_num(vty_bts, trx_nr); + struct gsm_bts_trx *trx = gsm_bts_trx_num(g_bts, trx_nr); struct gsm_bts_trx_ts *ts = &trx->ts[ts_nr]; struct gsm_lchan *lchan = &ts->lchan[lchan_nr]; @@ -282,9 +281,9 @@ DEFUN(set_tx_power, set_tx_power_cmd, { int trx_nr = atoi(argv[0]); int power = atoi(argv[1]); - struct gsm_bts_trx *trx = gsm_bts_trx_num(vty_bts, trx_nr); + struct gsm_bts_trx *trx = gsm_bts_trx_num(g_bts, trx_nr); - power_ramp_start(trx, to_mdB(power), 1); + power_ramp_start(trx, to_mdB(power), 1, NULL); return CMD_SUCCESS; } @@ -299,7 +298,7 @@ DEFUN(loopback, loopback_cmd, int trx_nr = atoi(argv[0]); int ts_nr = atoi(argv[1]); int lchan_nr = atoi(argv[2]); - struct gsm_bts_trx *trx = gsm_bts_trx_num(vty_bts, trx_nr); + struct gsm_bts_trx *trx = gsm_bts_trx_num(g_bts, trx_nr); struct gsm_bts_trx_ts *ts = &trx->ts[ts_nr]; struct gsm_lchan *lchan = &ts->lchan[lchan_nr]; @@ -318,7 +317,7 @@ DEFUN(no_loopback, no_loopback_cmd, int trx_nr = atoi(argv[0]); int ts_nr = atoi(argv[1]); int lchan_nr = atoi(argv[2]); - struct gsm_bts_trx *trx = gsm_bts_trx_num(vty_bts, trx_nr); + struct gsm_bts_trx *trx = gsm_bts_trx_num(g_bts, trx_nr); struct gsm_bts_trx_ts *ts = &trx->ts[ts_nr]; struct gsm_lchan *lchan = &ts->lchan[lchan_nr]; @@ -335,12 +334,6 @@ DEFUN(cfg_trx_nominal_power, cfg_trx_nominal_power_cmd, int nominal_power = atoi(argv[0]); struct gsm_bts_trx *trx = vty->index; - if (( nominal_power > 25 ) || ( nominal_power < 0 )) { - vty_out(vty, "Nominal Tx power level must be between 0 and 25 dBm (%d) %s", - nominal_power, VTY_NEWLINE); - return CMD_WARNING; - } - trx->nominal_power = nominal_power; trx->power_params.trx_p_max_out_mdBm = to_mdB(nominal_power); @@ -354,12 +347,6 @@ DEFUN(cfg_phy_max_cell_size, cfg_phy_max_cell_size_cmd, struct phy_instance *pinst = vty->index; int cell_size = (uint8_t)atoi(argv[0]); - if (( cell_size > 166 ) || ( cell_size < 0 )) { - vty_out(vty, "Max cell size must be between 0 and 166 qbits (%d) %s", - cell_size, VTY_NEWLINE); - return CMD_WARNING; - } - pinst->u.oc2g.max_cell_size = (uint8_t)cell_size; return CMD_SUCCESS; } @@ -372,10 +359,7 @@ DEFUN(cfg_phy_pedestal_mode, cfg_phy_pedestal_mode_cmd, struct phy_instance *pinst = vty->index; int val = get_string_value(oc2g_pedestal_mode_strs, argv[0]); - if((val < OC2G_PEDESTAL_OFF) || (val > OC2G_PEDESTAL_ON)) { - vty_out(vty, "Invalid unused time-slot transmission mode %d%s", val, VTY_NEWLINE); - return CMD_WARNING; - } + OSMO_ASSERT(val != -EINVAL); pinst->u.oc2g.pedestal_mode = (uint8_t)val; return CMD_SUCCESS; @@ -388,12 +372,6 @@ DEFUN(cfg_phy_dsp_alive_timer, cfg_phy_dsp_alive_timer_cmd, struct phy_instance *pinst = vty->index; uint8_t period = (uint8_t)atoi(argv[0]); - if (( period > 60 ) || ( period < 0 )) { - vty_out(vty, "DSP heart beat alive timer period must be between 0 and 60 seconds (%d) %s", - period, VTY_NEWLINE); - return CMD_WARNING; - } - pinst->u.oc2g.dsp_alive_period = period; return CMD_SUCCESS; } @@ -405,10 +383,7 @@ DEFUN(cfg_phy_auto_tx_pwr_adj, cfg_phy_auto_tx_pwr_adj_cmd, struct phy_instance *pinst = vty->index; int val = get_string_value(oc2g_auto_adj_pwr_strs, argv[0]); - if((val < OC2G_TX_PWR_ADJ_NONE) || (val > OC2G_TX_PWR_ADJ_AUTO)) { - vty_out(vty, "Invalid output power adjustment mode %d%s", val, VTY_NEWLINE); - return CMD_WARNING; - } + OSMO_ASSERT(val != -EINVAL); pinst->u.oc2g.tx_pwr_adj_mode = (uint8_t)val; return CMD_SUCCESS; @@ -421,12 +396,6 @@ DEFUN(cfg_phy_tx_red_pwr_8psk, cfg_phy_tx_red_pwr_8psk_cmd, struct phy_instance *pinst = vty->index; int val = atoi(argv[0]); - if ((val > 40) || (val < 0)) { - vty_out(vty, "Reduction Tx power level must be between 0 and 40 dB (%d) %s", - val, VTY_NEWLINE); - return CMD_WARNING; - } - pinst->u.oc2g.tx_pwr_red_8psk = (uint8_t)val; return CMD_SUCCESS; } @@ -438,19 +407,12 @@ DEFUN(cfg_phy_c0_idle_red_pwr, cfg_phy_c0_idle_red_pwr_cmd, struct phy_instance *pinst = vty->index; int val = atoi(argv[0]); - if ((val > 40) || (val < 0)) { - vty_out(vty, "Reduction Tx power level must be between 0 and 40 dB (%d) %s", - val, VTY_NEWLINE); - return CMD_WARNING; - } - pinst->u.oc2g.tx_c0_idle_pwr_red = (uint8_t)val; return CMD_SUCCESS; } DEFUN(trigger_ho_cause, trigger_ho_cause_cmd, "HIDDEN", TRX_STR) { - struct gsm_network *net = gsmnet_from_vty(vty); struct gsm_bts *bts; struct gsm_bts_trx *trx; struct gsm_bts_trx_ts *ts; @@ -460,7 +422,7 @@ DEFUN(trigger_ho_cause, trigger_ho_cause_cmd, "HIDDEN", TRX_STR) /* uint8_t old_ho_cause; */ /* get BTS pointer */ - bts = gsm_bts_num(net, 0); + bts = gsm_bts_num(g_bts_sm, 0); if (!bts) { vty_out(vty, "Can not get BTS node %s", VTY_NEWLINE); return CMD_WARNING; @@ -541,7 +503,7 @@ DEFUN(cfg_bts_rtp_drift_threshold, cfg_bts_rtp_drift_threshold_cmd, } */ -void bts_model_config_write_bts(struct vty *vty, struct gsm_bts *bts) +void bts_model_config_write_bts(struct vty *vty, const struct gsm_bts *bts) { /* TODO(oramadan) MERGE struct gsm_bts_role_bts *btsb = bts_role_bts(bts); @@ -555,16 +517,16 @@ void bts_model_config_write_bts(struct vty *vty, struct gsm_bts *bts) } -void bts_model_config_write_trx(struct vty *vty, struct gsm_bts_trx *trx) +void bts_model_config_write_trx(struct vty *vty, const struct gsm_bts_trx *trx) { vty_out(vty, " nominal-tx-power %d%s", trx->nominal_power,VTY_NEWLINE); } -void bts_model_config_write_phy(struct vty *vty, struct phy_link *plink) +void bts_model_config_write_phy(struct vty *vty, const struct phy_link *plink) { } -void bts_model_config_write_phy_inst(struct vty *vty, struct phy_instance *pinst) +void bts_model_config_write_phy_inst(struct vty *vty, const struct phy_instance *pinst) { int i; @@ -599,44 +561,42 @@ void bts_model_config_write_phy_inst(struct vty *vty, struct phy_instance *pinst pinst->u.oc2g.tx_c0_idle_pwr_red, VTY_NEWLINE); } -int bts_model_vty_init(struct gsm_bts *bts) +int bts_model_vty_init(void *ctx) { - vty_bts = bts; - /* runtime-patch the command strings with debug levels */ - dsp_trace_f_cmd.string = vty_cmd_string_from_valstr(bts, oc2gbts_tracef_names, + dsp_trace_f_cmd.string = vty_cmd_string_from_valstr(ctx, oc2gbts_tracef_names, "phy <0-1> dsp-trace-flag (", "|",")", VTY_DO_LOWER); - dsp_trace_f_cmd.doc = vty_cmd_string_from_valstr(bts, oc2gbts_tracef_docs, + dsp_trace_f_cmd.doc = vty_cmd_string_from_valstr(ctx, oc2gbts_tracef_docs, TRX_STR DSP_TRACE_F_STR, "\n", "", 0); - no_dsp_trace_f_cmd.string = vty_cmd_string_from_valstr(bts, oc2gbts_tracef_names, + no_dsp_trace_f_cmd.string = vty_cmd_string_from_valstr(ctx, oc2gbts_tracef_names, "no phy <0-1> dsp-trace-flag (", "|",")", VTY_DO_LOWER); - no_dsp_trace_f_cmd.doc = vty_cmd_string_from_valstr(bts, oc2gbts_tracef_docs, + no_dsp_trace_f_cmd.doc = vty_cmd_string_from_valstr(ctx, oc2gbts_tracef_docs, NO_STR TRX_STR DSP_TRACE_F_STR, "\n", "", 0); - cfg_phy_dsp_trace_f_cmd.string = vty_cmd_string_from_valstr(bts, + cfg_phy_dsp_trace_f_cmd.string = vty_cmd_string_from_valstr(ctx, oc2gbts_tracef_names, "dsp-trace-flag (", "|",")", VTY_DO_LOWER); - cfg_phy_dsp_trace_f_cmd.doc = vty_cmd_string_from_valstr(bts, + cfg_phy_dsp_trace_f_cmd.doc = vty_cmd_string_from_valstr(ctx, oc2gbts_tracef_docs, DSP_TRACE_F_STR, "\n", "", 0); - cfg_phy_no_dsp_trace_f_cmd.string = vty_cmd_string_from_valstr(bts, + cfg_phy_no_dsp_trace_f_cmd.string = vty_cmd_string_from_valstr(ctx, oc2gbts_tracef_names, "no dsp-trace-flag (", "|",")", VTY_DO_LOWER); - cfg_phy_no_dsp_trace_f_cmd.doc = vty_cmd_string_from_valstr(bts, + cfg_phy_no_dsp_trace_f_cmd.doc = vty_cmd_string_from_valstr(ctx, oc2gbts_tracef_docs, NO_STR DSP_TRACE_F_STR, "\n", "", 0); - trigger_ho_cause_cmd.string = vty_cmd_string_from_valstr(bts, + trigger_ho_cause_cmd.string = vty_cmd_string_from_valstr(ctx, oc2gbts_rsl_ho_causes, "trigger-ho-cause trx <0-1> ts <0-7> lchan <0-1> cause (", "|",")", VTY_DO_LOWER); diff --git a/src/osmo-bts-oc2g/oml.c b/src/osmo-bts-oc2g/oml.c index 32024090..4b434e83 100644 --- a/src/osmo-bts-oc2g/oml.c +++ b/src/osmo-bts-oc2g/oml.c @@ -14,7 +14,7 @@ * 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. + * 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/>. @@ -26,6 +26,7 @@ #include <osmocom/core/talloc.h> #include <osmocom/core/utils.h> +#include <osmocom/core/fsm.h> #include <nrw/oc2g/gsml1prim.h> #include <nrw/oc2g/gsml1const.h> @@ -42,11 +43,14 @@ #include <osmo-bts/phy_link.h> #include <osmo-bts/handover.h> #include <osmo-bts/l1sap.h> +#include <osmo-bts/nm_common_fsm.h> #include "l1_if.h" #include "oc2gbts.h" #include "utils.h" +static void dump_lch_par(int logl, GsmL1_LogChParam_t *lch_par, GsmL1_Sapi_t sapi); + static int mph_info_chan_confirm(struct gsm_lchan *lchan, enum osmo_mph_info_type type, uint8_t cause) { @@ -91,7 +95,7 @@ static const enum GsmL1_LogChComb_t pchan_to_logChComb[_GSM_PCHAN_MAX] = { [GSM_PCHAN_PDCH] = GsmL1_LogChComb_XIII, [GSM_PCHAN_UNKNOWN] = GsmL1_LogChComb_0, /* - * GSM_PCHAN_TCH_F_PDCH and GSM_PCHAN_TCH_F_TCH_H_PDCH should not be + * GSM_PCHAN_TCH_F_PDCH and GSM_PCHAN_OSMO_DYN should not be * part of this, only "real" pchan values will be looked up here. * See the callers of ts_connect_as(). */ @@ -268,36 +272,48 @@ static int opstart_compl(struct gsm_abis_mo *mo, struct msgb *l1_msg) { GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg); GsmL1_Status_t status = prim_status(l1p); + struct gsm_bts_trx *trx = gsm_bts_trx_num(mo->bts, mo->obj_inst.trx_nr); if (status != GsmL1_Status_Success) { LOGP(DL1C, LOGL_ERROR, "Rx %s, status: %s\n", get_value_string(oc2gbts_l1prim_names, l1p->id), get_value_string(oc2gbts_l1status_names, status)); msgb_free(l1_msg); - return oml_mo_opstart_nack(mo, NM_NACK_CANT_PERFORM); + switch (mo->obj_class) { + case NM_OC_RADIO_CARRIER: + return osmo_fsm_inst_dispatch(trx->mo.fi, NM_EV_OPSTART_NACK, + (void*)(intptr_t)NM_NACK_CANT_PERFORM); + case NM_OC_CHANNEL: + return osmo_fsm_inst_dispatch(trx->ts[mo->obj_inst.ts_nr].mo.fi, NM_EV_OPSTART_NACK, + (void*)(intptr_t)NM_NACK_CANT_PERFORM); + default: + OSMO_ASSERT(0); + } } msgb_free(l1_msg); - - /* Set to Operational State: Enabled */ - oml_mo_state_chg(mo, NM_OPSTATE_ENABLED, NM_AVSTATE_OK); - - /* ugly hack to auto-activate all SAPIs for the BCCH/CCCH on TS0 */ - if (mo->obj_class == NM_OC_CHANNEL && mo->obj_inst.trx_nr == 0 && - mo->obj_inst.ts_nr == 0) { - struct gsm_lchan *cbch = gsm_bts_get_cbch(mo->bts); - DEBUGP(DL1C, "====> trying to activate lchans of BCCH\n"); - mo->bts->c0->ts[0].lchan[CCCH_LCHAN].rel_act_kind = - LCHAN_REL_ACT_OML; - lchan_activate(&mo->bts->c0->ts[0].lchan[CCCH_LCHAN]); - if (cbch) { - cbch->rel_act_kind = LCHAN_REL_ACT_OML; - lchan_activate(cbch); + switch (mo->obj_class) { + case NM_OC_RADIO_CARRIER: + return osmo_fsm_inst_dispatch(trx->mo.fi, NM_EV_OPSTART_ACK, NULL); + case NM_OC_CHANNEL: + /* ugly hack to auto-activate all SAPIs for the BCCH/CCCH on TS0 */ + if (mo->obj_inst.trx_nr == 0 && + mo->obj_inst.ts_nr == 0) { + struct gsm_lchan *cbch = gsm_bts_get_cbch(mo->bts); + DEBUGP(DL1C, "====> trying to activate lchans of BCCH\n"); + mo->bts->c0->ts[0].lchan[CCCH_LCHAN].rel_act_kind = + LCHAN_REL_ACT_OML; + lchan_activate(&mo->bts->c0->ts[0].lchan[CCCH_LCHAN]); + if (cbch) { + cbch->rel_act_kind = LCHAN_REL_ACT_OML; + lchan_activate(cbch); + } } + return osmo_fsm_inst_dispatch(trx->ts[mo->obj_inst.ts_nr].mo.fi, + NM_EV_OPSTART_ACK, NULL); + default: + OSMO_ASSERT(0); } - - /* Send OPSTART ack */ - return oml_mo_opstart_ack(mo); } static int opstart_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg, @@ -371,7 +387,7 @@ static int trx_init_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg, */ /* Begin to ramp up the power */ - power_ramp_start(trx, get_p_target_mdBm(trx, 0), 0); + power_ramp_start(trx, get_p_target_mdBm(trx, 0), 0, NULL); return opstart_compl(&trx->mo, l1_msg); } @@ -433,9 +449,16 @@ static int trx_init(struct gsm_bts_trx *trx) dev_par->freqBand = oc2g_band; dev_par->u16Arfcn = trx->arfcn; dev_par->u16BcchArfcn = trx->bts->c0->arfcn; - dev_par->u8NbTsc = trx->bts->bsic & 7; - dev_par->fRxPowerLevel = trx_ms_pwr_ctrl_is_osmo(trx) - ? 0.0 : trx->bts->ul_power_target; + dev_par->u8NbTsc = BTS_TSC(trx->bts); + + if (!trx_ms_pwr_ctrl_is_osmo(trx)) { + /* Target is in the middle between lower and upper RxLev thresholds */ + int lower_dbm = rxlev2dbm(trx->ms_dpc_params->rxlev_meas.lower_thresh); + int upper_dbm = rxlev2dbm(trx->ms_dpc_params->rxlev_meas.upper_thresh); + dev_par->fRxPowerLevel = (float) (lower_dbm + upper_dbm) / 2; + } else { + dev_par->fRxPowerLevel = 0.0; + } dev_par->fTxPowerLevel = 0.0; LOGP(DL1C, LOGL_NOTICE, "Init TRX (Band %d, ARFCN %u, TSC %u, RxPower % 2f dBm, " @@ -446,9 +469,9 @@ static int trx_init(struct gsm_bts_trx *trx) return l1if_gsm_req_compl(fl1h, msg, trx_init_compl_cb, NULL); } -uint32_t trx_get_hlayer1(struct gsm_bts_trx *trx) +uint32_t trx_get_hlayer1(const struct gsm_bts_trx *trx) { - struct oc2gl1_hdl *fl1h = trx_oc2gl1_hdl(trx); + const struct oc2gl1_hdl *fl1h = trx_oc2gl1_hdl(trx); return fl1h->hLayer1; } @@ -457,20 +480,24 @@ static int trx_close_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg, void *data) { msgb_free(l1_msg); + bts_model_trx_close_cb(trx, 0); return 0; } -int bts_model_trx_close(struct gsm_bts_trx *trx) +void bts_model_trx_close(struct gsm_bts_trx *trx) { struct oc2gl1_hdl *fl1h = trx_oc2gl1_hdl(trx); struct msgb *msg; + int rc; msg = l1p_msgb_alloc(); prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphCloseReq, fl1h, l1p_handle_for_trx(trx)); LOGP(DL1C, LOGL_NOTICE, "Close TRX %u\n", trx->nr); - return l1if_gsm_req_compl(fl1h, msg, trx_close_compl_cb, NULL); + rc = l1if_gsm_req_compl(fl1h, msg, trx_close_compl_cb, NULL); + if (rc < 0) + bts_model_trx_close_cb(trx, rc); } static int trx_rf_lock(struct gsm_bts_trx *trx, int locked, l1if_compl_cb *cb) @@ -515,7 +542,7 @@ static int ts_connect_as(struct gsm_bts_trx_ts *ts, GsmL1_MphConnectReq_t *cr; if (pchan == GSM_PCHAN_TCH_F_PDCH - || pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH) { + || pchan == GSM_PCHAN_OSMO_DYN) { LOGP(DL1C, LOGL_ERROR, "%s Requested TS connect as %s," " expected a specific pchan instead\n", @@ -535,7 +562,7 @@ static int ts_opstart(struct gsm_bts_trx_ts *ts) { enum gsm_phys_chan_config pchan = ts->pchan; switch (pchan) { - case GSM_PCHAN_TCH_F_TCH_H_PDCH: + case GSM_PCHAN_OSMO_DYN: ts->dyn.pchan_is = ts->dyn.pchan_want = GSM_PCHAN_NONE; /* First connect as NONE, until first RSL CHAN ACT. */ pchan = GSM_PCHAN_NONE; @@ -559,8 +586,7 @@ GsmL1_Sapi_t lchan_to_GsmL1_Sapi_t(const struct gsm_lchan *lchan) case GSM_LCHAN_TCH_H: return GsmL1_Sapi_TchH; default: - LOGP(DL1C, LOGL_NOTICE, "%s cannot determine L1 SAPI\n", - gsm_lchan_name(lchan)); + LOGPLCHAN(lchan, DL1C, LOGL_NOTICE, "cannot determine L1 SAPI\n"); break; } return GsmL1_Sapi_Idle; @@ -570,7 +596,7 @@ GsmL1_SubCh_t lchan_to_GsmL1_SubCh_t(const struct gsm_lchan *lchan) { enum gsm_phys_chan_config pchan = lchan->ts->pchan; - if (pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH) + if (pchan == GSM_PCHAN_OSMO_DYN) pchan = lchan->ts->dyn.pchan_want; switch (pchan) { @@ -589,7 +615,7 @@ GsmL1_SubCh_t lchan_to_GsmL1_SubCh_t(const struct gsm_lchan *lchan) case GSM_PCHAN_PDCH: case GSM_PCHAN_UNKNOWN: default: - /* case GSM_PCHAN_TCH_F_TCH_H_PDCH: is caught above */ + /* case GSM_PCHAN_OSMO_DYN: is caught above */ return GsmL1_SubCh_NA; } @@ -652,10 +678,6 @@ static const struct sapi_dir pdtch_sapis[] = { #endif }; -static const struct sapi_dir ho_sapis[] = { - { GsmL1_Sapi_Rach, GsmL1_Dir_RxUplink }, -}; - struct lchan_sapis { const struct sapi_dir *sapis; unsigned int num_sapis; @@ -688,11 +710,6 @@ static const struct lchan_sapis sapis_for_lchan[_GSM_LCHAN_MAX] = { }, }; -static const struct lchan_sapis sapis_for_ho = { - .sapis = ho_sapis, - .num_sapis = ARRAY_SIZE(ho_sapis), -}; - static int mph_send_activate_req(struct gsm_lchan *lchan, struct sapi_cmd *cmd); static int mph_send_deactivate_req(struct gsm_lchan *lchan, struct sapi_cmd *cmd); static int mph_send_config_ciphering(struct gsm_lchan *lchan, struct sapi_cmd *cmd); @@ -780,12 +797,8 @@ static void sapi_queue_dispatch(struct gsm_lchan *lchan, int status) talloc_free(cmd); if (end || llist_empty(&lchan->sapi_cmds)) { - LOGP(DL1C, LOGL_DEBUG, - "%s End of SAPI cmd queue encountered.%s\n", - gsm_lchan_name(lchan), - llist_empty(&lchan->sapi_cmds) - ? " Queue is now empty." - : " More pending."); + LOGPLCHAN(lchan, DL1C, LOGL_DEBUG, "End of SAPI cmd queue encountered.%s\n", + llist_empty(&lchan->sapi_cmds) ? " Queue is now empty." : " More pending."); return; } @@ -825,9 +838,8 @@ static int lchan_act_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg, goto err; } - LOGP(DL1C, LOGL_INFO, "%s MPH-ACTIVATE.conf (%s ", - gsm_lchan_name(lchan), - get_value_string(oc2gbts_l1sapi_names, ic->sapi)); + LOGPLCHAN(lchan, DL1C, LOGL_INFO, "MPH-ACTIVATE.conf (%s ", + get_value_string(oc2gbts_l1sapi_names, ic->sapi)); LOGPC(DL1C, LOGL_INFO, "%s)\n", get_value_string(oc2gbts_dir_names, ic->dir)); @@ -848,19 +860,15 @@ static int lchan_act_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg, lchan->sapis_ul[ic->sapi] = status; if (llist_empty(&lchan->sapi_cmds)) { - LOGP(DL1C, LOGL_ERROR, - "%s Got activation confirmation with empty queue\n", - gsm_lchan_name(lchan)); + LOGPLCHAN(lchan, DL1C, LOGL_ERROR, "Got activation confirmation with empty queue\n"); goto err; } cmd = llist_entry(lchan->sapi_cmds.next, struct sapi_cmd, entry); if (cmd->sapi != ic->sapi || cmd->dir != ic->dir || cmd->type != SAPI_CMD_ACTIVATE) { - LOGP(DL1C, LOGL_ERROR, - "%s Confirmation mismatch (%d, %d) (%d, %d)\n", - gsm_lchan_name(lchan), cmd->sapi, cmd->dir, - ic->sapi, ic->dir); + LOGPLCHAN(lchan, DL1C, LOGL_ERROR, "Confirmation mismatch (%d, %d) (%d, %d)\n", + cmd->sapi, cmd->dir, ic->sapi, ic->dir); goto err; } @@ -934,15 +942,14 @@ static void set_payload_format(GsmL1_LogChParam_t *lch_par) lch_par->tch.tchPlFmt = GsmL1_TchPlFmt_Rtp; } -static void lchan2lch_par(GsmL1_LogChParam_t *lch_par, struct gsm_lchan *lchan) +static int lchan2lch_par(GsmL1_LogChParam_t *lch_par, struct gsm_lchan *lchan) { struct amr_multirate_conf *amr_mrc = &lchan->tch.amr_mr; struct gsm48_multi_rate_conf *mr_conf = (struct gsm48_multi_rate_conf *) amr_mrc->gsm48_ie; int j; - LOGP(DL1C, LOGL_INFO, "%s: %s tch_mode=0x%02x\n", - gsm_lchan_name(lchan), __FUNCTION__, lchan->tch_mode); + LOGPLCHAN(lchan, DL1C, LOGL_INFO, "%s tch_mode=0x%02x\n", __func__, lchan->tch_mode); switch (lchan->tch_mode) { case GSM48_CMODE_SIGN: @@ -970,7 +977,9 @@ static void lchan2lch_par(GsmL1_LogChParam_t *lch_par, struct gsm_lchan *lchan) case GSM48_CMODE_SPEECH_AMR: lch_par->tch.tchPlType = GsmL1_TchPlType_Amr; set_payload_format(lch_par); - lch_par->tch.amrCmiPhase = GsmL1_AmrCmiPhase_Odd; /* FIXME? */ + /* At call set-up, after every successful handover and after a channel mode modify, the + * default phase (odd) shall be used in downlink direction. */ + lch_par->tch.amrCmiPhase = GsmL1_AmrCmiPhase_Odd; lch_par->tch.amrInitCodecMode = amr_get_initial_mode(lchan); /* initialize to clean state */ @@ -1019,10 +1028,13 @@ static void lchan2lch_par(GsmL1_LogChParam_t *lch_par, struct gsm_lchan *lchan) case GSM48_CMODE_DATA_12k0: case GSM48_CMODE_DATA_6k0: case GSM48_CMODE_DATA_3k6: - LOGP(DL1C, LOGL_ERROR, "%s: CSD not supported!\n", - gsm_lchan_name(lchan)); - break; + default: + LOGPLCHAN(lchan, DL1C, LOGL_ERROR, "Channel mode %s is not supported!\n", + gsm48_chan_mode_name(lchan->tch_mode)); + return -ENOTSUP; } + + return 0; } static int mph_send_activate_req(struct gsm_lchan *lchan, struct sapi_cmd *cmd) @@ -1031,6 +1043,7 @@ static int mph_send_activate_req(struct gsm_lchan *lchan, struct sapi_cmd *cmd) struct msgb *msg = l1p_msgb_alloc(); int sapi = cmd->sapi; int dir = cmd->dir; + int rc; GsmL1_MphActivateReq_t *act_req; GsmL1_LogChParam_t *lch_par; @@ -1053,7 +1066,10 @@ static int mph_send_activate_req(struct gsm_lchan *lchan, struct sapi_cmd *cmd) break; case GsmL1_Sapi_TchH: case GsmL1_Sapi_TchF: - lchan2lch_par(lch_par, lchan); + if ((rc = lchan2lch_par(lch_par, lchan)) != 0) { + talloc_free(msg); + return rc; + } /* * Be sure that every packet is received, even if it * fails. In this case the length might be lower or 0. @@ -1086,9 +1102,9 @@ static int mph_send_activate_req(struct gsm_lchan *lchan, struct sapi_cmd *cmd) break; } - LOGP(DL1C, LOGL_INFO, "%s MPH-ACTIVATE.req (hL2=0x%08x, %s ", - gsm_lchan_name(lchan), (uint32_t)act_req->hLayer2, - get_value_string(oc2gbts_l1sapi_names, act_req->sapi)); + LOGPLCHAN(lchan, DL1C, LOGL_INFO, "MPH-ACTIVATE.req (hL2=0x%08x, %s ", + (uint32_t)act_req->hLayer2, get_value_string(oc2gbts_l1sapi_names, act_req->sapi)); + dump_lch_par(LOGL_INFO, lch_par, act_req->sapi); LOGPC(DL1C, LOGL_INFO, "%s)\n", get_value_string(oc2gbts_dir_names, act_req->dir)); @@ -1112,9 +1128,7 @@ static int sapi_activate_cb(struct gsm_lchan *lchan, int status) /* FIXME: Error handling */ if (status != GsmL1_Status_Success) { - LOGP(DL1C, LOGL_ERROR, - "%s act failed mark broken due status: %d\n", - gsm_lchan_name(lchan), status); + LOGPLCHAN(lchan, DL1C, LOGL_ERROR, "act failed mark broken due status: %d\n", status); lchan_set_state(lchan, LCHAN_S_BROKEN); sapi_clear_queue(&lchan->sapi_cmds); mph_info_chan_confirm(lchan, PRIM_INFO_ACTIVATE, RSL_ERR_PROCESSOR_OVERLOAD); @@ -1161,14 +1175,12 @@ int lchan_activate(struct gsm_lchan *lchan) lchan_set_state(lchan, LCHAN_S_ACT_REQ); if (!llist_empty(&lchan->sapi_cmds)) - LOGP(DL1C, LOGL_ERROR, - "%s Trying to activate lchan, but commands in queue\n", - gsm_lchan_name(lchan)); + LOGPLCHAN(lchan, DL1C, LOGL_ERROR, "Trying to activate lchan, but commands in queue\n"); - /* override the regular SAPIs if this is the first hand-over - * related activation of the LCHAN */ - if (lchan->ho.active == HANDOVER_ENABLED) - s4l = &sapis_for_ho; + /* For handover, always start the main channel immediately. lchan->want_dl_sacch_active indicates whether dl + * SACCH should be activated. Also, for HO, start the RACH SAPI. */ + if (lchan->ho.active == HANDOVER_ENABLED || rsl_chan_rt_is_asci(lchan->rsl_chan_rt)) + enqueue_sapi_act_cmd(lchan, GsmL1_Sapi_Rach, GsmL1_Dir_RxUplink); for (i = 0; i < s4l->num_sapis; i++) { int sapi = s4l->sapis[i].sapi; @@ -1181,12 +1193,14 @@ int lchan_activate(struct gsm_lchan *lchan) fl1h->alive_prim_cnt = 0; osmo_timer_schedule(&fl1h->alive_timer, 5, 0); } - enqueue_sapi_act_cmd(lchan, sapi, dir); - } -#warning "FIXME: Should this be in sapi_activate_cb?" - lchan_init_lapdm(lchan); + /* For handover, possibly postpone activating the dl SACCH until the HO RACH is received. */ + if (sapi == GsmL1_Sapi_Sacch && dir == GsmL1_Dir_TxDownlink + && !lchan->want_dl_sacch_active) + continue; + enqueue_sapi_act_cmd(lchan, sapi, dir); + } return 0; } @@ -1318,9 +1332,8 @@ static int chmod_modif_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg, goto err; } - LOGP(DL1C, LOGL_INFO, "%s MPH-CONFIG.conf (%s) ", - gsm_lchan_name(lchan), - get_value_string(oc2gbts_l1cfgt_names, cc->cfgParamId)); + LOGPLCHAN(lchan, DL1C, LOGL_INFO, "MPH-CONFIG.conf (%s) ", + get_value_string(oc2gbts_l1cfgt_names, cc->cfgParamId)); switch (cc->cfgParamId) { case GsmL1_ConfigParamId_SetLogChParams: @@ -1352,9 +1365,7 @@ static int chmod_modif_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg, break; } if (llist_empty(&lchan->sapi_cmds)) { - LOGP(DL1C, LOGL_ERROR, - "%s Got ciphering conf with empty queue\n", - gsm_lchan_name(lchan)); + LOGPLCHAN(lchan, DL1C, LOGL_ERROR, "Got ciphering conf with empty queue\n"); goto err; } @@ -1379,6 +1390,7 @@ static int mph_send_config_logchpar(struct gsm_lchan *lchan, struct sapi_cmd *cm struct msgb *msg = l1p_msgb_alloc(); GsmL1_MphConfigReq_t *conf_req; GsmL1_LogChParam_t *lch_par; + int rc; /* channel mode, encryption and/or multirate have changed */ @@ -1393,7 +1405,10 @@ static int mph_send_config_logchpar(struct gsm_lchan *lchan, struct sapi_cmd *cm conf_req->hLayer3 = (HANDLE)l1if_lchan_to_hLayer(lchan); lch_par = &conf_req->cfgParams.setLogChParams.logChParams; - lchan2lch_par(lch_par, lchan); + if ((rc = lchan2lch_par(lch_par, lchan)) != 0) { + talloc_free(msg); + return rc; + } /* Update the MS Power Level */ if (cmd->sapi == GsmL1_Sapi_Sacch && trx_ms_pwr_ctrl_is_osmo(trx)) @@ -1401,10 +1416,8 @@ static int mph_send_config_logchpar(struct gsm_lchan *lchan, struct sapi_cmd *cm /* FIXME: update encryption */ - LOGP(DL1C, LOGL_INFO, "%s MPH-CONFIG.req (%s) ", - gsm_lchan_name(lchan), - get_value_string(oc2gbts_l1sapi_names, - conf_req->cfgParams.setLogChParams.sapi)); + LOGPLCHAN(lchan, DL1C, LOGL_INFO, "MPH-CONFIG.req (%s) ", + get_value_string(oc2gbts_l1sapi_names, conf_req->cfgParams.setLogChParams.sapi)); LOGPC(DL1C, LOGL_INFO, "cfgParams Tn=%u, subCh=%u, dir=0x%x ", conf_req->cfgParams.setLogChParams.u8Tn, conf_req->cfgParams.setLogChParams.subCh, @@ -1512,11 +1525,9 @@ static int mph_send_config_ciphering(struct gsm_lchan *lchan, struct sapi_cmd *c return -EINVAL; cfgr->cfgParams.setCipheringParams.cipherId = rsl2l1_ciph[lchan->encr.alg_id]; - LOGP(DL1C, LOGL_NOTICE, "%s SET_CIPHERING (ALG=%u %s)\n", - gsm_lchan_name(lchan), - cfgr->cfgParams.setCipheringParams.cipherId, - get_value_string(oc2gbts_dir_names, - cfgr->cfgParams.setCipheringParams.dir)); + LOGPLCHAN(lchan, DL1C, LOGL_NOTICE, "SET_CIPHERING (ALG=%u %s)\n", + cfgr->cfgParams.setCipheringParams.cipherId, + get_value_string(oc2gbts_dir_names, cfgr->cfgParams.setCipheringParams.dir)); memcpy(cfgr->cfgParams.setCipheringParams.u8Kc, lchan->encr.key, lchan->encr.key_len); @@ -1594,9 +1605,8 @@ static int lchan_deact_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg, goto err; } - LOGP(DL1C, LOGL_INFO, "%s MPH-DEACTIVATE.conf (%s ", - gsm_lchan_name(lchan), - get_value_string(oc2gbts_l1sapi_names, ic->sapi)); + LOGPLCHAN(lchan, DL1C, LOGL_INFO, "MPH-DEACTIVATE.conf (%s ", + get_value_string(oc2gbts_l1sapi_names, ic->sapi)); LOGPC(DL1C, LOGL_INFO, "%s)\n", get_value_string(oc2gbts_dir_names, ic->dir)); @@ -1618,19 +1628,15 @@ static int lchan_deact_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg, if (llist_empty(&lchan->sapi_cmds)) { - LOGP(DL1C, LOGL_ERROR, - "%s Got de-activation confirmation with empty queue\n", - gsm_lchan_name(lchan)); + LOGPLCHAN(lchan, DL1C, LOGL_ERROR, "Got de-activation confirmation with empty queue\n"); goto err; } cmd = llist_entry(lchan->sapi_cmds.next, struct sapi_cmd, entry); if (cmd->sapi != ic->sapi || cmd->dir != ic->dir || cmd->type != SAPI_CMD_DEACTIVATE) { - LOGP(DL1C, LOGL_ERROR, - "%s Confirmation mismatch (%d, %d) (%d, %d)\n", - gsm_lchan_name(lchan), cmd->sapi, cmd->dir, - ic->sapi, ic->dir); + LOGPLCHAN(lchan, DL1C, LOGL_ERROR, "Confirmation mismatch (%d, %d) (%d, %d)\n", + cmd->sapi, cmd->dir, ic->sapi, ic->dir); goto err; } @@ -1655,9 +1661,8 @@ static int mph_send_deactivate_req(struct gsm_lchan *lchan, struct sapi_cmd *cmd deact_req->sapi = cmd->sapi; deact_req->hLayer3 = (HANDLE)l1if_lchan_to_hLayer(lchan); - LOGP(DL1C, LOGL_INFO, "%s MPH-DEACTIVATE.req (%s ", - gsm_lchan_name(lchan), - get_value_string(oc2gbts_l1sapi_names, deact_req->sapi)); + LOGPLCHAN(lchan, DL1C, LOGL_INFO, "MPH-DEACTIVATE.req (%s ", + get_value_string(oc2gbts_l1sapi_names, deact_req->sapi)); LOGPC(DL1C, LOGL_INFO, "%s)\n", get_value_string(oc2gbts_dir_names, deact_req->dir)); @@ -1669,8 +1674,7 @@ static int sapi_deactivate_cb(struct gsm_lchan *lchan, int status) { /* FIXME: Error handling. There is no NACK... */ if (status != GsmL1_Status_Success && lchan->state == LCHAN_S_REL_REQ) { - LOGP(DL1C, LOGL_ERROR, "%s is now broken. Stopping the release.\n", - gsm_lchan_name(lchan)); + LOGPLCHAN(lchan, DL1C, LOGL_ERROR, "is now broken. Stopping the release.\n"); lchan_set_state(lchan, LCHAN_S_BROKEN); sapi_clear_queue(&lchan->sapi_cmds); mph_info_chan_confirm(lchan, PRIM_INFO_DEACTIVATE, 0); @@ -1727,17 +1731,9 @@ static int check_sapi_release(struct gsm_lchan *lchan, int sapi, int dir) return enqueue_sapi_deact_cmd(lchan, sapi, dir); } -static int release_sapis_for_ho(struct gsm_lchan *lchan) +static int release_sapi_ul_rach(struct gsm_lchan *lchan) { - int res = 0; - int i; - - const struct lchan_sapis *s4l = &sapis_for_ho; - - for (i = s4l->num_sapis-1; i >= 0; i--) - res |= check_sapi_release(lchan, - s4l->sapis[i].sapi, s4l->sapis[i].dir); - return res; + return check_sapi_release(lchan, GsmL1_Sapi_Rach, GsmL1_Dir_RxUplink); } static int lchan_deactivate_sapis(struct gsm_lchan *lchan) @@ -1759,12 +1755,11 @@ static int lchan_deactivate_sapis(struct gsm_lchan *lchan) } /* always attempt to disable the RACH burst */ - res |= release_sapis_for_ho(lchan); + res |= release_sapi_ul_rach(lchan); /* nothing was queued */ if (res == 0) { - LOGP(DL1C, LOGL_ERROR, "%s all SAPIs already released?\n", - gsm_lchan_name(lchan)); + LOGPLCHAN(lchan, DL1C, LOGL_ERROR, "all SAPIs already released?\n"); lchan_set_state(lchan, LCHAN_S_BROKEN); mph_info_chan_confirm(lchan, PRIM_INFO_DEACTIVATE, 0); } @@ -1811,33 +1806,24 @@ int bts_model_check_oml(struct gsm_bts *bts, uint8_t msg_type, void *obj) { /* FIXME: more checks if the attributes are valid */ - - switch (msg_type) { - case NM_MT_SET_CHAN_ATTR: - /* our L1 only supports one global TSC for all channels - * one one TRX, so we need to make sure not to activate - * channels with a different TSC!! */ - if (TLVP_PRESENT(new_attr, NM_ATT_TSC) && - TLVP_LEN(new_attr, NM_ATT_TSC) >= 1 && - *TLVP_VAL(new_attr, NM_ATT_TSC) != (bts->bsic & 7)) { - LOGP(DOML, LOGL_ERROR, "Channel TSC %u != BSIC-TSC %u\n", - *TLVP_VAL(new_attr, NM_ATT_TSC), bts->bsic & 7); - return -NM_NACK_PARAM_RANGE; - } - break; - } return 0; } /* callback from OML */ -int bts_model_apply_oml(struct gsm_bts *bts, struct msgb *msg, - struct tlv_parsed *new_attr, int kind, void *obj) -{ - if (kind == NM_OC_RADIO_CARRIER) { - struct gsm_bts_trx *trx = obj; - struct oc2gl1_hdl *fl1h = trx_oc2gl1_hdl(trx); +int bts_model_apply_oml(struct gsm_bts *bts, const struct msgb *msg, + struct gsm_abis_mo *mo, void *obj) +{ + struct abis_om_fom_hdr *foh = msgb_l3(msg); + struct gsm_bts_trx *trx; + struct oc2gl1_hdl *fl1h; + uint8_t cell_size; + + switch (foh->msg_type) { + case NM_MT_SET_RADIO_ATTR: + trx = obj; + fl1h = trx_oc2gl1_hdl(trx); /* convert max TA to max cell size in qbits */ - uint8_t cell_size = bts->max_ta << 2; + cell_size = bts->max_ta << 2; /* We do not need to check for L1 handle * because the max cell size parameter can receive before MphInit */ @@ -1850,7 +1836,7 @@ int bts_model_apply_oml(struct gsm_bts *bts, struct msgb *msg, /* Did we go through MphInit yet? If yes fire and forget */ if (fl1h->hLayer1) { - power_ramp_start(trx, get_p_target_mdBm(trx, 0), 0); + power_ramp_start(trx, get_p_target_mdBm(trx, 0), 0, NULL); if (fl1h->phy_inst->u.oc2g.tx_pwr_red_8psk != trx->max_power_backoff_8psk) { /* update current Tx power backoff for 8-PSK */ @@ -1866,40 +1852,37 @@ int bts_model_apply_oml(struct gsm_bts *bts, struct msgb *msg, l1if_set_txpower_c0_idle_pwr_red(fl1h, fl1h->phy_inst->u.oc2g.tx_c0_idle_pwr_red); } } - + break; } - /* FIXME: we actaully need to send a ACK or NACK for the OML message */ - return oml_fom_ack_nack(msg, 0); + return 0; } /* callback from OML */ int bts_model_opstart(struct gsm_bts *bts, struct gsm_abis_mo *mo, void *obj) { + struct gsm_bts_bb_trx *bb_transc; + struct gsm_bts_trx* trx; + struct gsm_bts_trx_ts *ts; int rc; switch (mo->obj_class) { - case NM_OC_RADIO_CARRIER: - rc = trx_init(obj); - break; - case NM_OC_CHANNEL: - rc = ts_opstart(obj); - break; - case NM_OC_BTS: case NM_OC_SITE_MANAGER: + case NM_OC_BTS: case NM_OC_BASEB_TRANSC: case NM_OC_GPRS_NSE: case NM_OC_GPRS_CELL: case NM_OC_GPRS_NSVC: - oml_mo_state_chg(mo, NM_OPSTATE_ENABLED, -1); - rc = oml_mo_opstart_ack(mo); - if (mo->obj_class == NM_OC_BTS) { - oml_mo_state_chg(&bts->mo, -1, NM_AVSTATE_OK); - oml_mo_state_chg(&bts->gprs.nse.mo, -1, NM_AVSTATE_OK); - oml_mo_state_chg(&bts->gprs.cell.mo, -1, NM_AVSTATE_OK); - oml_mo_state_chg(&bts->gprs.nsvc[0].mo, -1, NM_AVSTATE_OK); - } + rc = osmo_fsm_inst_dispatch(mo->fi, NM_EV_OPSTART_ACK, NULL); + break; + case NM_OC_RADIO_CARRIER: + trx = (struct gsm_bts_trx *) obj; + rc = trx_init(trx); + break; + case NM_OC_CHANNEL: + ts = (struct gsm_bts_trx_ts*) obj; + rc = ts_opstart(ts); break; default: rc = oml_mo_opstart_nack(mo, NM_NACK_OBJCLASS_NOTSUPP); @@ -1970,24 +1953,17 @@ int l1if_rsl_chan_act(struct gsm_lchan *lchan) */ int l1if_rsl_chan_mod(struct gsm_lchan *lchan) { - const struct lchan_sapis *s4l = &sapis_for_lchan[lchan->type]; - unsigned int i; - if (lchan->ho.active == HANDOVER_NONE) return -1; - LOGP(DHO, LOGL_ERROR, "%s modifying channel for handover\n", - gsm_lchan_name(lchan)); + LOGPLCHAN(lchan, DHO, LOGL_ERROR, "modifying channel for handover\n"); /* Give up listening to RACH bursts */ - release_sapis_for_ho(lchan); + release_sapi_ul_rach(lchan); - /* Activate the normal SAPIs */ - for (i = 0; i < s4l->num_sapis; i++) { - int sapi = s4l->sapis[i].sapi; - int dir = s4l->sapis[i].dir; - enqueue_sapi_act_cmd(lchan, sapi, dir); - } + /* All the normal SAPIs have already been activated, only DL SACCH may still be missing. */ + if (lchan->want_dl_sacch_active) + enqueue_sapi_act_cmd(lchan, GsmL1_Sapi_Sacch, GsmL1_Dir_TxDownlink); return 0; } @@ -1996,8 +1972,7 @@ int l1if_rsl_chan_rel(struct gsm_lchan *lchan) { /* A duplicate RF Release Request, ignore it */ if (lchan->state == LCHAN_S_REL_REQ) { - LOGP(DL1C, LOGL_ERROR, "%s already in release request state.\n", - gsm_lchan_name(lchan)); + LOGPLCHAN(lchan, DL1C, LOGL_ERROR, "already in release request state.\n"); return 0; } @@ -2033,8 +2008,7 @@ static int ts_disconnect_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg, struct gsm_bts_trx_ts *ts = &trx->ts[cnf->u8Tn]; OSMO_ASSERT(cnf->u8Tn < TRX_NR_TS); - LOGP(DL1C, LOGL_DEBUG, "%s Rx mphDisconnectCnf\n", - gsm_lchan_name(ts->lchan)); + LOGPLCHAN(ts->lchan, DL1C, LOGL_DEBUG, "Rx mphDisconnectCnf\n"); cb_ts_disconnected(ts); @@ -2049,7 +2023,7 @@ int bts_model_ts_disconnect(struct gsm_bts_trx_ts *ts) struct oc2gl1_hdl *fl1h = trx_oc2gl1_hdl(ts->trx); GsmL1_MphDisconnectReq_t *cr; - DEBUGP(DRSL, "%s TS disconnect\n", gsm_lchan_name(ts->lchan)); + LOGPLCHAN(ts->lchan, DRSL, LOGL_DEBUG, "TS disconnect\n"); cr = prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphDisconnectReq, fl1h, l1p_handle_for_ts(ts)); cr->u8Tn = ts->nr; @@ -2065,12 +2039,11 @@ static int ts_connect_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg, struct gsm_bts_trx_ts *ts = &trx->ts[cnf->u8Tn]; OSMO_ASSERT(cnf->u8Tn < TRX_NR_TS); - DEBUGP(DL1C, "%s %s Rx mphConnectCnf flags=%s%s%s\n", - gsm_lchan_name(ts->lchan), - gsm_pchan_name(ts->pchan), - ts->flags & TS_F_PDCH_ACTIVE ? "ACTIVE " : "", - ts->flags & TS_F_PDCH_ACT_PENDING ? "ACT_PENDING " : "", - ts->flags & TS_F_PDCH_DEACT_PENDING ? "DEACT_PENDING " : ""); + LOGPLCHAN(ts->lchan, DL1C, LOGL_DEBUG, "%s Rx mphConnectCnf flags=%s%s%s\n", + gsm_pchan_name(ts->pchan), + ts->flags & TS_F_PDCH_ACTIVE ? "ACTIVE " : "", + ts->flags & TS_F_PDCH_ACT_PENDING ? "ACT_PENDING " : "", + ts->flags & TS_F_PDCH_DEACT_PENDING ? "DEACT_PENDING " : ""); cb_ts_connected(ts, 0); diff --git a/src/osmo-bts-oc2g/oml_router.c b/src/osmo-bts-oc2g/oml_router.c deleted file mode 100644 index 198d5e30..00000000 --- a/src/osmo-bts-oc2g/oml_router.c +++ /dev/null @@ -1,132 +0,0 @@ -/* Beginnings of an OML router */ - -/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com> - * - * Based on sysmoBTS: - * (C) 2014 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 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 "oml_router.h" - -#include <osmo-bts/bts.h> -#include <osmo-bts/logging.h> -#include <osmo-bts/oml.h> -#include <osmo-bts/msg_utils.h> - -#include <osmocom/core/socket.h> -#include <osmocom/core/select.h> - -#include <errno.h> -#include <string.h> -#include <unistd.h> - -static int oml_router_read_cb(struct osmo_fd *fd, unsigned int what) -{ - struct msgb *msg; - int rc; - - msg = oml_msgb_alloc(); - if (!msg) { - LOGP(DL1C, LOGL_ERROR, "Failed to allocate oml msgb.\n"); - return -1; - } - - rc = recv(fd->fd, msg->tail, msg->data_len, 0); - if (rc <= 0) { - close(fd->fd); - osmo_fd_unregister(fd); - fd->fd = -1; - goto err; - } - - msg->l1h = msgb_put(msg, rc); - rc = msg_verify_ipa_structure(msg); - if (rc < 0) { - LOGP(DL1C, LOGL_ERROR, - "OML Router: Invalid IPA message rc(%d)\n", rc); - goto err; - } - - rc = msg_verify_oml_structure(msg); - if (rc < 0) { - LOGP(DL1C, LOGL_ERROR, - "OML Router: Invalid OML message rc(%d)\n", rc); - goto err; - } - - /* todo dispatch message */ - -err: - msgb_free(msg); - return -1; -} - -static int oml_router_accept_cb(struct osmo_fd *accept_fd, unsigned int what) -{ - int fd; - struct osmo_fd *read_fd = (struct osmo_fd *) accept_fd->data; - - /* Accept only one connection at a time. De-register it */ - if (read_fd->fd > -1) { - LOGP(DL1C, LOGL_NOTICE, - "New OML router connection. Closing old one.\n"); - close(read_fd->fd); - osmo_fd_unregister(read_fd); - read_fd->fd = -1; - } - - fd = accept(accept_fd->fd, NULL, NULL); - if (fd < 0) { - LOGP(DL1C, LOGL_ERROR, "Failed to accept. errno: %s.\n", - strerror(errno)); - return -1; - } - - read_fd->fd = fd; - if (osmo_fd_register(read_fd) != 0) { - LOGP(DL1C, LOGL_ERROR, "Registering the read fd failed.\n"); - close(fd); - read_fd->fd = -1; - return -1; - } - - return 0; -} - -int oml_router_init(struct gsm_bts *bts, const char *path, - struct osmo_fd *accept_fd, struct osmo_fd *read_fd) -{ - int rc; - - memset(accept_fd, 0, sizeof(*accept_fd)); - memset(read_fd, 0, sizeof(*read_fd)); - - accept_fd->cb = oml_router_accept_cb; - accept_fd->data = read_fd; - - read_fd->cb = oml_router_read_cb; - read_fd->data = bts; - read_fd->when = BSC_FD_READ; - read_fd->fd = -1; - - rc = osmo_sock_unix_init_ofd(accept_fd, SOCK_SEQPACKET, 0, - path, - OSMO_SOCK_F_BIND | OSMO_SOCK_F_NONBLOCK); - return rc; -} diff --git a/src/osmo-bts-oc2g/oml_router.h b/src/osmo-bts-oc2g/oml_router.h deleted file mode 100644 index 4b22e9c5..00000000 --- a/src/osmo-bts-oc2g/oml_router.h +++ /dev/null @@ -1,13 +0,0 @@ -#pragma once - -struct gsm_bts; -struct osmo_fd; - -/** - * The default path oc2gbts will listen for incoming - * registrations for OML routing and sending. - */ -#define OML_ROUTER_PATH "/var/run/oc2gbts_oml_router" - - -int oml_router_init(struct gsm_bts *bts, const char *path, struct osmo_fd *accept, struct osmo_fd *read); diff --git a/src/osmo-bts-oc2g/tch.c b/src/osmo-bts-oc2g/tch.c index 1bd93e4b..4ea1eb61 100644 --- a/src/osmo-bts-oc2g/tch.c +++ b/src/osmo-bts-oc2g/tch.c @@ -15,7 +15,7 @@ * 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. + * 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/>. @@ -68,7 +68,7 @@ static struct msgb *l1_to_rtppayload_fr(uint8_t *l1_payload, uint8_t payload_len cur = msgb_put(msg, GSM_FR_BYTES); memcpy(cur, l1_payload, GSM_FR_BYTES); - lchan_set_marker(osmo_fr_check_sid(l1_payload, payload_len), lchan); + lchan_set_marker(osmo_fr_is_any_sid(l1_payload), lchan); return msg; } @@ -101,12 +101,8 @@ static struct msgb *l1_to_rtppayload_efr(uint8_t *l1_payload, /* new L1 can deliver bits like we need them */ cur = msgb_put(msg, GSM_EFR_BYTES); memcpy(cur, l1_payload, GSM_EFR_BYTES); - enum osmo_amr_type ft; - enum osmo_amr_quality bfi; - uint8_t cmr; - int8_t sti, cmi; - osmo_amr_rtp_dec(l1_payload, payload_len, &cmr, &cmi, &ft, &bfi, &sti); - lchan_set_marker(ft == AMR_GSM_EFR_SID, lchan); + + lchan_set_marker(osmo_efr_is_any_sid(l1_payload), lchan); return msg; } @@ -259,7 +255,10 @@ int l1if_tch_encode(struct gsm_lchan *lchan, uint8_t *data, uint8_t *len, *payload_type = GsmL1_TchPlType_Efr; rc = rtppayload_to_l1_efr(l1_payload, rtp_pl, rtp_pl_len); - /* FIXME: detect and save EFR SID */ + if (rc && lchan->ts->trx->bts->dtxd) + is_sid = osmo_efr_check_sid(rtp_pl, rtp_pl_len); + if (is_sid) + dtx_cache_payload(lchan, rtp_pl, rtp_pl_len, fn, -1); break; case GSM48_CMODE_SPEECH_AMR: if (use_cache) { @@ -360,7 +359,7 @@ int l1if_tch_rx(struct gsm_bts_trx *trx, uint8_t chan_nr, struct msgb *l1p_msg) { GsmL1_Prim_t *l1p = msgb_l1prim(l1p_msg); GsmL1_PhDataInd_t *data_ind = &l1p->u.phDataInd; - uint8_t *payload, payload_type, payload_len, sid_first[9] = { 0 }; + uint8_t *payload, payload_type, payload_len; struct msgb *rmsg = NULL; struct gsm_lchan *lchan = &trx->ts[L1SAP_CHAN2TS(chan_nr)].lchan[l1sap_chan2ss(chan_nr)]; @@ -368,12 +367,12 @@ int l1if_tch_rx(struct gsm_bts_trx *trx, uint8_t chan_nr, struct msgb *l1p_msg) return -EAGAIN; if (data_ind->msgUnitParam.u8Size < 1) { - LOGPFN(DL1P, LOGL_DEBUG, data_ind->u32Fn, "chan_nr %d Rx Payload size 0\n", chan_nr); + LOGPLCFN(lchan, data_ind->u32Fn, DL1P, LOGL_DEBUG, "chan_nr %d Rx Payload size 0\n", chan_nr); /* Push empty payload to upper layers */ rmsg = msgb_alloc_headroom(256, 128, "L1P-to-RTP"); return add_l1sap_header(trx, rmsg, lchan, chan_nr, data_ind->u32Fn, data_ind->measParam.fBer * 10000, - data_ind->measParam.fLinkQuality * 10); + data_ind->measParam.fLinkQuality * 10, 0, 0, 0); } payload_type = data_ind->msgUnitParam.u8Buffer[0]; @@ -403,6 +402,8 @@ int l1if_tch_rx(struct gsm_bts_trx *trx, uint8_t chan_nr, struct msgb *l1p_msg) if (lchan->type != GSM_LCHAN_TCH_H && lchan->type != GSM_LCHAN_TCH_F) goto err_payload_match; + LOGPLCFN(lchan, data_ind->u32Fn, DL1P, LOGL_DEBUG, "DTX: received ONSET from L1 " "(%d bytes)\n", + payload_len); /* according to 3GPP TS 26.093 ONSET frames precede the first speech frame of a speech burst - set the marker for next RTP frame */ @@ -412,43 +413,40 @@ int l1if_tch_rx(struct gsm_bts_trx *trx, uint8_t chan_nr, struct msgb *l1p_msg) case GsmL1_TchPlType_Amr_SidFirstP1: if (lchan->type != GSM_LCHAN_TCH_H) goto err_payload_match; - LOGPFN(DL1P, LOGL_DEBUG, data_ind->u32Fn, "DTX: received SID_FIRST_P1 from L1 " - "(%d bytes)\n", payload_len); + LOGPLCFN(lchan, data_ind->u32Fn, DL1P, LOGL_DEBUG, "DTX: received SID_FIRST_P1 from L1 " + "(%d bytes)\n", payload_len); break; case GsmL1_TchPlType_Amr_SidFirstP2: if (lchan->type != GSM_LCHAN_TCH_H) goto err_payload_match; - LOGPFN(DL1P, LOGL_DEBUG, data_ind->u32Fn, "DTX: received SID_FIRST_P2 from L1 " - "(%d bytes)\n", payload_len); + LOGPLCFN(lchan, data_ind->u32Fn, DL1P, LOGL_DEBUG, "DTX: received SID_FIRST_P2 from L1 " + "(%d bytes)\n", payload_len); break; case GsmL1_TchPlType_Amr_SidFirstInH: if (lchan->type != GSM_LCHAN_TCH_H) goto err_payload_match; lchan->tch.dtx.is_speech_resume = true; lchan->rtp_tx_marker = true; - LOGPFN(DL1P, LOGL_DEBUG, data_ind->u32Fn, "DTX: received SID_FIRST_INH from L1 " - "(%d bytes)\n", payload_len); + LOGPLCFN(lchan, data_ind->u32Fn, DL1P, LOGL_DEBUG, "DTX: received SID_FIRST_INH from L1 " + "(%d bytes)\n", payload_len); break; case GsmL1_TchPlType_Amr_SidUpdateInH: if (lchan->type != GSM_LCHAN_TCH_H) goto err_payload_match; lchan->tch.dtx.is_speech_resume = true; lchan->rtp_tx_marker = true; - LOGPFN(DL1P, LOGL_DEBUG, data_ind->u32Fn, "DTX: received SID_UPDATE_INH from L1 " - "(%d bytes)\n", payload_len); + LOGPLCFN(lchan, data_ind->u32Fn, DL1P, LOGL_DEBUG, "DTX: received SID_UPDATE_INH from L1 " + "(%d bytes)\n", payload_len); break; default: - LOGPFN(DL1P, LOGL_NOTICE, data_ind->u32Fn, "%s Rx Payload Type %s is unsupported\n", - gsm_lchan_name(lchan), - get_value_string(oc2gbts_tch_pl_names, payload_type)); + LOGPLCFN(lchan, data_ind->u32Fn, DL1P, LOGL_NOTICE, "%s Rx Payload Type %s is unsupported\n", + gsm_lchan_name(lchan), get_value_string(oc2gbts_tch_pl_names, payload_type)); break; } - LOGP(DL1P, LOGL_DEBUG, "%s %s lchan->rtp_tx_marker = %s, len=%u\n", - gsm_lchan_name(lchan), - get_value_string(oc2gbts_tch_pl_names, payload_type), - lchan->rtp_tx_marker ? "true" : "false", - payload_len); + LOGPLCHAN(lchan, DL1P, LOGL_DEBUG, "%s lchan->rtp_tx_marker = %s, len=%u\n", + get_value_string(oc2gbts_tch_pl_names, payload_type), + lchan->rtp_tx_marker ? "true" : "false", payload_len); switch (payload_type) { case GsmL1_TchPlType_Fr: @@ -461,27 +459,21 @@ int l1if_tch_rx(struct gsm_bts_trx *trx, uint8_t chan_nr, struct msgb *l1p_msg) rmsg = l1_to_rtppayload_efr(payload, payload_len, lchan); break; case GsmL1_TchPlType_Amr: - rmsg = l1_to_rtppayload_amr(payload, payload_len, lchan); - break; case GsmL1_TchPlType_Amr_SidFirstP1: - memcpy(sid_first, payload, payload_len); - int len = osmo_amr_rtp_enc(sid_first, 0, AMR_SID, AMR_GOOD); - if (len < 0) - return 0; - rmsg = l1_to_rtppayload_amr(sid_first, len, lchan); + rmsg = l1_to_rtppayload_amr(payload, payload_len, lchan); break; } if (rmsg) return add_l1sap_header(trx, rmsg, lchan, chan_nr, data_ind->u32Fn, data_ind->measParam.fBer * 10000, - data_ind->measParam.fLinkQuality * 10); + data_ind->measParam.fLinkQuality * 10, 0, 0, 0); return 0; err_payload_match: - LOGPFN(DL1P, LOGL_ERROR, data_ind->u32Fn, "%s Rx Payload Type %s incompatible with lchan\n", - gsm_lchan_name(lchan), get_value_string(oc2gbts_tch_pl_names, payload_type)); + LOGPLCFN(lchan, data_ind->u32Fn, DL1P, LOGL_ERROR, "%s Rx Payload Type %s incompatible with lchan\n", + gsm_lchan_name(lchan), get_value_string(oc2gbts_tch_pl_names, payload_type)); return -EINVAL; } diff --git a/src/osmo-bts-oc2g/utils.c b/src/osmo-bts-oc2g/utils.c index cb65f45a..f98e88af 100644 --- a/src/osmo-bts-oc2g/utils.c +++ b/src/osmo-bts-oc2g/utils.c @@ -16,7 +16,7 @@ * 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. + * 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-bts-octphy/Makefile.am b/src/osmo-bts-octphy/Makefile.am index 43d9cd7d..fb3f6691 100644 --- a/src/osmo-bts-octphy/Makefile.am +++ b/src/osmo-bts-octphy/Makefile.am @@ -1,12 +1,42 @@ AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include $(OCTSDR2G_INCDIR) -AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOCODEC_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(LIBOSMOTRAU_CFLAGS) $(LIBOSMOABIS_CFLAGS) $(LIBOSMOCTRL_CFLAGS) -COMMON_LDADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOCODEC_LIBS) $(LIBOSMOGSM_LIBS) $(LIBOSMOVTY_LIBS) $(LIBOSMOTRAU_LIBS) $(LIBOSMOABIS_LIBS) $(LIBOSMOCTRL_LIBS) + +AM_CFLAGS = \ + -Wall \ + $(LIBOSMOCORE_CFLAGS) \ + $(LIBOSMOCODEC_CFLAGS) \ + $(LIBOSMOGSM_CFLAGS) \ + $(LIBOSMOVTY_CFLAGS) \ + $(LIBOSMOCTRL_CFLAGS) \ + $(LIBOSMOABIS_CFLAGS) \ + $(LIBOSMOTRAU_CFLAGS) \ + $(LIBOSMONETIF_CFLAGS) \ + $(NULL) + +COMMON_LDADD = \ + $(LIBOSMOCORE_LIBS) \ + $(LIBOSMOCODEC_LIBS) \ + $(LIBOSMOGSM_LIBS) \ + $(LIBOSMOVTY_LIBS) \ + $(LIBOSMOCTRL_LIBS) \ + $(LIBOSMOABIS_LIBS) \ + $(LIBOSMOTRAU_LIBS) \ + $(LIBOSMONETIF_LIBS) \ + $(NULL) EXTRA_DIST = l1_if.h l1_oml.h l1_utils.h octphy_hw_api.h octpkt.h bin_PROGRAMS = osmo-bts-octphy -COMMON_SOURCES = main.c l1_if.c l1_oml.c l1_utils.c l1_tch.c octphy_hw_api.c octphy_vty.c octpkt.c +COMMON_SOURCES = \ + main.c \ + l1_if.c \ + l1_oml.c \ + l1_utils.c \ + l1_tch.c \ + octphy_hw_api.c \ + octphy_vty.c \ + octpkt.c \ + $(NULL) osmo_bts_octphy_SOURCES = $(COMMON_SOURCES) osmo_bts_octphy_LDADD = $(top_builddir)/src/common/libbts.a $(COMMON_LDADD) diff --git a/src/osmo-bts-octphy/l1_if.c b/src/osmo-bts-octphy/l1_if.c index 612c29ad..074a1a7f 100644 --- a/src/osmo-bts-octphy/l1_if.c +++ b/src/osmo-bts-octphy/l1_if.c @@ -36,6 +36,7 @@ #include <osmocom/core/talloc.h> #include <osmocom/core/socket.h> +#include <osmocom/core/fsm.h> #include <osmo-bts/gsm_data.h> #include <osmo-bts/bts_model.h> @@ -43,6 +44,8 @@ #include <osmo-bts/logging.h> #include <osmo-bts/l1sap.h> #include <osmo-bts/handover.h> +#include <osmo-bts/bts.h> +#include <osmo-bts/nm_common_fsm.h> #include "l1_if.h" #include "l1_oml.h" @@ -307,22 +310,13 @@ int l1if_req_compl(struct octphy_hdl *fl1h, struct msgb *msg, /* For OctPHY, this only about sending state changes to BSC */ int l1if_activate_rf(struct gsm_bts_trx *trx, int on) { - int i; if (on) { /* signal availability */ - oml_mo_state_chg(&trx->mo, NM_OPSTATE_DISABLED, NM_AVSTATE_OK); - oml_mo_tx_sw_act_rep(&trx->mo); - oml_mo_state_chg(&trx->bb_transc.mo, -1, NM_AVSTATE_OK); - oml_mo_tx_sw_act_rep(&trx->bb_transc.mo); - - for (i = 0; i < ARRAY_SIZE(trx->ts); i++) - oml_mo_state_chg(&trx->ts[i].mo, NM_OPSTATE_DISABLED, - NM_AVSTATE_DEPENDENCY); + osmo_fsm_inst_dispatch(trx->mo.fi, NM_EV_SW_ACT, NULL); + osmo_fsm_inst_dispatch(trx->bb_transc.mo.fi, NM_EV_SW_ACT, NULL); } else { - oml_mo_state_chg(&trx->mo, NM_OPSTATE_DISABLED, - NM_AVSTATE_OFF_LINE); - oml_mo_state_chg(&trx->bb_transc.mo, NM_OPSTATE_DISABLED, - NM_AVSTATE_OFF_LINE); + osmo_fsm_inst_dispatch(trx->mo.fi, NM_EV_DISABLE, NULL); + osmo_fsm_inst_dispatch(trx->bb_transc.mo.fi, NM_EV_DISABLE, NULL); } return 0; @@ -335,7 +329,7 @@ static enum gsm_phys_chan_config pick_pchan(struct gsm_bts_trx_ts *ts) if (ts->flags & TS_F_PDCH_ACTIVE) return GSM_PCHAN_PDCH; return GSM_PCHAN_TCH_F; - case GSM_PCHAN_TCH_F_TCH_H_PDCH: + case GSM_PCHAN_OSMO_DYN: return ts->dyn.pchan_is; default: return ts->pchan; @@ -350,7 +344,7 @@ static uint8_t chan_nr_by_sapi(struct gsm_bts_trx_ts *ts, enum gsm_phys_chan_config pchan = pick_pchan(ts); OSMO_ASSERT(pchan != GSM_PCHAN_TCH_F_PDCH); - OSMO_ASSERT(pchan != GSM_PCHAN_TCH_F_TCH_H_PDCH); + OSMO_ASSERT(pchan != GSM_PCHAN_OSMO_DYN); switch (sapi) { case cOCTVC1_GSM_SAPI_ENUM_BCCH: @@ -485,7 +479,7 @@ static int ph_data_req(struct gsm_bts_trx *trx, struct msgb *msg, abort(); } - len = msgb_l2len(msg); + len = (msg->l2h) ? msgb_l2len(msg) : 0; chan_nr = l1sap->u.data.chan_nr; link_id = l1sap->u.data.link_id; @@ -595,11 +589,10 @@ static int ph_tch_req(struct gsm_bts_trx *trx, struct msgb *msg, if (msg) { nmsg = l1p_msgb_alloc(); if (!nmsg) { - LOGPFN(DL1C, LOGL_FATAL, u32Fn, "L1SAP PH-TCH.req msg alloc failed\n"); + LOGPLCFN(lchan, u32Fn, DL1C, LOGL_FATAL, "L1SAP PH-TCH.req msg alloc failed\n"); return -ENOMEM; } - msgb_pull(msg, sizeof(*l1sap)); tOCTVC1_GSM_MSG_TRX_REQUEST_LOGICAL_CHANNEL_DATA_CMD *data_req = (tOCTVC1_GSM_MSG_TRX_REQUEST_LOGICAL_CHANNEL_DATA_CMD *) msgb_put(nmsg, sizeof(*data_req)); @@ -621,7 +614,7 @@ static int ph_tch_req(struct gsm_bts_trx *trx, struct msgb *msg, &data_req->Data.ulPayloadType, data_req->Data.abyDataContent, &data_req->Data.ulDataLength, - msg->data, msg->len); + msgb_l2(msg), msgb_l2len(msg)); mOCTVC1_GSM_MSG_TRX_REQUEST_LOGICAL_CHANNEL_DATA_CMD_SWAP(data_req); } else { @@ -775,25 +768,48 @@ int bts_model_init(struct gsm_bts *bts) bts->variant = BTS_OSMO_OCTPHY; bts->support.ciphers = CIPHER_A5(1) | CIPHER_A5(2) | CIPHER_A5(3); + bts->gprs.cell.support.gprs_codings = NM_IPAC_MASK_GPRS_CODING_CS; /* FIXME: what is the nominal transmit power of the PHY/board? */ bts->c0->nominal_power = 15; - gsm_bts_set_feature(bts, BTS_FEAT_GPRS); - gsm_bts_set_feature(bts, BTS_FEAT_OML_ALERTS); + /* order alphabetically */ #if defined(cOCTVC1_GSM_LOGICAL_CHANNEL_COMBINATION_ENUM_FCCH_SCH_BCCH_CCCH_SDCCH4_CBCH_SACCHC4) && defined(cOCTVC1_GSM_LOGICAL_CHANNEL_COMBINATION_ENUM_SDCCH8_CBCH_SACCHC8) - gsm_bts_set_feature(bts, BTS_FEAT_CBCH); + osmo_bts_set_feature(bts->features, BTS_FEAT_CBCH); #endif - gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_F_V1); - gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_H_V1); - - bts_model_vty_init(bts); + osmo_bts_set_feature(bts->features, BTS_FEAT_GPRS); + osmo_bts_set_feature(bts->features, BTS_FEAT_OML_ALERTS); + osmo_bts_set_feature(bts->features, BTS_FEAT_SPEECH_F_V1); + osmo_bts_set_feature(bts->features, BTS_FEAT_SPEECH_H_V1); return 0; } int bts_model_trx_init(struct gsm_bts_trx *trx) { + /* Frequency bands indicated to the BSC */ + trx->support.freq_bands = NM_IPAC_F_FREQ_BAND_PGSM + | NM_IPAC_F_FREQ_BAND_EGSM + | NM_IPAC_F_FREQ_BAND_RGSM + | NM_IPAC_F_FREQ_BAND_DCS + | NM_IPAC_F_FREQ_BAND_PCS + | NM_IPAC_F_FREQ_BAND_850 + | NM_IPAC_F_FREQ_BAND_480 + | NM_IPAC_F_FREQ_BAND_450; + + /* Channel types and modes indicated to the BSC */ + trx->support.chan_types = NM_IPAC_MASK_CHANT_COMMON +#if defined(cOCTVC1_GSM_LOGICAL_CHANNEL_COMBINATION_ENUM_FCCH_SCH_BCCH_CCCH_SDCCH4_CBCH_SACCHC4) + | NM_IPAC_F_CHANT_BCCH_SDCCH4_CBCH +#endif +#if defined(cOCTVC1_GSM_LOGICAL_CHANNEL_COMBINATION_ENUM_SDCCH8_CBCH_SACCHC8) + | NM_IPAC_F_CHANT_SDCCH8_CBCH +#endif + | NM_IPAC_F_CHANT_PDCHF + | NM_IPAC_F_CHANT_TCHF_PDCHF; + trx->support.chan_modes = NM_IPAC_F_CHANM_SPEECH_FS + | NM_IPAC_F_CHANM_SPEECH_HS; + return 0; } @@ -906,14 +922,9 @@ static void process_meas_res(struct gsm_bts_trx *trx, uint8_t chan_nr, l1sap_up(trx, &l1sap); } -static void dump_meas_res(int ll, tOCTVC1_GSM_MEASUREMENT_INFO * m) -{ - LOGP(DMEAS, ll, - "Meas: RSSI %d dBm, Burst Timing %d Quarter of bits :%d, " - "BER Error Count %d , BER Toatal Bit count %d in last decoded frame\n", - m->sRSSIDbm, m->sBurstTiming, m->sBurstTiming4x, m->usBERCnt, - m->usBERTotalBitCnt); -} + +#define LOG_FMT_MEAS "Meas: RSSI %d dBm, Burst Timing %d Quarter of bits: %d, BER Error Count %d, BER Toatal Bit count %d in last decoded frame" +#define LOG_PARAM_MEAS(meas_param) (meas_param)->sRSSIDbm, (meas_param)->sBurstTiming, (meas_param)->sBurstTiming4x, (meas_param)->usBERCnt, (meas_param)->usBERTotalBitCnt static int handle_mph_time_ind(struct octphy_hdl *fl1, uint8_t trx_id, uint32_t fn) { @@ -938,6 +949,47 @@ static int handle_mph_time_ind(struct octphy_hdl *fl1, uint8_t trx_id, uint32_t return 0; } +/* octv1_gsm_api.h does not have an end marker for CTVC1_GSM_SAPI_ENUM */ +#define _OCTVC1_GSM_SAPI_ENUM_LENGTH (cOCTVC1_GSM_SAPI_ENUM_PRACH + 1) + +static const enum l1sap_common_sapi common_sapi_by_oct_sapi[] = { + [cOCTVC1_GSM_SAPI_ENUM_IDLE] = L1SAP_COMMON_SAPI_IDLE, + [cOCTVC1_GSM_SAPI_ENUM_FCCH] = L1SAP_COMMON_SAPI_FCCH, + [cOCTVC1_GSM_SAPI_ENUM_SCH] = L1SAP_COMMON_SAPI_SCH, + [cOCTVC1_GSM_SAPI_ENUM_SACCH] = L1SAP_COMMON_SAPI_SACCH, + [cOCTVC1_GSM_SAPI_ENUM_SDCCH] = L1SAP_COMMON_SAPI_SDCCH, + [cOCTVC1_GSM_SAPI_ENUM_BCCH] = L1SAP_COMMON_SAPI_BCCH, + [cOCTVC1_GSM_SAPI_ENUM_PCH_AGCH] = L1SAP_COMMON_SAPI_PCH, + [cOCTVC1_GSM_SAPI_ENUM_CBCH] = L1SAP_COMMON_SAPI_CBCH, + [cOCTVC1_GSM_SAPI_ENUM_RACH] = L1SAP_COMMON_SAPI_RACH, + [cOCTVC1_GSM_SAPI_ENUM_TCHF] = L1SAP_COMMON_SAPI_TCH_F, + [cOCTVC1_GSM_SAPI_ENUM_FACCHF] = L1SAP_COMMON_SAPI_FACCH_F, + [cOCTVC1_GSM_SAPI_ENUM_TCHH] = L1SAP_COMMON_SAPI_TCH_H, + [cOCTVC1_GSM_SAPI_ENUM_FACCHH] = L1SAP_COMMON_SAPI_FACCH_H, + [cOCTVC1_GSM_SAPI_ENUM_NCH] = L1SAP_COMMON_SAPI_NCH, + [cOCTVC1_GSM_SAPI_ENUM_PDTCH] = L1SAP_COMMON_SAPI_PDTCH, + [cOCTVC1_GSM_SAPI_ENUM_PACCH] = L1SAP_COMMON_SAPI_PACCH, + [cOCTVC1_GSM_SAPI_ENUM_PBCCH] = L1SAP_COMMON_SAPI_PBCCH, + [cOCTVC1_GSM_SAPI_ENUM_PAGCH] = L1SAP_COMMON_SAPI_PAGCH, + [cOCTVC1_GSM_SAPI_ENUM_PPCH] = L1SAP_COMMON_SAPI_PPCH, + [cOCTVC1_GSM_SAPI_ENUM_PNCH] = L1SAP_COMMON_SAPI_PNCH, + [cOCTVC1_GSM_SAPI_ENUM_PTCCH] = L1SAP_COMMON_SAPI_PTCCH, + [cOCTVC1_GSM_SAPI_ENUM_PRACH] = L1SAP_COMMON_SAPI_PRACH, +}; + +static enum l1sap_common_sapi get_common_sapi(tOCT_UINT8 sapi) +{ + if (sapi >= _OCTVC1_GSM_SAPI_ENUM_LENGTH) + return L1SAP_COMMON_SAPI_UNKNOWN; + return common_sapi_by_oct_sapi[sapi]; +} + +static void set_log_ctx_sapi(tOCT_UINT8 sapi) +{ + l1sap_log_ctx_sapi = get_common_sapi(sapi); + log_set_context(LOG_CTX_L1_SAPI, &l1sap_log_ctx_sapi); +} + static int handle_ph_readytosend_ind(struct octphy_hdl *fl1, tOCTVC1_GSM_MSG_TRX_LOGICAL_CHANNEL_READY_TO_SEND_INDICATION_EVT *evt, struct msgb *l1p_msg) @@ -955,7 +1007,9 @@ static int handle_ph_readytosend_ind(struct octphy_hdl *fl1, struct msgb *resp_msg; tOCTVC1_GSM_MSG_TRX_REQUEST_LOGICAL_CHANNEL_DATA_CMD *data_req; - /* Retrive the data */ + set_log_ctx_sapi(evt->LchId.bySAPI); + + /* Retrieve the data */ fn = evt->ulFrameNumber; ts_num = (uint8_t) evt->LchId.byTimeslotNb; sc = (uint8_t) evt->LchId.bySubChannelNb; @@ -1079,6 +1133,8 @@ static int handle_ph_data_ind(struct octphy_hdl *fl1, uint8_t ts_num = (uint8_t) data_ind->LchId.byTimeslotNb; uint8_t sc = (uint8_t) data_ind->LchId.bySubChannelNb; + set_log_ctx_sapi(data_ind->LchId.bySAPI); + /* Need to combine two 16bit MSB and LSB to form 32bit FN */ fn = data_ind->Data.ulFrameNumber; @@ -1149,7 +1205,7 @@ static int handle_ph_data_ind(struct octphy_hdl *fl1, /* burst timing in 1x but PCU is expecting 4X */ l1sap->u.data.ta_offs_256bits = data_ind->MeasurementInfo.sBurstTiming4x*64; snr = data_ind->MeasurementInfo.sSNRDb; - /* FIXME: better converion formulae for SnR -> C / I? + /* FIXME: better conversion formulae for SnR -> C / I? l1sap->u.data.lqual_cb = (snr ? snr : (snr - 65536)) * 10 / 256; LOGP(DL1C, LOGL_ERROR, "SnR: raw %d, computed %d\n", snr, l1sap->u.data.lqual_cb); */ @@ -1171,7 +1227,10 @@ static int handle_ph_rach_ind(struct octphy_hdl *fl1, int rc; struct ph_rach_ind_param rach_ind_param; - dump_meas_res(LOGL_DEBUG, &ra_ind->MeasurementInfo); + set_log_ctx_sapi(ra_ind->LchId.bySAPI); + + LOGPFN(DL1C, LOGL_DEBUG, ra_ind->ulFrameNumber, "Rx PH-RA.ind, " LOG_FMT_MEAS "\n", + LOG_PARAM_MEAS(&ra_ind->MeasurementInfo)); if (ra_ind->ulMsgLength != 1) { LOGPFN(DL1C, LOGL_ERROR, ra_ind->ulFrameNumber, @@ -1267,7 +1326,11 @@ static int retransmit_wlc_upto(struct octphy_hdl *fl1h, uint32_t trans_id) wlc->num_retrans++; msg = msgb_copy(wlc->cmd_msg, "PHY CMD Retrans"); msg_set_retrans_flag(msg); - osmo_wqueue_enqueue(&fl1h->phy_wq, msg); + if (osmo_wqueue_enqueue(&fl1h->phy_wq, msg) < 0) { + LOGP(DL1C, LOGL_ERROR, "Queue full on wlc retransmit\n"); + msgb_free(msg); + return 0; + } osmo_timer_schedule(&wlc->timer, CMD_TIMEOUT, 0); count++; LOGP(DL1C, LOGL_INFO, "Re-transmitting %s " @@ -1634,7 +1697,7 @@ static int rx_octphy_msg(struct msgb *msg) } /* we first need to decode the common OCTPKT header and dispatch - * based on contrl (command/resp) or data (event=indication) */ + * based on control (command/resp) or data (event=indication) */ switch (format) { case cOCTVOCNET_PKT_FORMAT_CTRL: ctlh = (tOCTVOCNET_PKT_CTL_HEADER *) (msg->l1h + 4); @@ -1782,10 +1845,7 @@ struct octphy_hdl *l1if_open(struct phy_link *plink) osmo_wqueue_init(&fl1h->phy_wq, 10); fl1h->phy_wq.write_cb = octphy_write_cb; fl1h->phy_wq.read_cb = octphy_read_cb; - fl1h->phy_wq.bfd.fd = sfd; - fl1h->phy_wq.bfd.when = BSC_FD_READ; - fl1h->phy_wq.bfd.cb = osmo_wqueue_bfd_cb; - fl1h->phy_wq.bfd.data = fl1h; + osmo_fd_setup(&fl1h->phy_wq.bfd, sfd, OSMO_FD_READ, osmo_wqueue_bfd_cb, fl1h, 0); rc = osmo_fd_register(&fl1h->phy_wq.bfd); if (rc < 0) { close(sfd); diff --git a/src/osmo-bts-octphy/l1_oml.c b/src/osmo-bts-octphy/l1_oml.c index d44f7211..bb519a04 100644 --- a/src/osmo-bts-octphy/l1_oml.c +++ b/src/osmo-bts-octphy/l1_oml.c @@ -28,6 +28,7 @@ #include <osmocom/core/talloc.h> #include <osmocom/core/utils.h> +#include <osmocom/core/fsm.h> #include <osmo-bts/gsm_data.h> #include <osmo-bts/logging.h> @@ -38,6 +39,7 @@ #include <osmo-bts/bts.h> #include <osmo-bts/bts_model.h> #include <osmo-bts/l1sap.h> +#include <osmo-bts/nm_common_fsm.h> #include "l1_if.h" #include "l1_oml.h" @@ -54,7 +56,7 @@ bool no_fw_check = 0; -#define LOGPTRX(byTrxId, level, fmt, args...) \ +#define LOGPOCTTRX(byTrxId, level, fmt, args...) \ LOGP(DL1C, level, "(byTrxId %u) " fmt, byTrxId, ## args) /* Map OSMOCOM logical channel type to OctPHY Logical channel type */ @@ -185,26 +187,31 @@ extern uint8_t rach_detected_Other_g; static int opstart_compl(struct gsm_abis_mo *mo) { + struct gsm_bts_trx *trx = gsm_bts_trx_num(mo->bts, mo->obj_inst.trx_nr); /* TODO: Send NACK in case of error! */ - /* Set to Operational State: Enabled */ - oml_mo_state_chg(mo, NM_OPSTATE_ENABLED, NM_AVSTATE_OK); - - /* hack to auto-activate all SAPIs for the BCCH/CCCH on TS0 */ - if (mo->obj_class == NM_OC_CHANNEL && mo->obj_inst.trx_nr == 0 && - mo->obj_inst.ts_nr == 7) { - struct gsm_lchan *cbch = gsm_bts_get_cbch(mo->bts); - mo->bts->c0->ts[0].lchan[CCCH_LCHAN].rel_act_kind = - LCHAN_REL_ACT_OML; - lchan_activate(&mo->bts->c0->ts[0].lchan[CCCH_LCHAN]); - if (cbch) { - cbch->rel_act_kind = LCHAN_REL_ACT_OML; - lchan_activate(cbch); + switch (mo->obj_class) { + case NM_OC_RADIO_CARRIER: + return osmo_fsm_inst_dispatch(trx->mo.fi, NM_EV_OPSTART_ACK, NULL); + case NM_OC_CHANNEL: + /* ugly hack to auto-activate all SAPIs for the BCCH/CCCH on TS0 */ + if (mo->obj_inst.trx_nr == 0 && + mo->obj_inst.ts_nr == 0) { + struct gsm_lchan *cbch = gsm_bts_get_cbch(mo->bts); + DEBUGP(DL1C, "====> trying to activate lchans of BCCH\n"); + mo->bts->c0->ts[0].lchan[CCCH_LCHAN].rel_act_kind = + LCHAN_REL_ACT_OML; + lchan_activate(&mo->bts->c0->ts[0].lchan[CCCH_LCHAN]); + if (cbch) { + cbch->rel_act_kind = LCHAN_REL_ACT_OML; + lchan_activate(cbch); + } } + return osmo_fsm_inst_dispatch(trx->ts[mo->obj_inst.ts_nr].mo.fi, + NM_EV_OPSTART_ACK, NULL); + default: + OSMO_ASSERT(0); } - - /* Send OPSTART ack */ - return oml_mo_opstart_ack(mo); } static @@ -243,7 +250,7 @@ static void clear_amr_params(tOCTVC1_GSM_LOGICAL_CHANNEL_CONFIG * p_Config) p_Config->abyRate[i] = cOCTVC1_GSM_AMR_CODEC_MODE_ENUM_UNSET; } -static void lchan2lch_par(struct gsm_lchan *lchan, +static int lchan2lch_par(struct gsm_lchan *lchan, tOCTVC1_GSM_LOGICAL_CHANNEL_CONFIG * p_Config) { struct amr_multirate_conf *amr_mrc = &lchan->tch.amr_mr; @@ -252,7 +259,7 @@ static void lchan2lch_par(struct gsm_lchan *lchan, int j; LOGP(DL1C, LOGL_INFO, "%s: %s tch_mode=0x%02x\n", - gsm_lchan_name(lchan), __FUNCTION__, lchan->tch_mode); + gsm_lchan_name(lchan), __func__, lchan->tch_mode); switch (lchan->tch_mode) { case GSM48_CMODE_SIGN: @@ -341,11 +348,13 @@ static void lchan2lch_par(struct gsm_lchan *lchan, case GSM48_CMODE_DATA_12k0: case GSM48_CMODE_DATA_6k0: case GSM48_CMODE_DATA_3k6: - LOGP(DL1C, LOGL_ERROR, "%s: CSD not supported!\n", - gsm_lchan_name(lchan)); - break; - + default: + LOGPLCHAN(lchan, DL1C, LOGL_ERROR, "Channel mode %s is not supported!\n", + gsm48_chan_mode_name(lchan->tch_mode)); + return -ENOTSUP; } + + return 0; } /*********************************************************************** @@ -381,7 +390,7 @@ static int lchan_act_compl_cb(struct octphy_hdl *fl1, struct msgb *resp, void *d mOCTVC1_GSM_MSG_TRX_ACTIVATE_LOGICAL_CHANNEL_RSP_SWAP(ar); trx = trx_by_l1h(fl1, ar->TrxId.byTrxId); if (!trx) { - LOGPTRX(ar->TrxId.byTrxId, LOGL_ERROR, "response with unexpected physical transceiver-id during lchan activation\n"); + LOGPOCTTRX(ar->TrxId.byTrxId, LOGL_ERROR, "response with unexpected physical transceiver-id during lchan activation\n"); return -EINVAL; } @@ -437,6 +446,7 @@ static int mph_send_activate_req(struct gsm_lchan *lchan, struct sapi_cmd *cmd) struct octphy_hdl *fl1h = pinst->phy_link->u.octphy.hdl; struct msgb *msg = l1p_msgb_alloc(); tOCTVC1_GSM_MSG_TRX_ACTIVATE_LOGICAL_CHANNEL_CMD *lac; + int rc; lac = (tOCTVC1_GSM_MSG_TRX_ACTIVATE_LOGICAL_CHANNEL_CMD *) msgb_put(msg, sizeof(*lac)); @@ -449,10 +459,12 @@ static int mph_send_activate_req(struct gsm_lchan *lchan, struct sapi_cmd *cmd) lac->LchId.bySAPI = cmd->sapi; lac->LchId.byDirection = cmd->dir; - lac->Config.byTimingAdvance = lchan->rqd_ta; + lac->Config.byTimingAdvance = lchan->ta_ctrl.current; lac->Config.byBSIC = lchan->ts->trx->bts->bsic; - - lchan2lch_par(lchan, &lac->Config); + if ((rc = lchan2lch_par(lchan, &lac->Config)) != 0) { + talloc_free(msg); + return rc; + } mOCTVC1_GSM_MSG_TRX_ACTIVATE_LOGICAL_CHANNEL_CMD_SWAP(lac); @@ -496,7 +508,7 @@ static int set_ciph_compl_cb(struct octphy_hdl *fl1, struct msgb *resp, void *da trx = trx_by_l1h(fl1, pcr->TrxId.byTrxId); if (!trx) { - LOGPTRX(pcr->TrxId.byTrxId, LOGL_ERROR, "response with unexpected physical transceiver-id during cipher mode activation\n"); + LOGPOCTTRX(pcr->TrxId.byTrxId, LOGL_ERROR, "response with unexpected physical transceiver-id during cipher mode activation\n"); return -EINVAL; } @@ -506,7 +518,7 @@ static int set_ciph_compl_cb(struct octphy_hdl *fl1, struct msgb *resp, void *da * sub-channel, only th request contains this information :( */ lchan = &ts->lchan[(unsigned long) data]; - /* TODO: This state machine should be shared accross BTS models? */ + /* TODO: This state machine should be shared across BTS models? */ switch (lchan->ciph_state) { case LCHAN_CIPH_RX_REQ: lchan->ciph_state = LCHAN_CIPH_RX_CONF; @@ -700,7 +712,7 @@ static int lchan_deact_compl_cb(struct octphy_hdl *fl1, struct msgb *resp, void mOCTVC1_GSM_MSG_TRX_DEACTIVATE_LOGICAL_CHANNEL_RSP_SWAP(ldr); trx = trx_by_l1h(fl1, ldr->TrxId.byTrxId); if (!trx) { - LOGPTRX(ldr->TrxId.byTrxId, LOGL_ERROR, "response with unexpected physical transceiver-id during lchan deactivation\n"); + LOGPOCTTRX(ldr->TrxId.byTrxId, LOGL_ERROR, "response with unexpected physical transceiver-id during lchan deactivation\n"); return -EINVAL; } @@ -1095,9 +1107,6 @@ int lchan_activate(struct gsm_lchan *lchan) } enqueue_sapi_act_cmd(lchan, sapi, dir); } - - lchan_init_lapdm(lchan); - return 0; } @@ -1107,13 +1116,6 @@ int l1if_rsl_chan_act(struct gsm_lchan *lchan) return 0; } -#define talloc_replace(dst, ctx, src) \ - do { \ - if (dst) \ - talloc_free(dst); \ - dst = talloc_strdup(ctx, (const char *) src); \ - } while (0) - static int app_info_sys_compl_cb(struct octphy_hdl *fl1h, struct msgb *resp, void *data) { tOCTVC1_MAIN_MSG_APPLICATION_INFO_SYSTEM_RSP *aisr = @@ -1133,8 +1135,10 @@ static int app_info_sys_compl_cb(struct octphy_hdl *fl1h, struct msgb *resp, voi LOGP(DL1C, LOGL_INFO, "Note: compiled without multi-trx support.\n"); #endif - talloc_replace(fl1h->info.system.platform, fl1h, aisr->szPlatform); - talloc_replace(fl1h->info.system.version, fl1h, aisr->szVersion); + osmo_talloc_replace_string(fl1h, &fl1h->info.system.platform, + (const char *) aisr->szPlatform); + osmo_talloc_replace_string(fl1h, &fl1h->info.system.version, + (const char *) aisr->szVersion); msgb_free(resp); @@ -1169,7 +1173,7 @@ static int app_info_compl_cb(struct octphy_hdl *fl1h, struct msgb *resp, tOCTVC1_MAIN_MSG_APPLICATION_INFO_RSP *air = (tOCTVC1_MAIN_MSG_APPLICATION_INFO_RSP *) resp->l2h; - snprintf(ver_hdr, sizeof(ver_hdr), "%02i.%02i.%02i-B%i", + snprintf(ver_hdr, sizeof(ver_hdr), "%02d.%02d.%02d-B%d", cOCTVC1_MAIN_VERSION_MAJOR, cOCTVC1_MAIN_VERSION_MINOR, cOCTVC1_MAIN_VERSION_MAINTENANCE, cOCTVC1_MAIN_VERSION_BUILD); @@ -1191,16 +1195,19 @@ static int app_info_compl_cb(struct octphy_hdl *fl1h, struct msgb *resp, if (!no_fw_check) { LOGP(DL1C, LOGL_ERROR, - "use option -I to override the check (not recommened)\n"); + "use option -I to override the check (not recommended)\n"); LOGP(DL1C, LOGL_ERROR, "exiting...\n"); exit(1); } } - talloc_replace(fl1h->info.app.name, fl1h, air->szName); - talloc_replace(fl1h->info.app.description, fl1h, air->szDescription); - talloc_replace(fl1h->info.app.version, fl1h, air->szVersion); + osmo_talloc_replace_string(fl1h, &fl1h->info.app.name, + (const char *) air->szName); + osmo_talloc_replace_string(fl1h, &fl1h->info.app.description, + (const char *) air->szDescription); + osmo_talloc_replace_string(fl1h, &fl1h->info.app.version, + (const char *) air->szVersion); OSMO_ASSERT(strlen(ver_hdr) < sizeof(pinst->version)); osmo_strlcpy(pinst->version, ver_hdr, strlen(ver_hdr)); @@ -1283,7 +1290,7 @@ static int trx_open_compl_cb(struct octphy_hdl *fl1h, struct msgb *resp, void *d mOCTVC1_GSM_MSG_TRX_OPEN_RSP_SWAP(or); trx = trx_by_l1h(fl1h, or->TrxId.byTrxId); if (!trx) { - LOGPTRX(or->TrxId.byTrxId, LOGL_ERROR, "response with unexpected physical transceiver-id during TRX opening procedure -- abort\n"); + LOGPOCTTRX(or->TrxId.byTrxId, LOGL_ERROR, "response with unexpected physical transceiver-id during TRX opening procedure -- abort\n"); exit(1); } @@ -1340,13 +1347,13 @@ int l1if_trx_open(struct gsm_bts_trx *trx) } oc->Config.usBcchArfcn = trx->bts->c0->arfcn; #endif - oc->Config.usTsc = trx->bts->bsic & 0x7; + oc->Config.usTsc = BTS_TSC(trx->bts); oc->RfConfig.ulRxGainDb = plink->u.octphy.rx_gain_db; /* FIXME: compute this based on nominal transmit power, etc. */ if (plink->u.octphy.tx_atten_flag) { oc->RfConfig.ulTxAttndB = plink->u.octphy.tx_atten_db; } else { - /* Take the Tx Attn received in set radio attribures + /* Take the Tx Attn received in set radio attributes * x4 is for the value in db */ oc->RfConfig.ulTxAttndB = (trx->max_power_red) << 2; } @@ -1426,7 +1433,7 @@ static int l1if_over_sample_16x_modif(struct gsm_bts_trx *trx) } #endif -uint32_t trx_get_hlayer1(struct gsm_bts_trx * trx) +uint32_t trx_get_hlayer1(const struct gsm_bts_trx *trx) { return 0; } @@ -1437,8 +1444,9 @@ static int trx_init(struct gsm_bts_trx *trx) ARRAY_SIZE(trx_rqd_attr))) { /* HACK: spec says we need to decline, but openbsc * doesn't deal with this very well */ - return oml_mo_opstart_ack(&trx->mo); - /* return oml_mo_opstart_nack(&trx->mo, NM_NACK_CANT_PERFORM); */ + return osmo_fsm_inst_dispatch(trx->mo.fi, NM_EV_OPSTART_ACK, NULL); + //return osmo_fsm_inst_dispatch(trx->mo.fi, NM_EV_OPSTART_NACK, + // (void*)(intptr_t)NM_NACK_CANT_PERFORM); } l1if_check_app_version(trx); @@ -1470,7 +1478,7 @@ static int pchan_act_compl_cb(struct octphy_hdl *fl1, struct msgb *resp, void *d mOCTVC1_GSM_MSG_TRX_ACTIVATE_PHYSICAL_CHANNEL_RSP_SWAP(ar); trx = trx_by_l1h(fl1, ar->TrxId.byTrxId); if (!trx) { - LOGPTRX(ar->TrxId.byTrxId, LOGL_ERROR, "response with unexpected physical transceiver-id during physical channel activation -- abort\n"); + LOGPOCTTRX(ar->TrxId.byTrxId, LOGL_ERROR, "response with unexpected physical transceiver-id during physical channel activation -- abort\n"); exit(1); } @@ -1507,8 +1515,7 @@ static int ts_connect_as(struct gsm_bts_trx_ts *ts, struct phy_instance *pinst = trx_phy_instance(ts->trx); struct octphy_hdl *fl1h = pinst->phy_link->u.octphy.hdl; struct msgb *msg = l1p_msgb_alloc(); - tOCTVC1_GSM_MSG_TRX_ACTIVATE_PHYSICAL_CHANNEL_CMD *oc = - (tOCTVC1_GSM_MSG_TRX_ACTIVATE_PHYSICAL_CHANNEL_CMD *) oc; + tOCTVC1_GSM_MSG_TRX_ACTIVATE_PHYSICAL_CHANNEL_CMD *oc; oc = (tOCTVC1_GSM_MSG_TRX_ACTIVATE_PHYSICAL_CHANNEL_CMD*) msgb_put(msg, sizeof(*oc)); @@ -1553,7 +1560,7 @@ static int ts_disconnect_cb(struct octphy_hdl *fl1, struct msgb *resp, trx = trx_by_l1h(fl1, ar->TrxId.byTrxId); if (!trx) { - LOGPTRX(ar->TrxId.byTrxId, LOGL_ERROR, "response with unexpected physical transceiver-id during ts disconnection\n"); + LOGPOCTTRX(ar->TrxId.byTrxId, LOGL_ERROR, "response with unexpected physical transceiver-id during ts disconnection\n"); return -EINVAL; } @@ -1581,7 +1588,7 @@ static int ts_connect_cb(struct octphy_hdl *fl1, struct msgb *resp, void *data) mOCTVC1_GSM_MSG_TRX_ACTIVATE_PHYSICAL_CHANNEL_RSP_SWAP(ar); trx = trx_by_l1h(fl1, ar->TrxId.byTrxId); if (!trx) { - LOGPTRX(ar->TrxId.byTrxId, LOGL_ERROR, "response with unexpected physical transceiver-id while connecting ts\n"); + LOGPOCTTRX(ar->TrxId.byTrxId, LOGL_ERROR, "response with unexpected physical transceiver-id while connecting ts\n"); return -EINVAL; } @@ -1678,7 +1685,7 @@ int bts_model_chg_adm_state(struct gsm_bts *bts, struct gsm_abis_mo *mo, bts_model_trx_deact_rf(trx); /* Close TRX */ - rc = bts_model_trx_close(trx); + rc = trx_close(trx); if (rc != 0) { LOGP(DL1C, LOGL_ERROR, "Cannot close TRX %d, it is already closed.\n", @@ -1716,10 +1723,11 @@ int bts_model_trx_deact_rf(struct gsm_bts_trx *trx) return l1if_activate_rf(trx, 0); } -int bts_model_trx_close(struct gsm_bts_trx *trx) +void bts_model_trx_close(struct gsm_bts_trx *trx) { /* FIXME: close only one TRX */ - return trx_close(trx); + int rc = trx_close(trx); + bts_model_trx_close_cb(trx, rc); } @@ -1733,41 +1741,46 @@ int bts_model_check_oml(struct gsm_bts *bts, uint8_t msg_type, } /* callback from OML */ -int bts_model_apply_oml(struct gsm_bts *bts, struct msgb *msg, - struct tlv_parsed *new_attr, int kind, void *obj) +int bts_model_apply_oml(struct gsm_bts *bts, const struct msgb *msg, + struct gsm_abis_mo *mo, void *obj) { - if (kind == NM_OC_RADIO_CARRIER) { - struct gsm_bts_trx *trx = obj; - /*struct octphy_hdl *fl1h = trx_octphy_hdl(trx); */ + struct abis_om_fom_hdr *foh = msgb_l3(msg); + struct gsm_bts_trx *trx; - power_ramp_start(trx, get_p_target_mdBm(trx, 0), 0); + switch (foh->msg_type) { + case NM_MT_SET_RADIO_ATTR: + trx = obj; + power_ramp_start(trx, get_p_target_mdBm(trx, 0), 0, NULL); + break; } - return oml_fom_ack_nack(msg, 0); + + return 0; } /* callback from OML */ int bts_model_opstart(struct gsm_bts *bts, struct gsm_abis_mo *mo, void *obj) { - int rc = -1; + struct gsm_bts_trx* trx; struct gsm_bts_trx_ts *ts; + int rc; switch (mo->obj_class) { - case NM_OC_RADIO_CARRIER: - rc = trx_init(obj); - break; - case NM_OC_CHANNEL: - ts = (struct gsm_bts_trx_ts*) obj; - rc = ts_connect_as(ts, ts->pchan, pchan_act_compl_cb, NULL); - break; - case NM_OC_BTS: case NM_OC_SITE_MANAGER: + case NM_OC_BTS: case NM_OC_BASEB_TRANSC: case NM_OC_GPRS_NSE: case NM_OC_GPRS_CELL: case NM_OC_GPRS_NSVC: - oml_mo_state_chg(mo, NM_OPSTATE_ENABLED, -1); - rc = oml_mo_opstart_ack(mo); + rc = osmo_fsm_inst_dispatch(mo->fi, NM_EV_OPSTART_ACK, NULL); + break; + case NM_OC_RADIO_CARRIER: + trx = (struct gsm_bts_trx*) obj; + rc = trx_init(trx); + break; + case NM_OC_CHANNEL: + ts = (struct gsm_bts_trx_ts*) obj; + rc = ts_connect_as(ts, ts->pchan, pchan_act_compl_cb, NULL); break; default: rc = oml_mo_opstart_nack(mo, NM_NACK_OBJCLASS_NOTSUPP); @@ -1786,8 +1799,7 @@ int bts_model_ts_disconnect(struct gsm_bts_trx_ts *ts) struct phy_instance *pinst = trx_phy_instance(ts->trx); struct octphy_hdl *fl1h = pinst->phy_link->u.octphy.hdl; struct msgb *msg = l1p_msgb_alloc(); - tOCTVC1_GSM_MSG_TRX_DEACTIVATE_PHYSICAL_CHANNEL_CMD *oc = - (tOCTVC1_GSM_MSG_TRX_DEACTIVATE_PHYSICAL_CHANNEL_CMD *) oc; + tOCTVC1_GSM_MSG_TRX_DEACTIVATE_PHYSICAL_CHANNEL_CMD *oc; oc = (tOCTVC1_GSM_MSG_TRX_DEACTIVATE_PHYSICAL_CHANNEL_CMD *) msgb_put(msg, sizeof(*oc)); @@ -1810,7 +1822,7 @@ void bts_model_ts_connect(struct gsm_bts_trx_ts *ts, { int rc; if (as_pchan == GSM_PCHAN_TCH_F_PDCH - || as_pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH) { + || as_pchan == GSM_PCHAN_OSMO_DYN) { LOGP(DL1C, LOGL_ERROR, "%s Requested TS connect as %s," " expected a specific pchan instead\n", diff --git a/src/osmo-bts-octphy/l1_oml.h b/src/osmo-bts-octphy/l1_oml.h index 4729df5b..3c814c7f 100644 --- a/src/osmo-bts-octphy/l1_oml.h +++ b/src/osmo-bts-octphy/l1_oml.h @@ -10,7 +10,7 @@ int l1if_rsl_mode_modify(struct gsm_lchan *lchan); int l1if_set_ciphering(struct gsm_lchan *lchan, int dir_downlink); -uint32_t trx_get_hlayer1(struct gsm_bts_trx *trx); +uint32_t trx_get_hlayer1(const struct gsm_bts_trx *trx); int gsm_abis_mo_check_attr(const struct gsm_abis_mo *mo, const uint8_t * attr_ids, unsigned int num_attr_ids); diff --git a/src/osmo-bts-octphy/l1_tch.c b/src/osmo-bts-octphy/l1_tch.c index df0469dd..4b542d11 100644 --- a/src/osmo-bts-octphy/l1_tch.c +++ b/src/osmo-bts-octphy/l1_tch.c @@ -146,12 +146,12 @@ int l1if_tch_rx(struct gsm_bts_trx *trx, uint8_t chan_nr, &trx->ts[L1SAP_CHAN2TS(chan_nr)].lchan[l1sap_chan2ss(chan_nr)]; if (data_ind->Data.ulDataLength < 1) { - LOGPFN(DL1P, LOGL_DEBUG, fn, "chan_nr %d Rx Payload size 0\n", chan_nr); + LOGPLCFN(lchan, fn, DL1P, LOGL_DEBUG, "chan_nr %d Rx Payload size 0\n", chan_nr); /* Push empty payload to upper layers */ rmsg = msgb_alloc_headroom(256, 128, "L1P-to-RTP"); return add_l1sap_header(trx, rmsg, lchan, chan_nr, data_ind->Data.ulFrameNumber, - ber10k, lqual_cb); + ber10k, lqual_cb, 0, 0, 0); } payload_len = data_ind->Data.ulDataLength; @@ -173,13 +173,13 @@ int l1if_tch_rx(struct gsm_bts_trx *trx, uint8_t chan_nr, goto err_payload_match; break; default: - LOGPFN(DL1P, LOGL_NOTICE, fn, "%s Rx Payload Type %d is unsupported\n", - gsm_lchan_name(lchan), payload_type); + LOGPLCFN(lchan, fn, DL1P, LOGL_NOTICE, "%s Rx Payload Type %d is unsupported\n", + gsm_lchan_name(lchan), payload_type); break; } - LOGPFN(DL1P, LOGL_DEBUG, fn, "%s Rx codec frame (%u): %s\n", gsm_lchan_name(lchan), - payload_len, osmo_hexdump(payload, payload_len)); + LOGPLCFN(lchan, fn, DL1P, LOGL_DEBUG, "%s Rx codec frame (%u): %s\n", gsm_lchan_name(lchan), payload_len, + osmo_hexdump(payload, payload_len)); switch (payload_type) { case cOCTVC1_GSM_PAYLOAD_TYPE_ENUM_FULL_RATE: @@ -201,7 +201,7 @@ int l1if_tch_rx(struct gsm_bts_trx *trx, uint8_t chan_nr, rmsg = l1_to_rtppayload_amr(payload, payload_len, &lchan->tch.amr_mr); #else - LOGPFN(DL1P, LOGL_ERROR, fn, "OctPHY only supports FR!\n"); + LOGPLCFN(lchan, fn, DL1P, LOGL_ERROR, "OctPHY only supports FR!\n"); return -1; #endif break; @@ -210,13 +210,13 @@ int l1if_tch_rx(struct gsm_bts_trx *trx, uint8_t chan_nr, if (rmsg) return add_l1sap_header(trx, rmsg, lchan, chan_nr, data_ind->Data.ulFrameNumber, - ber10k, lqual_cb); + ber10k, lqual_cb, 0, 0, 0); return 0; err_payload_match: - LOGPFN(DL1P, LOGL_ERROR, fn, "%s Rx Payload Type %d incompatible with lchan\n", - gsm_lchan_name(lchan), payload_type); + LOGPLCFN(lchan, fn, DL1P, LOGL_ERROR, "%s Rx Payload Type %d incompatible with lchan\n", + gsm_lchan_name(lchan), payload_type); return -EINVAL; } @@ -241,8 +241,7 @@ void l1if_tch_encode(struct gsm_lchan *lchan, uint32_t *payload_type, uint8_t *l1_payload; int rc = -1; - DEBUGP(DRTP, "%s RTP IN: %s\n", gsm_lchan_name(lchan), - osmo_hexdump(rtp_pl, rtp_pl_len)); + LOGPLCHAN(lchan, DRTP, LOGL_DEBUG, "RTP IN: %s\n", osmo_hexdump(rtp_pl, rtp_pl_len)); l1_payload = &data[0]; @@ -271,13 +270,11 @@ void l1if_tch_encode(struct gsm_lchan *lchan, uint32_t *payload_type, } if (rc < 0) { - LOGP(DRTP, LOGL_ERROR, "%s unable to parse RTP payload\n", - gsm_lchan_name(lchan)); + LOGPLCHAN(lchan, DRTP, LOGL_ERROR, "unable to parse RTP payload\n"); return; } *len = rc; - DEBUGP(DRTP, "%s RTP->L1: %s\n", gsm_lchan_name(lchan), - osmo_hexdump(data, *len)); + LOGPLCHAN(lchan, DRTP, LOGL_DEBUG, "RTP->L1: %s\n", osmo_hexdump(data, *len)); } diff --git a/src/osmo-bts-octphy/l1_utils.h b/src/osmo-bts-octphy/l1_utils.h index d1a87170..1c1d2225 100644 --- a/src/osmo-bts-octphy/l1_utils.h +++ b/src/osmo-bts-octphy/l1_utils.h @@ -2,8 +2,8 @@ #include <osmocom/core/utils.h> -const struct value_string octphy_l1sapi_names[23]; -const struct value_string octphy_dir_names[5]; -const struct value_string octphy_clkmgr_state_vals[8]; -const struct value_string octphy_cid_vals[37]; -const struct value_string octphy_eid_vals[7]; +extern const struct value_string octphy_l1sapi_names[23]; +extern const struct value_string octphy_dir_names[5]; +extern const struct value_string octphy_clkmgr_state_vals[8]; +extern const struct value_string octphy_cid_vals[37]; +extern const struct value_string octphy_eid_vals[7]; diff --git a/src/osmo-bts-octphy/main.c b/src/osmo-bts-octphy/main.c index 928a4c81..56849b55 100644 --- a/src/osmo-bts-octphy/main.c +++ b/src/osmo-bts-octphy/main.c @@ -55,7 +55,8 @@ extern bool no_fw_check; int bts_model_print_help() { - printf(" -I --no-fw-check Override firmware version check\n"); + printf("\nModel specific options:\n"); + printf(" -I --no-fw-check Override firmware version check\n"); return 0; } diff --git a/src/osmo-bts-octphy/octphy_hw_api.c b/src/osmo-bts-octphy/octphy_hw_api.c index 6da038b1..271ed04f 100644 --- a/src/osmo-bts-octphy/octphy_hw_api.c +++ b/src/osmo-bts-octphy/octphy_hw_api.c @@ -119,7 +119,7 @@ static int rf_port_stats_compl_cb(struct octphy_hdl *fl1, struct msgb *resp, LOGP(DL1C, LOGL_INFO, "RF-PORT-STATS.resp Idx=%u RadioStandard=%s, " "Rx(Bytes=%u, Overflow=%u, AvgBps=%u, Period=%uus, Freq=%u) " - "Tx(Bytes=%i, Underflow=%u, AvgBps=%u, Period=%uus, Freq=%u)\n", + "Tx(Bytes=%d, Underflow=%u, AvgBps=%u, Period=%uus, Freq=%u)\n", psr->ulPortIndex, get_value_string(radio_std_vals, psr->ulRadioStandard), psr->RxStats.ulRxByteCnt, psr->RxStats.ulRxOverflowCnt, diff --git a/src/osmo-bts-octphy/octphy_vty.c b/src/osmo-bts-octphy/octphy_vty.c index d250a957..308252bb 100644 --- a/src/osmo-bts-octphy/octphy_vty.c +++ b/src/osmo-bts-octphy/octphy_vty.c @@ -54,13 +54,11 @@ #define OCT_STR "OCTPHY Um interface\n" -static struct gsm_bts *vty_bts; - /* configuration */ DEFUN(cfg_phy_hwaddr, cfg_phy_hwaddr_cmd, "octphy hw-addr HWADDR", - OCT_STR "Configure the hardware addess of the OCTPHY\n" + OCT_STR "Configure the hardware address of the OCTPHY\n" "hardware address in aa:bb:cc:dd:ee:ff format\n") { struct phy_link *plink = vty->index; @@ -360,7 +358,7 @@ DEFUN(show_clk_sync_stats, show_clk_sync_stats_cmd, return CMD_SUCCESS; } -void bts_model_config_write_phy(struct vty *vty, struct phy_link *plink) +void bts_model_config_write_phy(struct vty *vty, const struct phy_link *plink) { if (plink->u.octphy.netdev_name) vty_out(vty, " octphy net-device %s%s", @@ -399,15 +397,15 @@ void bts_model_config_write_phy(struct vty *vty, struct phy_link *plink) #endif } -void bts_model_config_write_phy_inst(struct vty *vty, struct phy_instance *pinst) +void bts_model_config_write_phy_inst(struct vty *vty, const struct phy_instance *pinst) { } -void bts_model_config_write_bts(struct vty *vty, struct gsm_bts *bts) +void bts_model_config_write_bts(struct vty *vty, const struct gsm_bts *bts) { } -void bts_model_config_write_trx(struct vty *vty, struct gsm_bts_trx *trx) +void bts_model_config_write_trx(struct vty *vty, const struct gsm_bts_trx *trx) { } @@ -437,10 +435,8 @@ DEFUN(show_sys_info, show_sys_info_cmd, } -int bts_model_vty_init(struct gsm_bts *bts) +int bts_model_vty_init(void *ctx) { - vty_bts = bts; - install_element(PHY_NODE, &cfg_phy_hwaddr_cmd); install_element(PHY_NODE, &cfg_phy_netdev_cmd); install_element(PHY_NODE, &cfg_phy_rf_port_idx_cmd); diff --git a/src/osmo-bts-octphy/octpkt.c b/src/osmo-bts-octphy/octpkt.c index d96d93d8..c6aea1f7 100644 --- a/src/osmo-bts-octphy/octpkt.c +++ b/src/osmo-bts-octphy/octpkt.c @@ -101,7 +101,7 @@ void octvc1_fill_msg_hdr(tOCTVC1_MSG_HEADER *mh, uint32_t len, #include <net/ethernet.h> /*! \brief Initialize a packet socket - * \param[in] tye Socket type like SOCK_RAW or SOCK_DGRAM + * \param[in] type Socket type like SOCK_RAW or SOCK_DGRAM * \param[in] proto The link-layer protocol in network byte order * \param[in] bind_dev The name of the interface to bind to (if any) * \param[in] flags flags like \ref OSMO_SOCK_F_BIND diff --git a/src/osmo-bts-omldummy/Makefile.am b/src/osmo-bts-omldummy/Makefile.am index 5a4ce7c5..f7a05047 100644 --- a/src/osmo-bts-omldummy/Makefile.am +++ b/src/osmo-bts-omldummy/Makefile.am @@ -1,6 +1,28 @@ -AM_CFLAGS = -Wall -fno-strict-aliasing $(LIBOSMOCORE_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(LIBOSMOTRAU_CFLAGS) $(LIBOSMOABIS_CFLAGS) $(LIBOSMOCTRL_CFLAGS) $(LIBOSMOABIS_CFLAGS) $(LIBGPS_CFLAGS) +AM_CFLAGS = \ + -Wall -fno-strict-aliasing \ + $(LIBOSMOCORE_CFLAGS) \ + $(LIBOSMOGSM_CFLAGS) \ + $(LIBOSMOVTY_CFLAGS) \ + $(LIBOSMOCTRL_CFLAGS) \ + $(LIBOSMOABIS_CFLAGS) \ + $(LIBOSMOTRAU_CFLAGS) \ + $(LIBOSMONETIF_CFLAGS) \ + $(LIBGPS_CFLAGS) \ + $(NULL) + AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include -Iinclude -COMMON_LDADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOGSM_LIBS) $(LIBOSMOVTY_LIBS) $(LIBOSMOTRAU_LIBS) $(LIBOSMOABIS_LIBS) $(LIBOSMOCTRL_LIBS) -ldl + +COMMON_LDADD = \ + $(LIBOSMOCORE_LIBS) \ + $(LIBOSMOGSM_LIBS) \ + $(LIBOSMOVTY_LIBS) \ + $(LIBOSMOCTRL_LIBS) \ + $(LIBOSMOABIS_LIBS) \ + $(LIBOSMOTRAU_LIBS) \ + $(LIBOSMONETIF_LIBS) \ + $(LIBOSMOCODEC_LIBS) \ + -ldl \ + $(NULL) bin_PROGRAMS = osmo-bts-omldummy diff --git a/src/osmo-bts-omldummy/bts_model.c b/src/osmo-bts-omldummy/bts_model.c index c0114015..0690d9db 100644 --- a/src/osmo-bts-omldummy/bts_model.c +++ b/src/osmo-bts-omldummy/bts_model.c @@ -10,7 +10,7 @@ * 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. + * 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/>. @@ -23,6 +23,7 @@ #include <osmocom/core/talloc.h> #include <osmocom/core/utils.h> #include <osmocom/codec/codec.h> +#include <osmocom/core/fsm.h> #include <osmo-bts/gsm_data.h> #include <osmo-bts/phy_link.h> @@ -34,6 +35,7 @@ #include <osmo-bts/bts_model.h> #include <osmo-bts/handover.h> #include <osmo-bts/l1sap.h> +#include <osmo-bts/nm_common_fsm.h> /* TODO: check if dummy method is sufficient, else implement */ int bts_model_lchan_deactivate(struct gsm_lchan *lchan) @@ -50,10 +52,9 @@ int osmo_amr_rtp_dec(const uint8_t *rtppayload, int payload_len, uint8_t *cmr, return -1; } -int bts_model_trx_close(struct gsm_bts_trx *trx) +void bts_model_trx_close(struct gsm_bts_trx *trx) { - LOGP(DL1C, LOGL_NOTICE, "Unimplemented %s\n", __func__); - return 0; + bts_model_trx_close_cb(trx, 0); } int bts_model_adjst_ms_pwr(struct gsm_lchan *lchan) @@ -72,18 +73,11 @@ int bts_model_check_oml(struct gsm_bts *bts, uint8_t msg_type, static uint8_t vbts_set_bts(struct gsm_bts *bts) { struct gsm_bts_trx *trx; - uint8_t tn; llist_for_each_entry(trx, &bts->trx_list, list) { - oml_mo_state_chg(&trx->mo, NM_OPSTATE_DISABLED, NM_AVSTATE_OK); - oml_mo_state_chg(&trx->bb_transc.mo, -1, NM_AVSTATE_OK); - - for (tn = 0; tn < TRX_NR_TS; tn++) - oml_mo_state_chg(&trx->ts[tn].mo, NM_OPSTATE_DISABLED, NM_AVSTATE_DEPENDENCY); - /* report availability of trx to the bts. this will trigger the rsl connection */ - oml_mo_tx_sw_act_rep(&trx->mo); - oml_mo_tx_sw_act_rep(&trx->bb_transc.mo); + osmo_fsm_inst_dispatch(trx->mo.fi, NM_EV_SW_ACT, NULL); + osmo_fsm_inst_dispatch(trx->bb_transc.mo.fi, NM_EV_SW_ACT, NULL); } return 0; } @@ -99,24 +93,28 @@ static uint8_t vbts_set_ts(struct gsm_bts_trx_ts *ts) return 0; } -int bts_model_apply_oml(struct gsm_bts *bts, struct msgb *msg, - struct tlv_parsed *new_attr, int kind, void *obj) +int bts_model_apply_oml(struct gsm_bts *bts, const struct msgb *msg, + struct gsm_abis_mo *mo, void *obj) { struct abis_om_fom_hdr *foh = msgb_l3(msg); - int cause = 0; + int rc; switch (foh->msg_type) { case NM_MT_SET_BTS_ATTR: - cause = vbts_set_bts(obj); + rc = vbts_set_bts(obj); break; case NM_MT_SET_RADIO_ATTR: - cause = vbts_set_trx(obj); + rc = vbts_set_trx(obj); break; case NM_MT_SET_CHAN_ATTR: - cause = vbts_set_ts(obj); + rc = vbts_set_ts(obj); + break; + default: + rc = 0; break; } - return oml_fom_ack_nack(msg, cause); + + return rc; } /* MO: TS 12.21 Managed Object */ @@ -125,16 +123,15 @@ int bts_model_opstart(struct gsm_bts *bts, struct gsm_abis_mo *mo, void *obj) int rc; switch (mo->obj_class) { - case NM_OC_RADIO_CARRIER: - case NM_OC_CHANNEL: case NM_OC_SITE_MANAGER: - case NM_OC_BASEB_TRANSC: case NM_OC_BTS: + case NM_OC_BASEB_TRANSC: + case NM_OC_RADIO_CARRIER: + case NM_OC_CHANNEL: case NM_OC_GPRS_NSE: case NM_OC_GPRS_CELL: case NM_OC_GPRS_NSVC: - oml_mo_state_chg(mo, NM_OPSTATE_ENABLED, NM_AVSTATE_OK); - rc = oml_mo_opstart_ack(mo); + rc = osmo_fsm_inst_dispatch(mo->fi, NM_EV_OPSTART_ACK, NULL); break; default: rc = oml_mo_opstart_nack(mo, NM_NACK_OBJCLASS_NOTSUPP); @@ -151,14 +148,13 @@ int bts_model_chg_adm_state(struct gsm_bts *bts, struct gsm_abis_mo *mo, int bts_model_trx_deact_rf(struct gsm_bts_trx *trx) { - LOGP(DL1C, LOGL_NOTICE, "Unimplemented %s\n", __func__); return 0; } int bts_model_change_power(struct gsm_bts_trx *trx, int p_trxout_mdBm) { - LOGP(DL1C, LOGL_NOTICE, "Unimplemented %s\n", __func__); + power_trx_change_compl(trx, p_trxout_mdBm); return 0; } @@ -168,7 +164,7 @@ int bts_model_ctrl_cmds_install(struct gsm_bts *bts) return 0; } -uint32_t trx_get_hlayer1(struct gsm_bts_trx *trx) +uint32_t trx_get_hlayer1(const struct gsm_bts_trx *trx) { return 0; } @@ -176,11 +172,20 @@ uint32_t trx_get_hlayer1(struct gsm_bts_trx *trx) int bts_model_init(struct gsm_bts *bts) { bts->variant = BTS_OSMO_OMLDUMMY; + /* order alphabetically */ + osmo_bts_set_feature(bts->features, BTS_FEAT_BCCH_POWER_RED); + osmo_bts_set_feature(bts->features, BTS_FEAT_CBCH); + osmo_bts_set_feature(bts->features, BTS_FEAT_HOPPING); + osmo_bts_set_feature(bts->features, BTS_FEAT_VBS); + osmo_bts_set_feature(bts->features, BTS_FEAT_VGCS); return 0; } int bts_model_trx_init(struct gsm_bts_trx *trx) { + struct trx_power_params *tpp = &trx->power_params; + /* Speed up shutdown, we don't care about power ramping in omldummy */ + tpp->ramp.step_interval_sec = 0; return 0; } @@ -220,3 +225,8 @@ int bts_model_l1sap_down(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap) { return 0; } + +int bts_model_phy_link_open(struct phy_link *plink) +{ + return 0; +} diff --git a/src/osmo-bts-omldummy/main.c b/src/osmo-bts-omldummy/main.c index 3f1d58c5..167d43a2 100644 --- a/src/osmo-bts-omldummy/main.c +++ b/src/osmo-bts-omldummy/main.c @@ -1,35 +1,136 @@ +#include <unistd.h> +#include <stdlib.h> +#include <stdio.h> + +#define _GNU_SOURCE +#include <getopt.h> #include <osmocom/core/talloc.h> #include <osmocom/core/application.h> #include <osmo-bts/logging.h> #include <osmo-bts/abis.h> #include <osmo-bts/bts.h> +#include <osmo-bts/bts_sm.h> #include <osmo-bts/oml.h> +static void print_usage(const char *prog_name) +{ + printf("Usage: %s [-h] [--features FOO,BAR,BAZ] dst_host site_id [trx_num]\n", prog_name); +} + +static void print_help(const char *prog_name) +{ + print_usage(prog_name); + printf(" -h --help This text.\n"); + printf(" -f --features FOO,BAR,BAZ BTS features to issue on OML startup.\n" + " The names correspond to BTS_FEAT_* constants\n" + " as defined in osmocom/gsm/bts_features.h,\n" + " e.g. '-f VAMOS'\n"); +} + +struct { + char *dst_host; + int site_id; + int trx_num; + char *features; +} cmdline = { + .trx_num = 8, +}; + +void parse_cmdline(int argc, char **argv) +{ + while (1) { + int option_index = 0, c; + static struct option long_options[] = { + {"help", 0, 0, 'h'}, + {"features", 1, 0, 'f'}, + {0} + }; + + c = getopt_long(argc, argv, "hf:", long_options, &option_index); + if (c == -1) + break; + + switch (c) { + case 'h': + print_help(argv[0]); + exit(0); + case 'f': + cmdline.features = optarg; + break; + default: + /* catch unknown options *as well as* missing arguments. */ + fprintf(stderr, "Error in command line options. Exiting.\n"); + exit(-1); + } + } + + if (optind + 2 > argc) { + print_usage(argv[0]); + exit(1); + } + + cmdline.dst_host = argv[optind]; + cmdline.site_id = atoi(argv[optind + 1]); + if (optind + 2 < argc) + cmdline.trx_num = atoi(argv[optind + 2]); + + if (optind + 3 < argc) { + print_usage(argv[0]); + exit(1); + } +} + +void set_bts_features(struct bitvec *features, char *features_str) +{ + char *saveptr = NULL; + char *token; + + if (!features_str) + return; + + while ((token = strtok_r(features_str, ",", &saveptr))) { + enum osmo_bts_features feat; + features_str = NULL; + + feat = get_string_value(osmo_bts_features_names, token); + + if ((int)feat < 0) { + fprintf(stderr, "Unknown BTS feature: '%s'\n", token); + exit(-1); + } + + osmo_bts_set_feature(features, feat); + } +} int main(int argc, char **argv) { struct gsm_bts *bts; struct gsm_bts_trx *trx; - struct e1inp_line *line; + struct bsc_oml_host *bsc_oml_host; int i; - char *dst_host = argv[1]; - int site_id = atoi(argv[2]); + parse_cmdline(argc, argv); tall_bts_ctx = talloc_named_const(NULL, 1, "OsmoBTS context"); msgb_talloc_ctx_init(tall_bts_ctx, 10*1024); osmo_init_logging2(tall_bts_ctx, &bts_log_info); - bts = gsm_bts_alloc(tall_bts_ctx, 0); + g_bts_sm = gsm_bts_sm_alloc(tall_bts_ctx); + if (!g_bts_sm) + exit(1); + + bts = gsm_bts_alloc(g_bts_sm, 0); if (!bts) exit(1); - bts->ip_access.site_id = site_id; + + bts->ip_access.site_id = cmdline.site_id; bts->ip_access.bts_id = 0; /* Additional TRXs */ - for (i = 1; i < 8; i++) { + for (i = 1; i < cmdline.trx_num; i++) { trx = gsm_bts_trx_alloc(bts); if (!trx) exit(1); @@ -37,13 +138,25 @@ int main(int argc, char **argv) if (bts_init(bts) < 0) exit(1); + + set_bts_features(bts->features, cmdline.features); + + /* VAMOS: allocate shadow timeslots for each TRX */ + if (osmo_bts_has_feature(bts->features, BTS_FEAT_VAMOS)) { + llist_for_each_entry(trx, &bts->trx_list, list) + gsm_bts_trx_init_shadow_ts(trx); + } + //btsb = bts_role_bts(bts); abis_init(bts); - - line = abis_open(bts, dst_host, "OMLdummy"); - if (!line) - exit(2); + bsc_oml_host = talloc_zero(bts, struct bsc_oml_host); + OSMO_ASSERT(bsc_oml_host); + bsc_oml_host->addr = talloc_strdup(bsc_oml_host, cmdline.dst_host); + OSMO_ASSERT(bsc_oml_host->addr); + llist_add_tail(&bsc_oml_host->list, &bts->bsc_oml_hosts); + if (abis_open(bts, "OMLdummy") != 0) + exit(1); while (1) { osmo_select_main(0); diff --git a/src/osmo-bts-sysmo/Makefile.am b/src/osmo-bts-sysmo/Makefile.am index 4901ea3c..12dea3b4 100644 --- a/src/osmo-bts-sysmo/Makefile.am +++ b/src/osmo-bts-sysmo/Makefile.am @@ -1,16 +1,62 @@ AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include $(SYSMOBTS_INCDIR) -AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOCODEC_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(LIBOSMOTRAU_CFLAGS) $(LIBOSMOABIS_CFLAGS) $(LIBOSMOCTRL_CFLAGS) $(LIBOSMOABIS_CFLAGS) $(LIBGPS_CFLAGS) -COMMON_LDADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOCODEC_LIBS) $(LIBOSMOGSM_LIBS) $(LIBOSMOVTY_LIBS) $(LIBOSMOTRAU_LIBS) $(LIBOSMOABIS_LIBS) $(LIBOSMOCTRL_LIBS) +AM_CFLAGS = \ + -Wall \ + $(LIBOSMOCORE_CFLAGS) \ + $(LIBOSMOCODEC_CFLAGS) \ + $(LIBOSMOGSM_CFLAGS) \ + $(LIBOSMOVTY_CFLAGS) \ + $(LIBOSMOCTRL_CFLAGS) \ + $(LIBOSMOABIS_CFLAGS) \ + $(LIBOSMOTRAU_CFLAGS) \ + $(LIBOSMONETIF_CFLAGS) \ + $(LIBGPS_CFLAGS) \ + $(NULL) -EXTRA_DIST = misc/sysmobts_mgr.h misc/sysmobts_misc.h misc/sysmobts_par.h \ - misc/sysmobts_eeprom.h misc/sysmobts_nl.h femtobts.h hw_misc.h \ +COMMON_LDADD = \ + $(LIBOSMOCORE_LIBS) \ + $(LIBOSMOCODEC_LIBS) \ + $(LIBOSMOGSM_LIBS) \ + $(LIBOSMOVTY_LIBS) \ + $(LIBOSMOCTRL_LIBS) \ + $(LIBOSMOABIS_LIBS) \ + $(LIBOSMOTRAU_LIBS) \ + $(LIBOSMONETIF_LIBS) \ + $(NULL) + +EXTRA_DIST = \ + misc/sysmobts_mgr.h \ + misc/sysmobts_misc.h \ + misc/sysmobts_par.h \ + misc/sysmobts_eeprom.h \ + misc/sysmobts_nl.h \ misc/sysmobts-layer1.h \ - l1_fwd.h l1_if.h l1_transp.h eeprom.h utils.h oml_router.h + femtobts.h \ + hw_misc.h \ + l1_fwd.h \ + l1_if.h \ + l1_transp.h \ + eeprom.h \ + utils.h \ + $(NULL) bin_PROGRAMS = osmo-bts-sysmo osmo-bts-sysmo-remote l1fwd-proxy sysmobts-mgr sysmobts-util -COMMON_SOURCES = main.c femtobts.c l1_if.c oml.c sysmobts_vty.c tch.c hw_misc.c calib_file.c \ - eeprom.c calib_fixup.c utils.c misc/sysmobts_par.c oml_router.c sysmobts_ctrl.c +COMMON_SOURCES = \ + main.c \ + femtobts.c \ + l1_if.c \ + oml.c \ + sysmobts_vty.c \ + tch.c \ + hw_misc.c \ + calib_file.c \ + eeprom.c \ + calib_fixup.c \ + utils.c \ + misc/sysmobts_par.c \ + sysmobts_ctrl.c \ + $(NULL) + osmo_bts_sysmo_SOURCES = $(COMMON_SOURCES) l1_transp_hw.c osmo_bts_sysmo_LDADD = $(top_builddir)/src/common/libbts.a $(COMMON_LDADD) @@ -22,7 +68,7 @@ l1fwd_proxy_SOURCES = l1_fwd_main.c l1_transp_hw.c l1fwd_proxy_LDADD = $(top_builddir)/src/common/libbts.a $(COMMON_LDADD) if ENABLE_SYSMOBTS_CALIB -bin_PROGRAMS = sysmobts-calib +bin_PROGRAMS += sysmobts-calib sysmobts_calib_SOURCES = misc/sysmobts-calib.c misc/sysmobts-layer1.c sysmobts_calib_LDADD = -lrt $(COMMON_LDADD) @@ -37,7 +83,15 @@ sysmobts_mgr_SOURCES = \ misc/sysmobts_mgr_temp.c \ misc/sysmobts_mgr_calib.c \ eeprom.c -sysmobts_mgr_LDADD = $(LIBGPS_LIBS) $(top_builddir)/src/common/libbts.a $(COMMON_LDADD) +sysmobts_mgr_LDADD = \ + $(LIBGPS_LIBS) \ + $(top_builddir)/src/common/libbts.a \ + $(COMMON_LDADD) \ + $(NULL) -sysmobts_util_SOURCES = misc/sysmobts_util.c misc/sysmobts_par.c eeprom.c +sysmobts_util_SOURCES = \ + misc/sysmobts_util.c \ + misc/sysmobts_par.c \ + eeprom.c \ + $(NULL) sysmobts_util_LDADD = $(LIBOSMOCORE_LIBS) diff --git a/src/osmo-bts-sysmo/calib_file.c b/src/osmo-bts-sysmo/calib_file.c index 2f723dd0..bb6cabfd 100644 --- a/src/osmo-bts-sysmo/calib_file.c +++ b/src/osmo-bts-sysmo/calib_file.c @@ -12,7 +12,7 @@ * 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. + * 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-bts-sysmo/eeprom.c b/src/osmo-bts-sysmo/eeprom.c index 472b78e2..6c887d41 100644 --- a/src/osmo-bts-sysmo/eeprom.c +++ b/src/osmo-bts-sysmo/eeprom.c @@ -226,12 +226,12 @@ typedef struct char szSn[16]; ///< Serial number uint32_t u8Rev : 8; ///< Board revision - uint32_t u2Tcxo : 2; ///< TCXO present (0:absent, 1:present, x:unknows) - uint32_t u2Ocxo : 2; ///< OCXO present (0:absent, 1:present, x:unknows) - uint32_t u2GSM850 : 2; ///< GSM-850 supported (0:unsupported, 1:supported, x:unknows) - uint32_t u2GSM900 : 2; ///< GSM-900 supported (0:unsupported, 1:supported, x:unknows) - uint32_t u2DCS1800 : 2; ///< GSM-1800 supported (0:unsupported, 1:supported, x:unknows) - uint32_t u2PCS1900 : 2; ///< GSM-1900 supported (0:unsupported, 1:supported, x:unknows) + uint32_t u2Tcxo : 2; ///< TCXO present (0:absent, 1:present, x:unknown) + uint32_t u2Ocxo : 2; ///< OCXO present (0:absent, 1:present, x:unknown) + uint32_t u2GSM850 : 2; ///< GSM-850 supported (0:unsupported, 1:supported, x:unknown) + uint32_t u2GSM900 : 2; ///< GSM-900 supported (0:unsupported, 1:supported, x:unknown) + uint32_t u2DCS1800 : 2; ///< GSM-1800 supported (0:unsupported, 1:supported, x:unknown) + uint32_t u2PCS1900 : 2; ///< GSM-1900 supported (0:unsupported, 1:supported, x:unknown) uint32_t : 12; ///< unused } __attribute__((packed)) sysInfo; @@ -307,12 +307,12 @@ typedef struct uint32_t u32Time; ///< Epoch time char szSn[16]; ///< Serial number uint32_t u8Rev : 8; ///< Board revision - uint32_t u2Tcxo : 2; ///< TCXO present (0:absent, 1:present, x:unknows) - uint32_t u2Ocxo : 2; ///< OCXO present (0:absent, 1:present, x:unknows) - uint32_t u2GSM850 : 2; ///< GSM-850 supported (0:unsupported, 1:supported, x:unknows) - uint32_t u2GSM900 : 2; ///< GSM-900 supported (0:unsupported, 1:supported, x:unknows) - uint32_t u2DCS1800 : 2; ///< GSM-1800 supported (0:unsupported, 1:supported, x:unknows) - uint32_t u2PCS1900 : 2; ///< GSM-1900 supported (0:unsupported, 1:supported, x:unknows) + uint32_t u2Tcxo : 2; ///< TCXO present (0:absent, 1:present, x:unknown) + uint32_t u2Ocxo : 2; ///< OCXO present (0:absent, 1:present, x:unknown) + uint32_t u2GSM850 : 2; ///< GSM-850 supported (0:unsupported, 1:supported, x:unknown) + uint32_t u2GSM900 : 2; ///< GSM-900 supported (0:unsupported, 1:supported, x:unknown) + uint32_t u2DCS1800 : 2; ///< GSM-1800 supported (0:unsupported, 1:supported, x:unknown) + uint32_t u2PCS1900 : 2; ///< GSM-1900 supported (0:unsupported, 1:supported, x:unknown) uint32_t : 12; ///< unused } __attribute__((packed)) sysInfo; diff --git a/src/osmo-bts-sysmo/femtobts.c b/src/osmo-bts-sysmo/femtobts.c index 480fe06b..7b02adc5 100644 --- a/src/osmo-bts-sysmo/femtobts.c +++ b/src/osmo-bts-sysmo/femtobts.c @@ -12,7 +12,7 @@ * 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. + * 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-bts-sysmo/femtobts.h b/src/osmo-bts-sysmo/femtobts.h index 9163ebbf..b048fc4e 100644 --- a/src/osmo-bts-sysmo/femtobts.h +++ b/src/osmo-bts-sysmo/femtobts.h @@ -68,25 +68,25 @@ enum uperfemto_clk_src { }; #endif -const enum l1prim_type femtobts_l1prim_type[GsmL1_PrimId_NUM]; -const struct value_string femtobts_l1prim_names[GsmL1_PrimId_NUM+1]; -const GsmL1_PrimId_t femtobts_l1prim_req2conf[GsmL1_PrimId_NUM]; +extern const enum l1prim_type femtobts_l1prim_type[GsmL1_PrimId_NUM]; +extern const struct value_string femtobts_l1prim_names[GsmL1_PrimId_NUM+1]; +extern const GsmL1_PrimId_t femtobts_l1prim_req2conf[GsmL1_PrimId_NUM]; -const enum l1prim_type femtobts_sysprim_type[SuperFemto_PrimId_NUM]; -const struct value_string femtobts_sysprim_names[SuperFemto_PrimId_NUM+1]; -const SuperFemto_PrimId_t femtobts_sysprim_req2conf[SuperFemto_PrimId_NUM]; +extern const enum l1prim_type femtobts_sysprim_type[SuperFemto_PrimId_NUM]; +extern const struct value_string femtobts_sysprim_names[SuperFemto_PrimId_NUM+1]; +extern const SuperFemto_PrimId_t femtobts_sysprim_req2conf[SuperFemto_PrimId_NUM]; -const struct value_string femtobts_l1sapi_names[GsmL1_Sapi_NUM+1]; -const struct value_string femtobts_l1status_names[GSML1_STATUS_NUM+1]; +extern const struct value_string femtobts_l1sapi_names[GsmL1_Sapi_NUM+1]; +extern const struct value_string femtobts_l1status_names[GSML1_STATUS_NUM+1]; -const struct value_string femtobts_tracef_names[29]; -const struct value_string femtobts_tracef_docs[29]; +extern const struct value_string femtobts_tracef_names[29]; +extern const struct value_string femtobts_tracef_docs[29]; -const struct value_string femtobts_tch_pl_names[15]; -const struct value_string femtobts_chcomb_names[8]; -const struct value_string femtobts_clksrc_names[10]; +extern const struct value_string femtobts_tch_pl_names[15]; +extern const struct value_string femtobts_chcomb_names[8]; +extern const struct value_string femtobts_clksrc_names[10]; -const struct value_string femtobts_dir_names[6]; +extern const struct value_string femtobts_dir_names[6]; enum pdch_cs { PDCH_CS_1, @@ -105,6 +105,6 @@ enum pdch_cs { _NUM_PDCH_CS }; -const uint8_t pdch_msu_size[_NUM_PDCH_CS]; +extern const uint8_t pdch_msu_size[_NUM_PDCH_CS]; #endif /* FEMTOBTS_H */ diff --git a/src/osmo-bts-sysmo/hw_misc.c b/src/osmo-bts-sysmo/hw_misc.c index 6aa3b83f..21d8dfdf 100644 --- a/src/osmo-bts-sysmo/hw_misc.c +++ b/src/osmo-bts-sysmo/hw_misc.c @@ -12,7 +12,7 @@ * 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. + * 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-bts-sysmo/l1_fwd_main.c b/src/osmo-bts-sysmo/l1_fwd_main.c index bc9fc21c..76c45e17 100644 --- a/src/osmo-bts-sysmo/l1_fwd_main.c +++ b/src/osmo-bts-sysmo/l1_fwd_main.c @@ -12,7 +12,7 @@ * 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. + * 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/>. @@ -213,9 +213,7 @@ int main(int argc, char **argv) wq->write_cb = udp_write_cb; wq->read_cb = udp_read_cb; - wq->bfd.when |= BSC_FD_READ; - wq->bfd.data = l1fh; - wq->bfd.priv_nr = i; + osmo_fd_setup(&wq->bfd, -1, OSMO_FD_READ, osmo_wqueue_bfd_cb, l1fh, i); rc = osmo_sock_init_ofd(&wq->bfd, AF_UNSPEC, SOCK_DGRAM, IPPROTO_UDP, NULL, fwd_udp_ports[i], OSMO_SOCK_F_BIND); diff --git a/src/osmo-bts-sysmo/l1_if.c b/src/osmo-bts-sysmo/l1_if.c index df39e2f4..aa4a7399 100644 --- a/src/osmo-bts-sysmo/l1_if.c +++ b/src/osmo-bts-sysmo/l1_if.c @@ -13,7 +13,7 @@ * 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. + * 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/>. @@ -51,6 +51,7 @@ #include <osmo-bts/msg_utils.h> #include <osmo-bts/dtx_dl_amr_fsm.h> #include <osmo-bts/tx_power.h> +#include <osmo-bts/nm_common_fsm.h> #include <sysmocom/femtobts/superfemto.h> #include <sysmocom/femtobts/gsml1prim.h> @@ -343,7 +344,7 @@ static int ph_data_req(struct gsm_bts_trx *trx, struct msgb *msg, abort(); } - len = msgb_l2len(msg); + len = (msg->l2h) ? msgb_l2len(msg) : 0; chan_nr = l1sap->u.data.chan_nr; link_id = l1sap->u.data.link_id; @@ -391,9 +392,8 @@ static int ph_data_req(struct gsm_bts_trx *trx, struct msgb *msg, else sapi = GsmL1_Sapi_Agch; } else { - LOGPFN(DL1C, LOGL_NOTICE, u32Fn, "unknown prim %d op %d " - "chan_nr %d link_id %d\n", l1sap->oph.primitive, - l1sap->oph.operation, chan_nr, link_id); + LOGPLCFN(lchan, u32Fn, DL1C, LOGL_NOTICE, "unknown prim %d op %d " "chan_nr %d link_id %d\n", + l1sap->oph.primitive, l1sap->oph.operation, chan_nr, link_id); msgb_free(l1msg); return -EINVAL; } @@ -454,9 +454,8 @@ static int ph_data_req(struct gsm_bts_trx *trx, struct msgb *msg, memcpy(l1p->u.phDataReq.msgUnitParam.u8Buffer, msg->l2h, msgb_l2len(msg)); } - LOGPFN(DL1P, LOGL_DEBUG, u32Fn, "PH-DATA.req(%s)\n", - osmo_hexdump(l1p->u.phDataReq.msgUnitParam.u8Buffer, - l1p->u.phDataReq.msgUnitParam.u8Size)); + LOGPLCFN(lchan, u32Fn, DL1P, LOGL_DEBUG, "PH-DATA.req(%s)\n", + osmo_hexdump(l1p->u.phDataReq.msgUnitParam.u8Buffer, l1p->u.phDataReq.msgUnitParam.u8Size)); } else { /* empty frame */ GsmL1_Prim_t *l1p = msgb_l1prim(l1msg); @@ -466,7 +465,7 @@ static int ph_data_req(struct gsm_bts_trx *trx, struct msgb *msg, /* send message to DSP's queue */ if (osmo_wqueue_enqueue(&fl1->write_q[MQ_L1_WRITE], l1msg) != 0) { - LOGPFN(DL1P, LOGL_ERROR, u32Fn, "MQ_L1_WRITE queue full. Dropping msg.\n"); + LOGPLCFN(lchan, u32Fn, DL1P, LOGL_ERROR, "MQ_L1_WRITE queue full. Dropping msg.\n"); msgb_free(l1msg); } else dtx_int_signal(lchan); @@ -504,7 +503,6 @@ static int ph_tch_req(struct gsm_bts_trx *trx, struct msgb *msg, /* create new message and fill data */ if (msg) { - msgb_pull(msg, sizeof(*l1sap)); /* create new message */ nmsg = l1p_msgb_alloc(); if (!nmsg) @@ -513,7 +511,7 @@ static int ph_tch_req(struct gsm_bts_trx *trx, struct msgb *msg, rc = l1if_tch_encode(lchan, l1p->u.phDataReq.msgUnitParam.u8Buffer, &l1p->u.phDataReq.msgUnitParam.u8Size, - msg->data, msg->len, u32Fn, use_cache, + msgb_l2(msg), msgb_l2len(msg), u32Fn, use_cache, l1sap->u.tch.marker); if (rc < 0) { /* no data encoded for L1: smth will be generated below */ @@ -550,7 +548,7 @@ static int ph_tch_req(struct gsm_bts_trx *trx, struct msgb *msg, } /* send message to DSP's queue */ if (osmo_wqueue_enqueue(&fl1->write_q[MQ_L1_WRITE], nmsg) != 0) { - LOGPFN(DL1P, LOGL_ERROR, u32Fn, "MQ_L1_WRITE queue full. Dropping msg.\n"); + LOGPLCFN(lchan, u32Fn, DL1P, LOGL_ERROR, "MQ_L1_WRITE queue full. Dropping msg.\n"); msgb_free(nmsg); return -ENOBUFS; } @@ -606,6 +604,15 @@ static int mph_info_req(struct gsm_bts_trx *trx, struct msgb *msg, else l1if_rsl_chan_rel(lchan); break; + case PRIM_INFO_ACT_UL_ACC: + case PRIM_INFO_DEACT_UL_ACC: + chan_nr = l1sap->u.info.u.ulacc_req.chan_nr; + lchan = get_lchan_by_chan_nr(trx, chan_nr); + if (l1sap->u.info.type == PRIM_INFO_ACT_UL_ACC) + l1if_set_ul_acc(lchan, true); + else + l1if_set_ul_acc(lchan, false); + break; default: LOGP(DL1C, LOGL_NOTICE, "unknown MPH-INFO.req %d\n", l1sap->u.info.type); @@ -682,7 +689,7 @@ static enum gsm_phys_chan_config pick_pchan(struct gsm_bts_trx_ts *ts) if (ts->flags & TS_F_PDCH_ACTIVE) return GSM_PCHAN_PDCH; return GSM_PCHAN_TCH_F; - case GSM_PCHAN_TCH_F_TCH_H_PDCH: + case GSM_PCHAN_OSMO_DYN: return ts->dyn.pchan_is; default: return ts->pchan; @@ -696,7 +703,7 @@ static uint8_t chan_nr_by_sapi(struct gsm_bts_trx_ts *ts, uint8_t cbits = 0; enum gsm_phys_chan_config pchan = pick_pchan(ts); OSMO_ASSERT(pchan != GSM_PCHAN_TCH_F_PDCH); - OSMO_ASSERT(pchan != GSM_PCHAN_TCH_F_TCH_H_PDCH); + OSMO_ASSERT(pchan != GSM_PCHAN_OSMO_DYN); switch (sapi) { case GsmL1_Sapi_Bcch: @@ -796,6 +803,45 @@ static uint8_t chan_nr_by_sapi(struct gsm_bts_trx_ts *ts, return (cbits << 3) | u8Tn; } +static const enum l1sap_common_sapi common_sapi_by_sapi_t[] = { + [GsmL1_Sapi_Idle] = L1SAP_COMMON_SAPI_IDLE, + [GsmL1_Sapi_Fcch] = L1SAP_COMMON_SAPI_FCCH, + [GsmL1_Sapi_Sch] = L1SAP_COMMON_SAPI_SCH, + [GsmL1_Sapi_Sacch] = L1SAP_COMMON_SAPI_SACCH, + [GsmL1_Sapi_Sdcch] = L1SAP_COMMON_SAPI_SDCCH, + [GsmL1_Sapi_Bcch] = L1SAP_COMMON_SAPI_BCCH, + [GsmL1_Sapi_Pch] = L1SAP_COMMON_SAPI_PCH, + [GsmL1_Sapi_Agch] = L1SAP_COMMON_SAPI_AGCH, + [GsmL1_Sapi_Cbch] = L1SAP_COMMON_SAPI_CBCH, + [GsmL1_Sapi_Rach] = L1SAP_COMMON_SAPI_RACH, + [GsmL1_Sapi_TchF] = L1SAP_COMMON_SAPI_TCH_F, + [GsmL1_Sapi_FacchF] = L1SAP_COMMON_SAPI_FACCH_F, + [GsmL1_Sapi_TchH] = L1SAP_COMMON_SAPI_TCH_H, + [GsmL1_Sapi_FacchH] = L1SAP_COMMON_SAPI_FACCH_H, + [GsmL1_Sapi_Nch] = L1SAP_COMMON_SAPI_NCH, + [GsmL1_Sapi_Pdtch] = L1SAP_COMMON_SAPI_PDTCH, + [GsmL1_Sapi_Pacch] = L1SAP_COMMON_SAPI_PACCH, + [GsmL1_Sapi_Pbcch] = L1SAP_COMMON_SAPI_PBCCH, + [GsmL1_Sapi_Pagch] = L1SAP_COMMON_SAPI_PAGCH, + [GsmL1_Sapi_Ppch] = L1SAP_COMMON_SAPI_PPCH, + [GsmL1_Sapi_Pnch] = L1SAP_COMMON_SAPI_PNCH, + [GsmL1_Sapi_Ptcch] = L1SAP_COMMON_SAPI_PTCCH, + [GsmL1_Sapi_Prach] = L1SAP_COMMON_SAPI_PRACH, +}; + +static enum l1sap_common_sapi get_common_sapi(GsmL1_Sapi_t sapi) +{ + if (sapi >= GsmL1_Sapi_NUM) + return L1SAP_COMMON_SAPI_UNKNOWN; + return common_sapi_by_sapi_t[sapi]; +} + +static void set_log_ctx_sapi(GsmL1_Sapi_t sapi) +{ + l1sap_log_ctx_sapi = get_common_sapi(sapi); + log_set_context(LOG_CTX_L1_SAPI, &l1sap_log_ctx_sapi); +} + static int handle_ph_readytosend_ind(struct femtol1_hdl *fl1, GsmL1_PhReadyToSendInd_t *rts_ind, struct msgb *l1p_msg) @@ -812,6 +858,8 @@ static int handle_ph_readytosend_ind(struct femtol1_hdl *fl1, uint8_t chan_nr, link_id; uint32_t fn; + set_log_ctx_sapi(rts_ind->sapi); + /* check if primitive should be handled by common part */ chan_nr = chan_nr_by_sapi(&trx->ts[rts_ind->u8Tn], rts_ind->sapi, rts_ind->subCh, rts_ind->u8Tn, rts_ind->u32Fn); @@ -896,31 +944,8 @@ empty_frame: goto tx; } -static void dump_meas_res(int ll, GsmL1_MeasParam_t *m) -{ - LOGPC(DL1C, ll, ", Meas: RSSI %-3.2f dBm, Qual %-3.2f dB, " - "BER %-3.2f, Timing %d\n", m->fRssi, m->fLinkQuality, - m->fBer, m->i16BurstTiming); -} - -static int process_meas_res(struct gsm_bts_trx *trx, uint8_t chan_nr, - uint32_t fn, GsmL1_MeasParam_t *m) -{ - struct osmo_phsap_prim l1sap; - memset(&l1sap, 0, sizeof(l1sap)); - osmo_prim_init(&l1sap.oph, SAP_GSM_PH, PRIM_MPH_INFO, - PRIM_OP_INDICATION, NULL); - l1sap.u.info.type = PRIM_INFO_MEAS; - l1sap.u.info.u.meas_ind.chan_nr = chan_nr; - l1sap.u.info.u.meas_ind.ta_offs_256bits = m->i16BurstTiming * 64; - l1sap.u.info.u.meas_ind.ber10k = (unsigned int) (m->fBer * 10000); - l1sap.u.info.u.meas_ind.inv_rssi = (uint8_t) (m->fRssi * -1); - l1sap.u.info.u.meas_ind.fn = fn; - - /* l1sap wants to take msgb ownership. However, as there is no - * msg, it will msgb_free(l1sap.oph.msg == NULL) */ - return l1sap_up(trx, &l1sap); -} +#define LOG_FMT_MEAS "Meas: RSSI %-3.2f dBm, Qual %-3.2f dB, BER %-3.2f, Timing %d" +#define LOG_PARAM_MEAS(meas_param) (meas_param)->fRssi, (meas_param)->fLinkQuality, (meas_param)->fBer, (meas_param)->i16BurstTiming static int handle_ph_data_ind(struct femtol1_hdl *fl1, GsmL1_PhDataInd_t *data_ind, struct msgb *l1p_msg) @@ -933,6 +958,8 @@ static int handle_ph_data_ind(struct femtol1_hdl *fl1, GsmL1_PhDataInd_t *data_i struct gsm_time g_time; int rc = 0; + set_log_ctx_sapi(data_ind->sapi); + chan_nr = chan_nr_by_sapi(&trx->ts[data_ind->u8Tn], data_ind->sapi, data_ind->subCh, data_ind->u8Tn, data_ind->u32Fn); if (!chan_nr) { @@ -944,14 +971,12 @@ static int handle_ph_data_ind(struct femtol1_hdl *fl1, GsmL1_PhDataInd_t *data_i fn = data_ind->u32Fn; link_id = (data_ind->sapi == GsmL1_Sapi_Sacch) ? LID_SACCH : LID_DEDIC; - process_meas_res(trx, chan_nr, fn, &data_ind->measParam); - gsm_fn2gsmtime(&g_time, fn); - DEBUGPGT(DL1P, &g_time, "Rx PH-DATA.ind %s (hL2 %08x): %s\n", + DEBUGPGT(DL1P, &g_time, "Rx PH-DATA.ind %s (hL2 %08x): %s, " LOG_FMT_MEAS "\n", get_value_string(femtobts_l1sapi_names, data_ind->sapi), data_ind->hLayer2, - osmo_hexdump(data_ind->msgUnitParam.u8Buffer, data_ind->msgUnitParam.u8Size)); - dump_meas_res(LOGL_DEBUG, &data_ind->measParam); + osmo_hexdump(data_ind->msgUnitParam.u8Buffer, data_ind->msgUnitParam.u8Size), + LOG_PARAM_MEAS(&data_ind->measParam)); /* check for TCH */ if (data_ind->sapi == GsmL1_Sapi_TchF @@ -962,6 +987,30 @@ static int handle_ph_data_ind(struct femtol1_hdl *fl1, GsmL1_PhDataInd_t *data_i return rc; } + /* When TCH UL receiver in sysmoBTS PHY detects FACCH, + * it behaves as follows (observed): + * + * - In each 20 ms window (both TCH/F and TCH/H), a single + * PH-DATA.ind arrives. + * - In the case of TCH/F, this PH-DATA.ind carries GsmL1_Sapi_FacchF + * instead of GsmL1_Sapi_TchF. + * - In the case of TCH/H, the PH-DATA.ind for block 0 carries + * GsmL1_Sapi_FacchH instead of GsmL1_Sapi_TchH. However, + * in the following 20 ms window (block 1 of FACCH/H) + * a PH-DATA.ind with GsmL1_Sapi_TchH arrives as normal. + * Typically the latter PHY message carries a BFI (0 length payload), + * but nonzero payloads have also been observed under some conditions. + * (The latter case remains to be investigated further.) + * + * In those 20 ms windows where we receive PH-DATA.ind for FACCH + * instead of the usual TCH kind, we need to pass an empty payload + * to the upper layers so we can produce the correct effect in + * the outgoing RTP stream depending on configuration. + */ + if (data_ind->sapi == GsmL1_Sapi_FacchF || + data_ind->sapi == GsmL1_Sapi_FacchH) + l1if_tch_rx_facch(trx, chan_nr, l1p_msg); + /* fill L1SAP header */ sap_msg = l1sap_msgb_alloc(data_ind->msgUnitParam.u8Size); l1sap = msgb_l1sap_prim(sap_msg); @@ -971,11 +1020,10 @@ static int handle_ph_data_ind(struct femtol1_hdl *fl1, GsmL1_PhDataInd_t *data_i l1sap->u.data.chan_nr = chan_nr; l1sap->u.data.fn = fn; l1sap->u.data.rssi = (int8_t) (data_ind->measParam.fRssi); - if (!pcu_direct) { /* FIXME: if pcu_direct=1, then this is not set, what to do in pcu_tx_data_ind() in this case ?*/ - l1sap->u.data.ber10k = data_ind->measParam.fBer * 10000; - l1sap->u.data.ta_offs_256bits = data_ind->measParam.i16BurstTiming * 64; - l1sap->u.data.lqual_cb = data_ind->measParam.fLinkQuality * 10; - } + l1sap->u.data.ber10k = data_ind->measParam.fBer * 10000; + l1sap->u.data.ta_offs_256bits = data_ind->measParam.i16BurstTiming * 64; + l1sap->u.data.lqual_cb = data_ind->measParam.fLinkQuality * 10; + /* copy data from L1 primitive to L1SAP primitive */ sap_msg->l2h = msgb_put(sap_msg, data_ind->msgUnitParam.u8Size); memcpy(sap_msg->l2h, data_ind->msgUnitParam.u8Buffer, @@ -996,7 +1044,9 @@ static int handle_ph_ra_ind(struct femtol1_hdl *fl1, GsmL1_PhRaInd_t *ra_ind, int rc; struct ph_rach_ind_param rach_ind_param; - dump_meas_res(LOGL_DEBUG, &ra_ind->measParam); + set_log_ctx_sapi(ra_ind->sapi); + LOGPFN(DL1C, LOGL_DEBUG, ra_ind->u32Fn, "Rx PH-RA.ind, " LOG_FMT_MEAS "\n", + LOG_PARAM_MEAS(&ra_ind->measParam)); if ((ra_ind->msgUnitParam.u8Size != 1) && (ra_ind->msgUnitParam.u8Size != 2)) { @@ -1192,13 +1242,127 @@ int l1if_handle_sysprim(struct femtol1_hdl *fl1h, struct msgb *msg) return l1if_handle_ind(fl1h, msg); } +static void mute_handle_ts(struct gsm_bts_trx_ts *ts, int is_muted) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(ts->lchan); i++) { + struct gsm_lchan *lchan = &ts->lchan[i]; + + if (!is_muted) + continue; + + if (lchan->state != LCHAN_S_ACTIVE) + continue; + + /* skip channels that might be active for another reason */ + if (lchan->type == GSM_LCHAN_CCCH) + continue; + if (lchan->type == GSM_LCHAN_PDTCH) + continue; + + if (lchan->s <= 0) + continue; + + lchan->s = 0; + rsl_tx_conn_fail(lchan, RSL_ERR_RADIO_LINK_FAIL); + } +} + +#if SUPERFEMTO_API_VERSION >= SUPERFEMTO_API(3, 6, 0) +static int mute_rf_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp, + void *data) +{ + struct femtol1_hdl *fl1h = trx_femtol1_hdl(trx); + SuperFemto_Prim_t *sysp = msgb_sysprim(resp); + GsmL1_Status_t status; + + status = sysp->u.muteRfCnf.status; + + if (status != GsmL1_Status_Success) { + LOGP(DL1C, LOGL_ERROR, "Rx RF-MUTE.conf with status %s\n", + get_value_string(femtobts_l1status_names, status)); + if (trx->mo.fi->state != NM_RCARRIER_ST_OP_DISABLED_NOTINSTALLED) + oml_mo_rf_lock_chg(&trx->mo, fl1h->last_rf_mute, 0); + } else { + int i; + + LOGP(DL1C, LOGL_INFO, "Rx RF-MUTE.conf with status=%s\n", + get_value_string(femtobts_l1status_names, status)); + bts_update_status(BTS_STATUS_RF_MUTE, fl1h->last_rf_mute[0]); + if (trx->mo.fi->state != NM_RCARRIER_ST_OP_DISABLED_NOTINSTALLED) + oml_mo_rf_lock_chg(&trx->mo, fl1h->last_rf_mute, 1); + + osmo_static_assert( + ARRAY_SIZE(trx->ts) >= ARRAY_SIZE(fl1h->last_rf_mute), + ts_array_size); + + for (i = 0; i < ARRAY_SIZE(fl1h->last_rf_mute); ++i) + mute_handle_ts(&trx->ts[i], fl1h->last_rf_mute[i]); + } + + msgb_free(resp); + + return 0; +} +#endif + +/* mute/unmute RF time slots */ +int l1if_mute_rf(struct femtol1_hdl *hdl, uint8_t mute[8], l1if_compl_cb *cb) +{ + + LOGP(DL1C, LOGL_INFO, "Tx RF-MUTE.req (%d, %d, %d, %d, %d, %d, %d, %d)\n", + mute[0], mute[1], mute[2], mute[3], + mute[4], mute[5], mute[6], mute[7] + ); + +#if SUPERFEMTO_API_VERSION < SUPERFEMTO_API(3, 6, 0) + const uint8_t unmuted[8] = { 0, 0, 0, 0, 0, 0, 0, 0 }; + struct gsm_bts_trx *trx = hdl->phy_inst->trx; + int i; + LOGP(DL1C, LOGL_ERROR, "RF-MUTE.req not supported by SuperFemto\n"); + /* always acknowledge an un-MUTE (which is a no-op if MUTE is not supported */ + if (!memcmp(mute, unmuted, ARRAY_SIZE(unmuted))) { + bts_update_status(BTS_STATUS_RF_MUTE, mute[0]); + oml_mo_rf_lock_chg(&trx->mo, mute, 1); + for (i = 0; i < ARRAY_SIZE(unmuted); ++i) + mute_handle_ts(&trx->ts[i], mute[i]); + return 0; + } + return -ENOTSUP; +#else + struct msgb *msg = sysp_msgb_alloc(); + SuperFemto_Prim_t *sysp = msgb_sysprim(msg); + sysp->id = SuperFemto_PrimId_MuteRfReq; + memcpy(sysp->u.muteRfReq.u8Mute, mute, sizeof(sysp->u.muteRfReq.u8Mute)); + /* save for later use */ + memcpy(hdl->last_rf_mute, mute, sizeof(hdl->last_rf_mute)); + + return l1if_req_compl(hdl, msg, cb ? cb : mute_rf_compl_cb, NULL); +#endif /* < 3.6.0 */ +} + +static int activate_rf_mute_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp, void *data) +{ +#if SUPERFEMTO_API_VERSION >= SUPERFEMTO_API(3, 6, 0) + mute_rf_compl_cb(trx, resp, data); +#endif + + /* signal availability */ + osmo_fsm_inst_dispatch(trx->mo.fi, NM_EV_SW_ACT, NULL); + osmo_fsm_inst_dispatch(trx->bb_transc.mo.fi, NM_EV_SW_ACT, NULL); + + return 0; +} + +int trx_rf_lock(struct gsm_bts_trx *trx, int locked, l1if_compl_cb *cb); + static int activate_rf_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp, void *data) { SuperFemto_Prim_t *sysp = msgb_sysprim(resp); GsmL1_Status_t status; int on = 0; - unsigned int i; if (sysp->id == SuperFemto_PrimId_ActivateRfCnf) on = 1; @@ -1217,21 +1381,18 @@ static int activate_rf_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp, LOGP(DL1C, LOGL_FATAL, "RF-ACT.conf with status %s\n", get_value_string(femtobts_l1status_names, status)); bts_shutdown(trx->bts, "RF-ACT failure"); - } else + } else { bts_update_status(BTS_STATUS_RF_ACTIVE, 1); - - /* signal availability */ - oml_mo_state_chg(&trx->mo, NM_OPSTATE_DISABLED, NM_AVSTATE_OK); - oml_mo_tx_sw_act_rep(&trx->mo); - oml_mo_state_chg(&trx->bb_transc.mo, -1, NM_AVSTATE_OK); - oml_mo_tx_sw_act_rep(&trx->bb_transc.mo); - - for (i = 0; i < ARRAY_SIZE(trx->ts); i++) - oml_mo_state_chg(&trx->ts[i].mo, NM_OPSTATE_DISABLED, NM_AVSTATE_DEPENDENCY); +#if SUPERFEMTO_API_VERSION >= SUPERFEMTO_API(3, 6, 0) + trx_rf_lock(trx, 1, activate_rf_mute_compl_cb); +#else + activate_rf_mute_compl_cb(trx, resp, NULL); +#endif + } } else { bts_update_status(BTS_STATUS_RF_ACTIVE, 0); - oml_mo_state_chg(&trx->mo, NM_OPSTATE_DISABLED, NM_AVSTATE_OFF_LINE); - oml_mo_state_chg(&trx->bb_transc.mo, NM_OPSTATE_DISABLED, NM_AVSTATE_OFF_LINE); + osmo_fsm_inst_dispatch(trx->mo.fi, NM_EV_DISABLE, NULL); + osmo_fsm_inst_dispatch(trx->bb_transc.mo.fi, NM_EV_DISABLE, NULL); } msgb_free(resp); @@ -1325,104 +1486,6 @@ int l1if_activate_rf(struct femtol1_hdl *hdl, int on) return l1if_req_compl(hdl, msg, activate_rf_compl_cb, NULL); } -static void mute_handle_ts(struct gsm_bts_trx_ts *ts, int is_muted) -{ - int i; - - for (i = 0; i < ARRAY_SIZE(ts->lchan); i++) { - struct gsm_lchan *lchan = &ts->lchan[i]; - - if (!is_muted) - continue; - - if (lchan->state != LCHAN_S_ACTIVE) - continue; - - /* skip channels that might be active for another reason */ - if (lchan->type == GSM_LCHAN_CCCH) - continue; - if (lchan->type == GSM_LCHAN_PDTCH) - continue; - - if (lchan->s <= 0) - continue; - - lchan->s = 0; - rsl_tx_conn_fail(lchan, RSL_ERR_RADIO_LINK_FAIL); - } -} - -#if SUPERFEMTO_API_VERSION >= SUPERFEMTO_API(3,6,0) -static int mute_rf_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp, - void *data) -{ - struct femtol1_hdl *fl1h = trx_femtol1_hdl(trx); - SuperFemto_Prim_t *sysp = msgb_sysprim(resp); - GsmL1_Status_t status; - - status = sysp->u.muteRfCnf.status; - - if (status != GsmL1_Status_Success) { - LOGP(DL1C, LOGL_ERROR, "Rx RF-MUTE.conf with status %s\n", - get_value_string(femtobts_l1status_names, status)); - oml_mo_rf_lock_chg(&trx->mo, fl1h->last_rf_mute, 0); - } else { - int i; - - LOGP(DL1C, LOGL_INFO, "Rx RF-MUTE.conf with status=%s\n", - get_value_string(femtobts_l1status_names, status)); - bts_update_status(BTS_STATUS_RF_MUTE, fl1h->last_rf_mute[0]); - oml_mo_rf_lock_chg(&trx->mo, fl1h->last_rf_mute, 1); - - osmo_static_assert( - ARRAY_SIZE(trx->ts) >= ARRAY_SIZE(fl1h->last_rf_mute), - ts_array_size); - - for (i = 0; i < ARRAY_SIZE(fl1h->last_rf_mute); ++i) - mute_handle_ts(&trx->ts[i], fl1h->last_rf_mute[i]); - } - - msgb_free(resp); - - return 0; -} -#endif - -/* mute/unmute RF time slots */ -int l1if_mute_rf(struct femtol1_hdl *hdl, uint8_t mute[8], l1if_compl_cb *cb) -{ - - LOGP(DL1C, LOGL_INFO, "Tx RF-MUTE.req (%d, %d, %d, %d, %d, %d, %d, %d)\n", - mute[0], mute[1], mute[2], mute[3], - mute[4], mute[5], mute[6], mute[7] - ); - -#if SUPERFEMTO_API_VERSION < SUPERFEMTO_API(3,6,0) - const uint8_t unmuted[8] = { 0,0,0,0,0,0,0,0 }; - struct gsm_bts_trx *trx = hdl->phy_inst->trx; - int i; - LOGP(DL1C, LOGL_ERROR, "RF-MUTE.req not supported by SuperFemto\n"); - /* always acknowledge an un-MUTE (which is a no-op if MUTE is not supported */ - if (!memcmp(mute, unmuted, ARRAY_SIZE(unmuted))) { - bts_update_status(BTS_STATUS_RF_MUTE, mute[0]); - oml_mo_rf_lock_chg(&trx->mo, mute, 1); - for (i = 0; i < ARRAY_SIZE(unmuted); ++i) - mute_handle_ts(&trx->ts[i], mute[i]); - return 0; - } - return -ENOTSUP; -#else - struct msgb *msg = sysp_msgb_alloc(); - SuperFemto_Prim_t *sysp = msgb_sysprim(msg); - sysp->id = SuperFemto_PrimId_MuteRfReq; - memcpy(sysp->u.muteRfReq.u8Mute, mute, sizeof(sysp->u.muteRfReq.u8Mute)); - /* save for later use */ - memcpy(hdl->last_rf_mute, mute, sizeof(hdl->last_rf_mute)); - - return l1if_req_compl(hdl, msg, cb ? cb : mute_rf_compl_cb, NULL); -#endif /* < 3.6.0 */ -} - /* call-back on arrival of DSP+FPGA version + band capability */ static int info_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp, void *data) @@ -1444,10 +1507,12 @@ static int info_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp, fl1h->hw_info.ver_minor = sic->boardVersion.option; #endif - LOGP(DL1C, LOGL_INFO, "DSP v%u.%u.%u, FPGA v%u.%u.%u\nn", - sic->dspVersion.major, sic->dspVersion.minor, - sic->dspVersion.build, sic->fpgaVersion.major, - sic->fpgaVersion.minor, sic->fpgaVersion.build); + snprintf(trx->pinst->version, sizeof(trx->pinst->version), "%u.%u dsp %u.%u.%u fpga %u.%u.%u", + fl1h->hw_info.ver_major, fl1h->hw_info.ver_minor, + fl1h->hw_info.dsp_version[0], fl1h->hw_info.dsp_version[1], fl1h->hw_info.dsp_version[2], + fl1h->hw_info.fpga_version[0], fl1h->hw_info.fpga_version[1], fl1h->hw_info.fpga_version[2]); + + LOGP(DL1C, LOGL_INFO, "%s\n", trx->pinst->version); #ifdef HW_SYSMOBTS_V1 if (sic->rfBand.gsm850) @@ -1483,6 +1548,8 @@ static int info_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp, msgb_free(resp); + phy_link_state_set(trx->pinst->phy_link, PHY_LINK_CONNECTED); + /* FIXME: clock related */ return 0; } @@ -1846,7 +1913,8 @@ static void fill_trx_power_params(struct gsm_bts_trx *trx, LOGP(DL1C, LOGL_NOTICE, "Assuming 1002 for sysmoBTS " "Model number %u\n", fl1h->hw_info.model_nr); /* fall-through */ - case 1002: + case 1002: /* sysmoBTS 1002 */ + case 1003: /* sysmoBTS 1002 with GPS and PoE */ set_power_param(&trx->power_params, 23, 0); } } @@ -1883,12 +1951,28 @@ int bts_model_phy_link_open(struct phy_link *plink) hdl = pinst->u.sysmobts.hdl; osmo_strlcpy(bts->sub_model, sysmobts_model(hdl->hw_info.model_nr, hdl->hw_info.trx_nr), sizeof(bts->sub_model)); - snprintf(pinst->version, sizeof(pinst->version), "%u.%u dsp %u.%u.%u fpga %u.%u.%u", - hdl->hw_info.ver_major, hdl->hw_info.ver_minor, - hdl->hw_info.dsp_version[0], hdl->hw_info.dsp_version[1], hdl->hw_info.dsp_version[2], - hdl->hw_info.fpga_version[0], hdl->hw_info.fpga_version[1], hdl->hw_info.fpga_version[2]); - phy_link_state_set(plink, PHY_LINK_CONNECTED); + /* Frequency bands indicated to the BSC */ + for (unsigned int i = 0; i < sizeof(hdl->hw_info.band_support) * 8; i++) { + if (~hdl->hw_info.band_support & (1 << i)) + continue; + switch (1 << i) { + case GSM_BAND_850: + pinst->trx->support.freq_bands |= NM_IPAC_F_FREQ_BAND_850; + break; + case GSM_BAND_900: + pinst->trx->support.freq_bands |= NM_IPAC_F_FREQ_BAND_PGSM; + /* XXX: does GSM_BAND_900 include NM_IPAC_F_FREQ_BAND_EGSM? */ + /* XXX: does GSM_BAND_900 include NM_IPAC_F_FREQ_BAND_RGSM? */ + break; + case GSM_BAND_1800: + pinst->trx->support.freq_bands |= NM_IPAC_F_FREQ_BAND_DCS; + break; + case GSM_BAND_1900: + pinst->trx->support.freq_bands |= NM_IPAC_F_FREQ_BAND_PCS; + break; + } + } return 0; } diff --git a/src/osmo-bts-sysmo/l1_if.h b/src/osmo-bts-sysmo/l1_if.h index 1b214be7..c81b6bd4 100644 --- a/src/osmo-bts-sysmo/l1_if.h +++ b/src/osmo-bts-sysmo/l1_if.h @@ -129,6 +129,8 @@ int l1if_tch_encode(struct gsm_lchan *lchan, uint8_t *data, uint8_t *len, const uint8_t *rtp_pl, unsigned int rtp_pl_len, uint32_t fn, bool use_cache, bool marker); int l1if_tch_rx(struct gsm_bts_trx *trx, uint8_t chan_nr, struct msgb *l1p_msg); +int l1if_tch_rx_facch(struct gsm_bts_trx *trx, uint8_t chan_nr, + struct msgb *l1p_msg); int l1if_tch_fill(struct gsm_lchan *lchan, uint8_t *l1_buffer); struct msgb *gen_empty_tch_msg(struct gsm_lchan *lchan, uint32_t fn); @@ -143,6 +145,7 @@ int l1if_rsl_chan_rel(struct gsm_lchan *lchan); int l1if_rsl_chan_mod(struct gsm_lchan *lchan); int l1if_rsl_deact_sacch(struct gsm_lchan *lchan); int l1if_rsl_mode_modify(struct gsm_lchan *lchan); +int l1if_set_ul_acc(struct gsm_lchan *lchan, bool active); /* calibration loading */ int calib_load(struct femtol1_hdl *fl1h); @@ -156,9 +159,9 @@ int l1if_rf_clock_info_correct(struct femtol1_hdl *fl1h); int bts_check_for_ciph_cmd(struct femtol1_hdl *fl1h, struct msgb *msg, struct gsm_lchan *lchan); -static inline struct femtol1_hdl *trx_femtol1_hdl(struct gsm_bts_trx *trx) +static inline struct femtol1_hdl *trx_femtol1_hdl(const struct gsm_bts_trx *trx) { - struct phy_instance *pinst = trx_phy_instance(trx); + const struct phy_instance *pinst = trx_phy_instance(trx); OSMO_ASSERT(pinst); return pinst->u.sysmobts.hdl; } diff --git a/src/osmo-bts-sysmo/l1_transp_fwd.c b/src/osmo-bts-sysmo/l1_transp_fwd.c index 87c230bb..ac510eda 100644 --- a/src/osmo-bts-sysmo/l1_transp_fwd.c +++ b/src/osmo-bts-sysmo/l1_transp_fwd.c @@ -12,7 +12,7 @@ * 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. + * 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/>. @@ -126,10 +126,7 @@ int l1if_transport_open(int q, struct femtol1_hdl *fl1h) wq->write_cb = prim_write_cb; wq->read_cb = fwd_read_cb; - ofd->data = fl1h; - ofd->priv_nr = q; - ofd->when |= BSC_FD_READ; - + osmo_fd_setup(ofd, -1, OSMO_FD_READ, osmo_wqueue_bfd_cb, fl1h, q); rc = osmo_sock_init_ofd(ofd, AF_UNSPEC, SOCK_DGRAM, IPPROTO_UDP, bts_host, fwd_udp_ports[q], OSMO_SOCK_F_CONNECT); diff --git a/src/osmo-bts-sysmo/l1_transp_hw.c b/src/osmo-bts-sysmo/l1_transp_hw.c index 01bc2005..cfbc77c9 100644 --- a/src/osmo-bts-sysmo/l1_transp_hw.c +++ b/src/osmo-bts-sysmo/l1_transp_hw.c @@ -12,7 +12,7 @@ * 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. + * 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/>. @@ -95,18 +95,18 @@ static int wqueue_vector_cb(struct osmo_fd *fd, unsigned int what) queue = container_of(fd, struct osmo_wqueue, bfd); - if (what & BSC_FD_READ) + if (what & OSMO_FD_READ) queue->read_cb(fd); - if (what & BSC_FD_EXCEPT) + if (what & OSMO_FD_EXCEPT) queue->except_cb(fd); - if (what & BSC_FD_WRITE) { + if (what & OSMO_FD_WRITE) { struct iovec iov[5]; struct msgb *msg, *tmp; int written, count = 0; - fd->when &= ~BSC_FD_WRITE; + osmo_fd_write_disable(fd); llist_for_each_entry(msg, &queue->msg_queue, list) { /* more writes than we have */ @@ -124,7 +124,7 @@ static int wqueue_vector_cb(struct osmo_fd *fd, unsigned int what) /* Nothing scheduled? This should not happen. */ if (count == 0) { if (!llist_empty(&queue->msg_queue)) - fd->when |= BSC_FD_WRITE; + osmo_fd_write_enable(fd); return 0; } @@ -132,7 +132,7 @@ static int wqueue_vector_cb(struct osmo_fd *fd, unsigned int what) if (written < 0) { /* nothing written?! */ if (!llist_empty(&queue->msg_queue)) - fd->when |= BSC_FD_WRITE; + osmo_fd_write_enable(fd); return 0; } @@ -151,7 +151,7 @@ static int wqueue_vector_cb(struct osmo_fd *fd, unsigned int what) } if (!llist_empty(&queue->msg_queue)) - fd->when |= BSC_FD_WRITE; + osmo_fd_write_enable(fd); } return 0; @@ -271,11 +271,7 @@ int l1if_transport_open(int q, struct femtol1_hdl *hdl) q, rd_devnames[q], strerror(errno)); return rc; } - read_ofd->fd = rc; - read_ofd->priv_nr = q; - read_ofd->data = hdl; - read_ofd->cb = l1if_fd_cb; - read_ofd->when = BSC_FD_READ; + osmo_fd_setup(read_ofd, rc, OSMO_FD_READ, l1if_fd_cb, hdl, q); rc = osmo_fd_register(read_ofd); if (rc < 0) { close(read_ofd->fd); @@ -291,11 +287,7 @@ int l1if_transport_open(int q, struct femtol1_hdl *hdl) } osmo_wqueue_init(wq, 10); wq->write_cb = l1fd_write_cb; - write_ofd->cb = wqueue_vector_cb; - write_ofd->fd = rc; - write_ofd->priv_nr = q; - write_ofd->data = hdl; - write_ofd->when = BSC_FD_WRITE; + osmo_fd_setup(write_ofd, rc, OSMO_FD_WRITE, wqueue_vector_cb, hdl, q); rc = osmo_fd_register(write_ofd); if (rc < 0) { close(write_ofd->fd); diff --git a/src/osmo-bts-sysmo/main.c b/src/osmo-bts-sysmo/main.c index ad7118ae..20b425da 100644 --- a/src/osmo-bts-sysmo/main.c +++ b/src/osmo-bts-sysmo/main.c @@ -12,7 +12,7 @@ * 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. + * 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/>. @@ -53,47 +53,61 @@ #include "eeprom.h" #include "l1_if.h" #include "hw_misc.h" -#include "oml_router.h" int bts_model_init(struct gsm_bts *bts) { struct stat st; - static struct osmo_fd accept_fd, read_fd; - int rc; bts->variant = BTS_OSMO_SYSMO; bts->support.ciphers = CIPHER_A5(1) | CIPHER_A5(2) | CIPHER_A5(3); - - rc = oml_router_init(bts, OML_ROUTER_PATH, &accept_fd, &read_fd); - if (rc < 0) { - fprintf(stderr, "Error creating the OML router: %s rc=%d\n", - OML_ROUTER_PATH, rc); - exit(1); - } + bts->gprs.cell.support.gprs_codings = NM_IPAC_MASK_GPRS_CODING_CS + | NM_IPAC_MASK_GPRS_CODING_MCS; if (stat(SYSMOBTS_RF_LOCK_PATH, &st) == 0) { LOGP(DL1C, LOGL_NOTICE, "Not starting BTS due to RF_LOCK file present\n"); exit(23); } - gsm_bts_set_feature(bts, BTS_FEAT_CBCH); - gsm_bts_set_feature(bts, BTS_FEAT_GPRS); - gsm_bts_set_feature(bts, BTS_FEAT_EGPRS); - gsm_bts_set_feature(bts, BTS_FEAT_OML_ALERTS); - gsm_bts_set_feature(bts, BTS_FEAT_AGCH_PCH_PROP); - gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_F_V1); - gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_H_V1); - gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_F_EFR); - gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_F_AMR); - gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_H_AMR); - - bts_model_vty_init(bts); + /* order alphabetically */ + osmo_bts_set_feature(bts->features, BTS_FEAT_AGCH_PCH_PROP); + osmo_bts_set_feature(bts->features, BTS_FEAT_CBCH); + osmo_bts_set_feature(bts->features, BTS_FEAT_EGPRS); + osmo_bts_set_feature(bts->features, BTS_FEAT_GPRS); + osmo_bts_set_feature(bts->features, BTS_FEAT_OML_ALERTS); + osmo_bts_set_feature(bts->features, BTS_FEAT_SPEECH_F_AMR); + osmo_bts_set_feature(bts->features, BTS_FEAT_SPEECH_F_EFR); + osmo_bts_set_feature(bts->features, BTS_FEAT_SPEECH_F_V1); + osmo_bts_set_feature(bts->features, BTS_FEAT_SPEECH_H_AMR); + osmo_bts_set_feature(bts->features, BTS_FEAT_SPEECH_H_V1); + osmo_bts_set_feature(bts->features, BTS_FEAT_VBS); + osmo_bts_set_feature(bts->features, BTS_FEAT_VGCS); + + bts_internal_flag_set(bts, BTS_INTERNAL_FLAG_MEAS_PAYLOAD_COMB); + bts_internal_flag_set(bts, BTS_INTERNAL_FLAG_MS_PWR_CTRL_DSP); + bts_internal_flag_set(bts, BTS_INTERNAL_FLAG_NM_RCHANNEL_DEPENDS_RCARRIER); + + /* The default HR codec output format in the absence of saved + * vty config needs to match what was implemented previously, + * for the sake of existing deployments, i.e., to avoid + * a surprise functional change upon software update. */ + bts->emit_hr_rfc5993 = false; return 0; } int bts_model_trx_init(struct gsm_bts_trx *trx) { + /* Frequency bands indicated to the BSC */ + trx->support.freq_bands = 0x00; /* updated in bts_model_phy_link_open() */ + + /* Channel types and modes indicated to the BSC */ + trx->support.chan_types = NM_IPAC_MASK_CHANT_COMMON + | NM_IPAC_F_CHANT_BCCH_SDCCH4_CBCH + | NM_IPAC_F_CHANT_SDCCH8_CBCH + | NM_IPAC_F_CHANT_PDCHF + | NM_IPAC_F_CHANT_TCHF_PDCHF; + trx->support.chan_modes = NM_IPAC_MASK_CHANM_SPEECH; + return 0; } @@ -128,10 +142,10 @@ void bts_update_status(enum bts_global_status which, int on) void bts_model_print_help() { - printf( - " -w --hw-version Print the targeted HW Version\n" - " -M --pcu-direct Force PCU to access message queue for " - "PDCH dchannel directly\n" + printf( "\nModel specific options:\n" + " -w --hw-version Print the targeted HW Version\n" + " -M --pcu-direct Force PCU to access message queue for " + "PDCH dchannel directly\n" ); }; diff --git a/src/osmo-bts-sysmo/misc/sysmobts-layer1.c b/src/osmo-bts-sysmo/misc/sysmobts-layer1.c index 4b34f50e..20ca6f51 100644 --- a/src/osmo-bts-sysmo/misc/sysmobts-layer1.c +++ b/src/osmo-bts-sysmo/misc/sysmobts-layer1.c @@ -622,7 +622,7 @@ int set_clock_cor(int clock_cor, int calib, int source) return -1; } if (prim.u.rfClockSetupCnf.status != GsmL1_Status_Success) { - printf("Clock setup was not successfull.\n"); + printf("Clock setup was not successful.\n"); return -2; } diff --git a/src/osmo-bts-sysmo/misc/sysmobts_eeprom.h b/src/osmo-bts-sysmo/misc/sysmobts_eeprom.h index b7a27fb7..9c2b839a 100644 --- a/src/osmo-bts-sysmo/misc/sysmobts_eeprom.h +++ b/src/osmo-bts-sysmo/misc/sysmobts_eeprom.h @@ -32,6 +32,7 @@ struct sysmobts_eeprom { /* offset */ enum sysmobts_model_number { MODEL_SYSMOBTS_1002 = 1002, + MODEL_SYSMOBTS_1003 = 1003, MODEL_SYSMOBTS_1020 = 1020, MODEL_SYSMOBTS_2050 = 2050, }; diff --git a/src/osmo-bts-sysmo/misc/sysmobts_mgr.c b/src/osmo-bts-sysmo/misc/sysmobts_mgr.c index a8289c25..79bbf77e 100644 --- a/src/osmo-bts-sysmo/misc/sysmobts_mgr.c +++ b/src/osmo-bts-sysmo/misc/sysmobts_mgr.c @@ -13,7 +13,7 @@ * 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. + * 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/>. @@ -192,11 +192,11 @@ static int parse_options(int argc, char **argv) return 0; } -static void signal_handler(int signal) +static void signal_handler(int signum) { - fprintf(stderr, "signal %u received\n", signal); + fprintf(stderr, "signal %u received\n", signum); - switch (signal) { + switch (signum) { case SIGINT: case SIGTERM: sysmobts_check_temp(no_eeprom_write); @@ -204,6 +204,16 @@ static void signal_handler(int signal) exit(0); break; case SIGABRT: + /* in case of abort, we want to obtain a talloc report and + * then run default SIGABRT handler, who will generate coredump + * and abort the process. abort() should do this for us after we + * return, but program wouldn't exit if an external SIGABRT is + * received. + */ + talloc_report_full(tall_mgr_ctx, stderr); + signal(SIGABRT, SIG_DFL); + raise(SIGABRT); + break; case SIGUSR1: case SIGUSR2: talloc_report_full(tall_mgr_ctx, stderr); @@ -218,25 +228,25 @@ static struct log_info_cat mgr_log_info_cat[] = { .name = "DTEMP", .description = "Temperature monitoring", .color = "\033[1;35m", - .enabled = 1, .loglevel = LOGL_INFO, + .enabled = 1, .loglevel = LOGL_NOTICE, }, [DFW] = { .name = "DFW", .description = "DSP/FPGA firmware management", .color = "\033[1;36m", - .enabled = 1, .loglevel = LOGL_INFO, + .enabled = 1, .loglevel = LOGL_NOTICE, }, [DFIND] = { .name = "DFIND", .description = "ipaccess-find handling", .color = "\033[1;37m", - .enabled = 1, .loglevel = LOGL_INFO, + .enabled = 1, .loglevel = LOGL_NOTICE, }, [DCALIB] = { .name = "DCALIB", .description = "Calibration handling", .color = "\033[1;37m", - .enabled = 1, .loglevel = LOGL_INFO, + .enabled = 1, .loglevel = LOGL_NOTICE, }, }; @@ -262,6 +272,7 @@ int main(int argc, char **argv) osmo_init_ignore_signals(); signal(SIGINT, &signal_handler); signal(SIGTERM, &signal_handler); + signal(SIGABRT, &signal_handler); signal(SIGUSR1, &signal_handler); signal(SIGUSR2, &signal_handler); @@ -277,7 +288,7 @@ int main(int argc, char **argv) exit(1); } - rc = telnet_init(tall_mgr_ctx, NULL, OSMO_VTY_PORT_BTSMGR); + rc = telnet_init_default(tall_mgr_ctx, NULL, OSMO_VTY_PORT_BTSMGR); if (rc < 0) { fprintf(stderr, "Error initializing telnet\n"); exit(1); @@ -312,10 +323,10 @@ int main(int argc, char **argv) LOGP(DLCTRL, LOGL_ERROR, "Can't connect to CTRL @ localhost:%u\n", OSMO_CTRL_PORT_BTS); else - LOGP(DLCTRL, LOGL_NOTICE, "CTRL connected to locahost:%u\n", + LOGP(DLCTRL, LOGL_NOTICE, "CTRL connected to localhost:%u\n", OSMO_CTRL_PORT_BTS); - sysmobts_mgr_temp_init(&manager, ccon); + sysmobts_mgr_temp_init(&manager, ccon); if (sysmobts_mgr_calib_init(&manager) != 0) exit(3); diff --git a/src/osmo-bts-sysmo/misc/sysmobts_mgr_2050.c b/src/osmo-bts-sysmo/misc/sysmobts_mgr_2050.c index 12961e3f..45b024e1 100644 --- a/src/osmo-bts-sysmo/misc/sysmobts_mgr_2050.c +++ b/src/osmo-bts-sysmo/misc/sysmobts_mgr_2050.c @@ -10,7 +10,7 @@ * 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. + * 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-bts-sysmo/misc/sysmobts_mgr_calib.c b/src/osmo-bts-sysmo/misc/sysmobts_mgr_calib.c index a0ba6493..ac35f392 100644 --- a/src/osmo-bts-sysmo/misc/sysmobts_mgr_calib.c +++ b/src/osmo-bts-sysmo/misc/sysmobts_mgr_calib.c @@ -14,7 +14,7 @@ * 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. + * 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/>. @@ -54,7 +54,7 @@ enum calib_result { CALIB_FAIL_START, CALIB_FAIL_GPS, CALIB_FAIL_CTRL, - CALIB_SUCESS, + CALIB_SUCCESS, }; static inline int compat_gps_read(struct gps_data_t *data) @@ -167,10 +167,8 @@ static void mgr_gps_open(struct sysmobts_mgr_instance *mgr) #else gps_stream(mgr->calib.gpsdata, WATCH_ENABLE, NULL); #endif - mgr->calib.gpsfd.data = mgr; - mgr->calib.gpsfd.cb = mgr_gps_read; - mgr->calib.gpsfd.when = BSC_FD_READ | BSC_FD_EXCEPT; - mgr->calib.gpsfd.fd = mgr->calib.gpsdata->gps_fd; + osmo_fd_setup(&mgr->calib.gpsfd, mgr->calib.gpsdata->gps_fd, OSMO_FD_READ | OSMO_FD_EXCEPT, + mgr_gps_read, mgr, 0); if (osmo_fd_register(&mgr->calib.gpsfd) < 0) { LOGP(DCALIB, LOGL_ERROR, "Failed to register GPSD fd\n"); calib_state_reset(mgr, CALIB_FAIL_GPS); @@ -271,7 +269,7 @@ static void calib_state_reset(struct sysmobts_mgr_instance *mgr, int outcome) * and in case of a failure in some minutes. */ int timeout = 2 * 60 * 60; - if (outcome != CALIB_SUCESS) + if (outcome != CALIB_SUCCESS) timeout = 5 * 60; mgr->calib.calib_timeout.data = mgr; @@ -390,7 +388,7 @@ static void handle_ctrl_set_cor( LOGP(DCALIB, LOGL_NOTICE, "Calibration process completed\n"); - calib_state_reset(mgr, CALIB_SUCESS); + calib_state_reset(mgr, CALIB_SUCCESS); } static void handle_ctrl(struct sysmobts_mgr_instance *mgr, struct msgb *msg) @@ -459,7 +457,7 @@ static void bts_recon_timer_cb(void *data) struct sysmobts_mgr_instance *mgr = data; /* The connection failures are to be expected during boot */ - mgr->calib.bts_conn->ofd->when |= BSC_FD_WRITE; + osmo_fd_write_enable(mgr->calib.bts_conn->ofd); rc = ipa_client_conn_open(mgr->calib.bts_conn); if (rc < 0) { LOGP(DLCTRL, LOGL_NOTICE, "Failed to connect to BTS.\n"); @@ -543,8 +541,8 @@ int sysmobts_mgr_calib_init(struct sysmobts_mgr_instance *mgr) return 0; } - mgr->calib.bts_conn = ipa_client_conn_create(tall_mgr_ctx, NULL, 0, - "localhost", 4238, + mgr->calib.bts_conn = ipa_client_conn_create2(tall_mgr_ctx, NULL, 0, + NULL, 0, "localhost", 4238, bts_updown_cb, bts_read_cb, NULL, mgr); if (!mgr->calib.bts_conn) { diff --git a/src/osmo-bts-sysmo/misc/sysmobts_mgr_nl.c b/src/osmo-bts-sysmo/misc/sysmobts_mgr_nl.c index 48a03124..fead4f27 100644 --- a/src/osmo-bts-sysmo/misc/sysmobts_mgr_nl.c +++ b/src/osmo-bts-sysmo/misc/sysmobts_mgr_nl.c @@ -13,7 +13,7 @@ * 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. + * 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-bts-sysmo/misc/sysmobts_mgr_temp.c b/src/osmo-bts-sysmo/misc/sysmobts_mgr_temp.c index 1be56ac2..eea68793 100644 --- a/src/osmo-bts-sysmo/misc/sysmobts_mgr_temp.c +++ b/src/osmo-bts-sysmo/misc/sysmobts_mgr_temp.c @@ -13,7 +13,7 @@ * 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. + * 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/>. @@ -233,7 +233,7 @@ static void sysmobts_mgr_temp_handle(struct sysmobts_mgr_instance *manager, rep->variable = "oml-alert"; rep->value = oml_alert; LOGP(DTEMP, LOGL_ERROR, "OML alert sent: %d\n", - ctrl_cmd_send(&ctrl->write_queue, rep)); + ctrl_cmd_send2(ctrl, rep)); talloc_free(rep); } diff --git a/src/osmo-bts-sysmo/misc/sysmobts_mgr_vty.c b/src/osmo-bts-sysmo/misc/sysmobts_mgr_vty.c index 444ee7c3..33cf0dcb 100644 --- a/src/osmo-bts-sysmo/misc/sysmobts_mgr_vty.c +++ b/src/osmo-bts-sysmo/misc/sysmobts_mgr_vty.c @@ -12,7 +12,7 @@ * 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. + * 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/>. @@ -69,28 +69,10 @@ static int go_to_parent(struct vty *vty) return vty->node; } -static int is_config_node(struct vty *vty, int node) -{ - switch (node) { - case MGR_NODE: - case ACT_NORM_NODE: - case ACT_WARN_NODE: - case ACT_CRIT_NODE: - case LIMIT_RF_NODE: - case LIMIT_DIGITAL_NODE: - case LIMIT_BOARD_NODE: - case LIMIT_PA_NODE: - return 1; - default: - return 0; - } -} - static struct vty_app_info vty_info = { .name = "sysmobts-mgr", .version = PACKAGE_VERSION, .go_parent_cb = go_to_parent, - .is_config_node = is_config_node, .copyright = copyright, }; @@ -380,11 +362,11 @@ DEFUN(show_mgr, show_mgr_cmd, "show manager", vty_out(vty, "Temperature control state: %s%s", sysmobts_mgr_temp_get_state(s_mgr->state), VTY_NEWLINE); vty_out(vty, "Current Temperatures%s", VTY_NEWLINE); - vty_out(vty, " Digital: %f Celcius%s", + vty_out(vty, " Digital: %f Celsius%s", sysmobts_temp_get(SYSMOBTS_TEMP_DIGITAL, SYSMOBTS_TEMP_INPUT) / 1000.0f, VTY_NEWLINE); - vty_out(vty, " RF: %f Celcius%s", + vty_out(vty, " RF: %f Celsius%s", sysmobts_temp_get(SYSMOBTS_TEMP_RF, SYSMOBTS_TEMP_INPUT) / 1000.0f, VTY_NEWLINE); @@ -396,8 +378,8 @@ DEFUN(show_mgr, show_mgr_cmd, "show manager", is_sbts2050_master() ? "master" : "slave", VTY_NEWLINE); sbts2050_uc_check_temp(&temp_pa, &temp_board); - vty_out(vty, " sysmoBTS 2050 PA: %d Celcius%s", temp_pa, VTY_NEWLINE); - vty_out(vty, " sysmoBTS 2050 PA: %d Celcius%s", temp_board, VTY_NEWLINE); + vty_out(vty, " sysmoBTS 2050 PA: %d Celsius%s", temp_pa, VTY_NEWLINE); + vty_out(vty, " sysmoBTS 2050 PA: %d Celsius%s", temp_board, VTY_NEWLINE); sbts2050_uc_get_status(&status); vty_out(vty, "Power Status%s", VTY_NEWLINE); diff --git a/src/osmo-bts-sysmo/misc/sysmobts_misc.c b/src/osmo-bts-sysmo/misc/sysmobts_misc.c index d996d644..b391ce66 100644 --- a/src/osmo-bts-sysmo/misc/sysmobts_misc.c +++ b/src/osmo-bts-sysmo/misc/sysmobts_misc.c @@ -10,7 +10,7 @@ * 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. + * 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/>. @@ -46,7 +46,7 @@ * Temperature handling *********************************************************************/ -#define TEMP_PATH "/sys/class/hwmon/hwmon0/device/temp%u_%s" +#define TEMP_PATH "/sys/class/hwmon/hwmon0/device/hwmon/hwmon0/temp%u_%s" static const char *temp_type_str[_NUM_TEMP_TYPES] = { [SYSMOBTS_TEMP_INPUT] = "input", @@ -234,14 +234,14 @@ int sysmobts_firmware_reload(enum sysmobts_firmware_type type) fd_in = open(name, O_RDONLY); if (fd_in < 0) { - LOGP(DFW, LOGL_ERROR, "unable ot open firmware file %s: %s\n", + LOGP(DFW, LOGL_ERROR, "unable to open firmware file %s: %s\n", name, strerror(errno)); return fd_in; } fd_out = open(fw_devs[type], O_WRONLY); if (fd_out < 0) { - LOGP(DFW, LOGL_ERROR, "unable ot open firmware device %s: %s\n", + LOGP(DFW, LOGL_ERROR, "unable to open firmware device %s: %s\n", fw_devs[type], strerror(errno)); close(fd_in); return fd_out; diff --git a/src/osmo-bts-sysmo/misc/sysmobts_nl.c b/src/osmo-bts-sysmo/misc/sysmobts_nl.c index 67aa6636..5a937dcb 100644 --- a/src/osmo-bts-sysmo/misc/sysmobts_nl.c +++ b/src/osmo-bts-sysmo/misc/sysmobts_nl.c @@ -13,7 +13,7 @@ * 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. + * 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-bts-sysmo/misc/sysmobts_nl.h b/src/osmo-bts-sysmo/misc/sysmobts_nl.h index 84f4d9cc..5dda4679 100644 --- a/src/osmo-bts-sysmo/misc/sysmobts_nl.h +++ b/src/osmo-bts-sysmo/misc/sysmobts_nl.h @@ -11,7 +11,7 @@ * 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. + * 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-bts-sysmo/misc/sysmobts_par.c b/src/osmo-bts-sysmo/misc/sysmobts_par.c index de81fff5..0e8685f3 100644 --- a/src/osmo-bts-sysmo/misc/sysmobts_par.c +++ b/src/osmo-bts-sysmo/misc/sysmobts_par.c @@ -12,7 +12,7 @@ * 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. + * 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/>. @@ -341,6 +341,8 @@ char *sysmobts_model(int bts_type, int trx_num) case 0xffff: case 1002: return "sysmoBTS 1002"; + case 1003: + return "sysmoBTS 1002+GPS+PoE"; case 2050: switch(trx_num) { case 0: diff --git a/src/osmo-bts-sysmo/misc/sysmobts_util.c b/src/osmo-bts-sysmo/misc/sysmobts_util.c index c9930d8f..bd751431 100644 --- a/src/osmo-bts-sysmo/misc/sysmobts_util.c +++ b/src/osmo-bts-sysmo/misc/sysmobts_util.c @@ -12,7 +12,7 @@ * 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. + * 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-bts-sysmo/oml.c b/src/osmo-bts-sysmo/oml.c index ea7527dd..b4f6752d 100644 --- a/src/osmo-bts-sysmo/oml.c +++ b/src/osmo-bts-sysmo/oml.c @@ -11,7 +11,7 @@ * 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. + * 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/>. @@ -23,6 +23,7 @@ #include <osmocom/core/talloc.h> #include <osmocom/core/utils.h> +#include <osmocom/core/fsm.h> #include <sysmocom/femtobts/gsml1prim.h> #include <sysmocom/femtobts/gsml1const.h> @@ -39,11 +40,14 @@ #include <osmo-bts/phy_link.h> #include <osmo-bts/handover.h> #include <osmo-bts/l1sap.h> +#include <osmo-bts/nm_common_fsm.h> #include "l1_if.h" #include "femtobts.h" #include "utils.h" +static void dump_lch_par(int logl, GsmL1_LogChParam_t *lch_par, GsmL1_Sapi_t sapi); + static int mph_info_chan_confirm(struct gsm_lchan *lchan, enum osmo_mph_info_type type, uint8_t cause) { @@ -88,13 +92,13 @@ static const enum GsmL1_LogChComb_t pchan_to_logChComb[_GSM_PCHAN_MAX] = { [GSM_PCHAN_PDCH] = GsmL1_LogChComb_XIII, [GSM_PCHAN_UNKNOWN] = GsmL1_LogChComb_0, /* - * GSM_PCHAN_TCH_F_PDCH and GSM_PCHAN_TCH_F_TCH_H_PDCH should not be + * GSM_PCHAN_TCH_F_PDCH and GSM_PCHAN_OSMO_DYN should not be * part of this, only "real" pchan values will be looked up here. * See the callers of ts_connect_as(). */ }; -static int trx_rf_lock(struct gsm_bts_trx *trx, int locked, l1if_compl_cb *cb); +int trx_rf_lock(struct gsm_bts_trx *trx, int locked, l1if_compl_cb *cb); static void *prim_init(GsmL1_Prim_t *prim, GsmL1_PrimId_t id, struct femtol1_hdl *gl1, HANDLE hLayer3) @@ -267,36 +271,48 @@ static int opstart_compl(struct gsm_abis_mo *mo, struct msgb *l1_msg) { GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg); GsmL1_Status_t status = prim_status(l1p); + struct gsm_bts_trx *trx = gsm_bts_trx_num(mo->bts, mo->obj_inst.trx_nr); if (status != GsmL1_Status_Success) { LOGP(DL1C, LOGL_ERROR, "Rx %s, status: %s\n", get_value_string(femtobts_l1prim_names, l1p->id), get_value_string(femtobts_l1status_names, status)); msgb_free(l1_msg); - return oml_mo_opstart_nack(mo, NM_NACK_CANT_PERFORM); + switch (mo->obj_class) { + case NM_OC_RADIO_CARRIER: + return osmo_fsm_inst_dispatch(trx->mo.fi, NM_EV_OPSTART_NACK, + (void*)(intptr_t)NM_NACK_CANT_PERFORM); + case NM_OC_CHANNEL: + return osmo_fsm_inst_dispatch(trx->ts[mo->obj_inst.ts_nr].mo.fi, NM_EV_OPSTART_NACK, + (void*)(intptr_t)NM_NACK_CANT_PERFORM); + default: + OSMO_ASSERT(0); + } } msgb_free(l1_msg); - - /* Set to Operational State: Enabled */ - oml_mo_state_chg(mo, NM_OPSTATE_ENABLED, NM_AVSTATE_OK); - - /* ugly hack to auto-activate all SAPIs for the BCCH/CCCH on TS0 */ - if (mo->obj_class == NM_OC_CHANNEL && mo->obj_inst.trx_nr == 0 && - mo->obj_inst.ts_nr == 0) { - struct gsm_lchan *cbch = gsm_bts_get_cbch(mo->bts); - DEBUGP(DL1C, "====> trying to activate lchans of BCCH\n"); - mo->bts->c0->ts[0].lchan[CCCH_LCHAN].rel_act_kind = - LCHAN_REL_ACT_OML; - lchan_activate(&mo->bts->c0->ts[0].lchan[CCCH_LCHAN]); - if (cbch) { - cbch->rel_act_kind = LCHAN_REL_ACT_OML; - lchan_activate(cbch); + switch (mo->obj_class) { + case NM_OC_RADIO_CARRIER: + return osmo_fsm_inst_dispatch(trx->mo.fi, NM_EV_OPSTART_ACK, NULL); + case NM_OC_CHANNEL: + /* ugly hack to auto-activate all SAPIs for the BCCH/CCCH on TS0 */ + if (mo->obj_inst.trx_nr == 0 && + mo->obj_inst.ts_nr == 0) { + struct gsm_lchan *cbch = gsm_bts_get_cbch(mo->bts); + DEBUGP(DL1C, "====> trying to activate lchans of BCCH\n"); + mo->bts->c0->ts[0].lchan[CCCH_LCHAN].rel_act_kind = + LCHAN_REL_ACT_OML; + lchan_activate(&mo->bts->c0->ts[0].lchan[CCCH_LCHAN]); + if (cbch) { + cbch->rel_act_kind = LCHAN_REL_ACT_OML; + lchan_activate(cbch); + } } + return osmo_fsm_inst_dispatch(trx->ts[mo->obj_inst.ts_nr].mo.fi, + NM_EV_OPSTART_ACK, NULL); + default: + OSMO_ASSERT(0); } - - /* Send OPSTART ack */ - return oml_mo_opstart_ack(mo); } static int opstart_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg, @@ -325,6 +341,8 @@ static int trx_mute_on_init_cb(struct gsm_bts_trx *trx, struct msgb *resp, bts_shutdown(trx->bts, "RF-MUTE failure"); } + bts_update_status(BTS_STATUS_RF_MUTE, 1); + msgb_free(resp); return 0; @@ -358,7 +376,7 @@ static int trx_init_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg, #endif /* Begin to ramp up the power */ - power_ramp_start(trx, get_p_target_mdBm(trx, 0), 0); + power_ramp_start(trx, get_p_target_mdBm(trx, 0), 0, NULL); return opstart_compl(&trx->mo, l1_msg); } @@ -394,8 +412,9 @@ static int trx_init(struct gsm_bts_trx *trx) ARRAY_SIZE(trx_rqd_attr))) { /* HACK: spec says we need to decline, but openbsc * doesn't deal with this very well */ - return oml_mo_opstart_ack(&trx->mo); - //return oml_mo_opstart_nack(&trx->mo, NM_NACK_CANT_PERFORM); + return osmo_fsm_inst_dispatch(trx->mo.fi, NM_EV_OPSTART_ACK, NULL); + //return osmo_fsm_inst_dispatch(trx->mo.fi, NM_EV_OPSTART_NACK, + // (void*)(intptr_t)NM_NACK_CANT_PERFORM); } femto_band = sysmobts_select_femto_band(trx, trx->arfcn); @@ -412,9 +431,16 @@ static int trx_init(struct gsm_bts_trx *trx) dev_par->freqBand = femto_band; dev_par->u16Arfcn = trx->arfcn; dev_par->u16BcchArfcn = trx->bts->c0->arfcn; - dev_par->u8NbTsc = trx->bts->bsic & 7; - dev_par->fRxPowerLevel = trx_ms_pwr_ctrl_is_osmo(trx) - ? 0.0 : trx->bts->ul_power_target; + dev_par->u8NbTsc = BTS_TSC(trx->bts); + + if (!trx_ms_pwr_ctrl_is_osmo(trx)) { + /* Target is in the middle between lower and upper RxLev thresholds */ + int lower_dbm = rxlev2dbm(trx->ms_dpc_params->rxlev_meas.lower_thresh); + int upper_dbm = rxlev2dbm(trx->ms_dpc_params->rxlev_meas.upper_thresh); + dev_par->fRxPowerLevel = (float) (lower_dbm + upper_dbm) / 2; + } else { + dev_par->fRxPowerLevel = 0.0; + } dev_par->fTxPowerLevel = ((float) initial_mdBm) / 1000; LOGP(DL1C, LOGL_NOTICE, "Init TRX (ARFCN %u, TSC %u, RxPower % 2f dBm, " @@ -426,9 +452,9 @@ static int trx_init(struct gsm_bts_trx *trx) return l1if_gsm_req_compl(fl1h, msg, trx_init_compl_cb, NULL); } -uint32_t trx_get_hlayer1(struct gsm_bts_trx *trx) +uint32_t trx_get_hlayer1(const struct gsm_bts_trx *trx) { - struct femtol1_hdl *fl1h = trx_femtol1_hdl(trx); + const struct femtol1_hdl *fl1h = trx_femtol1_hdl(trx); return fl1h->hLayer1; } @@ -437,23 +463,27 @@ static int trx_close_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg, void *data) { msgb_free(l1_msg); + bts_model_trx_close_cb(trx, 0); return 0; } -int bts_model_trx_close(struct gsm_bts_trx *trx) +void bts_model_trx_close(struct gsm_bts_trx *trx) { struct femtol1_hdl *fl1h = trx_femtol1_hdl(trx); struct msgb *msg; + int rc; msg = l1p_msgb_alloc(); prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphCloseReq, fl1h, l1p_handle_for_trx(trx)); LOGP(DL1C, LOGL_NOTICE, "Close TRX %u\n", trx->nr); - return l1if_gsm_req_compl(fl1h, msg, trx_close_compl_cb, NULL); + rc = l1if_gsm_req_compl(fl1h, msg, trx_close_compl_cb, NULL); + if (rc < 0) + bts_model_trx_close_cb(trx, rc); } -static int trx_rf_lock(struct gsm_bts_trx *trx, int locked, l1if_compl_cb *cb) +int trx_rf_lock(struct gsm_bts_trx *trx, int locked, l1if_compl_cb *cb) { struct femtol1_hdl *fl1h = trx_femtol1_hdl(trx); uint8_t mute[8]; @@ -495,7 +525,7 @@ static int ts_connect_as(struct gsm_bts_trx_ts *ts, GsmL1_MphConnectReq_t *cr; if (pchan == GSM_PCHAN_TCH_F_PDCH - || pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH) { + || pchan == GSM_PCHAN_OSMO_DYN) { LOGP(DL1C, LOGL_ERROR, "%s Requested TS connect as %s," " expected a specific pchan instead\n", @@ -508,10 +538,9 @@ static int ts_connect_as(struct gsm_bts_trx_ts *ts, cr->u8Tn = ts->nr; cr->logChComb = pchan_to_logChComb[pchan]; - DEBUGP(DL1C, "%s pchan=%s ts_connect_as(%s) logChComb=%s\n", - gsm_lchan_name(ts->lchan), gsm_pchan_name(ts->pchan), - gsm_pchan_name(pchan), get_value_string(femtobts_chcomb_names, - cr->logChComb)); + LOGPLCHAN(ts->lchan, DL1C, LOGL_DEBUG, "pchan=%s ts_connect_as(%s) logChComb=%s\n", + gsm_pchan_name(ts->pchan), gsm_pchan_name(pchan), + get_value_string(femtobts_chcomb_names, cr->logChComb)); return l1if_gsm_req_compl(fl1h, msg, cb, NULL); } @@ -520,7 +549,7 @@ static int ts_opstart(struct gsm_bts_trx_ts *ts) { enum gsm_phys_chan_config pchan = ts->pchan; switch (pchan) { - case GSM_PCHAN_TCH_F_TCH_H_PDCH: + case GSM_PCHAN_OSMO_DYN: ts->dyn.pchan_is = ts->dyn.pchan_want = GSM_PCHAN_NONE; /* First connect as NONE, until first RSL CHAN ACT. */ pchan = GSM_PCHAN_NONE; @@ -544,8 +573,7 @@ GsmL1_Sapi_t lchan_to_GsmL1_Sapi_t(const struct gsm_lchan *lchan) case GSM_LCHAN_TCH_H: return GsmL1_Sapi_TchH; default: - LOGP(DL1C, LOGL_NOTICE, "%s cannot determine L1 SAPI\n", - gsm_lchan_name(lchan)); + LOGPLCHAN(lchan, DL1C, LOGL_NOTICE, "cannot determine L1 SAPI\n"); break; } return GsmL1_Sapi_Idle; @@ -555,7 +583,7 @@ GsmL1_SubCh_t lchan_to_GsmL1_SubCh_t(const struct gsm_lchan *lchan) { enum gsm_phys_chan_config pchan = lchan->ts->pchan; - if (pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH) + if (pchan == GSM_PCHAN_OSMO_DYN) pchan = lchan->ts->dyn.pchan_want; switch (pchan) { @@ -575,7 +603,7 @@ GsmL1_SubCh_t lchan_to_GsmL1_SubCh_t(const struct gsm_lchan *lchan) case GSM_PCHAN_TCH_F_PDCH: case GSM_PCHAN_UNKNOWN: default: - /* case GSM_PCHAN_TCH_F_TCH_H_PDCH: is caught above */ + /* case GSM_PCHAN_OSMO_DYN: is caught above */ return GsmL1_SubCh_NA; } @@ -638,10 +666,6 @@ static const struct sapi_dir pdtch_sapis[] = { #endif }; -static const struct sapi_dir ho_sapis[] = { - { GsmL1_Sapi_Rach, GsmL1_Dir_RxUplink }, -}; - struct lchan_sapis { const struct sapi_dir *sapis; unsigned int num_sapis; @@ -674,11 +698,6 @@ static const struct lchan_sapis sapis_for_lchan[_GSM_LCHAN_MAX] = { }, }; -static const struct lchan_sapis sapis_for_ho = { - .sapis = ho_sapis, - .num_sapis = ARRAY_SIZE(ho_sapis), -}; - static int mph_send_activate_req(struct gsm_lchan *lchan, struct sapi_cmd *cmd); static int mph_send_deactivate_req(struct gsm_lchan *lchan, struct sapi_cmd *cmd); static int mph_send_config_ciphering(struct gsm_lchan *lchan, struct sapi_cmd *cmd); @@ -766,12 +785,8 @@ static void sapi_queue_dispatch(struct gsm_lchan *lchan, int status) talloc_free(cmd); if (end || llist_empty(&lchan->sapi_cmds)) { - LOGP(DL1C, LOGL_DEBUG, - "%s End of SAPI cmd queue encountered.%s\n", - gsm_lchan_name(lchan), - llist_empty(&lchan->sapi_cmds) - ? " Queue is now empty." - : " More pending."); + LOGPLCHAN(lchan, DL1C, LOGL_DEBUG, "End of SAPI cmd queue encountered.%s\n", + llist_empty(&lchan->sapi_cmds) ? " Queue is now empty." : " More pending."); return; } @@ -811,8 +826,7 @@ static int lchan_act_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg, goto err; } - LOGP(DL1C, LOGL_INFO, "%s MPH-ACTIVATE.conf (%s ", - gsm_lchan_name(lchan), + LOGPLCHAN(lchan, DL1C, LOGL_INFO, "MPH-ACTIVATE.conf (%s ", get_value_string(femtobts_l1sapi_names, ic->sapi)); LOGPC(DL1C, LOGL_INFO, "%s)\n", get_value_string(femtobts_dir_names, ic->dir)); @@ -834,19 +848,15 @@ static int lchan_act_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg, lchan->sapis_ul[ic->sapi] = status; if (llist_empty(&lchan->sapi_cmds)) { - LOGP(DL1C, LOGL_ERROR, - "%s Got activation confirmation with empty queue\n", - gsm_lchan_name(lchan)); + LOGPLCHAN(lchan, DL1C, LOGL_ERROR, "Got activation confirmation with empty queue\n"); goto err; } cmd = llist_entry(lchan->sapi_cmds.next, struct sapi_cmd, entry); if (cmd->sapi != ic->sapi || cmd->dir != ic->dir || cmd->type != SAPI_CMD_ACTIVATE) { - LOGP(DL1C, LOGL_ERROR, - "%s Confirmation mismatch (%d, %d) (%d, %d)\n", - gsm_lchan_name(lchan), cmd->sapi, cmd->dir, - ic->sapi, ic->dir); + LOGPLCHAN(lchan, DL1C, LOGL_ERROR, "Confirmation mismatch (%d, %d) (%d, %d)\n", + cmd->sapi, cmd->dir, ic->sapi, ic->dir); goto err; } @@ -926,15 +936,14 @@ static void set_payload_format(GsmL1_LogChParam_t *lch_par) #endif /* L1_HAS_RTP_MODE */ } -static void lchan2lch_par(GsmL1_LogChParam_t *lch_par, struct gsm_lchan *lchan) +static int lchan2lch_par(GsmL1_LogChParam_t *lch_par, struct gsm_lchan *lchan) { struct amr_multirate_conf *amr_mrc = &lchan->tch.amr_mr; struct gsm48_multi_rate_conf *mr_conf = (struct gsm48_multi_rate_conf *) amr_mrc->gsm48_ie; int j; - LOGP(DL1C, LOGL_INFO, "%s: %s tch_mode=0x%02x\n", - gsm_lchan_name(lchan), __FUNCTION__, lchan->tch_mode); + LOGPLCHAN(lchan, DL1C, LOGL_INFO, ": %s tch_mode=0x%02x\n", __func__, lchan->tch_mode); switch (lchan->tch_mode) { case GSM48_CMODE_SIGN: @@ -962,7 +971,9 @@ static void lchan2lch_par(GsmL1_LogChParam_t *lch_par, struct gsm_lchan *lchan) case GSM48_CMODE_SPEECH_AMR: lch_par->tch.tchPlType = GsmL1_TchPlType_Amr; set_payload_format(lch_par); - lch_par->tch.amrCmiPhase = GsmL1_AmrCmiPhase_Odd; /* FIXME? */ + /* At call set-up, after every successful handover and after a channel mode modify, the + * default phase (odd) shall be used in downlink direction. */ + lch_par->tch.amrCmiPhase = GsmL1_AmrCmiPhase_Odd; lch_par->tch.amrInitCodecMode = amr_get_initial_mode(lchan); /* initialize to clean state */ @@ -1011,10 +1022,13 @@ static void lchan2lch_par(GsmL1_LogChParam_t *lch_par, struct gsm_lchan *lchan) case GSM48_CMODE_DATA_12k0: case GSM48_CMODE_DATA_6k0: case GSM48_CMODE_DATA_3k6: - LOGP(DL1C, LOGL_ERROR, "%s: CSD not supported!\n", - gsm_lchan_name(lchan)); - break; + default: + LOGPLCHAN(lchan, DL1C, LOGL_ERROR, "Channel mode %s is not supported!\n", + gsm48_chan_mode_name(lchan->tch_mode)); + return -ENOTSUP; } + + return 0; } static int mph_send_activate_req(struct gsm_lchan *lchan, struct sapi_cmd *cmd) @@ -1023,6 +1037,7 @@ static int mph_send_activate_req(struct gsm_lchan *lchan, struct sapi_cmd *cmd) struct msgb *msg = l1p_msgb_alloc(); int sapi = cmd->sapi; int dir = cmd->dir; + int rc; GsmL1_MphActivateReq_t *act_req; GsmL1_LogChParam_t *lch_par; @@ -1045,7 +1060,10 @@ static int mph_send_activate_req(struct gsm_lchan *lchan, struct sapi_cmd *cmd) break; case GsmL1_Sapi_TchH: case GsmL1_Sapi_TchF: - lchan2lch_par(lch_par, lchan); + if ((rc = lchan2lch_par(lch_par, lchan)) != 0) { + talloc_free(msg); + return rc; + } /* * Be sure that every packet is received, even if it * fails. In this case the length might be lower or 0. @@ -1078,9 +1096,9 @@ static int mph_send_activate_req(struct gsm_lchan *lchan, struct sapi_cmd *cmd) break; } - LOGP(DL1C, LOGL_INFO, "%s MPH-ACTIVATE.req (hL2=0x%08x, %s ", - gsm_lchan_name(lchan), act_req->hLayer2, - get_value_string(femtobts_l1sapi_names, act_req->sapi)); + LOGPLCHAN(lchan, DL1C, LOGL_INFO, "MPH-ACTIVATE.req (hL2=0x%08x, %s ", act_req->hLayer2, + get_value_string(femtobts_l1sapi_names, act_req->sapi)); + dump_lch_par(LOGL_INFO, lch_par, act_req->sapi); LOGPC(DL1C, LOGL_INFO, "%s)\n", get_value_string(femtobts_dir_names, act_req->dir)); @@ -1104,9 +1122,7 @@ static int sapi_activate_cb(struct gsm_lchan *lchan, int status) /* FIXME: Error handling */ if (status != GsmL1_Status_Success) { - LOGP(DL1C, LOGL_ERROR, - "%s act failed mark broken due status: %d\n", - gsm_lchan_name(lchan), status); + LOGPLCHAN(lchan, DL1C, LOGL_ERROR, "act failed mark broken due status: %d\n", status); lchan_set_state(lchan, LCHAN_S_BROKEN); sapi_clear_queue(&lchan->sapi_cmds); mph_info_chan_confirm(lchan, PRIM_INFO_ACTIVATE, RSL_ERR_PROCESSOR_OVERLOAD); @@ -1153,14 +1169,12 @@ int lchan_activate(struct gsm_lchan *lchan) lchan_set_state(lchan, LCHAN_S_ACT_REQ); if (!llist_empty(&lchan->sapi_cmds)) - LOGP(DL1C, LOGL_ERROR, - "%s Trying to activate lchan, but commands in queue\n", - gsm_lchan_name(lchan)); + LOGPLCHAN(lchan, DL1C, LOGL_ERROR, "Trying to activate lchan, but commands in queue\n"); - /* override the regular SAPIs if this is the first hand-over - * related activation of the LCHAN */ + /* For handover, always start the main channel immediately. lchan->want_dl_sacch_active indicates whether dl + * SACCH should be activated. */ if (lchan->ho.active == HANDOVER_ENABLED) - s4l = &sapis_for_ho; + enqueue_sapi_act_cmd(lchan, GsmL1_Sapi_Rach, GsmL1_Dir_RxUplink); for (i = 0; i < s4l->num_sapis; i++) { int sapi = s4l->sapis[i].sapi; @@ -1173,12 +1187,13 @@ int lchan_activate(struct gsm_lchan *lchan) fl1h->alive_prim_cnt = 0; osmo_timer_schedule(&fl1h->alive_timer, 5, 0); } + + /* For handover, possibly postpone activating the dl SACCH until the HO RACH is received. */ + if (sapi == GsmL1_Sapi_Sacch && dir == GsmL1_Dir_TxDownlink + && !lchan->want_dl_sacch_active) + continue; enqueue_sapi_act_cmd(lchan, sapi, dir); } - -#warning "FIXME: Should this be in sapi_activate_cb?" - lchan_init_lapdm(lchan); - return 0; } @@ -1264,9 +1279,8 @@ static int chmod_modif_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg, goto err; } - LOGP(DL1C, LOGL_INFO, "%s MPH-CONFIG.conf (%s) ", - gsm_lchan_name(lchan), - get_value_string(femtobts_l1cfgt_names, cc->cfgParamId)); + LOGPLCHAN(lchan, DL1C, LOGL_INFO, "MPH-CONFIG.conf (%s) ", + get_value_string(femtobts_l1cfgt_names, cc->cfgParamId)); switch (cc->cfgParamId) { case GsmL1_ConfigParamId_SetLogChParams: @@ -1298,9 +1312,7 @@ static int chmod_modif_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg, break; } if (llist_empty(&lchan->sapi_cmds)) { - LOGP(DL1C, LOGL_ERROR, - "%s Got ciphering conf with empty queue\n", - gsm_lchan_name(lchan)); + LOGPLCHAN(lchan, DL1C, LOGL_ERROR, "Got ciphering conf with empty queue\n"); goto err; } @@ -1325,6 +1337,7 @@ static int mph_send_config_logchpar(struct gsm_lchan *lchan, struct sapi_cmd *cm struct msgb *msg = l1p_msgb_alloc(); GsmL1_MphConfigReq_t *conf_req; GsmL1_LogChParam_t *lch_par; + int rc; /* channel mode, encryption and/or multirate have changed */ @@ -1339,7 +1352,10 @@ static int mph_send_config_logchpar(struct gsm_lchan *lchan, struct sapi_cmd *cm conf_req->hLayer3 = l1if_lchan_to_hLayer(lchan); lch_par = &conf_req->cfgParams.setLogChParams.logChParams; - lchan2lch_par(lch_par, lchan); + if ((rc = lchan2lch_par(lch_par, lchan)) != 0) { + talloc_free(msg); + return rc; + } /* Update the MS Power Level */ if (cmd->sapi == GsmL1_Sapi_Sacch && trx_ms_pwr_ctrl_is_osmo(trx)) @@ -1347,10 +1363,8 @@ static int mph_send_config_logchpar(struct gsm_lchan *lchan, struct sapi_cmd *cm /* FIXME: update encryption */ - LOGP(DL1C, LOGL_INFO, "%s MPH-CONFIG.req (%s) ", - gsm_lchan_name(lchan), - get_value_string(femtobts_l1sapi_names, - conf_req->cfgParams.setLogChParams.sapi)); + LOGPLCHAN(lchan, DL1C, LOGL_INFO, "MPH-CONFIG.req (%s) ", + get_value_string(femtobts_l1sapi_names, conf_req->cfgParams.setLogChParams.sapi)); LOGPC(DL1C, LOGL_INFO, "cfgParams Tn=%u, subCh=%u, dir=0x%x ", conf_req->cfgParams.setLogChParams.u8Tn, conf_req->cfgParams.setLogChParams.subCh, @@ -1417,11 +1431,9 @@ static int mph_send_config_ciphering(struct gsm_lchan *lchan, struct sapi_cmd *c return -EINVAL; cfgr->cfgParams.setCipheringParams.cipherId = rsl2l1_ciph[lchan->encr.alg_id]; - LOGP(DL1C, LOGL_NOTICE, "%s SET_CIPHERING (ALG=%u %s)\n", - gsm_lchan_name(lchan), - cfgr->cfgParams.setCipheringParams.cipherId, - get_value_string(femtobts_dir_names, - cfgr->cfgParams.setCipheringParams.dir)); + LOGPLCHAN(lchan, DL1C, LOGL_NOTICE, "SET_CIPHERING (ALG=%u %s)\n", + cfgr->cfgParams.setCipheringParams.cipherId, + get_value_string(femtobts_dir_names, cfgr->cfgParams.setCipheringParams.dir)); memcpy(cfgr->cfgParams.setCipheringParams.u8Kc, lchan->encr.key, lchan->encr.key_len); @@ -1458,6 +1470,16 @@ int l1if_set_ciphering(struct femtol1_hdl *fl1h, return 0; } +int l1if_set_ul_acc(struct gsm_lchan *lchan, bool active) +{ + if (active) + enqueue_sapi_act_cmd(lchan, GsmL1_Sapi_Rach, GsmL1_Dir_RxUplink); + else + check_sapi_release(lchan, GsmL1_Sapi_Rach, GsmL1_Dir_RxUplink); + + return 0; +} + int bts_model_adjst_ms_pwr(struct gsm_lchan *lchan) { if (lchan->state != LCHAN_S_ACTIVE) @@ -1499,9 +1521,8 @@ static int lchan_deact_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg, goto err; } - LOGP(DL1C, LOGL_INFO, "%s MPH-DEACTIVATE.conf (%s ", - gsm_lchan_name(lchan), - get_value_string(femtobts_l1sapi_names, ic->sapi)); + LOGPLCHAN(lchan, DL1C, LOGL_INFO, "MPH-DEACTIVATE.conf (%s ", + get_value_string(femtobts_l1sapi_names, ic->sapi)); LOGPC(DL1C, LOGL_INFO, "%s)\n", get_value_string(femtobts_dir_names, ic->dir)); @@ -1523,19 +1544,15 @@ static int lchan_deact_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg, if (llist_empty(&lchan->sapi_cmds)) { - LOGP(DL1C, LOGL_ERROR, - "%s Got de-activation confirmation with empty queue\n", - gsm_lchan_name(lchan)); + LOGPLCHAN(lchan, DL1C, LOGL_ERROR, "Got de-activation confirmation with empty queue\n"); goto err; } cmd = llist_entry(lchan->sapi_cmds.next, struct sapi_cmd, entry); if (cmd->sapi != ic->sapi || cmd->dir != ic->dir || cmd->type != SAPI_CMD_DEACTIVATE) { - LOGP(DL1C, LOGL_ERROR, - "%s Confirmation mismatch (%d, %d) (%d, %d)\n", - gsm_lchan_name(lchan), cmd->sapi, cmd->dir, - ic->sapi, ic->dir); + LOGPLCHAN(lchan, DL1C, LOGL_ERROR, "Confirmation mismatch (%d, %d) (%d, %d)\n", + cmd->sapi, cmd->dir, ic->sapi, ic->dir); goto err; } @@ -1560,8 +1577,7 @@ static int mph_send_deactivate_req(struct gsm_lchan *lchan, struct sapi_cmd *cmd deact_req->sapi = cmd->sapi; deact_req->hLayer3 = l1if_lchan_to_hLayer(lchan); - LOGP(DL1C, LOGL_INFO, "%s MPH-DEACTIVATE.req (%s ", - gsm_lchan_name(lchan), + LOGPLCHAN(lchan, DL1C, LOGL_INFO, "MPH-DEACTIVATE.req (%s ", get_value_string(femtobts_l1sapi_names, deact_req->sapi)); LOGPC(DL1C, LOGL_INFO, "%s)\n", get_value_string(femtobts_dir_names, deact_req->dir)); @@ -1574,8 +1590,7 @@ static int sapi_deactivate_cb(struct gsm_lchan *lchan, int status) { /* FIXME: Error handling. There is no NACK... */ if (status != GsmL1_Status_Success && lchan->state == LCHAN_S_REL_REQ) { - LOGP(DL1C, LOGL_ERROR, "%s is now broken. Stopping the release.\n", - gsm_lchan_name(lchan)); + LOGPLCHAN(lchan, DL1C, LOGL_ERROR, "is now broken. Stopping the release.\n"); lchan_set_state(lchan, LCHAN_S_BROKEN); sapi_clear_queue(&lchan->sapi_cmds); mph_info_chan_confirm(lchan, PRIM_INFO_DEACTIVATE, 0); @@ -1632,17 +1647,9 @@ static int check_sapi_release(struct gsm_lchan *lchan, int sapi, int dir) return enqueue_sapi_deact_cmd(lchan, sapi, dir); } -static int release_sapis_for_ho(struct gsm_lchan *lchan) +static int release_sapi_ul_rach(struct gsm_lchan *lchan) { - int res = 0; - int i; - - const struct lchan_sapis *s4l = &sapis_for_ho; - - for (i = s4l->num_sapis-1; i >= 0; i--) - res |= check_sapi_release(lchan, - s4l->sapis[i].sapi, s4l->sapis[i].dir); - return res; + return check_sapi_release(lchan, GsmL1_Sapi_Rach, GsmL1_Dir_RxUplink); } static int lchan_deactivate_sapis(struct gsm_lchan *lchan) @@ -1664,12 +1671,11 @@ static int lchan_deactivate_sapis(struct gsm_lchan *lchan) } /* always attempt to disable the RACH burst */ - res |= release_sapis_for_ho(lchan); + res |= release_sapi_ul_rach(lchan); /* nothing was queued */ if (res == 0) { - LOGP(DL1C, LOGL_ERROR, "%s all SAPIs already released?\n", - gsm_lchan_name(lchan)); + LOGPLCHAN(lchan, DL1C, LOGL_ERROR, "all SAPIs already released?\n"); lchan_set_state(lchan, LCHAN_S_BROKEN); mph_info_chan_confirm(lchan, PRIM_INFO_DEACTIVATE, 0); } @@ -1716,67 +1722,55 @@ int bts_model_check_oml(struct gsm_bts *bts, uint8_t msg_type, void *obj) { /* FIXME: more checks if the attributes are valid */ - - switch (msg_type) { - case NM_MT_SET_CHAN_ATTR: - /* our L1 only supports one global TSC for all channels - * one one TRX, so we need to make sure not to activate - * channels with a different TSC!! */ - if (TLVP_PRES_LEN(new_attr, NM_ATT_TSC, 1) && - *TLVP_VAL(new_attr, NM_ATT_TSC) != (bts->bsic & 7)) { - LOGP(DOML, LOGL_ERROR, "Channel TSC %u != BSIC-TSC %u\n", - *TLVP_VAL(new_attr, NM_ATT_TSC), bts->bsic & 7); - return -NM_NACK_PARAM_RANGE; - } - break; - } return 0; } /* callback from OML */ -int bts_model_apply_oml(struct gsm_bts *bts, struct msgb *msg, - struct tlv_parsed *new_attr, int kind, void *obj) +int bts_model_apply_oml(struct gsm_bts *bts, const struct msgb *msg, + struct gsm_abis_mo *mo, void *obj) { - if (kind == NM_OC_RADIO_CARRIER) { - struct gsm_bts_trx *trx = obj; - struct femtol1_hdl *fl1h = trx_femtol1_hdl(trx); + struct abis_om_fom_hdr *foh = msgb_l3(msg); + struct gsm_bts_trx *trx; + struct femtol1_hdl *fl1h; + + switch (foh->msg_type) { + case NM_MT_SET_RADIO_ATTR: + trx = obj; + fl1h = trx_femtol1_hdl(trx); /* Did we go through MphInit yet? If yes fire and forget */ if (fl1h->hLayer1) - power_ramp_start(trx, get_p_target_mdBm(trx, 0), 0); + power_ramp_start(trx, get_p_target_mdBm(trx, 0), 0, NULL); + break; } - /* FIXME: we actaully need to send a ACK or NACK for the OML message */ - return oml_fom_ack_nack(msg, 0); + return 0; } /* callback from OML */ int bts_model_opstart(struct gsm_bts *bts, struct gsm_abis_mo *mo, void *obj) { + struct gsm_bts_trx* trx; + struct gsm_bts_trx_ts *ts; int rc; switch (mo->obj_class) { - case NM_OC_RADIO_CARRIER: - rc = trx_init(obj); - break; - case NM_OC_CHANNEL: - rc = ts_opstart(obj); - break; - case NM_OC_BTS: case NM_OC_SITE_MANAGER: + case NM_OC_BTS: case NM_OC_BASEB_TRANSC: case NM_OC_GPRS_NSE: case NM_OC_GPRS_CELL: case NM_OC_GPRS_NSVC: - oml_mo_state_chg(mo, NM_OPSTATE_ENABLED, -1); - rc = oml_mo_opstart_ack(mo); - if (mo->obj_class == NM_OC_BTS) { - oml_mo_state_chg(&bts->mo, -1, NM_AVSTATE_OK); - oml_mo_state_chg(&bts->gprs.nse.mo, -1, NM_AVSTATE_OK); - oml_mo_state_chg(&bts->gprs.cell.mo, -1, NM_AVSTATE_OK); - oml_mo_state_chg(&bts->gprs.nsvc[0].mo, -1, NM_AVSTATE_OK); - } + rc = osmo_fsm_inst_dispatch(mo->fi, NM_EV_OPSTART_ACK, NULL); + break; + case NM_OC_RADIO_CARRIER: + trx = (struct gsm_bts_trx *) obj; + rc = trx_init(trx); + break; + case NM_OC_CHANNEL: + ts = (struct gsm_bts_trx_ts*) obj; + rc = ts_opstart(ts); break; default: rc = oml_mo_opstart_nack(mo, NM_NACK_OBJCLASS_NOTSUPP); @@ -1847,24 +1841,26 @@ int l1if_rsl_chan_act(struct gsm_lchan *lchan) */ int l1if_rsl_chan_mod(struct gsm_lchan *lchan) { - const struct lchan_sapis *s4l = &sapis_for_lchan[lchan->type]; - unsigned int i; - if (lchan->ho.active == HANDOVER_NONE) return -1; - LOGP(DHO, LOGL_ERROR, "%s modifying channel for handover\n", - gsm_lchan_name(lchan)); + LOGPLCHAN(lchan, DHO, LOGL_ERROR, "modifying channel for handover\n"); /* Give up listening to RACH bursts */ - release_sapis_for_ho(lchan); - - /* Activate the normal SAPIs */ - for (i = 0; i < s4l->num_sapis; i++) { - int sapi = s4l->sapis[i].sapi; - int dir = s4l->sapis[i].dir; - enqueue_sapi_act_cmd(lchan, sapi, dir); - } + release_sapi_ul_rach(lchan); + + /* All the normal SAPIs have already been activated, only DL SACCH may still be missing. + * + * Note: theoretically, it would only be necessary to activate the DL SACCH when it is not active yet. With + * repeated HO RACH received, we shouldn't need to re-send the SAPI activation every time. However, tests with + * sysmoBTS show that when sending this SAPI activation only once, the lchan will release some seconds after a + * handover, with error messages indicating "Lost SACCH block, faking meas reports and ms pwr". When re-sending + * the SAPI activation for every RACH received, the problem goes away. + * Before introducing lchan->want_dl_sacch_active, this code here would activate all SAPIs for the main channel, + * which would also repeat for each RACH received. Now we only activate DL SACCH here. + */ + if (lchan->want_dl_sacch_active) + enqueue_sapi_act_cmd(lchan, GsmL1_Sapi_Sacch, GsmL1_Dir_TxDownlink); return 0; } @@ -1873,8 +1869,7 @@ int l1if_rsl_chan_rel(struct gsm_lchan *lchan) { /* A duplicate RF Release Request, ignore it */ if (lchan->state == LCHAN_S_REL_REQ) { - LOGP(DL1C, LOGL_ERROR, "%s already in release request state.\n", - gsm_lchan_name(lchan)); + LOGPLCHAN(lchan, DL1C, LOGL_ERROR, "already in release request state.\n"); return 0; } @@ -1910,8 +1905,7 @@ static int ts_disconnect_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg, struct gsm_bts_trx_ts *ts = &trx->ts[cnf->u8Tn]; OSMO_ASSERT(cnf->u8Tn < TRX_NR_TS); - LOGP(DL1C, LOGL_DEBUG, "%s Rx mphDisconnectCnf\n", - gsm_lchan_name(ts->lchan)); + LOGPLCHAN(ts->lchan, DL1C, LOGL_DEBUG, "Rx mphDisconnectCnf\n"); cb_ts_disconnected(ts); @@ -1924,7 +1918,7 @@ int bts_model_ts_disconnect(struct gsm_bts_trx_ts *ts) struct femtol1_hdl *fl1h = trx_femtol1_hdl(ts->trx); GsmL1_MphDisconnectReq_t *cr; - DEBUGP(DRSL, "%s TS disconnect\n", gsm_lchan_name(ts->lchan)); + LOGPLCHAN(ts->lchan, DRSL, LOGL_DEBUG, "TS disconnect\n"); cr = prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphDisconnectReq, fl1h, l1p_handle_for_ts(ts)); cr->u8Tn = ts->nr; @@ -1940,8 +1934,7 @@ static int ts_connect_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg, struct gsm_bts_trx_ts *ts = &trx->ts[cnf->u8Tn]; OSMO_ASSERT(cnf->u8Tn < TRX_NR_TS); - DEBUGP(DL1C, "%s %s Rx mphConnectCnf flags=%s%s%s\n", - gsm_lchan_name(ts->lchan), + LOGPLCHAN(ts->lchan, DL1C, LOGL_DEBUG, "%s Rx mphConnectCnf flags=%s%s%s\n", gsm_pchan_name(ts->pchan), ts->flags & TS_F_PDCH_ACTIVE ? "ACTIVE " : "", ts->flags & TS_F_PDCH_ACT_PENDING ? "ACT_PENDING " : "", diff --git a/src/osmo-bts-sysmo/oml_router.c b/src/osmo-bts-sysmo/oml_router.c deleted file mode 100644 index f3d08373..00000000 --- a/src/osmo-bts-sysmo/oml_router.c +++ /dev/null @@ -1,129 +0,0 @@ -/* Beginnings of an OML router */ - -/* (C) 2014 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 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 "oml_router.h" - -#include <osmo-bts/bts.h> -#include <osmo-bts/logging.h> -#include <osmo-bts/oml.h> -#include <osmo-bts/msg_utils.h> - -#include <osmocom/core/socket.h> -#include <osmocom/core/select.h> - -#include <errno.h> -#include <string.h> -#include <unistd.h> - -static int oml_router_read_cb(struct osmo_fd *fd, unsigned int what) -{ - struct msgb *msg; - int rc; - - msg = oml_msgb_alloc(); - if (!msg) { - LOGP(DL1C, LOGL_ERROR, "Failed to allocate oml msgb.\n"); - return -1; - } - - rc = recv(fd->fd, msg->tail, msg->data_len, 0); - if (rc <= 0) { - close(fd->fd); - osmo_fd_unregister(fd); - fd->fd = -1; - goto err; - } - - msg->l1h = msgb_put(msg, rc); - rc = msg_verify_ipa_structure(msg); - if (rc < 0) { - LOGP(DL1C, LOGL_ERROR, - "OML Router: Invalid IPA message rc(%d)\n", rc); - goto err; - } - - rc = msg_verify_oml_structure(msg); - if (rc < 0) { - LOGP(DL1C, LOGL_ERROR, - "OML Router: Invalid OML message rc(%d)\n", rc); - goto err; - } - - /* todo dispatch message */ - -err: - msgb_free(msg); - return -1; -} - -static int oml_router_accept_cb(struct osmo_fd *accept_fd, unsigned int what) -{ - int fd; - struct osmo_fd *read_fd = (struct osmo_fd *) accept_fd->data; - - /* Accept only one connection at a time. De-register it */ - if (read_fd->fd > -1) { - LOGP(DL1C, LOGL_NOTICE, - "New OML router connection. Closing old one.\n"); - close(read_fd->fd); - osmo_fd_unregister(read_fd); - read_fd->fd = -1; - } - - fd = accept(accept_fd->fd, NULL, NULL); - if (fd < 0) { - LOGP(DL1C, LOGL_ERROR, "Failed to accept. errno: %s.\n", - strerror(errno)); - return -1; - } - - read_fd->fd = fd; - if (osmo_fd_register(read_fd) != 0) { - LOGP(DL1C, LOGL_ERROR, "Registering the read fd failed.\n"); - close(fd); - read_fd->fd = -1; - return -1; - } - - return 0; -} - -int oml_router_init(struct gsm_bts *bts, const char *path, - struct osmo_fd *accept_fd, struct osmo_fd *read_fd) -{ - int rc; - - memset(accept_fd, 0, sizeof(*accept_fd)); - memset(read_fd, 0, sizeof(*read_fd)); - - accept_fd->cb = oml_router_accept_cb; - accept_fd->data = read_fd; - - read_fd->cb = oml_router_read_cb; - read_fd->data = bts; - read_fd->when = BSC_FD_READ; - read_fd->fd = -1; - - rc = osmo_sock_unix_init_ofd(accept_fd, SOCK_SEQPACKET, 0, - path, - OSMO_SOCK_F_BIND | OSMO_SOCK_F_NONBLOCK); - return rc; -} diff --git a/src/osmo-bts-sysmo/oml_router.h b/src/osmo-bts-sysmo/oml_router.h deleted file mode 100644 index 55f0681d..00000000 --- a/src/osmo-bts-sysmo/oml_router.h +++ /dev/null @@ -1,13 +0,0 @@ -#pragma once - -struct gsm_bts; -struct osmo_fd; - -/** - * The default path sysmobts will listen for incoming - * registrations for OML routing and sending. - */ -#define OML_ROUTER_PATH "/var/run/sysmobts_oml_router" - - -int oml_router_init(struct gsm_bts *bts, const char *path, struct osmo_fd *accept, struct osmo_fd *read); diff --git a/src/osmo-bts-sysmo/sysmobts_ctrl.c b/src/osmo-bts-sysmo/sysmobts_ctrl.c index 21df88e5..6beeaf62 100644 --- a/src/osmo-bts-sysmo/sysmobts_ctrl.c +++ b/src/osmo-bts-sysmo/sysmobts_ctrl.c @@ -12,7 +12,7 @@ * 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. + * 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/>. @@ -94,7 +94,7 @@ static int get_clock_info(struct ctrl_cmd *cmd, void *data) SuperFemto_Prim_t *sysp = msgb_sysprim(msg); struct ctrl_cmd_def *cd; - /* geneate a deferred control command */ + /* generate a deferred control command */ cd = ctrl_cmd_def_make(fl1h, cmd, NULL, 10); sysp->id = SuperFemto_PrimId_RfClockInfoReq; @@ -136,7 +136,7 @@ static int set_clock_info(struct ctrl_cmd *cmd, void *data) SuperFemto_Prim_t *sysp = msgb_sysprim(msg); struct ctrl_cmd_def *cd; - /* geneate a deferred control command */ + /* generate a deferred control command */ cd = ctrl_cmd_def_make(fl1h, cmd, NULL, 10); /* Set GPS/PPS as reference */ @@ -197,7 +197,7 @@ static int get_clock_corr(struct ctrl_cmd *cmd, void *data) * prefer to to ask the actual L1 about the currently used value to * avoid any mistakes */ - /* geneate a deferred control command */ + /* generate a deferred control command */ cd = ctrl_cmd_def_make(fl1h, cmd, NULL, 10); sysp->id = SuperFemto_PrimId_RfClockInfoReq; @@ -241,7 +241,7 @@ static int set_clock_corr(struct ctrl_cmd *cmd, void *data) fl1h->clk_cal = atoi(cmd->value); - /* geneate a deferred control command */ + /* generate a deferred control command */ cd = ctrl_cmd_def_make(fl1h, cmd, NULL, 10); sysp->id = SuperFemto_PrimId_RfClockSetupReq; diff --git a/src/osmo-bts-sysmo/sysmobts_vty.c b/src/osmo-bts-sysmo/sysmobts_vty.c index 3199c8e2..2e853356 100644 --- a/src/osmo-bts-sysmo/sysmobts_vty.c +++ b/src/osmo-bts-sysmo/sysmobts_vty.c @@ -45,6 +45,7 @@ #include <osmo-bts/bts_model.h> #include <osmo-bts/vty.h> #include <osmo-bts/rsl.h> +#include <osmo-bts/bts.h> #include "femtobts.h" #include "l1_if.h" @@ -59,8 +60,6 @@ extern int lchan_activate(struct gsm_lchan *lchan); TRX_STR #define DSP_TRACE_F_STR "DSP Trace Flag\n" -static struct gsm_bts *vty_bts; - /* configuration */ DEFUN(cfg_phy_clkcal_eeprom, cfg_phy_clkcal_eeprom_cmd, @@ -153,9 +152,20 @@ DEFUN_DEPRECATED(cfg_trx_ul_power_target, cfg_trx_ul_power_target_cmd, "Obsolete alias for bts uplink-power-target\n" "Target uplink Rx level in dBm\n") { + struct gsm_power_ctrl_meas_params *mp; struct gsm_bts_trx *trx = vty->index; + int rxlev_dbm = atoi(argv[0]); + + mp = &trx->bts->ms_dpc_params.rxlev_meas; + mp->lower_thresh = mp->upper_thresh = dbm2rxlev(rxlev_dbm); - trx->bts->ul_power_target = atoi(argv[0]); + vty_out(vty, "%% Command '%s' has been deprecated.%s" + "%% MS/BS Power control parameters should be configured in osmo-bsc: " + "use 'rxlev-thresh lower %u upper %u'.%s", + self->string, VTY_NEWLINE, + mp->lower_thresh, + mp->upper_thresh, + VTY_NEWLINE); return CMD_SUCCESS; } @@ -178,7 +188,7 @@ DEFUN(cfg_phy_dsp_trace_f, cfg_phy_dsp_trace_f_cmd, struct phy_instance *pinst = vty->index; unsigned int flag; - flag = get_string_value(femtobts_tracef_names, argv[1]); + flag = get_string_value(femtobts_tracef_names, argv[0]); pinst->u.sysmobts.dsp_trace_f |= flag; return CMD_SUCCESS; @@ -190,7 +200,7 @@ DEFUN(cfg_phy_no_dsp_trace_f, cfg_phy_no_dsp_trace_f_cmd, struct phy_instance *pinst = vty->index; unsigned int flag; - flag = get_string_value(femtobts_tracef_names, argv[1]); + flag = get_string_value(femtobts_tracef_names, argv[0]); pinst->u.sysmobts.dsp_trace_f &= ~flag; return CMD_SUCCESS; @@ -216,11 +226,11 @@ DEFUN(show_phy_clksrc, show_trx_clksrc_cmd, } DEFUN(show_dsp_trace_f, show_dsp_trace_f_cmd, - "show trx <0-0> dsp-trace-flags", + "show dsp-trace-flags trx <0-0>", SHOW_TRX_STR "Display the current setting of the DSP trace flags") { int trx_nr = atoi(argv[0]); - struct gsm_bts_trx *trx = gsm_bts_trx_num(vty_bts, trx_nr); + struct gsm_bts_trx *trx = gsm_bts_trx_num(g_bts, trx_nr); struct femtol1_hdl *fl1h; int i; @@ -340,7 +350,7 @@ DEFUN(activate_lchan, activate_lchan_cmd, int trx_nr = atoi(argv[0]); int ts_nr = atoi(argv[1]); int lchan_nr = atoi(argv[3]); - struct gsm_bts_trx *trx = gsm_bts_trx_num(vty_bts, trx_nr); + struct gsm_bts_trx *trx = gsm_bts_trx_num(g_bts, trx_nr); struct gsm_bts_trx_ts *ts = &trx->ts[ts_nr]; struct gsm_lchan *lchan = &ts->lchan[lchan_nr]; @@ -360,9 +370,9 @@ DEFUN(set_tx_power, set_tx_power_cmd, { int trx_nr = atoi(argv[0]); int power = atoi(argv[1]); - struct gsm_bts_trx *trx = gsm_bts_trx_num(vty_bts, trx_nr); + struct gsm_bts_trx *trx = gsm_bts_trx_num(g_bts, trx_nr); - power_ramp_start(trx, to_mdB(power), 1); + power_ramp_start(trx, to_mdB(power), 1, NULL); return CMD_SUCCESS; } @@ -373,7 +383,7 @@ DEFUN(reset_rf_clock_ctr, reset_rf_clock_ctr_cmd, "RF Clock Information\n" "Reset the counter\n") { int trx_nr = atoi(argv[0]); - struct gsm_bts_trx *trx = gsm_bts_trx_num(vty_bts, trx_nr); + struct gsm_bts_trx *trx = gsm_bts_trx_num(g_bts, trx_nr); struct femtol1_hdl *fl1h = trx_femtol1_hdl(trx); l1if_rf_clock_info_reset(fl1h); @@ -386,7 +396,7 @@ DEFUN(correct_rf_clock_ctr, correct_rf_clock_ctr_cmd, "RF Clock Information\n" "Apply\n") { int trx_nr = atoi(argv[0]); - struct gsm_bts_trx *trx = gsm_bts_trx_num(vty_bts, trx_nr); + struct gsm_bts_trx *trx = gsm_bts_trx_num(g_bts, trx_nr); struct femtol1_hdl *fl1h = trx_femtol1_hdl(trx); l1if_rf_clock_info_correct(fl1h); @@ -403,7 +413,7 @@ DEFUN(loopback, loopback_cmd, int trx_nr = atoi(argv[0]); int ts_nr = atoi(argv[1]); int lchan_nr = atoi(argv[2]); - struct gsm_bts_trx *trx = gsm_bts_trx_num(vty_bts, trx_nr); + struct gsm_bts_trx *trx = gsm_bts_trx_num(g_bts, trx_nr); struct gsm_bts_trx_ts *ts = &trx->ts[ts_nr]; struct gsm_lchan *lchan = &ts->lchan[lchan_nr]; @@ -422,7 +432,7 @@ DEFUN(no_loopback, no_loopback_cmd, int trx_nr = atoi(argv[0]); int ts_nr = atoi(argv[1]); int lchan_nr = atoi(argv[2]); - struct gsm_bts_trx *trx = gsm_bts_trx_num(vty_bts, trx_nr); + struct gsm_bts_trx *trx = gsm_bts_trx_num(g_bts, trx_nr); struct gsm_bts_trx_ts *ts = &trx->ts[ts_nr]; struct gsm_lchan *lchan = &ts->lchan[lchan_nr]; @@ -432,22 +442,22 @@ DEFUN(no_loopback, no_loopback_cmd, } -void bts_model_config_write_bts(struct vty *vty, struct gsm_bts *bts) +void bts_model_config_write_bts(struct vty *vty, const struct gsm_bts *bts) { } -void bts_model_config_write_trx(struct vty *vty, struct gsm_bts_trx *trx) +void bts_model_config_write_trx(struct vty *vty, const struct gsm_bts_trx *trx) { if (trx->nominal_power != get_p_max_out_mdBm(trx)) vty_out(vty, " nominal-tx-power %d%s", trx->nominal_power, VTY_NEWLINE); } -void bts_model_config_write_phy(struct vty *vty, struct phy_link *plink) +void bts_model_config_write_phy(struct vty *vty, const struct phy_link *plink) { } -void bts_model_config_write_phy_inst(struct vty *vty, struct phy_instance *pinst) +void bts_model_config_write_phy_inst(struct vty *vty, const struct phy_instance *pinst) { int i; @@ -474,39 +484,37 @@ void bts_model_config_write_phy_inst(struct vty *vty, struct phy_instance *pinst pinst->u.sysmobts.clk_src), VTY_NEWLINE); } -int bts_model_vty_init(struct gsm_bts *bts) +int bts_model_vty_init(void *ctx) { - vty_bts = bts; - /* runtime-patch the command strings with debug levels */ - dsp_trace_f_cmd.string = vty_cmd_string_from_valstr(bts, femtobts_tracef_names, + dsp_trace_f_cmd.string = vty_cmd_string_from_valstr(ctx, femtobts_tracef_names, "trx <0-0> dsp-trace-flag (", "|",")", VTY_DO_LOWER); - dsp_trace_f_cmd.doc = vty_cmd_string_from_valstr(bts, femtobts_tracef_docs, + dsp_trace_f_cmd.doc = vty_cmd_string_from_valstr(ctx, femtobts_tracef_docs, TRX_STR DSP_TRACE_F_STR, "\n", "", 0); - no_dsp_trace_f_cmd.string = vty_cmd_string_from_valstr(bts, femtobts_tracef_names, + no_dsp_trace_f_cmd.string = vty_cmd_string_from_valstr(ctx, femtobts_tracef_names, "no trx <0-0> dsp-trace-flag (", "|",")", VTY_DO_LOWER); - no_dsp_trace_f_cmd.doc = vty_cmd_string_from_valstr(bts, femtobts_tracef_docs, + no_dsp_trace_f_cmd.doc = vty_cmd_string_from_valstr(ctx, femtobts_tracef_docs, NO_STR TRX_STR DSP_TRACE_F_STR, "\n", "", 0); cfg_phy_dsp_trace_f_cmd.string = - vty_cmd_string_from_valstr(bts, femtobts_tracef_names, + vty_cmd_string_from_valstr(ctx, femtobts_tracef_names, "dsp-trace-flag (", "|", ")", VTY_DO_LOWER); cfg_phy_dsp_trace_f_cmd.doc = - vty_cmd_string_from_valstr(bts, femtobts_tracef_docs, + vty_cmd_string_from_valstr(ctx, femtobts_tracef_docs, DSP_TRACE_F_STR, "\n", "", 0); cfg_phy_no_dsp_trace_f_cmd.string = - vty_cmd_string_from_valstr(bts, femtobts_tracef_names, + vty_cmd_string_from_valstr(ctx, femtobts_tracef_names, "no dsp-trace-flag (", "|", ")", VTY_DO_LOWER); cfg_phy_no_dsp_trace_f_cmd.doc = - vty_cmd_string_from_valstr(bts, femtobts_tracef_docs, + vty_cmd_string_from_valstr(ctx, femtobts_tracef_docs, NO_STR DSP_TRACE_F_STR, "\n", "", 0); diff --git a/src/osmo-bts-sysmo/tch.c b/src/osmo-bts-sysmo/tch.c index 54e73136..2cf784e6 100644 --- a/src/osmo-bts-sysmo/tch.c +++ b/src/osmo-bts-sysmo/tch.c @@ -12,7 +12,7 @@ * 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. + * 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/>. @@ -77,7 +77,7 @@ static struct msgb *l1_to_rtppayload_fr(uint8_t *l1_payload, uint8_t payload_len cur[0] |= 0xD0; #endif /* USE_L1_RTP_MODE */ - lchan_set_marker(osmo_fr_check_sid(l1_payload, payload_len), lchan); + lchan_set_marker(osmo_fr_is_any_sid(l1_payload), lchan); return msg; } @@ -131,12 +131,8 @@ static struct msgb *l1_to_rtppayload_efr(uint8_t *l1_payload, cur[0] |= 0xC0; #endif /* USE_L1_RTP_MODE */ - enum osmo_amr_type ft; - enum osmo_amr_quality bfi; - uint8_t cmr; - int8_t sti, cmi; - osmo_amr_rtp_dec(l1_payload, payload_len, &cmr, &cmi, &ft, &bfi, &sti); - lchan_set_marker(ft == AMR_GSM_EFR_SID, lchan); + + lchan_set_marker(osmo_efr_is_any_sid(l1_payload), lchan); return msg; } @@ -298,7 +294,7 @@ static struct msgb *l1_to_rtppayload_amr(uint8_t *l1_payload, uint8_t payload_le LOGP(DL1P, LOGL_NOTICE, "L1->RTP: overriding CMR IDX %u\n", cmr_idx); cmr = AMR_CMR_NONE; } else { - cmr = amr_mrc->bts_mode[cmr_idx].mode; + cmr = amr_mrc->mode[cmr_idx].mode; lchan->tch.last_cmr = cmr; } @@ -403,7 +399,10 @@ int l1if_tch_encode(struct gsm_lchan *lchan, uint8_t *data, uint8_t *len, *payload_type = GsmL1_TchPlType_Efr; rc = rtppayload_to_l1_efr(l1_payload, rtp_pl, rtp_pl_len); - /* FIXME: detect and save EFR SID */ + if (rc && lchan->ts->trx->bts->dtxd) + is_sid = osmo_efr_check_sid(rtp_pl, rtp_pl_len); + if (is_sid) + dtx_cache_payload(lchan, rtp_pl, rtp_pl_len, fn, -1); break; #endif case GSM48_CMODE_SPEECH_AMR: @@ -505,7 +504,7 @@ int l1if_tch_rx(struct gsm_bts_trx *trx, uint8_t chan_nr, struct msgb *l1p_msg) { GsmL1_Prim_t *l1p = msgb_l1prim(l1p_msg); GsmL1_PhDataInd_t *data_ind = &l1p->u.phDataInd; - uint8_t *payload, payload_type, payload_len, sid_first[9] = { 0 }; + uint8_t *payload, payload_type, payload_len; struct msgb *rmsg = NULL; struct gsm_lchan *lchan = &trx->ts[L1SAP_CHAN2TS(chan_nr)].lchan[l1sap_chan2ss(chan_nr)]; @@ -513,12 +512,15 @@ int l1if_tch_rx(struct gsm_bts_trx *trx, uint8_t chan_nr, struct msgb *l1p_msg) return -EAGAIN; if (data_ind->msgUnitParam.u8Size < 1) { - LOGPFN(DL1P, LOGL_DEBUG, data_ind->u32Fn, "chan_nr %d Rx Payload size 0\n", chan_nr); + LOGPLCFN(lchan, data_ind->u32Fn, DL1P, LOGL_DEBUG, "chan_nr %d Rx Payload size 0\n", chan_nr); /* Push empty payload to upper layers */ rmsg = msgb_alloc_headroom(256, 128, "L1P-to-RTP"); return add_l1sap_header(trx, rmsg, lchan, chan_nr, data_ind->u32Fn, data_ind->measParam.fBer * 10000, - data_ind->measParam.fLinkQuality * 10); + data_ind->measParam.fLinkQuality * 10, + data_ind->measParam.fRssi, + data_ind->measParam.i16BurstTiming * 64, + 0); } payload_type = data_ind->msgUnitParam.u8Buffer[0]; @@ -546,6 +548,8 @@ int l1if_tch_rx(struct gsm_bts_trx *trx, uint8_t chan_nr, struct msgb *l1p_msg) if (lchan->type != GSM_LCHAN_TCH_H && lchan->type != GSM_LCHAN_TCH_F) goto err_payload_match; + LOGPLCFN(lchan, data_ind->u32Fn, DL1P, LOGL_DEBUG, "DTX: received ONSET from L1 " "(%d bytes)\n", + payload_len); /* according to 3GPP TS 26.093 ONSET frames precede the first speech frame of a speech burst - set the marker for next RTP frame */ @@ -554,33 +558,32 @@ int l1if_tch_rx(struct gsm_bts_trx *trx, uint8_t chan_nr, struct msgb *l1p_msg) case GsmL1_TchPlType_Amr_SidFirstP1: if (lchan->type != GSM_LCHAN_TCH_H) goto err_payload_match; - LOGPFN(DL1P, LOGL_DEBUG, data_ind->u32Fn, "DTX: received SID_FIRST_P1 from L1 " - "(%d bytes)\n", payload_len); + LOGPLCFN(lchan, data_ind->u32Fn, DL1P, LOGL_DEBUG, "DTX: received SID_FIRST_P1 from L1 " + "(%d bytes)\n", payload_len); break; case GsmL1_TchPlType_Amr_SidFirstP2: if (lchan->type != GSM_LCHAN_TCH_H) goto err_payload_match; - LOGPFN(DL1P, LOGL_DEBUG, data_ind->u32Fn, "DTX: received SID_FIRST_P2 from L1 " - "(%d bytes)\n", payload_len); + LOGPLCFN(lchan, data_ind->u32Fn, DL1P, LOGL_DEBUG, "DTX: received SID_FIRST_P2 from L1 " + "(%d bytes)\n", payload_len); break; case GsmL1_TchPlType_Amr_SidFirstInH: if (lchan->type != GSM_LCHAN_TCH_H) goto err_payload_match; lchan->rtp_tx_marker = true; - LOGPFN(DL1P, LOGL_DEBUG, data_ind->u32Fn, "DTX: received SID_FIRST_INH from L1 " - "(%d bytes)\n", payload_len); + LOGPLCFN(lchan, data_ind->u32Fn, DL1P, LOGL_DEBUG, "DTX: received SID_FIRST_INH from L1 " + "(%d bytes)\n", payload_len); break; case GsmL1_TchPlType_Amr_SidUpdateInH: if (lchan->type != GSM_LCHAN_TCH_H) goto err_payload_match; lchan->rtp_tx_marker = true; - LOGPFN(DL1P, LOGL_DEBUG, data_ind->u32Fn, "DTX: received SID_UPDATE_INH from L1 " - "(%d bytes)\n", payload_len); + LOGPLCFN(lchan, data_ind->u32Fn, DL1P, LOGL_DEBUG, "DTX: received SID_UPDATE_INH from L1 " + "(%d bytes)\n", payload_len); break; default: - LOGPFN(DL1P, LOGL_NOTICE, data_ind->u32Fn, "%s Rx Payload Type %s is unsupported\n", - gsm_lchan_name(lchan), - get_value_string(femtobts_tch_pl_names, payload_type)); + LOGPLCFN(lchan, data_ind->u32Fn, DL1P, LOGL_NOTICE, "%s Rx Payload Type %s is unsupported\n", + gsm_lchan_name(lchan), get_value_string(femtobts_tch_pl_names, payload_type)); break; } @@ -598,14 +601,8 @@ int l1if_tch_rx(struct gsm_bts_trx *trx, uint8_t chan_nr, struct msgb *l1p_msg) break; #endif case GsmL1_TchPlType_Amr: - rmsg = l1_to_rtppayload_amr(payload, payload_len, lchan); - break; case GsmL1_TchPlType_Amr_SidFirstP1: - memcpy(sid_first, payload, payload_len); - int len = osmo_amr_rtp_enc(sid_first, 0, AMR_SID, AMR_GOOD); - if (len < 0) - return 0; - rmsg = l1_to_rtppayload_amr(sid_first, len, lchan); + rmsg = l1_to_rtppayload_amr(payload, payload_len, lchan); break; /* FIXME: what about GsmL1_TchPlType_Amr_SidBad? not well documented. */ } @@ -613,17 +610,42 @@ int l1if_tch_rx(struct gsm_bts_trx *trx, uint8_t chan_nr, struct msgb *l1p_msg) if (rmsg) return add_l1sap_header(trx, rmsg, lchan, chan_nr, data_ind->u32Fn, data_ind->measParam.fBer * 10000, - data_ind->measParam.fLinkQuality * 10); + data_ind->measParam.fLinkQuality * 10, + data_ind->measParam.fRssi, + data_ind->measParam.i16BurstTiming * 64, + 0); return 0; err_payload_match: - LOGPFN(DL1P, LOGL_ERROR, data_ind->u32Fn, "%s Rx Payload Type %s incompatible with lchan\n", - gsm_lchan_name(lchan), - get_value_string(femtobts_tch_pl_names, payload_type)); + LOGPLCFN(lchan, data_ind->u32Fn, DL1P, LOGL_ERROR, "%s Rx Payload Type %s incompatible with lchan\n", + gsm_lchan_name(lchan), get_value_string(femtobts_tch_pl_names, payload_type)); return -EINVAL; } +/*! \brief provide an RTP empty payload "tick" to upper layers upon FACCH */ +int l1if_tch_rx_facch(struct gsm_bts_trx *trx, uint8_t chan_nr, + struct msgb *l1p_msg) +{ + GsmL1_Prim_t *l1p = msgb_l1prim(l1p_msg); + GsmL1_PhDataInd_t *data_ind = &l1p->u.phDataInd; + struct msgb *rmsg = NULL; + struct gsm_lchan *lchan = &trx->ts[L1SAP_CHAN2TS(chan_nr)].lchan[l1sap_chan2ss(chan_nr)]; + + if (is_recv_only(lchan->abis_ip.speech_mode)) + return -EAGAIN; + + LOGPLCFN(lchan, data_ind->u32Fn, DL1P, LOGL_DEBUG, "chan_nr %d Rx FACCH\n", chan_nr); + /* Push empty payload to upper layers */ + rmsg = msgb_alloc_headroom(256, 128, "L1P-to-RTP"); + return add_l1sap_header(trx, rmsg, lchan, chan_nr, data_ind->u32Fn, + data_ind->measParam.fBer * 10000, + data_ind->measParam.fLinkQuality * 10, + 0, /* suppress RSSI like in osmo-bts-trx */ + data_ind->measParam.i16BurstTiming * 64, + 0); +} + struct msgb *gen_empty_tch_msg(struct gsm_lchan *lchan, uint32_t fn) { struct msgb *msg; diff --git a/src/osmo-bts-sysmo/utils.c b/src/osmo-bts-sysmo/utils.c index 0e3ef273..735ebe0e 100644 --- a/src/osmo-bts-sysmo/utils.c +++ b/src/osmo-bts-sysmo/utils.c @@ -14,7 +14,7 @@ * 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. + * 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-bts-trx/Makefile.am b/src/osmo-bts-trx/Makefile.am index 19222405..63c00fec 100644 --- a/src/osmo-bts-trx/Makefile.am +++ b/src/osmo-bts-trx/Makefile.am @@ -1,10 +1,75 @@ -AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include -AM_CFLAGS = -Wall -fno-strict-aliasing $(LIBOSMOCORE_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBOSMOCODEC_CFLAGS) $(LIBOSMOCODING_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(LIBOSMOTRAU_CFLAGS) $(LIBOSMOABIS_CFLAGS) $(LIBOSMOCTRL_CFLAGS) -LDADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOGSM_LIBS) $(LIBOSMOCODEC_LIBS) $(LIBOSMOCODING_LIBS) $(LIBOSMOVTY_LIBS) $(LIBOSMOTRAU_LIBS) $(LIBOSMOABIS_LIBS) $(LIBOSMOCTRL_LIBS) -ldl +AM_CPPFLAGS = \ + $(all_includes) \ + -I$(top_srcdir)/include \ + -I$(top_builddir)/include \ + $(NULL) -EXTRA_DIST = trx_if.h l1_if.h loops.h +AM_CFLAGS = \ + -Wall -fno-strict-aliasing \ + $(LIBOSMOCORE_CFLAGS) \ + $(LIBOSMOGSM_CFLAGS) \ + $(LIBOSMOCODEC_CFLAGS) \ + $(LIBOSMOCODING_CFLAGS) \ + $(LIBOSMOVTY_CFLAGS) \ + $(LIBOSMOCTRL_CFLAGS) \ + $(LIBOSMOABIS_CFLAGS) \ + $(LIBOSMOTRAU_CFLAGS) \ + $(LIBOSMONETIF_CFLAGS) \ + $(NULL) + +LDADD = \ + $(LIBOSMOCORE_LIBS) \ + $(LIBOSMOGSM_LIBS) \ + $(LIBOSMOCODEC_LIBS) \ + $(LIBOSMOCODING_LIBS) \ + $(LIBOSMOVTY_LIBS) \ + $(LIBOSMOCTRL_LIBS) \ + $(LIBOSMOABIS_LIBS) \ + $(LIBOSMOTRAU_LIBS) \ + $(LIBOSMONETIF_LIBS) \ + -ldl \ + $(NULL) + +noinst_HEADERS = \ + sched_utils.h \ + trx_if.h \ + l1_if.h \ + amr_loop.h \ + trx_provision_fsm.h \ + $(NULL) bin_PROGRAMS = osmo-bts-trx -osmo_bts_trx_SOURCES = main.c trx_if.c l1_if.c scheduler_trx.c trx_vty.c loops.c -osmo_bts_trx_LDADD = $(top_builddir)/src/common/libl1sched.a $(top_builddir)/src/common/libbts.a $(LDADD) +osmo_bts_trx_SOURCES = \ + main.c \ + trx_if.c \ + l1_if.c \ + scheduler_trx.c \ + sched_lchan_fcch_sch.c \ + sched_lchan_rach.c \ + sched_lchan_xcch.c \ + sched_lchan_pdtch.c \ + sched_lchan_tchf.c \ + sched_lchan_tchh.c \ + trx_provision_fsm.c \ + trx_vty.c \ + amr_loop.c \ + probes.d \ + $(NULL) + +osmo_bts_trx_LDADD = \ + $(top_builddir)/src/common/libl1sched.a \ + $(top_builddir)/src/common/libbts.a \ + $(LDADD) \ + $(NULL) + +if ENABLE_SYSTEMTAP +probes.h: probes.d + $(DTRACE) -C -h -s $< -o $@ + +probes.lo: probes.d + $(LIBTOOL) --mode=compile $(AM_V_lt) --tag=CC env CFLAGS="$(CFLAGS)" $(DTRACE) -C -G -s $< -o $@ + +BUILT_SOURCES = probes.h probes.lo +osmo_bts_trx_LDADD += probes.lo +endif diff --git a/src/osmo-bts-trx/amr_loop.c b/src/osmo-bts-trx/amr_loop.c new file mode 100644 index 00000000..cf75a8fc --- /dev/null +++ b/src/osmo-bts-trx/amr_loop.c @@ -0,0 +1,107 @@ +/* AMR link adaptation loop (see 3GPP TS 45.009, section 3) */ + +/* (C) 2013 by Andreas Eversberg <jolly@eversberg.eu> + * (C) 2022 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 <stdint.h> +#include <unistd.h> +#include <stdlib.h> +#include <errno.h> +#include <inttypes.h> + +#include <osmo-bts/gsm_data.h> +#include <osmo-bts/logging.h> +#include <osmocom/gsm/gsm_utils.h> + +#include "amr_loop.h" + +void trx_loop_amr_input(struct l1sched_chan_state *chan_state, + const struct l1sched_meas_set *meas_set) +{ + const struct gsm_lchan *lchan = chan_state->lchan; + const struct amr_multirate_conf *cfg = &lchan->tch.amr_mr; + const uint8_t mi = chan_state->ul_ft; /* mode index 0..3 */ + int lqual_cb = meas_set->ci_cb; /* cB (centibel) */ + + /* count per-block C/I samples for further averaging */ + if (lchan->type == GSM_LCHAN_TCH_H) { + chan_state->lqual_cb_num += 2; + chan_state->lqual_cb_sum += (lqual_cb + lqual_cb); + } else { + chan_state->lqual_cb_num++; + chan_state->lqual_cb_sum += lqual_cb; + } + + /* wait for MS to use the requested codec */ + if (mi != chan_state->dl_cmr) + return; + + /* count frames */ + if (chan_state->lqual_cb_num < 48) + return; + + /* calculate average (reuse lqual_cb variable) */ + lqual_cb = chan_state->lqual_cb_sum / chan_state->lqual_cb_num; + + LOGPLCHAN(lchan, DLOOP, LOGL_DEBUG, "AMR link quality (C/I) is %d cB, " + "codec mode[%u]=%u\n", lqual_cb, mi, cfg->mode[mi].mode); + + /* reset the link quality measurements */ + chan_state->lqual_cb_num = 0; + chan_state->lqual_cb_sum = 0; + + /* If the current codec mode can be degraded */ + if (mi > 0) { + /* The threshold/hysteresis is in 0.5 dB steps, convert to cB: + * 1dB is 10cB, so 0.5dB is 5cB - this is why we multiply by 5. */ + const int thresh_lower_cb = cfg->mode[mi - 1].threshold * 5; + + /* Degrade if the link quality is below THR_MX_Dn(i - 1) */ + if (lqual_cb < thresh_lower_cb) { + LOGPLCHAN(lchan, DLOOP, LOGL_INFO, "Degrading AMR codec mode: " + "[%u]=%u -> [%u]=%u due to link quality %d cB < THR_MX_Dn=%d cB\n", + mi, cfg->mode[mi].mode, mi - 1, cfg->mode[mi - 1].mode, + lqual_cb, thresh_lower_cb); + chan_state->dl_cmr--; + return; + } + } + + /* If the current codec mode can be upgraded */ + if (mi < chan_state->codecs - 1) { + /* The threshold/hysteresis is in 0.5 dB steps, convert to cB: + * 1dB is 10cB, so 0.5dB is 5cB - this is why we multiply by 5. */ + const int thresh_upper_cb = cfg->mode[mi].threshold * 5 \ + + cfg->mode[mi].hysteresis * 5; + + /* Upgrade if the link quality is above THR_MX_Up(i) */ + if (lqual_cb > thresh_upper_cb) { + LOGPLCHAN(lchan, DLOOP, LOGL_INFO, "Upgrading AMR codec mode: " + "[%u]=%u -> [%u]=%u due to link quality %d cB > THR_MX_Up=%d cB\n", + mi, cfg->mode[mi].mode, mi + 1, cfg->mode[mi + 1].mode, + lqual_cb, thresh_upper_cb); + chan_state->dl_cmr++; + return; + } + } + + LOGPLCHAN(lchan, DLOOP, LOGL_DEBUG, "Keeping the current AMR codec " + "mode[%u]=%u\n", mi, cfg->mode[mi].mode); +} diff --git a/src/osmo-bts-trx/amr_loop.h b/src/osmo-bts-trx/amr_loop.h new file mode 100644 index 00000000..efff76cf --- /dev/null +++ b/src/osmo-bts-trx/amr_loop.h @@ -0,0 +1,16 @@ +#pragma once + +#include <osmo-bts/scheduler.h> + +/* + * calibration of loops + */ + +/* + * loops api + */ + +void trx_loop_amr_input(struct l1sched_chan_state *chan_state, + const struct l1sched_meas_set *meas_set); + +void trx_loop_amr_set(struct l1sched_chan_state *chan_state, int loop); diff --git a/src/osmo-bts-trx/l1_if.c b/src/osmo-bts-trx/l1_if.c index db53d4c6..13df7f72 100644 --- a/src/osmo-bts-trx/l1_if.c +++ b/src/osmo-bts-trx/l1_if.c @@ -29,8 +29,10 @@ #include <osmocom/core/talloc.h> #include <osmocom/core/bits.h> +#include <osmocom/core/fsm.h> #include <osmocom/codec/ecu.h> #include <osmocom/gsm/abis_nm.h> +#include <osmocom/gsm/rsl.h> #include <osmo-bts/logging.h> #include <osmo-bts/bts.h> @@ -41,10 +43,15 @@ #include <osmo-bts/amr.h> #include <osmo-bts/abis.h> #include <osmo-bts/scheduler.h> +#include <osmo-bts/pcu_if.h> +#include <osmo-bts/nm_common_fsm.h> +#include <osmo-bts/handover.h> #include "l1_if.h" #include "trx_if.h" +#include "trx_provision_fsm.h" +#define RF_DISABLED_mdB to_mdB(-10) static const uint8_t transceiver_chan_types[_GSM_PCHAN_MAX] = { [GSM_PCHAN_NONE] = 8, @@ -60,7 +67,7 @@ static const uint8_t transceiver_chan_types[_GSM_PCHAN_MAX] = { [GSM_PCHAN_UNKNOWN] = 0, }; -static enum gsm_phys_chan_config transceiver_chan_type_2_pchan(uint8_t type) +enum gsm_phys_chan_config transceiver_chan_type_2_pchan(uint8_t type) { int i; for (i = 0; i < _GSM_PCHAN_MAX; i++) { @@ -75,221 +82,78 @@ struct trx_l1h *trx_l1h_alloc(void *tall_ctx, struct phy_instance *pinst) struct trx_l1h *l1h; l1h = talloc_zero(tall_ctx, struct trx_l1h); l1h->phy_inst = pinst; + l1h->provision_fi = osmo_fsm_inst_alloc(&trx_prov_fsm, l1h, l1h, LOGL_INFO, NULL); + OSMO_ASSERT(osmo_fsm_inst_update_id_f_sanitize(l1h->provision_fi, '-', phy_instance_name(pinst)) == 0); trx_if_init(l1h); return l1h; } -static void check_transceiver_availability_trx(struct trx_l1h *l1h, int avail) -{ - struct phy_instance *pinst = l1h->phy_inst; - struct gsm_bts_trx *trx = pinst->trx; - uint8_t tn; - - /* HACK, we should change state when we receive first clock from - * transceiver */ - if (avail) { - /* signal availability */ - oml_mo_state_chg(&trx->mo, NM_OPSTATE_DISABLED, NM_AVSTATE_OK); - oml_mo_state_chg(&trx->bb_transc.mo, -1, NM_AVSTATE_OK); - if (!pinst->u.osmotrx.sw_act_reported) { - oml_mo_tx_sw_act_rep(&trx->mo); - oml_mo_tx_sw_act_rep(&trx->bb_transc.mo); - pinst->u.osmotrx.sw_act_reported = true; - } - - for (tn = 0; tn < TRX_NR_TS; tn++) - oml_mo_state_chg(&trx->ts[tn].mo, NM_OPSTATE_DISABLED, - (l1h->config.slotmask & (1 << tn)) ? - NM_AVSTATE_DEPENDENCY : - NM_AVSTATE_NOT_INSTALLED); - } else { - oml_mo_state_chg(&trx->mo, NM_OPSTATE_DISABLED, - NM_AVSTATE_OFF_LINE); - oml_mo_state_chg(&trx->bb_transc.mo, NM_OPSTATE_DISABLED, - NM_AVSTATE_OFF_LINE); - - for (tn = 0; tn < TRX_NR_TS; tn++) - oml_mo_state_chg(&trx->ts[tn].mo, NM_OPSTATE_DISABLED, - NM_AVSTATE_OFF_LINE); - } -} - -int check_transceiver_availability(struct gsm_bts *bts, int avail) -{ - struct gsm_bts_trx *trx; - - llist_for_each_entry(trx, &bts->trx_list, list) { - struct phy_instance *pinst = trx_phy_instance(trx); - struct trx_l1h *l1h = pinst->u.osmotrx.hdl; - check_transceiver_availability_trx(l1h, avail); - } - return 0; -} - int bts_model_lchan_deactivate(struct gsm_lchan *lchan) { - struct phy_instance *pinst = trx_phy_instance(lchan->ts->trx); - struct trx_l1h *l1h = pinst->u.osmotrx.hdl; + int rc; + /* set lchan inactive */ + lchan_set_state(lchan, LCHAN_S_NONE); + /* Disable it on the scheduler: */ + rc = trx_sched_set_lchan(lchan, gsm_lchan2chan_nr(lchan), LID_DEDIC, false); + + /* Reactivate CCCH due to SI3 update in RSL */ if (lchan->rel_act_kind == LCHAN_REL_ACT_REACT) { lchan->rel_act_kind = LCHAN_REL_ACT_RSL; - /* FIXME: perform whatever is needed (if any) to set proper PCH/AGCH allocation according to - 3GPP TS 44.018 Table 10.5.2.11.1 using num_agch(lchan->ts->trx, "TRX L1"); function */ - return 0; + trx_sched_set_lchan(lchan, gsm_lchan2chan_nr(lchan), LID_DEDIC, true); + lchan_set_state(lchan, LCHAN_S_ACTIVE); + return rc; } - /* set lchan inactive */ - lchan_set_state(lchan, LCHAN_S_NONE); - - return trx_sched_set_lchan(&l1h->l1s, gsm_lchan2chan_nr(lchan), - LID_DEDIC, 0); + return rc; } int bts_model_lchan_deactivate_sacch(struct gsm_lchan *lchan) { - struct phy_instance *pinst = trx_phy_instance(lchan->ts->trx); - struct trx_l1h *l1h = pinst->u.osmotrx.hdl; - return trx_sched_set_lchan(&l1h->l1s, gsm_lchan2chan_nr(lchan), - LID_SACCH, 0); + return trx_sched_set_lchan(lchan, gsm_lchan2chan_nr(lchan), LID_SACCH, false); } -static void l1if_setslot_cb(struct trx_l1h *l1h, uint8_t tn, uint8_t type, int rc) +int l1if_trx_start_power_ramp(struct gsm_bts_trx *trx, ramp_compl_cb_t ramp_compl_cb) { - struct phy_instance *pinst = l1h->phy_inst; - struct gsm_bts_trx *trx = pinst->trx; - struct gsm_bts_trx_ts *ts; - enum gsm_phys_chan_config pchan; - - if (tn >= TRX_NR_TS) { - LOGPPHI(pinst, DL1C, LOGL_ERROR, "transceiver SETSLOT invalid param TN (%" PRIu8 ")\n", - tn); - return; - } - - pchan = transceiver_chan_type_2_pchan(type); - if (pchan == GSM_PCHAN_UNKNOWN) { - LOGPPHI(pinst, DL1C, LOGL_ERROR, "transceiver SETSLOT invalid param TS_TYPE (%" PRIu8 ")\n", - type); - return; - } + struct phy_instance *pinst = trx_phy_instance(trx); + struct trx_l1h *l1h = pinst->u.osmotrx.hdl; - ts = &trx->ts[tn]; - LOGPPHI(pinst, DL1C, LOGL_DEBUG, "%s l1if_setslot_cb(as_pchan=%s)," - " calling cb_ts_connected(rc=%d)\n", - gsm_ts_name(ts), gsm_pchan_name(pchan), rc); - cb_ts_connected(ts, rc); + if (l1h->config.forced_max_power_red == -1) + return power_ramp_start(trx, get_p_nominal_mdBm(trx), 0, ramp_compl_cb); + else + return power_ramp_start(trx, get_p_max_out_mdBm(trx) - to_mdB(l1h->config.forced_max_power_red), 1, ramp_compl_cb); } - -/* - * transceiver provisioning - */ -int l1if_provision_transceiver_trx(struct trx_l1h *l1h) +/* Sets the nominal power, in dB */ +void l1if_trx_set_nominal_power(struct gsm_bts_trx *trx, int nominal_power) { - uint8_t tn; - struct phy_link *plink = l1h->phy_inst->phy_link; - - if (!transceiver_available) - return -EIO; - - if (l1h->config.poweron - && l1h->config.tsc_valid - && l1h->config.bsic_valid - && l1h->config.arfcn_valid) { - /* before power on */ - if (!l1h->config.arfcn_sent) { - trx_if_cmd_rxtune(l1h, l1h->config.arfcn); - trx_if_cmd_txtune(l1h, l1h->config.arfcn); - l1h->config.arfcn_sent = 1; - } - if (!l1h->config.tsc_sent) { - trx_if_cmd_settsc(l1h, l1h->config.tsc); - l1h->config.tsc_sent = 1; - } - if (!l1h->config.bsic_sent) { - trx_if_cmd_setbsic(l1h, l1h->config.bsic); - l1h->config.bsic_sent = 1; - } - - /* Ask transceiver to use the newest TRXD header version if not using it yet */ - if (!l1h->config.setformat_sent && - l1h->config.trxd_hdr_ver_use != plink->u.osmotrx.trxd_hdr_ver_max) { - trx_if_cmd_setformat(l1h, plink->u.osmotrx.trxd_hdr_ver_max); - l1h->config.trxd_hdr_ver_req = plink->u.osmotrx.trxd_hdr_ver_max; - l1h->config.setformat_sent = 1; - } - - if (!l1h->config.poweron_sent) { - trx_if_cmd_poweron(l1h); - l1h->config.poweron_sent = 1; - } - - /* after power on */ - if (l1h->config.rxgain_valid && !l1h->config.rxgain_sent) { - trx_if_cmd_setrxgain(l1h, l1h->config.rxgain); - l1h->config.rxgain_sent = 1; - } - if (l1h->config.power_valid && !l1h->config.power_sent) { - trx_if_cmd_setpower(l1h, l1h->config.power); - l1h->config.power_sent = 1; - } - if (l1h->config.maxdly_valid && !l1h->config.maxdly_sent) { - trx_if_cmd_setmaxdly(l1h, l1h->config.maxdly); - l1h->config.maxdly_sent = 1; - } - if (l1h->config.maxdlynb_valid && !l1h->config.maxdlynb_sent) { - trx_if_cmd_setmaxdlynb(l1h, l1h->config.maxdlynb); - l1h->config.maxdlynb_sent = 1; - } + struct phy_instance *pinst = trx_phy_instance(trx); + bool nom_pwr_changed = trx->nominal_power != nominal_power; - for (tn = 0; tn < TRX_NR_TS; tn++) { - if (l1h->config.slottype_valid[tn] - && !l1h->config.slottype_sent[tn]) { - trx_if_cmd_setslot(l1h, tn, - l1h->config.slottype[tn], l1if_setslot_cb); - l1h->config.slottype_sent[tn] = 1; - } - } - return 0; - } + trx->nominal_power = nominal_power; + trx->power_params.trx_p_max_out_mdBm = to_mdB(nominal_power); + /* If we receive ultra-low nominal Tx power (<0dBm), make sure to update where we are */ + trx->power_params.p_total_cur_mdBm = OSMO_MIN(trx->power_params.p_total_cur_mdBm, + trx->power_params.trx_p_max_out_mdBm); - if (!l1h->config.poweron && !l1h->config.poweron_sent) { - trx_if_cmd_poweroff(l1h); - l1h->config.poweron_sent = 1; - l1h->config.rxgain_sent = 0; - l1h->config.power_sent = 0; - l1h->config.maxdly_sent = 0; - l1h->config.maxdlynb_sent = 0; - for (tn = 0; tn < TRX_NR_TS; tn++) - l1h->config.slottype_sent[tn] = 0; - } + /* If TRX is not yet powered, delay ramping until it's ON */ + if (!nom_pwr_changed || !pinst->phy_link->u.osmotrx.powered || + trx->mo.nm_state.administrative == NM_STATE_UNLOCKED) + return; - return 0; + /* We are already ON and we got new information about nominal power, so + * let's make sure we adapt the tx power to it + */ + l1if_trx_start_power_ramp(trx, NULL); } -int l1if_provision_transceiver(struct gsm_bts *bts) +static void l1if_setpower_att_cb(struct trx_l1h *l1h, int power_att_db, int rc) { - struct gsm_bts_trx *trx; - uint8_t tn; + struct phy_instance *pinst = l1h->phy_inst; + struct gsm_bts_trx *trx = pinst->trx; - llist_for_each_entry(trx, &bts->trx_list, list) { - struct phy_instance *pinst = trx_phy_instance(trx); - struct trx_l1h *l1h = pinst->u.osmotrx.hdl; - l1h->config.trxd_hdr_ver_req = 0; - l1h->config.trxd_hdr_ver_use = 0; - l1h->config.setformat_sent = 0; - l1h->config.arfcn_sent = 0; - l1h->config.tsc_sent = 0; - l1h->config.bsic_sent = 0; - l1h->config.poweron_sent = 0; - l1h->config.rxgain_sent = 0; - l1h->config.power_sent = 0; - l1h->config.maxdly_sent = 0; - l1h->config.maxdlynb_sent = 0; - for (tn = 0; tn < TRX_NR_TS; tn++) - l1h->config.slottype_sent[tn] = 0; - l1if_provision_transceiver_trx(l1h); - } - return 0; + LOGPPHI(pinst, DL1C, LOGL_DEBUG, "l1if_setpower_att_cb(power_att_db=%d, rc=%d)\n", power_att_db, rc); + + power_trx_change_compl(trx, get_p_max_out_mdBm(trx) - to_mdB(power_att_db)); } /* @@ -301,57 +165,43 @@ static int trx_init(struct gsm_bts_trx *trx) { struct phy_instance *pinst = trx_phy_instance(trx); struct trx_l1h *l1h = pinst->u.osmotrx.hdl; + int rc; - /* power on transceiver, if not already */ - if (!l1h->config.poweron) { - l1h->config.poweron = 1; - l1h->config.poweron_sent = 0; - l1if_provision_transceiver_trx(l1h); - } - - if (trx == trx->bts->c0) - lchan_init_lapdm(&trx->ts[0].lchan[CCCH_LCHAN]); - - /* Set to Operational State: Enabled */ - oml_mo_state_chg(&trx->mo, NM_OPSTATE_ENABLED, NM_AVSTATE_OK); - + rc = osmo_fsm_inst_dispatch(l1h->provision_fi, TRX_PROV_EV_CFG_ENABLE, (void*)(intptr_t)true); + if (rc != 0) + return osmo_fsm_inst_dispatch(trx->mo.fi, NM_EV_OPSTART_NACK, + (void*)(intptr_t)NM_NACK_CANT_PERFORM); /* Send OPSTART ack */ - return oml_mo_opstart_ack(&trx->mo); + return osmo_fsm_inst_dispatch(trx->mo.fi, NM_EV_OPSTART_ACK, NULL); } -/* deactivate transceiver */ -int bts_model_trx_close(struct gsm_bts_trx *trx) +/* Deact RF on transceiver */ +int bts_model_trx_deact_rf(struct gsm_bts_trx *trx) { struct phy_instance *pinst = trx_phy_instance(trx); struct trx_l1h *l1h = pinst->u.osmotrx.hdl; - enum gsm_phys_chan_config pchan = trx->ts[0].pchan; - /* close all logical channels and reset timeslots */ - trx_sched_reset(&l1h->l1s); + return trx_if_cmd_rfmute(l1h, true); +} - /* deactivate lchan for CCCH */ - if (pchan == GSM_PCHAN_CCCH || pchan == GSM_PCHAN_CCCH_SDCCH4 || - pchan == GSM_PCHAN_CCCH_SDCCH4_CBCH) { - lchan_set_state(&trx->ts[0].lchan[CCCH_LCHAN], LCHAN_S_INACTIVE); - } +/* deactivate transceiver */ +void bts_model_trx_close(struct gsm_bts_trx *trx) +{ + struct phy_instance *pinst = trx_phy_instance(trx); + struct trx_l1h *l1h = pinst->u.osmotrx.hdl; - /* power off transceiver, if not already */ - if (l1h->config.poweron) { - l1h->config.poweron = 0; - l1h->config.poweron_sent = 0; - l1if_provision_transceiver_trx(l1h); - } + osmo_fsm_inst_dispatch(l1h->provision_fi, TRX_PROV_EV_CLOSE, NULL); /* Set to Operational State: Disabled */ - check_transceiver_availability_trx(l1h, 0); - - return 0; + osmo_fsm_inst_dispatch(trx->mo.fi, NM_EV_DISABLE, NULL); + osmo_fsm_inst_dispatch(trx->bb_transc.mo.fi, NM_EV_DISABLE, NULL); } -/* on RSL failure, deactivate transceiver */ void bts_model_abis_close(struct gsm_bts *bts) { - bts_shutdown(bts, "Abis close"); + /* Go into shutdown state deactivating transceivers until Abis link + * becomes up again */ + bts_shutdown_ext(bts, "Abis close", false, true); } int bts_model_adjst_ms_pwr(struct gsm_lchan *lchan) @@ -362,23 +212,22 @@ int bts_model_adjst_ms_pwr(struct gsm_lchan *lchan) } /* set bts attributes */ -static uint8_t trx_set_bts(struct gsm_bts *bts, struct tlv_parsed *new_attr) +static uint8_t trx_set_bts(struct gsm_bts *bts) { - struct gsm_bts_trx *trx; + struct phy_instance *pinst = trx_phy_instance(bts->c0); + struct trx_l1h *l1h = pinst->u.osmotrx.hdl; uint8_t bsic = bts->bsic; + struct gsm_bts_trx *trx; + + /* ARFCN for C0 is assigned during Set BTS Attr, see oml.c */ + osmo_fsm_inst_dispatch(l1h->provision_fi, TRX_PROV_EV_CFG_ARFCN, (void *)(intptr_t)pinst->trx->arfcn); llist_for_each_entry(trx, &bts->trx_list, list) { - struct phy_instance *pinst = trx_phy_instance(trx); - struct trx_l1h *l1h = pinst->u.osmotrx.hdl; - if (l1h->config.bsic != bsic || !l1h->config.bsic_valid) { - l1h->config.bsic = bsic; - l1h->config.bsic_valid = 1; - l1h->config.bsic_sent = 0; - l1if_provision_transceiver_trx(l1h); - } - } - check_transceiver_availability(bts, transceiver_available); + pinst = trx_phy_instance(trx); + l1h = pinst->u.osmotrx.hdl; + osmo_fsm_inst_dispatch(l1h->provision_fi, TRX_PROV_EV_CFG_BSIC, (void*)(intptr_t)bsic); + } return 0; } @@ -388,21 +237,19 @@ static uint8_t trx_set_trx(struct gsm_bts_trx *trx) { struct phy_instance *pinst = trx_phy_instance(trx); struct trx_l1h *l1h = pinst->u.osmotrx.hdl; + struct phy_link *plink = pinst->phy_link; uint16_t arfcn = trx->arfcn; - if (l1h->config.arfcn != arfcn || !l1h->config.arfcn_valid) { - l1h->config.arfcn = arfcn; - l1h->config.arfcn_valid = 1; - l1h->config.arfcn_sent = 0; - l1if_provision_transceiver_trx(l1h); - } + /* ARFCN for C0 is assigned during Set BTS Attr, see oml.c */ + if (trx != trx->bts->c0) + osmo_fsm_inst_dispatch(l1h->provision_fi, TRX_PROV_EV_CFG_ARFCN, (void *)(intptr_t)arfcn); - if (l1h->config.power_oml) { - l1h->config.power = trx->max_power_red; - l1h->config.power_valid = 1; - l1h->config.power_sent = 0; - l1if_provision_transceiver_trx(l1h); - } + /* Begin to ramp up the power if power reduction is set by OML and TRX + is already running. Otherwise skip, power ramping will be started + after TRX is running */ + if (plink->u.osmotrx.powered && l1h->config.forced_max_power_red == -1 && + trx->mo.nm_state.administrative == NM_STATE_UNLOCKED) + power_ramp_start(pinst->trx, get_p_nominal_mdBm(pinst->trx), 0, NULL); return 0; } @@ -414,20 +261,9 @@ static uint8_t trx_set_ts_as_pchan(struct gsm_bts_trx_ts *ts, struct phy_instance *pinst = trx_phy_instance(ts->trx); struct trx_l1h *l1h = pinst->u.osmotrx.hdl; uint8_t tn = ts->nr; - uint16_t tsc = ts->tsc; uint8_t slottype; int rc; - /* all TSC of all timeslots must be equal, because transceiver only - * supports one TSC per TRX */ - - if (l1h->config.tsc != tsc || !l1h->config.tsc_valid) { - l1h->config.tsc = tsc; - l1h->config.tsc_valid = 1; - l1h->config.tsc_sent = 0; - l1if_provision_transceiver_trx(l1h); - } - /* ignore disabled slots */ if (!(l1h->config.slotmask & (1 << tn))) return NM_NACK_RES_NOTAVAIL; @@ -435,28 +271,46 @@ static uint8_t trx_set_ts_as_pchan(struct gsm_bts_trx_ts *ts, /* set physical channel. For dynamic timeslots, the caller should have * decided on a more specific PCHAN type already. */ OSMO_ASSERT(pchan != GSM_PCHAN_TCH_F_PDCH); - OSMO_ASSERT(pchan != GSM_PCHAN_TCH_F_TCH_H_PDCH); - rc = trx_sched_set_pchan(&l1h->l1s, tn, pchan); + OSMO_ASSERT(pchan != GSM_PCHAN_OSMO_DYN); + rc = trx_sched_set_pchan(ts, pchan); if (rc) return NM_NACK_RES_NOTAVAIL; - /* activate lchan for CCCH */ - if (pchan == GSM_PCHAN_CCCH || pchan == GSM_PCHAN_CCCH_SDCCH4 || - pchan == GSM_PCHAN_CCCH_SDCCH4_CBCH) { + /* activate lchans for [CBCH/]BCCH/CCCH */ + switch (pchan) { + case GSM_PCHAN_SDCCH8_SACCH8C_CBCH: + /* using RSL_CHAN_OSMO_CBCH4 is correct here, because the scheduler + * does not distinguish between SDCCH/4+CBCH abd SDCCH/8+CBCH. */ + trx_sched_set_lchan(&ts->lchan[CBCH_LCHAN], + RSL_CHAN_OSMO_CBCH4, LID_DEDIC, true); + break; + case GSM_PCHAN_CCCH_SDCCH4_CBCH: + trx_sched_set_lchan(&ts->lchan[CBCH_LCHAN], + RSL_CHAN_OSMO_CBCH4, LID_DEDIC, true); + /* fall-through */ + case GSM_PCHAN_CCCH_SDCCH4: + case GSM_PCHAN_CCCH: + trx_sched_set_bcch_ccch(&ts->lchan[CCCH_LCHAN], true); ts->lchan[CCCH_LCHAN].rel_act_kind = LCHAN_REL_ACT_OML; lchan_set_state(&ts->lchan[CCCH_LCHAN], LCHAN_S_ACTIVE); + break; + default: + break; } slottype = transceiver_chan_types[pchan]; - if (l1h->config.slottype[tn] != slottype - || !l1h->config.slottype_valid[tn]) { - l1h->config.slottype[tn] = slottype; - l1h->config.slottype_valid[tn] = 1; - l1h->config.slottype_sent[tn] = 0; - l1if_provision_transceiver_trx(l1h); + + struct trx_prov_ev_cfg_ts_data data = { .tn = tn, .slottype = slottype }; + if (ts->tsc_set != 0) { + /* On TRXC we use 3GPP compliant numbering, so +1 */ + data.tsc_set = ts->tsc_set + 1; + data.tsc_val = ts->tsc; + data.tsc_valid = true; } + osmo_fsm_inst_dispatch(l1h->provision_fi, TRX_PROV_EV_CFG_TS, &data); + return 0; } @@ -473,7 +327,7 @@ static uint8_t trx_set_ts(struct gsm_bts_trx_ts *ts) pchan = (ts->flags & TS_F_PDCH_ACTIVE)? GSM_PCHAN_PDCH : GSM_PCHAN_TCH_F; break; - case GSM_PCHAN_TCH_F_TCH_H_PDCH: + case GSM_PCHAN_OSMO_DYN: OSMO_ASSERT(ts->dyn.pchan_is == ts->dyn.pchan_want); pchan = ts->dyn.pchan_is; break; @@ -491,37 +345,30 @@ static uint8_t trx_set_ts(struct gsm_bts_trx_ts *ts) */ /* enable ciphering */ -static int l1if_set_ciphering(struct trx_l1h *l1h, struct gsm_lchan *lchan, - uint8_t chan_nr, int downlink) +static int l1if_set_ciphering(struct gsm_lchan *lchan, uint8_t chan_nr, int downlink) { - /* ciphering already enabled in both directions */ - if (lchan->ciph_state == LCHAN_CIPH_RXTX_CONF) + /* ignore the request when the channel is not active */ + if (lchan->state != LCHAN_S_ACTIVE) return -EINVAL; if (!downlink) { /* set uplink */ - trx_sched_set_cipher(&l1h->l1s, chan_nr, 0, lchan->encr.alg_id - 1, - lchan->encr.key, lchan->encr.key_len); + trx_sched_set_cipher(lchan, chan_nr, false); lchan->ciph_state = LCHAN_CIPH_RX_CONF; } else { /* set downlink and also set uplink, if not already */ - if (lchan->ciph_state != LCHAN_CIPH_RX_CONF) { - trx_sched_set_cipher(&l1h->l1s, chan_nr, 0, - lchan->encr.alg_id - 1, lchan->encr.key, - lchan->encr.key_len); - } - trx_sched_set_cipher(&l1h->l1s, chan_nr, 1, lchan->encr.alg_id - 1, - lchan->encr.key, lchan->encr.key_len); + if (lchan->ciph_state != LCHAN_CIPH_RX_CONF) + trx_sched_set_cipher(lchan, chan_nr, false); + trx_sched_set_cipher(lchan, chan_nr, true); lchan->ciph_state = LCHAN_CIPH_RXTX_CONF; } return 0; } -static int mph_info_chan_confirm(struct trx_l1h *l1h, uint8_t chan_nr, - enum osmo_mph_info_type type, uint8_t cause) +static int mph_info_chan_confirm(struct gsm_bts_trx *trx, uint8_t chan_nr, + enum osmo_mph_info_type type, uint8_t cause) { - struct phy_instance *pinst = l1h->phy_inst; struct osmo_phsap_prim l1sap; memset(&l1sap, 0, sizeof(l1sap)); @@ -531,7 +378,7 @@ static int mph_info_chan_confirm(struct trx_l1h *l1h, uint8_t chan_nr, l1sap.u.info.u.act_cnf.chan_nr = chan_nr; l1sap.u.info.u.act_cnf.cause = cause; - return l1sap_up(pinst->trx, &l1sap); + return l1sap_up(trx, &l1sap); } int l1if_mph_time_ind(struct gsm_bts *bts, uint32_t fn) @@ -550,45 +397,9 @@ int l1if_mph_time_ind(struct gsm_bts *bts, uint32_t fn) return l1sap_up(bts->c0, &l1sap); } - -static void l1if_fill_meas_res(struct osmo_phsap_prim *l1sap, uint8_t chan_nr, int16_t toa256, - float ber, float rssi, uint32_t fn) -{ - memset(l1sap, 0, sizeof(*l1sap)); - osmo_prim_init(&l1sap->oph, SAP_GSM_PH, PRIM_MPH_INFO, - PRIM_OP_INDICATION, NULL); - l1sap->u.info.type = PRIM_INFO_MEAS; - l1sap->u.info.u.meas_ind.chan_nr = chan_nr; - l1sap->u.info.u.meas_ind.ta_offs_256bits = toa256; - l1sap->u.info.u.meas_ind.ber10k = (unsigned int) (ber * 10000); - l1sap->u.info.u.meas_ind.inv_rssi = (uint8_t) (rssi * -1); - l1sap->u.info.u.meas_ind.fn = fn; -} - -int l1if_process_meas_res(struct gsm_bts_trx *trx, uint8_t tn, uint32_t fn, uint8_t chan_nr, - int n_errors, int n_bits_total, float rssi, int16_t toa256) -{ - struct gsm_lchan *lchan = &trx->ts[tn].lchan[l1sap_chan2ss(chan_nr)]; - struct osmo_phsap_prim l1sap; - /* 100% BER is n_bits_total is 0 */ - float ber = n_bits_total==0 ? 1.0 : (float)n_errors / (float)n_bits_total; - - LOGPFN(DMEAS, LOGL_DEBUG, fn, "RX UL measurement for %s fn=%u chan_nr=0x%02x MS pwr=%ddBm rssi=%.1f dBFS " - "ber=%.2f%% (%d/%d bits) L1_ta=%d rqd_ta=%d toa256=%d\n", - gsm_lchan_name(lchan), fn, chan_nr, ms_pwr_dbm(lchan->ts->trx->bts->band, lchan->ms_power_ctrl.current), - rssi, ber*100, n_errors, n_bits_total, lchan->meas.l1_info[1], lchan->rqd_ta, toa256); - - l1if_fill_meas_res(&l1sap, chan_nr, toa256, ber, rssi, fn); - - return l1sap_up(trx, &l1sap); -} - - /* primitive from common part */ int bts_model_l1sap_down(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap) { - struct phy_instance *pinst = trx_phy_instance(trx); - struct trx_l1h *l1h = pinst->u.osmotrx.hdl; struct msgb *msg = l1sap->oph.msg; uint8_t chan_nr; int rc = 0; @@ -599,117 +410,116 @@ int bts_model_l1sap_down(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap) if (!msg) break; /* put data into scheduler's queue */ - return trx_sched_ph_data_req(&l1h->l1s, l1sap); + return trx_sched_ph_data_req(trx, l1sap); case OSMO_PRIM(PRIM_TCH, PRIM_OP_REQUEST): if (!msg) break; /* put data into scheduler's queue */ - return trx_sched_tch_req(&l1h->l1s, l1sap); + return trx_sched_tch_req(trx, l1sap); case OSMO_PRIM(PRIM_MPH_INFO, PRIM_OP_REQUEST): switch (l1sap->u.info.type) { case PRIM_INFO_ACT_CIPH: chan_nr = l1sap->u.info.u.ciph_req.chan_nr; - lchan = get_lchan_by_chan_nr(trx, chan_nr); + break; + case PRIM_INFO_ACT_UL_ACC: + case PRIM_INFO_DEACT_UL_ACC: + chan_nr = l1sap->u.info.u.ulacc_req.chan_nr; + break; + default: + /* u.act_req used by PRIM_INFO_{ACTIVATE,DEACTIVATE,MODIFY} */ + chan_nr = l1sap->u.info.u.act_req.chan_nr; + } + lchan = get_lchan_by_chan_nr(trx, chan_nr); + if (OSMO_UNLIKELY(lchan == NULL)) { + LOGP(DL1C, LOGL_ERROR, + "Rx MPH-INFO.req (type=0x%02x) for non-existent lchan (%s)\n", + l1sap->u.info.type, rsl_chan_nr_str(chan_nr)); + rc = -ENODEV; + break; + } + + switch (l1sap->u.info.type) { + case PRIM_INFO_ACT_CIPH: if (l1sap->u.info.u.ciph_req.uplink) - l1if_set_ciphering(l1h, lchan, chan_nr, 0); + l1if_set_ciphering(lchan, chan_nr, 0); if (l1sap->u.info.u.ciph_req.downlink) - l1if_set_ciphering(l1h, lchan, chan_nr, 1); + l1if_set_ciphering(lchan, chan_nr, 1); + break; + case PRIM_INFO_ACT_UL_ACC: + trx_sched_set_ul_access(lchan, chan_nr, true); + break; + case PRIM_INFO_DEACT_UL_ACC: + trx_sched_set_ul_access(lchan, chan_nr, false); break; case PRIM_INFO_ACTIVATE: - case PRIM_INFO_DEACTIVATE: - case PRIM_INFO_MODIFY: - chan_nr = l1sap->u.info.u.act_req.chan_nr; - lchan = get_lchan_by_chan_nr(trx, chan_nr); - if (l1sap->u.info.type == PRIM_INFO_ACTIVATE) { - if ((chan_nr & 0xE0) == 0x80) { - LOGP(DL1C, LOGL_ERROR, "Cannot activate" - " chan_nr 0x%02x\n", chan_nr); - break; - } - - /* attempt to allocate an Error Concealment Unit instance, if available */ - lchan->ecu_state = osmo_ecu_init(trx, lchan2ecu_codec(lchan)); - - /* trx_chan_desc[] in scheduler.c uses the RSL_CHAN_OSMO_PDCH cbits - * (0xc0) to indicate the need for PDTCH and PTCCH SAPI activation. - * However, 0xc0 is a cbits pattern exclusively used for Osmocom style - * dyn TS (a non-standard RSL Chan Activ mod); hence, for IPA style dyn - * TS, the chan_nr will never reflect 0xc0 and we would omit the - * PDTCH,PTTCH SAPIs. To properly de-/activate the PDTCH SAPIs in - * scheduler.c, make sure the 0xc0 cbits are set for de-/activating PDTCH - * lchans, i.e. both Osmocom and IPA style dyn TS. (For Osmocom style dyn - * TS, the chan_nr typically already reflects 0xc0, while it doesn't for - * IPA style.) */ - if (lchan->type == GSM_LCHAN_PDTCH) - chan_nr = RSL_CHAN_OSMO_PDCH | (chan_nr & ~RSL_CHAN_NR_MASK); - - /* activate dedicated channel */ - trx_sched_set_lchan(&l1h->l1s, chan_nr, LID_DEDIC, 1); - /* activate associated channel */ - trx_sched_set_lchan(&l1h->l1s, chan_nr, LID_SACCH, 1); - /* set mode */ - trx_sched_set_mode(&l1h->l1s, chan_nr, - lchan->rsl_cmode, lchan->tch_mode, - lchan->tch.amr_mr.num_modes, - lchan->tch.amr_mr.bts_mode[0].mode, - lchan->tch.amr_mr.bts_mode[1].mode, - lchan->tch.amr_mr.bts_mode[2].mode, - lchan->tch.amr_mr.bts_mode[3].mode, - amr_get_initial_mode(lchan), - (lchan->ho.active == 1)); - /* init lapdm */ - lchan_init_lapdm(lchan); - /* set lchan active */ - lchan_set_state(lchan, LCHAN_S_ACTIVE); - /* set initial ciphering */ - l1if_set_ciphering(l1h, lchan, chan_nr, 0); - l1if_set_ciphering(l1h, lchan, chan_nr, 1); - if (lchan->encr.alg_id) - lchan->ciph_state = LCHAN_CIPH_RXTX_CONF; - else - lchan->ciph_state = LCHAN_CIPH_NONE; - - /* confirm */ - mph_info_chan_confirm(l1h, chan_nr, - PRIM_INFO_ACTIVATE, 0); - break; - } - if (l1sap->u.info.type == PRIM_INFO_MODIFY) { - /* ECU for possibly new codec */ - if (lchan->ecu_state) - osmo_ecu_destroy(lchan->ecu_state); - lchan->ecu_state = osmo_ecu_init(trx, lchan2ecu_codec(lchan)); - /* change mode */ - trx_sched_set_mode(&l1h->l1s, chan_nr, - lchan->rsl_cmode, lchan->tch_mode, - lchan->tch.amr_mr.num_modes, - lchan->tch.amr_mr.bts_mode[0].mode, - lchan->tch.amr_mr.bts_mode[1].mode, - lchan->tch.amr_mr.bts_mode[2].mode, - lchan->tch.amr_mr.bts_mode[3].mode, - amr_get_initial_mode(lchan), - 0); + if ((chan_nr & 0xE0) == 0x80) { + LOGPLCHAN(lchan, DL1C, LOGL_ERROR, "Cannot activate" + " channel %s\n", rsl_chan_nr_str(chan_nr)); + rc = -EPERM; break; } - /* here, type == PRIM_INFO_DEACTIVATE */ + + /* activate dedicated channel */ + trx_sched_set_lchan(lchan, chan_nr, LID_DEDIC, true); + /* activate associated channel */ + trx_sched_set_lchan(lchan, chan_nr, LID_SACCH, true); + /* set mode */ + trx_sched_set_mode(lchan->ts, chan_nr, + lchan->rsl_cmode, lchan->tch_mode, + lchan->tch.amr_mr.num_modes, + lchan->tch.amr_mr.mode[0].mode, + lchan->tch.amr_mr.mode[1].mode, + lchan->tch.amr_mr.mode[2].mode, + lchan->tch.amr_mr.mode[3].mode, + amr_get_initial_mode(lchan), + (lchan->ho.active == HANDOVER_ENABLED) || + rsl_chan_rt_is_asci(lchan->rsl_chan_rt)); + /* set lchan active */ + lchan_set_state(lchan, LCHAN_S_ACTIVE); + /* set initial ciphering */ + l1if_set_ciphering(lchan, chan_nr, 0); + l1if_set_ciphering(lchan, chan_nr, 1); + if (lchan->encr.alg_id) + lchan->ciph_state = LCHAN_CIPH_RXTX_CONF; + else + lchan->ciph_state = LCHAN_CIPH_NONE; + + /* confirm */ + mph_info_chan_confirm(trx, chan_nr, PRIM_INFO_ACTIVATE, 0); + break; + case PRIM_INFO_MODIFY: + /* change mode */ + trx_sched_set_mode(lchan->ts, chan_nr, + lchan->rsl_cmode, lchan->tch_mode, + lchan->tch.amr_mr.num_modes, + lchan->tch.amr_mr.mode[0].mode, + lchan->tch.amr_mr.mode[1].mode, + lchan->tch.amr_mr.mode[2].mode, + lchan->tch.amr_mr.mode[3].mode, + amr_get_initial_mode(lchan), + 0); + /* update ciphering params */ + l1if_set_ciphering(lchan, chan_nr, 0); + l1if_set_ciphering(lchan, chan_nr, 1); + if (lchan->encr.alg_id) + lchan->ciph_state = LCHAN_CIPH_RXTX_CONF; + else + lchan->ciph_state = LCHAN_CIPH_NONE; + break; + case PRIM_INFO_DEACTIVATE: if ((chan_nr & 0xE0) == 0x80) { - LOGP(DL1C, LOGL_ERROR, "Cannot deactivate " - "chan_nr 0x%02x\n", chan_nr); + LOGPLCHAN(lchan, DL1C, LOGL_ERROR, "Cannot deactivate" + " channel %s\n", rsl_chan_nr_str(chan_nr)); + rc = -EPERM; break; } - /* clear ECU state (if any) */ - if (lchan->ecu_state) { - osmo_ecu_destroy(lchan->ecu_state); - lchan->ecu_state = NULL; - } /* deactivate associated channel */ bts_model_lchan_deactivate_sacch(lchan); if (!l1sap->u.info.u.act_req.sacch_only) { /* deactivate dedicated channel */ lchan_deactivate(lchan); /* confirm only on dedicated channel */ - mph_info_chan_confirm(l1h, chan_nr, - PRIM_INFO_DEACTIVATE, 0); + mph_info_chan_confirm(trx, chan_nr, PRIM_INFO_DEACTIVATE, 0); } break; default: @@ -747,48 +557,51 @@ int bts_model_check_oml(struct gsm_bts *bts, uint8_t msg_type, } /* callback from OML */ -int bts_model_apply_oml(struct gsm_bts *bts, struct msgb *msg, - struct tlv_parsed *new_attr, int kind, void *obj) +int bts_model_apply_oml(struct gsm_bts *bts, const struct msgb *msg, + struct gsm_abis_mo *mo, void *obj) { struct abis_om_fom_hdr *foh = msgb_l3(msg); - int cause = 0; + int rc; switch (foh->msg_type) { case NM_MT_SET_BTS_ATTR: - cause = trx_set_bts(obj, new_attr); + rc = trx_set_bts(obj); break; case NM_MT_SET_RADIO_ATTR: - cause = trx_set_trx(obj); + rc = trx_set_trx(obj); break; case NM_MT_SET_CHAN_ATTR: - cause = trx_set_ts(obj); + rc = trx_set_ts(obj); + break; + default: + rc = 0; break; } - return oml_fom_ack_nack(msg, cause); + return rc; } /* callback from OML */ int bts_model_opstart(struct gsm_bts *bts, struct gsm_abis_mo *mo, void *obj) { + struct gsm_bts_trx *trx; int rc; - LOGP(DOML, LOGL_DEBUG, "bts_model_opstart: %s received\n", - get_value_string(abis_nm_obj_class_names, mo->obj_class)); + switch (mo->obj_class) { - case NM_OC_RADIO_CARRIER: - /* activate transceiver */ - rc = trx_init(obj); - break; - case NM_OC_CHANNEL: - case NM_OC_BTS: case NM_OC_SITE_MANAGER: + case NM_OC_BTS: case NM_OC_BASEB_TRANSC: + case NM_OC_CHANNEL: case NM_OC_GPRS_NSE: case NM_OC_GPRS_CELL: case NM_OC_GPRS_NSVC: - oml_mo_state_chg(mo, NM_OPSTATE_ENABLED, NM_AVSTATE_OK); - rc = oml_mo_opstart_ack(mo); + rc = osmo_fsm_inst_dispatch(mo->fi, NM_EV_OPSTART_ACK, NULL); + break; + case NM_OC_RADIO_CARRIER: + /* activate transceiver */ + trx = (struct gsm_bts_trx *) obj; + rc = trx_init(trx); break; default: rc = oml_mo_opstart_nack(mo, NM_NACK_OBJCLASS_NOTSUPP); @@ -796,17 +609,91 @@ int bts_model_opstart(struct gsm_bts *bts, struct gsm_abis_mo *mo, return rc; } -int bts_model_chg_adm_state(struct gsm_bts *bts, struct gsm_abis_mo *mo, - void *obj, uint8_t adm_state) +static void bts_model_chg_adm_state_ramp_compl_cb(struct gsm_bts_trx *trx) { - /* blindly accept all state changes */ - mo->nm_state.administrative = adm_state; - return oml_mo_statechg_ack(mo); + LOGPTRX(trx, DL1C, LOGL_INFO, "power ramp due to ADM STATE change finished\n"); + trx->mo.procedure_pending = 0; + if (trx->mo.nm_state.administrative == NM_STATE_LOCKED) { + bts_model_trx_deact_rf(trx); + pcu_tx_info_ind(); + } } -int bts_model_trx_deact_rf(struct gsm_bts_trx *trx) +int bts_model_chg_adm_state(struct gsm_bts *bts, struct gsm_abis_mo *mo, + void *obj, uint8_t adm_state) { - return 0; + struct gsm_bts_trx *trx; + struct phy_instance *pinst; + struct trx_l1h *l1h; + int rc = 0; + + switch (mo->obj_class) { + case NM_OC_RADIO_CARRIER: + trx = (struct gsm_bts_trx *) obj; + pinst = trx_phy_instance(trx); + l1h = pinst->u.osmotrx.hdl; + + /* Begin to ramp the power if TRX is already running. Otherwise + * skip, power ramping will be started after TRX is running. + * We still want to make sure to update RFMUTE status on the + * other side. */ + if (!pinst->phy_link->u.osmotrx.powered) { + trx_if_cmd_rfmute(l1h, adm_state != NM_STATE_UNLOCKED); + break; + } + + if (mo->procedure_pending) { + LOGPTRX(trx, DL1C, LOGL_INFO, + "ADM change received while previous one still WIP\n"); + + if (mo->nm_state.administrative == NM_STATE_LOCKED && + adm_state == NM_STATE_UNLOCKED) { + /* Previous change was UNLOCKED->LOCKED, so we + * were ramping down and we didn't mute RF + * yet, so now simply skip old ramp down compl + * cb, skip RF unmute and go for ramp up + * directly. */ + goto ramp_up; + } else if (mo->nm_state.administrative == NM_STATE_UNLOCKED && + adm_state == NM_STATE_LOCKED) { + /* Previous change was LOCKED->UNLOCKED, so we + * simply need to skip ramping up and start + * ramping down instead, muting RF at the + * end as usual. Fall through usual procedure + * below. */ + } else if (mo->nm_state.administrative == adm_state) { + OSMO_ASSERT(0); + } + } + switch (adm_state) { + case NM_STATE_LOCKED: + mo->procedure_pending = 1; + rc = power_ramp_start(trx, RF_DISABLED_mdB, 1, bts_model_chg_adm_state_ramp_compl_cb); + break; + case NM_STATE_UNLOCKED: + mo->procedure_pending = 1; + trx_if_cmd_rfmute(l1h, false); +ramp_up: + rc = l1if_trx_start_power_ramp(trx, bts_model_chg_adm_state_ramp_compl_cb); + if (rc == 0) { + mo->nm_state.administrative = adm_state; + pcu_tx_info_ind(); + return oml_mo_statechg_ack(mo); + } + break; + default: + break; + } + break; + default: + break; + } + + if (rc == 0) { + mo->nm_state.administrative = adm_state; + return oml_mo_statechg_ack(mo); + } else + return oml_mo_statechg_nack(mo, NM_NACK_REQ_NOT_GRANT); } int bts_model_oml_estab(struct gsm_bts *bts) @@ -816,9 +703,10 @@ int bts_model_oml_estab(struct gsm_bts *bts) int bts_model_change_power(struct gsm_bts_trx *trx, int p_trxout_mdBm) { -#warning "implement bts_model_change_power\n" - LOGP(DL1C, LOGL_NOTICE, "Setting TRX output power not supported!\n"); - return 0; + struct phy_instance *pinst = trx_phy_instance(trx); + struct trx_l1h *l1h = pinst->u.osmotrx.hdl; + int power_att = (get_p_max_out_mdBm(trx) - p_trxout_mdBm) / 1000; + return trx_if_cmd_setpower_att(l1h, power_att, l1if_setpower_att_cb); } int bts_model_ts_disconnect(struct gsm_bts_trx_ts *ts) diff --git a/src/osmo-bts-trx/l1_if.h b/src/osmo-bts-trx/l1_if.h index 87df951b..84fd4b5b 100644 --- a/src/osmo-bts-trx/l1_if.h +++ b/src/osmo-bts-trx/l1_if.h @@ -1,58 +1,129 @@ #ifndef L1_IF_H_TRX #define L1_IF_H_TRX +#include <osmocom/core/rate_ctr.h> + #include <osmo-bts/scheduler.h> #include <osmo-bts/phy_link.h> #include "trx_if.h" +/* + * TRX frame clock handling + * + * In a "normal" synchronous PHY layer, we would be polled every time + * the PHY needs data for a given frame number. However, the + * OpenBTS-inherited TRX protocol works differently: We (L1) must + * autonomously send burst data based on our own clock, and every so + * often (currently every ~ 216 frames), we get a clock indication from + * the TRX. + * + * We're using a MONOTONIC timerfd interval timer for the 4.615ms frame + * intervals, and then compute + send the 8 bursts for that frame. + * + * Upon receiving a clock indication from the TRX, we compensate + * accordingly: If we were transmitting too fast, we're delaying the + * next interval timer accordingly. If we were too slow, we immediately + * send burst data for the missing frame numbers. + */ + +/* bts-trx specific rate counters */ +enum { + BTSTRX_CTR_SCHED_DL_MISS_FN, + BTSTRX_CTR_SCHED_DL_FH_NO_CARRIER, + BTSTRX_CTR_SCHED_DL_FH_CACHE_MISS, + BTSTRX_CTR_SCHED_UL_FH_NO_CARRIER, +}; + +/*! clock state of a given TRX */ +struct osmo_trx_clock_state { + /*! number of FN periods without TRX clock indication */ + uint32_t fn_without_clock_ind; + struct { + /*! last FN we processed based on FN period timer */ + uint32_t fn; + /*! time at which we last processed FN */ + struct timespec tv; + } last_fn_timer; + struct { + /*! last FN we received a clock indication for */ + uint32_t fn; + /*! time at which we received the last clock indication */ + struct timespec tv; + } last_clk_ind; + /*! Osmocom FD wrapper for timerfd */ + struct osmo_fd fn_timer_ofd; +}; + +/* gsm_bts->model_priv, specific to osmo-bts-trx */ +struct bts_trx_priv { + struct osmo_trx_clock_state clk_s; + struct rate_ctr_group *ctrs; /* bts-trx specific rate counters */ +}; + struct trx_config { - uint8_t trxd_hdr_ver_req; /* requested TRXD header version */ - uint8_t trxd_hdr_ver_use; /* actual TRXD header version in use */ - int setformat_sent; + uint8_t trxd_pdu_ver_req; /* requested TRXD PDU version */ + uint8_t trxd_pdu_ver_use; /* actual TRXD PDU version in use */ + bool setformat_sent; + bool setformat_acked; + + bool enabled; - uint8_t poweron; /* poweron(1) or poweroff(0) */ - int poweron_sent; - int arfcn_valid; + bool arfcn_valid; uint16_t arfcn; - int arfcn_sent; + bool rxtune_sent; + bool rxtune_acked; + bool txtune_sent; + bool txtune_acked; - int tsc_valid; + bool tsc_valid; uint8_t tsc; - int tsc_sent; + bool tsc_sent; + bool tsc_acked; - int bsic_valid; + bool bsic_valid; uint8_t bsic; - int bsic_sent; + bool bsic_sent; + bool bsic_acked; - int rxgain_valid; + bool rxgain_valid; uint8_t rxgain; - int rxgain_sent; + bool rxgain_sent; - int power_valid; - uint8_t power; - int power_oml; - int power_sent; + int forced_max_power_red; /* -1 if not forced by VTY config (default) */ - int maxdly_valid; + bool nominal_power_set_by_vty; /* whether nominal trx power was enforced/retreived from VTY config "nominal-tx-power" */ + bool nomtxpower_sent; + bool nomtxpower_acked; + + bool maxdly_valid; int maxdly; - int maxdly_sent; + bool maxdly_sent; - int maxdlynb_valid; + bool maxdlynb_valid; int maxdlynb; - int maxdlynb_sent; + bool maxdlynb_sent; uint8_t slotmask; - int slottype_valid[TRX_NR_TS]; - uint8_t slottype[TRX_NR_TS]; - int slottype_sent[TRX_NR_TS]; + bool setslot_valid[TRX_NR_TS]; + struct { + uint8_t slottype; + uint8_t tsc_set; + uint8_t tsc_val; + bool tsc_valid; + } setslot[TRX_NR_TS]; + bool setslot_sent[TRX_NR_TS]; }; struct trx_l1h { struct llist_head trx_ctrl_list; /* Latest RSPed cmd, used to catch duplicate RSPs from sent retransmissions */ struct trx_ctrl_msg *last_acked; + /* Whether the code path is in the middle of handling a received message. */ + bool in_trx_ctrl_read_cb; + /* Whether the l1h->trx_ctrl_list was flushed by the callback handling a received message */ + bool flushed_while_in_trx_ctrl_read_cb; //struct gsm_bts_trx *trx; struct phy_instance *phy_inst; @@ -63,23 +134,14 @@ struct trx_l1h { /* transceiver config */ struct trx_config config; - - struct l1sched_trx l1s; + struct osmo_fsm_inst *provision_fi; }; struct trx_l1h *trx_l1h_alloc(void *tall_ctx, struct phy_instance *pinst); -int check_transceiver_availability(struct gsm_bts *bts, int avail); int l1if_provision_transceiver_trx(struct trx_l1h *l1h); -int l1if_provision_transceiver(struct gsm_bts *bts); int l1if_mph_time_ind(struct gsm_bts *bts, uint32_t fn); -int l1if_process_meas_res(struct gsm_bts_trx *trx, uint8_t tn, uint32_t fn, uint8_t chan_nr, - int n_errors, int n_bits_total, float rssi, int16_t toa256); - -static inline struct l1sched_trx *trx_l1sched_hdl(struct gsm_bts_trx *trx) -{ - struct phy_instance *pinst = trx->role_bts.l1h; - struct trx_l1h *l1h = pinst->u.osmotrx.hdl; - return &l1h->l1s; -} +void l1if_trx_set_nominal_power(struct gsm_bts_trx *trx, int nominal_power); +int l1if_trx_start_power_ramp(struct gsm_bts_trx *trx, ramp_compl_cb_t ramp_compl_cb); +enum gsm_phys_chan_config transceiver_chan_type_2_pchan(uint8_t type); #endif /* L1_IF_H_TRX */ diff --git a/src/osmo-bts-trx/loops.c b/src/osmo-bts-trx/loops.c deleted file mode 100644 index 3fa2b3b4..00000000 --- a/src/osmo-bts-trx/loops.c +++ /dev/null @@ -1,322 +0,0 @@ -/* Loop control for OsmoBTS-TRX */ - -/* (C) 2013 by Andreas Eversberg <jolly@eversberg.eu> - * - * All Rights Reserved - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation; either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * - */ - -#include <stdint.h> -#include <unistd.h> -#include <stdlib.h> -#include <errno.h> - -#include <osmo-bts/gsm_data.h> -#include <osmo-bts/logging.h> -#include <osmo-bts/l1sap.h> -#include <osmocom/core/bits.h> - -#include "trx_if.h" -#include "l1_if.h" -#include "loops.h" - -/* - * MS Power loop - */ - -/*! compute the new MS POWER LEVEL communicated to the MS and store it in lchan. - * \param lchan logical channel for which to compute (and in which to store) new power value. - * \param[in] diff input delta value (in dB) */ -static void ms_power_diff(struct gsm_lchan *lchan, int8_t diff) -{ - struct gsm_bts_trx *trx = lchan->ts->trx; - enum gsm_band band = trx->bts->band; - uint16_t arfcn = trx->arfcn; - int8_t new_power; - - /* compute new target MS output power level based on current value subtracted by 'diff/2' */ - new_power = lchan->ms_power_ctrl.current - (diff >> 1); - - if (diff == 0) - return; - - /* ms transmit power level cannot become negative */ - if (new_power < 0) - new_power = 0; - - /* saturate at the maximum possible power level for the given band */ - // FIXME: to go above 1W, we need to know classmark of MS - if (arfcn >= 512 && arfcn <= 885) { - if (new_power > 15) - new_power = 15; - } else { - if (new_power > 19) - new_power = 19; - } - - /* don't ever change more than MS_{LOWER,RAISE}_MAX during one loop iteration, i.e. - * reduce the speed at which the MS transmit power can change */ - /* a higher value means a lower level (and vice versa) */ - if (new_power > lchan->ms_power_ctrl.current + MS_LOWER_MAX) - new_power = lchan->ms_power_ctrl.current + MS_LOWER_MAX; - else if (new_power < lchan->ms_power_ctrl.current - MS_RAISE_MAX) - new_power = lchan->ms_power_ctrl.current - MS_RAISE_MAX; - - if (lchan->ms_power_ctrl.current == new_power) { - LOGPLCHAN(lchan, DLOOP, LOGL_INFO, "Keeping MS new_power at control level %d (%d dBm)\n", - new_power, ms_pwr_dbm(band, new_power)); - } else { - LOGPLCHAN(lchan, DLOOP, LOGL_INFO, "%s MS new_power from control level %d (%d dBm) to %d (%d dBm)\n", - (diff > 0) ? "Raising" : "Lowering", - lchan->ms_power_ctrl.current, ms_pwr_dbm(band, lchan->ms_power_ctrl.current), - new_power, ms_pwr_dbm(band, new_power)); - - /* store the resulting new MS power level in the lchan */ - lchan->ms_power_ctrl.current = new_power; - } -} - -/*! Input a new RSSI value into the MS power control loop for the given logical channel. - * \param lchan logical channel - * \param chan_state L1 channel state of the logical channel. - * \param rssi Received Signal Strength Indication (in dBm) */ -static void ms_power_val(struct gsm_lchan *lchan, struct l1sched_chan_state *chan_state, int8_t rssi) -{ - /* ignore inserted dummy frames, treat as lost frames */ - if (rssi < -127) - return; - - LOGPLCHAN(lchan, DLOOP, LOGL_DEBUG, "Got RSSI value of %d\n", rssi); - - chan_state->meas.rssi_count++; - - chan_state->meas.rssi_got_burst = 1; - - /* store and process RSSI */ - if (chan_state->meas.rssi_valid_count == ARRAY_SIZE(chan_state->meas.rssi)) - return; - chan_state->meas.rssi[chan_state->meas.rssi_valid_count++] = rssi; -} - -/*! Process a single clock tick of the MS power control loop. - * \param lchan Logical channel to which the clock tick applies */ -static void ms_power_clock(struct gsm_lchan *lchan, struct l1sched_chan_state *chan_state) -{ - struct gsm_bts_trx *trx = lchan->ts->trx; - struct phy_instance *pinst = trx_phy_instance(trx); - int rssi; - int i; - - /* skip every second clock, to prevent oscillating due to roundtrip - * delay */ - if (!(chan_state->meas.clock & 1)) - return; - - LOGPLCHAN(lchan, DLOOP, LOGL_DEBUG, "Got SACCH master clock at RSSI count %d\n", - chan_state->meas.rssi_count); - - /* wait for initial burst */ - if (!chan_state->meas.rssi_got_burst) - return; - - /* if no burst was received from MS at clock */ - if (chan_state->meas.rssi_count == 0) { - LOGPLCHAN(lchan, DLOOP, LOGL_NOTICE, "LOST SACCH frame, so we raise MS power\n"); - ms_power_diff(lchan, MS_RAISE_MAX); - return; - } - - /* reset total counter */ - chan_state->meas.rssi_count = 0; - - /* check the minimum level received after MS acknowledged the ordered - * power level */ - if (chan_state->meas.rssi_valid_count == 0) - return; - for (rssi = 999, i = 0; i < chan_state->meas.rssi_valid_count; i++) { - if (rssi > chan_state->meas.rssi[i]) - rssi = chan_state->meas.rssi[i]; - } - - /* reset valid counter */ - chan_state->meas.rssi_valid_count = 0; - - /* change RSSI */ - LOGPLCHAN(lchan, DLOOP, LOGL_DEBUG, "Lowest RSSI: %d Target RSSI: %d Current " - "MS power: %d (%d dBm)\n", rssi, - pinst->phy_link->u.osmotrx.trx_target_rssi, lchan->ms_power_ctrl.current, - ms_pwr_dbm(trx->bts->band, lchan->ms_power_ctrl.current)); - ms_power_diff(lchan, pinst->phy_link->u.osmotrx.trx_target_rssi - rssi); -} - - -/* 90% of one bit duration in 1/256 symbols: 256*0.9 */ -#define TOA256_9OPERCENT 230 - -/* - * Timing Advance loop - */ - -void ta_val(struct gsm_lchan *lchan, struct l1sched_chan_state *chan_state, int16_t toa256) -{ - /* check if the current L1 header acks to the current ordered TA */ - if (lchan->meas.l1_info[1] != lchan->rqd_ta) - return; - - /* sum measurement */ - chan_state->meas.toa256_sum += toa256; - if (++(chan_state->meas.toa_num) < 16) - return; - - /* complete set */ - toa256 = chan_state->meas.toa256_sum / chan_state->meas.toa_num; - - /* check for change of TOA */ - if (toa256 < -TOA256_9OPERCENT && lchan->rqd_ta > 0) { - LOGPLCHAN(lchan, DLOOP, LOGL_INFO, "TOA is too early (%d), now lowering TA from %d to %d\n", - toa256, lchan->rqd_ta, lchan->rqd_ta - 1); - lchan->rqd_ta--; - } else if (toa256 > TOA256_9OPERCENT && lchan->rqd_ta < 63) { - LOGPLCHAN(lchan, DLOOP, LOGL_INFO, "TOA is too late (%d), now raising TA from %d to %d\n", - toa256, lchan->rqd_ta, lchan->rqd_ta + 1); - lchan->rqd_ta++; - } else - LOGPLCHAN(lchan, DLOOP, LOGL_INFO, "TOA is correct (%d), keeping current TA of %d\n", - toa256, lchan->rqd_ta); - - chan_state->meas.toa_num = 0; - chan_state->meas.toa256_sum = 0; -} - -/*! Process a SACCH event as input to the MS power control and TA loop. Function - * is called once every uplink SACCH block is received. - * \param l1t L1 TRX instance on which we operate - * \param chan_nr RSL channel number on which we operate - * \param chan_state L1 scheduler channel state of the channel on which we operate - * \param[in] rssi Receive Signal Strength Indication - * \param[in] toa256 Time of Arrival in 1/256 symbol periods */ -void trx_loop_sacch_input(struct l1sched_trx *l1t, uint8_t chan_nr, - struct l1sched_chan_state *chan_state, int8_t rssi, int16_t toa256) -{ - struct gsm_lchan *lchan = &l1t->trx->ts[L1SAP_CHAN2TS(chan_nr)] - .lchan[l1sap_chan2ss(chan_nr)]; - struct phy_instance *pinst = trx_phy_instance(l1t->trx); - - /* if MS power control loop is enabled, handle it */ - if (pinst->phy_link->u.osmotrx.trx_ms_power_loop) - ms_power_val(lchan, chan_state, rssi); - - /* if TA loop is enabled, handle it */ - if (pinst->phy_link->u.osmotrx.trx_ta_loop) - ta_val(lchan, chan_state, toa256); -} - -/*! Called once every downlink SACCH block needs to be sent. */ -void trx_loop_sacch_clock(struct l1sched_trx *l1t, uint8_t chan_nr, - struct l1sched_chan_state *chan_state) -{ - struct gsm_lchan *lchan = &l1t->trx->ts[L1SAP_CHAN2TS(chan_nr)] - .lchan[l1sap_chan2ss(chan_nr)]; - struct phy_instance *pinst = trx_phy_instance(l1t->trx); - - if (lchan->ms_power_ctrl.fixed) - return; - - if (pinst->phy_link->u.osmotrx.trx_ms_power_loop) - ms_power_clock(lchan, chan_state); - - /* count the number of SACCH clocks */ - chan_state->meas.clock++; -} - -void trx_loop_amr_input(struct l1sched_trx *l1t, uint8_t chan_nr, - struct l1sched_chan_state *chan_state, float ber) -{ - struct gsm_bts_trx *trx = l1t->trx; - struct gsm_lchan *lchan = &trx->ts[L1SAP_CHAN2TS(chan_nr)] - .lchan[l1sap_chan2ss(chan_nr)]; - - /* check if loop is enabled */ - if (!chan_state->amr_loop) - return; - - /* wait for MS to use the requested codec */ - if (chan_state->ul_ft != chan_state->dl_cmr) - return; - - /* count bit errors */ - if (L1SAP_IS_CHAN_TCHH(chan_nr)) { - chan_state->ber_num += 2; - chan_state->ber_sum += (ber + ber); - } else { - chan_state->ber_num++; - chan_state->ber_sum += ber; - } - - /* count frames */ - if (chan_state->ber_num < 48) - return; - - /* calculate average (reuse ber variable) */ - ber = chan_state->ber_sum / chan_state->ber_num; - - /* reset bit errors */ - chan_state->ber_num = 0; - chan_state->ber_sum = 0; - - LOGPLCHAN(lchan, DLOOP, LOGL_DEBUG, "Current bit error rate (BER) %.6f " - "codec id %d\n", ber, chan_state->ul_ft); - - /* degrade */ - if (chan_state->dl_cmr > 0) { - /* degrade, if ber is above threshold FIXME: C/I */ - if (ber > - lchan->tch.amr_mr.bts_mode[chan_state->dl_cmr-1].threshold) { - LOGPLCHAN(lchan, DLOOP, LOGL_DEBUG, "Degrading due to BER %.6f " - "from codec id %d to %d\n", ber, chan_state->dl_cmr, - chan_state->dl_cmr - 1); - chan_state->dl_cmr--; - } - } else if (chan_state->dl_cmr < chan_state->codecs - 1) { - /* degrade, if ber is above threshold FIXME: C/I*/ - if (ber < - lchan->tch.amr_mr.bts_mode[chan_state->dl_cmr].threshold - - lchan->tch.amr_mr.bts_mode[chan_state->dl_cmr].hysteresis) { - LOGPLCHAN(lchan, DLOOP, LOGL_DEBUG, "Upgrading due to BER %.6f " - "from codec id %d to %d\n", ber, chan_state->dl_cmr, - chan_state->dl_cmr + 1); - chan_state->dl_cmr++; - } - } -} - -void trx_loop_amr_set(struct l1sched_chan_state *chan_state, int loop) -{ - if (chan_state->amr_loop && !loop) { - chan_state->amr_loop = 0; - return; - } - - if (!chan_state->amr_loop && loop) { - chan_state->amr_loop = 1; - - /* reset bit errors */ - chan_state->ber_num = 0; - chan_state->ber_sum = 0; - - return; - } -} diff --git a/src/osmo-bts-trx/loops.h b/src/osmo-bts-trx/loops.h deleted file mode 100644 index 50a658d0..00000000 --- a/src/osmo-bts-trx/loops.h +++ /dev/null @@ -1,27 +0,0 @@ -#ifndef _TRX_LOOPS_H -#define _TRX_LOOPS_H - -/* - * calibration of loops - */ - -/* how much power levels do we raise/lower as maximum (1 level = 2 dB) */ -#define MS_RAISE_MAX 4 -#define MS_LOWER_MAX 2 - -/* - * loops api - */ - -void trx_loop_sacch_input(struct l1sched_trx *l1t, uint8_t chan_nr, - struct l1sched_chan_state *chan_state, int8_t rssi, int16_t toa); - -void trx_loop_sacch_clock(struct l1sched_trx *l1t, uint8_t chan_nr, - struct l1sched_chan_state *chan_state); - -void trx_loop_amr_input(struct l1sched_trx *l1t, uint8_t chan_nr, - struct l1sched_chan_state *chan_state, float ber); - -void trx_loop_amr_set(struct l1sched_chan_state *chan_state, int loop); - -#endif /* _TRX_LOOPS_H */ diff --git a/src/osmo-bts-trx/main.c b/src/osmo-bts-trx/main.c index b1fa2079..ddc44285 100644 --- a/src/osmo-bts-trx/main.c +++ b/src/osmo-bts-trx/main.c @@ -13,7 +13,7 @@ * 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. + * 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/>. @@ -43,6 +43,8 @@ #include <osmocom/core/gsmtap.h> #include <osmocom/core/gsmtap_util.h> #include <osmocom/core/bits.h> +#include <osmocom/core/rate_ctr.h> +#include <osmocom/core/stats.h> #include <osmo-bts/gsm_data.h> #include <osmo-bts/phy_link.h> @@ -59,8 +61,34 @@ #include "l1_if.h" #include "trx_if.h" +static const struct rate_ctr_desc btstrx_ctr_desc[] = { + [BTSTRX_CTR_SCHED_DL_MISS_FN] = { + "trx_clk:sched_dl_miss_fn", + "Downlink frames scheduled later than expected due to missed timerfd event (due to high system load)" + }, + [BTSTRX_CTR_SCHED_DL_FH_NO_CARRIER] = { + "trx_sched:dl_fh_no_carrier", + "Frequency hopping: no carrier found for a Downlink burst (check hopping parameters)" + }, + [BTSTRX_CTR_SCHED_DL_FH_CACHE_MISS] = { + "trx_sched:dl_fh_cache_miss", + "Frequency hopping: no Downlink carrier found in cache (lookup performed)" + }, + [BTSTRX_CTR_SCHED_UL_FH_NO_CARRIER] = { + "trx_sched:ul_fh_no_carrier", + "Frequency hopping: no carrier found for an Uplink burst (check hopping parameters)" + }, +}; +static const struct rate_ctr_group_desc btstrx_ctrg_desc = { + "bts-trx", + "osmo-bts-trx specific counters", + OSMO_STATS_CLASS_GLOBAL, + ARRAY_SIZE(btstrx_ctr_desc), + btstrx_ctr_desc +}; + /* dummy, since no direct dsp support */ -uint32_t trx_get_hlayer1(struct gsm_bts_trx *trx) +uint32_t trx_get_hlayer1(const struct gsm_bts_trx *trx) { return 0; } @@ -97,29 +125,85 @@ int bts_model_handle_options(int argc, char **argv) int bts_model_init(struct gsm_bts *bts) { + struct bts_trx_priv *bts_trx = talloc_zero(bts, struct bts_trx_priv); + bts_trx->clk_s.fn_timer_ofd.fd = -1; + bts_trx->ctrs = rate_ctr_group_alloc(bts_trx, &btstrx_ctrg_desc, 0); + + bts->model_priv = bts_trx; bts->variant = BTS_OSMO_TRX; - bts->support.ciphers = CIPHER_A5(1) | CIPHER_A5(2) | CIPHER_A5(3); + bts->support.ciphers = CIPHER_A5(1) | CIPHER_A5(2) | CIPHER_A5(3) | CIPHER_A5(4); + bts->gprs.cell.support.gprs_codings = NM_IPAC_MASK_GPRS_CODING_CS + | NM_IPAC_MASK_GPRS_CODING_MCS; - /* FIXME: this needs to be overridden with the real hardrware - * value */ + /* The nominal value for each TRX is later overwritten through VTY cmd + * 'nominal-tx-power' if present, otherwise through TRXC cmd NOMTXPOWER. + */ bts->c0->nominal_power = 23; - gsm_bts_set_feature(bts, BTS_FEAT_GPRS); - gsm_bts_set_feature(bts, BTS_FEAT_OML_ALERTS); - gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_F_V1); - gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_H_V1); - gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_F_EFR); - gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_F_AMR); - gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_H_AMR); - gsm_bts_set_feature(bts, BTS_FEAT_CBCH); - - bts_model_vty_init(bts); + /* order alphabetically */ + osmo_bts_set_feature(bts->features, BTS_FEAT_ACCH_REP); + osmo_bts_set_feature(bts->features, BTS_FEAT_ACCH_TEMP_OVP); + osmo_bts_set_feature(bts->features, BTS_FEAT_BCCH_POWER_RED); + osmo_bts_set_feature(bts->features, BTS_FEAT_CBCH); + osmo_bts_set_feature(bts->features, BTS_FEAT_EGPRS); + osmo_bts_set_feature(bts->features, BTS_FEAT_GPRS); + osmo_bts_set_feature(bts->features, BTS_FEAT_HOPPING); + osmo_bts_set_feature(bts->features, BTS_FEAT_MULTI_TSC); + osmo_bts_set_feature(bts->features, BTS_FEAT_OML_ALERTS); + osmo_bts_set_feature(bts->features, BTS_FEAT_SPEECH_F_AMR); + osmo_bts_set_feature(bts->features, BTS_FEAT_SPEECH_F_EFR); + osmo_bts_set_feature(bts->features, BTS_FEAT_SPEECH_F_V1); + osmo_bts_set_feature(bts->features, BTS_FEAT_SPEECH_H_AMR); + osmo_bts_set_feature(bts->features, BTS_FEAT_SPEECH_H_V1); + osmo_bts_set_feature(bts->features, BTS_FEAT_VAMOS); + osmo_bts_set_feature(bts->features, BTS_FEAT_VGCS); + osmo_bts_set_feature(bts->features, BTS_FEAT_VBS); + + bts_internal_flag_set(bts, BTS_INTERNAL_FLAG_MEAS_PAYLOAD_COMB); + bts_internal_flag_set(bts, BTS_INTERNAL_FLAG_INTERF_MEAS); + + /* The default HR codec output format in the absence of saved + * vty config needs to match what was implemented previously, + * for the sake of existing deployments, i.e., to avoid + * a surprise functional change upon software update. */ + bts->emit_hr_rfc5993 = true; + + /* For the same reason as the above, rtp internal-uplink-ecu + * needs to be enabled by default on osmo-bts-trx model only. */ + bts->use_ul_ecu = true; return 0; } int bts_model_trx_init(struct gsm_bts_trx *trx) { + /* Frequency bands indicated to the BSC */ + trx->support.freq_bands = NM_IPAC_F_FREQ_BAND_PGSM + | NM_IPAC_F_FREQ_BAND_EGSM + | NM_IPAC_F_FREQ_BAND_RGSM + | NM_IPAC_F_FREQ_BAND_DCS + | NM_IPAC_F_FREQ_BAND_PCS + | NM_IPAC_F_FREQ_BAND_850 + | NM_IPAC_F_FREQ_BAND_480 + | NM_IPAC_F_FREQ_BAND_450; + + /* Channel types and modes indicated to the BSC */ + trx->support.chan_types = NM_IPAC_MASK_CHANT_COMMON + | NM_IPAC_F_CHANT_BCCH_SDCCH4_CBCH + | NM_IPAC_F_CHANT_SDCCH8_CBCH + | NM_IPAC_F_CHANT_PDCHF + | NM_IPAC_F_CHANT_TCHF_PDCHF; + trx->support.chan_modes = NM_IPAC_MASK_CHANM_SPEECH + | NM_IPAC_MASK_CHANM_CSD_NT + | NM_IPAC_MASK_CHANM_CSD_T; + /* TODO: missing rate adaptation for TCH/F14.4 (see OS#6167) */ + trx->support.chan_modes &= ~NM_IPAC_F_CHANM_CSD_T_14k4; + trx->support.chan_modes &= ~NM_IPAC_F_CHANM_CSD_NT_14k4; + + /* The nominal value for each TRX is later overwritten through VTY cmd + * 'nominal-tx-power' if present, otherwise through TRXC cmd NOMTXPOWER. + */ + l1if_trx_set_nominal_power(trx, trx->bts->c0->nominal_power); return 0; } @@ -129,13 +213,10 @@ void bts_model_phy_link_set_defaults(struct phy_link *plink) plink->u.osmotrx.remote_ip = talloc_strdup(plink, "127.0.0.1"); plink->u.osmotrx.base_port_local = 5800; plink->u.osmotrx.base_port_remote = 5700; - plink->u.osmotrx.clock_advance = 20; - plink->u.osmotrx.rts_advance = 5; - plink->u.osmotrx.trx_ta_loop = true; - plink->u.osmotrx.trx_ms_power_loop = false; - plink->u.osmotrx.trx_target_rssi = -10; + plink->u.osmotrx.clock_advance = 2; + plink->u.osmotrx.rts_advance = 3; /* attempt use newest TRXD version by default: */ - plink->u.osmotrx.trxd_hdr_ver_max = TRX_DATA_FORMAT_VER; + plink->u.osmotrx.trxd_pdu_ver_max = TRX_DATA_PDU_VER; } void bts_model_phy_instance_set_defaults(struct phy_instance *pinst) @@ -144,7 +225,7 @@ void bts_model_phy_instance_set_defaults(struct phy_instance *pinst) l1h = trx_l1h_alloc(tall_bts_ctx, pinst); pinst->u.osmotrx.hdl = l1h; - l1h->config.power_oml = 1; + l1h->config.forced_max_power_red = -1; } int main(int argc, char **argv) diff --git a/src/osmo-bts-trx/probes.d b/src/osmo-bts-trx/probes.d new file mode 100644 index 00000000..2f905bd1 --- /dev/null +++ b/src/osmo-bts-trx/probes.d @@ -0,0 +1,7 @@ +provider osmo_bts_trx { + probe ul_data_start(int, int, int); /* trx_nr, ts_nr, fn */ + probe ul_data_done(int, int, int); /* trx_nr, ts_nr, fn */ + + probe dl_rts_start(int, int, int); /* trx_nr, ts_nr, fn */ + probe dl_rts_done(int, int, int); /* trx_nr, ts_nr, fn */ +}; diff --git a/src/osmo-bts-trx/sched_lchan_fcch_sch.c b/src/osmo-bts-trx/sched_lchan_fcch_sch.c new file mode 100644 index 00000000..5722b288 --- /dev/null +++ b/src/osmo-bts-trx/sched_lchan_fcch_sch.c @@ -0,0 +1,89 @@ +/* + * (C) 2013 by Andreas Eversberg <jolly@eversberg.eu> + * (C) 2015-2017 by Harald Welte <laforge@gnumonks.org> + * Contributions 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 <stdint.h> +#include <errno.h> + +#include <osmocom/core/bits.h> +#include <osmocom/core/utils.h> +#include <osmocom/coding/gsm0503_coding.h> + +#include <osmo-bts/bts.h> +#include <osmo-bts/l1sap.h> +#include <osmo-bts/logging.h> +#include <osmo-bts/scheduler.h> +#include <osmo-bts/scheduler_backend.h> + +#include <sched_utils.h> + +/* obtain a to-be-transmitted FCCH (frequency correction channel) burst */ +int tx_fcch_fn(struct l1sched_ts *l1ts, struct trx_dl_burst_req *br) +{ + LOGL1SB(DL1P, LOGL_DEBUG, l1ts, br, "Transmitting FCCH\n"); + + /* A frequency correction burst is basically a sequence of zeros */ + memset(br->burst, 0x00, GSM_BURST_LEN); + br->burst_len = GSM_BURST_LEN; + + return 0; +} + +/* obtain a to-be-transmitted SCH (synchronization channel) burst */ +int tx_sch_fn(struct l1sched_ts *l1ts, struct trx_dl_burst_req *br) +{ + ubit_t burst[78]; + uint8_t sb_info[4]; + struct gsm_time t; + uint8_t t3p, bsic; + + LOGL1SB(DL1P, LOGL_DEBUG, l1ts, br, "Transmitting SCH\n"); + + /* BURST BYPASS */ + + /* create SB info from GSM time and BSIC */ + gsm_fn2gsmtime(&t, br->fn); + t3p = t.t3 / 10; + bsic = l1ts->ts->trx->bts->bsic; + sb_info[0] = + ((bsic & 0x3f) << 2) | + ((t.t1 & 0x600) >> 9); + sb_info[1] = + ((t.t1 & 0x1fe) >> 1); + sb_info[2] = + ((t.t1 & 0x001) << 7) | + ((t.t2 & 0x1f) << 2) | + ((t3p & 0x6) >> 1); + sb_info[3] = + (t3p & 0x1); + + /* encode bursts */ + gsm0503_sch_encode(burst, sb_info); + + /* compose burst */ + memcpy(br->burst + 3, burst, 39); + memcpy(br->burst + 42, _sched_train_seq_gmsk_sb, 64); + memcpy(br->burst + 106, burst + 39, 39); + + br->burst_len = GSM_BURST_LEN; + + return 0; +} diff --git a/src/osmo-bts-trx/sched_lchan_pdtch.c b/src/osmo-bts-trx/sched_lchan_pdtch.c new file mode 100644 index 00000000..466a4144 --- /dev/null +++ b/src/osmo-bts-trx/sched_lchan_pdtch.c @@ -0,0 +1,221 @@ +/* + * (C) 2013 by Andreas Eversberg <jolly@eversberg.eu> + * (C) 2015-2017 by Harald Welte <laforge@gnumonks.org> + * Contributions 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 <stdint.h> +#include <errno.h> + +#include <osmocom/core/bits.h> +#include <osmocom/core/utils.h> +#include <osmocom/coding/gsm0503_coding.h> + +#include <osmo-bts/bts.h> +#include <osmo-bts/l1sap.h> +#include <osmo-bts/logging.h> +#include <osmo-bts/scheduler.h> +#include <osmo-bts/scheduler_backend.h> + +#include <sched_utils.h> + +/* Maximum size of a EGPRS message in bytes */ +#define EGPRS_0503_MAX_BYTES 155 + +/*! \brief a single PDTCH burst was received by the PHY, process it */ +int rx_pdtch_fn(struct l1sched_ts *l1ts, const struct trx_ul_burst_ind *bi) +{ + struct l1sched_chan_state *chan_state = &l1ts->chan_state[bi->chan]; + sbit_t *burst, *bursts_p = chan_state->ul_bursts; + uint32_t first_fn; + uint32_t *mask = &chan_state->ul_mask; + struct l1sched_meas_set meas_avg; + uint8_t l2[EGPRS_0503_MAX_BYTES]; + int n_errors = 0; + int n_bursts_bits = 0; + int n_bits_total = 0; + uint16_t ber10k; + int rc; + enum osmo_ph_pres_info_type presence_info; + + LOGL1SB(DL1P, LOGL_DEBUG, l1ts, bi, "Received PDTCH bid=%u\n", bi->bid); + + /* An MS may be polled to send an ACK in form of four Access Bursts */ + if (bi->flags & TRX_BI_F_ACCESS_BURST) + return rx_rach_fn(l1ts, bi); + + /* clear burst */ + if (bi->bid == 0) { + memset(bursts_p, 0, GSM0503_EGPRS_BURSTS_NBITS); + *mask = 0x0; + } + + /* update mask */ + *mask |= (1 << bi->bid); + + /* store measurements */ + trx_sched_meas_push(chan_state, bi); + + /* copy burst to buffer of 4 bursts */ + switch (bi->burst_len) { + case EGPRS_BURST_LEN: + burst = bursts_p + bi->bid * 348; + memcpy(burst, bi->burst + 9, 174); + memcpy(burst + 174, bi->burst + 261, 174); + n_bursts_bits = GSM0503_EGPRS_BURSTS_NBITS; + break; + case GSM_BURST_LEN: + burst = bursts_p + bi->bid * 116; + memcpy(burst, bi->burst + 3, 58); + memcpy(burst + 58, bi->burst + 87, 58); + n_bursts_bits = GSM0503_GPRS_BURSTS_NBITS; + break; + case 0: + /* NOPE.ind, assume GPRS? */ + n_bursts_bits = GSM0503_GPRS_BURSTS_NBITS; + } + + /* wait until complete set of bursts */ + if (bi->bid != 3) + return 0; + + /* average measurements of the last 4 bursts */ + trx_sched_meas_avg(chan_state, &meas_avg, SCHED_MEAS_AVG_M_S4N4); + + /* check for complete set of bursts */ + if ((*mask & 0xf) != 0xf) { + LOGL1SB(DL1P, LOGL_DEBUG, l1ts, bi, "Received incomplete frame (%u/%u)\n", + bi->fn % l1ts->mf_period, l1ts->mf_period); + } + *mask = 0x0; + + /* + * Attempt to decode EGPRS bursts first. For 8-PSK EGPRS this is all we + * do. Attempt GPRS decoding on EGPRS failure. If the burst is GPRS, + * then we incur decoding overhead of 31 bits on the Type 3 EGPRS + * header, which is tolerable. + */ + rc = gsm0503_pdtch_egprs_decode(l2, bursts_p, n_bursts_bits, + NULL, &n_errors, &n_bits_total); + + if ((bi->burst_len == GSM_BURST_LEN) && (rc < 0)) { + rc = gsm0503_pdtch_decode(l2, bursts_p, NULL, + &n_errors, &n_bits_total); + } + + if (rc > 0) { + presence_info = PRES_INFO_BOTH; + } else { + LOGL1SB(DL1P, LOGL_DEBUG, l1ts, bi, + BAD_DATA_MSG_FMT "\n", BAD_DATA_MSG_ARGS); + rc = 0; + presence_info = PRES_INFO_INVALID; + } + + ber10k = compute_ber10k(n_bits_total, n_errors); + + first_fn = GSM_TDMA_FN_SUB(bi->fn, 3); + return _sched_compose_ph_data_ind(l1ts, first_fn, bi->chan, + &l2[0], rc, + ber10k, + meas_avg.rssi, + meas_avg.toa256, + meas_avg.ci_cb, + presence_info); +} + +/* obtain a to-be-transmitted PDTCH (packet data) burst */ +int tx_pdtch_fn(struct l1sched_ts *l1ts, struct trx_dl_burst_req *br) +{ + struct msgb *msg = NULL; /* make GCC happy */ + struct l1sched_chan_state *chan_state = &l1ts->chan_state[br->chan]; + ubit_t *burst, *bursts_p = chan_state->dl_bursts; + enum trx_mod_type *mod = &chan_state->dl_mod_type; + uint8_t *mask = &chan_state->dl_mask; + int rc = 0; + + /* send burst, if we already got a frame */ + if (br->bid > 0) { + if ((*mask & 0x01) != 0x01) + return -ENOMSG; + goto send_burst; + } + + *mask = *mask << 4; + + /* get mac block from queue */ + msg = _sched_dequeue_prim(l1ts, br); + if (!msg) { + /* It's totally fine to get no block here, since PCU may submit + * empty blocks when there's no MS listening. The scheduler will + * take care of filling C0 with dummy bursts to keep expected + * power transmit levels + */ + return -ENODEV; + } + + /* BURST BYPASS */ + + /* encode bursts */ + rc = gsm0503_pdtch_egprs_encode(bursts_p, msg->l2h, msgb_l2len(msg)); + if (rc < 0) + rc = gsm0503_pdtch_encode(bursts_p, msg->l2h, msgb_l2len(msg)); + + /* check validity of message */ + if (rc < 0) { + LOGL1SB(DL1P, LOGL_FATAL, l1ts, br, "Prim invalid length, please FIX! " + "(len=%u)\n", msgb_l2len(msg)); + /* free message */ + msgb_free(msg); + return -EINVAL; + } else if (rc == GSM0503_EGPRS_BURSTS_NBITS) { + *mod = TRX_MOD_T_8PSK; + } else { + *mod = TRX_MOD_T_GMSK; + } + + /* free message */ + msgb_free(msg); + +send_burst: + /* compose burst */ + if (*mod == TRX_MOD_T_8PSK) { + burst = bursts_p + br->bid * 348; + memset(br->burst, 1, 9); + memcpy(br->burst + 9, burst, 174); + memcpy(br->burst + 183, TRX_8PSK_NB_TSC(br), 78); + memcpy(br->burst + 261, burst + 174, 174); + memset(br->burst + 435, 1, 9); + + br->burst_len = EGPRS_BURST_LEN; + } else { + burst = bursts_p + br->bid * 116; + memcpy(br->burst + 3, burst, 58); + memcpy(br->burst + 61, TRX_GMSK_NB_TSC(br), 26); + memcpy(br->burst + 87, burst + 58, 58); + + br->burst_len = GSM_BURST_LEN; + } + + *mask |= (1 << br->bid); + + LOGL1SB(DL1P, LOGL_DEBUG, l1ts, br, "Transmitting burst=%u.\n", br->bid); + + return 0; +} diff --git a/src/osmo-bts-trx/sched_lchan_rach.c b/src/osmo-bts-trx/sched_lchan_rach.c new file mode 100644 index 00000000..c92dfe24 --- /dev/null +++ b/src/osmo-bts-trx/sched_lchan_rach.c @@ -0,0 +1,214 @@ +/* + * (C) 2013 by Andreas Eversberg <jolly@eversberg.eu> + * (C) 2015-2017 by Harald Welte <laforge@gnumonks.org> + * (C) 2019 by Vadim Yanitskiy <axilirator@gmail.com> + * Contributions 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 <stdint.h> +#include <limits.h> +#include <errno.h> + +#include <osmocom/core/bits.h> +#include <osmocom/core/utils.h> +#include <osmocom/coding/gsm0503_coding.h> + +#include <osmo-bts/bts.h> +#include <osmo-bts/l1sap.h> +#include <osmo-bts/logging.h> +#include <osmo-bts/scheduler.h> +#include <osmo-bts/scheduler_backend.h> + +#include <sched_utils.h> + +/* 3GPP TS 05.02, section 5.2.7 */ +#define RACH_EXT_TAIL_LEN 8 +#define RACH_SYNCH_SEQ_LEN 41 + +enum rach_synch_seq_t { + RACH_SYNCH_SEQ_UNKNOWN = -1, + RACH_SYNCH_SEQ_TS0, /* GSM, GMSK (default) */ + RACH_SYNCH_SEQ_TS1, /* EGPRS, 8-PSK */ + RACH_SYNCH_SEQ_TS2, /* EGPRS, GMSK */ + RACH_SYNCH_SEQ_NUM +}; + +static struct value_string rach_synch_seq_names[] = { + { RACH_SYNCH_SEQ_UNKNOWN, "UNKNOWN" }, + { RACH_SYNCH_SEQ_TS0, "TS0: GSM, GMSK" }, + { RACH_SYNCH_SEQ_TS1, "TS1: EGPRS, 8-PSK" }, + { RACH_SYNCH_SEQ_TS2, "TS2: EGPRS, GMSK" }, + { 0, NULL }, +}; + +static enum rach_synch_seq_t rach_get_synch_seq(sbit_t *bits, int *best_score) +{ + sbit_t *synch_seq_burst = bits + RACH_EXT_TAIL_LEN; + enum rach_synch_seq_t seq = RACH_SYNCH_SEQ_TS0; + int score[RACH_SYNCH_SEQ_NUM] = { 0 }; + int max_score = INT_MIN; + int i, j; + + /* 3GPP TS 05.02, section 5.2.7 "Access burst (AB)", synch. sequence bits */ + static const char synch_seq_ref[RACH_SYNCH_SEQ_NUM][RACH_SYNCH_SEQ_LEN] = { + [RACH_SYNCH_SEQ_TS0] = "01001011011111111001100110101010001111000", + [RACH_SYNCH_SEQ_TS1] = "01010100111110001000011000101111001001101", + [RACH_SYNCH_SEQ_TS2] = "11101111001001110101011000001101101110111", + }; + + /* Get a multiplier for j-th bit of i-th synch. sequence */ +#define RACH_SYNCH_SEQ_MULT \ + (synch_seq_ref[i][j] == '1' ? -1 : 1) + + /* For each synch. sequence, count the bit match score. Since we deal with + * soft-bits (-127...127), we sum the absolute values of matching ones, + * and subtract the absolute values of different ones, so the resulting + * score is more accurate than it could be with hard-bits. */ + for (i = 0; i < RACH_SYNCH_SEQ_NUM; i++) { + for (j = 0; j < RACH_SYNCH_SEQ_LEN; j++) + score[i] += RACH_SYNCH_SEQ_MULT * synch_seq_burst[j]; + + /* Keep the maximum value updated */ + if (score[i] > max_score) { + max_score = score[i]; + seq = i; + } + } + + /* Calculate an approximate level of our confidence */ + if (best_score != NULL) + *best_score = max_score; + + /* At least 1/3 of a synch. sequence shall match */ + if (max_score < (127 * RACH_SYNCH_SEQ_LEN / 3)) + return RACH_SYNCH_SEQ_UNKNOWN; + + return seq; +} + +int rx_rach_fn(struct l1sched_ts *l1ts, const struct trx_ul_burst_ind *bi) +{ + struct gsm_bts_trx *trx = l1ts->ts->trx; + struct osmo_phsap_prim l1sap; + int n_errors = 0; + int n_bits_total = 0; + uint16_t ra11; + uint8_t ra; + int rc; + + /* Ignore NOPE indications, they're of no use here */ + if (bi->flags & TRX_BI_F_NOPE_IND) + return 0; + + /* TSC (Training Sequence Code) is an optional parameter of the UL burst + * indication. We need this information in order to decide whether an + * Access Burst is 11-bit encoded or not (see OS#1854). If this information + * is absent, we try to correlate the received synch. sequence with the + * known ones (3GPP TS 05.02, section 5.2.7), and fall-back to the default + * TS0 if it fails. */ + enum rach_synch_seq_t synch_seq = RACH_SYNCH_SEQ_TS0; + int best_score = 127 * RACH_SYNCH_SEQ_LEN; + + /* If logical channel is not either of RACH, PDTCH or PTCCH, this is a + * handover Access Burst, which is always encoded as 8-bit and shall + * contain the generic training sequence (TS0). */ + if (bi->chan == TRXC_RACH || bi->chan == TRXC_PDTCH || bi->chan == TRXC_PTCCH) { + if (bi->flags & TRX_BI_F_TS_INFO) + synch_seq = (enum rach_synch_seq_t) bi->tsc; + else + synch_seq = rach_get_synch_seq((sbit_t *) bi->burst, &best_score); + } + + LOGL1SB(DL1P, LOGL_DEBUG, l1ts, bi, + "Received%s RACH (%s): rssi=%d toa256=%d", + TRX_CHAN_IS_DEDIC(bi->chan) ? " handover" : "", + get_value_string(rach_synch_seq_names, synch_seq), + bi->rssi, bi->toa256); + if (bi->flags & TRX_BI_F_CI_CB) + LOGPC(DL1P, LOGL_DEBUG, " C/I=%d cB", bi->ci_cb); + else + LOGPC(DL1P, LOGL_DEBUG, " match=%.1f%%", + best_score * 100.0 / (127 * RACH_SYNCH_SEQ_LEN)); + LOGPC(DL1P, LOGL_DEBUG, "\n"); + + /* Compose a new L1SAP primitive */ + memset(&l1sap, 0x00, sizeof(l1sap)); + osmo_prim_init(&l1sap.oph, SAP_GSM_PH, PRIM_PH_RACH, PRIM_OP_INDICATION, NULL); + l1sap.u.rach_ind.chan_nr = trx_chan_desc[bi->chan].chan_nr | bi->tn; + l1sap.u.rach_ind.acc_delay = (bi->toa256 >= 0) ? bi->toa256 / 256 : 0; + l1sap.u.rach_ind.acc_delay_256bits = bi->toa256; + l1sap.u.rach_ind.rssi = bi->rssi; + l1sap.u.rach_ind.fn = bi->fn; + + /* Link quality is defined by C/I (Carrier-to-Interference ratio), + * which has optional presence. If it's absent, report the + * minimum acceptable value to pass L1SAP checks. */ + if (bi->flags & TRX_BI_F_CI_CB) + l1sap.u.rach_ind.lqual_cb = bi->ci_cb; + else + l1sap.u.rach_ind.lqual_cb = trx->bts->min_qual_rach; + + /* Decode RACH depending on its synch. sequence */ + switch (synch_seq) { + case RACH_SYNCH_SEQ_TS1: + case RACH_SYNCH_SEQ_TS2: + rc = gsm0503_rach_ext_decode_ber(&ra11, bi->burst + RACH_EXT_TAIL_LEN + RACH_SYNCH_SEQ_LEN, + trx->bts->bsic, &n_errors, &n_bits_total); + if (rc) { + LOGL1SB(DL1P, LOGL_DEBUG, l1ts, bi, "Received bad Access Burst\n"); + return 0; + } + + if (synch_seq == RACH_SYNCH_SEQ_TS1) + l1sap.u.rach_ind.burst_type = GSM_L1_BURST_TYPE_ACCESS_1; + else + l1sap.u.rach_ind.burst_type = GSM_L1_BURST_TYPE_ACCESS_2; + + l1sap.u.rach_ind.is_11bit = 1; + l1sap.u.rach_ind.ra = ra11; + break; + + case RACH_SYNCH_SEQ_TS0: + default: + /* Fall-back to the default TS0 if needed */ + if (synch_seq != RACH_SYNCH_SEQ_TS0) { + LOGL1SB(DL1P, LOGL_DEBUG, l1ts, bi, "Falling-back to the default TS0\n"); + synch_seq = RACH_SYNCH_SEQ_TS0; + } + + rc = gsm0503_rach_decode_ber(&ra, bi->burst + RACH_EXT_TAIL_LEN + RACH_SYNCH_SEQ_LEN, + trx->bts->bsic, &n_errors, &n_bits_total); + if (rc) { + LOGL1SB(DL1P, LOGL_DEBUG, l1ts, bi, "Received bad Access Burst\n"); + return 0; + } + + l1sap.u.rach_ind.burst_type = GSM_L1_BURST_TYPE_ACCESS_0; + l1sap.u.rach_ind.is_11bit = 0; + l1sap.u.rach_ind.ra = ra; + break; + } + + l1sap.u.rach_ind.ber10k = compute_ber10k(n_bits_total, n_errors); + + /* forward primitive */ + l1sap_up(trx, &l1sap); + + return 0; +} diff --git a/src/osmo-bts-trx/sched_lchan_tchf.c b/src/osmo-bts-trx/sched_lchan_tchf.c new file mode 100644 index 00000000..5a3e80ac --- /dev/null +++ b/src/osmo-bts-trx/sched_lchan_tchf.c @@ -0,0 +1,677 @@ +/* + * (C) 2013 by Andreas Eversberg <jolly@eversberg.eu> + * (C) 2015-2017 by Harald Welte <laforge@gnumonks.org> + * (C) 2020-2023 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 <stdint.h> +#include <errno.h> + +#include <osmocom/core/bits.h> +#include <osmocom/core/msgb.h> +#include <osmocom/core/utils.h> + +#include <osmocom/gsm/gsm0502.h> + +#include <osmocom/codec/codec.h> + +#include <osmocom/coding/gsm0503_coding.h> +#include <osmocom/coding/gsm0503_amr_dtx.h> + +#include <osmocom/netif/amr.h> + +#include <osmo-bts/bts.h> +#include <osmo-bts/l1sap.h> +#include <osmo-bts/logging.h> +#include <osmo-bts/scheduler.h> +#include <osmo-bts/scheduler_backend.h> +#include <osmo-bts/msg_utils.h> + +#include <sched_utils.h> +#include <amr_loop.h> + +/* 3GPP TS 45.009, table 3.2.1.3-{1,3}: AMR on Uplink TCH/F. + * + * +---+---+---+---+---+---+---+---+ + * | a | b | c | d | e | f | g | h | Burst 'a' received first + * +---+---+---+---+---+---+---+---+ + * ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Speech/FACCH frame (bursts 'a' .. 'h') + * + * TDMA frame number of burst 'h' is always used as the table index. */ +static const uint8_t sched_tchf_ul_amr_cmi_map[26] = { + [7] = 1, /* TCH/F: a=0 / h=7 */ + [16] = 1, /* TCH/F: a=8 / h=16 */ + [24] = 1, /* TCH/F: a=17 / h=24 */ +}; + +/* TDMA frame number of burst 'a' should be used as the table index. */ +static const uint8_t sched_tchf_dl_amr_cmi_map[26] = { + [4] = 1, /* TCH/F: a=4 */ + [13] = 1, /* TCH/F: a=13 */ + [21] = 1, /* TCH/F: a=21 */ +}; + +extern const uint8_t sched_tchh_dl_amr_cmi_map[26]; + +static int decode_fr_facch(struct l1sched_ts *l1ts, + const struct trx_ul_burst_ind *bi) +{ + struct l1sched_chan_state *chan_state = &l1ts->chan_state[bi->chan]; + const sbit_t *bursts_p = chan_state->ul_bursts; + struct l1sched_meas_set meas_avg; + uint8_t data[GSM_MACBLOCK_LEN]; + int n_errors, n_bits_total; + int rc; + + rc = gsm0503_tch_fr_facch_decode(&data[0], BUFTAIL8(bursts_p), + &n_errors, &n_bits_total); + if (rc != GSM_MACBLOCK_LEN) + return rc; + + /* average measurements of the last 8 bursts, obtain TDMA Fn of the first burst */ + trx_sched_meas_avg(chan_state, &meas_avg, SCHED_MEAS_AVG_M_S8N8); + + _sched_compose_ph_data_ind(l1ts, meas_avg.fn, bi->chan, + &data[0], GSM_MACBLOCK_LEN, + compute_ber10k(n_bits_total, n_errors), + meas_avg.rssi, + meas_avg.toa256, + meas_avg.ci_cb, + PRES_INFO_UNKNOWN); + return GSM_MACBLOCK_LEN; +} + +/* Process a single Uplink TCH/F burst received by the PHY. + * This function is visualized in file 'doc/trx_sched_tch.txt'. */ +int rx_tchf_fn(struct l1sched_ts *l1ts, const struct trx_ul_burst_ind *bi) +{ + struct l1sched_chan_state *chan_state = &l1ts->chan_state[bi->chan]; + struct gsm_lchan *lchan = chan_state->lchan; + sbit_t *burst, *bursts_p = chan_state->ul_bursts; + uint32_t *mask = &chan_state->ul_mask; + uint8_t rsl_cmode = chan_state->rsl_cmode; + uint8_t tch_mode = chan_state->tch_mode; + uint8_t tch_data[290]; /* large enough to hold 290 unpacked bits for CSD */ + enum sched_meas_avg_mode meas_avg_mode = SCHED_MEAS_AVG_M_S8N8; + struct l1sched_meas_set meas_avg; + int rc, amr = 0; + int n_errors = 0; + int n_bits_total = 0; + unsigned int fn_begin; + uint16_t ber10k; + uint8_t is_sub = 0; + uint8_t ft; + bool amr_is_cmr; + + /* If handover RACH detection is turned on, treat this burst as an Access Burst. + * Handle NOPE.ind as usually to ensure proper Uplink measurement reporting. */ + if (chan_state->ho_rach_detect == 1 && ~bi->flags & TRX_BI_F_NOPE_IND) + return rx_rach_fn(l1ts, bi); + + LOGL1SB(DL1P, LOGL_DEBUG, l1ts, bi, "Received TCH/F, bid=%u\n", bi->bid); + + /* shift the buffer by 4 bursts leftwards */ + if (bi->bid == 0) { + memmove(BUFPOS(bursts_p, 0), BUFPOS(bursts_p, 4), 20 * BPLEN); + memset(BUFPOS(bursts_p, 20), 0, 4 * BPLEN); + *mask = *mask << 4; + } + + /* update mask */ + *mask |= (1 << bi->bid); + + /* store measurements */ + trx_sched_meas_push(chan_state, bi); + + /* copy burst to end of buffer of 24 bursts */ + burst = BUFPOS(bursts_p, 20 + bi->bid); + if (bi->burst_len > 0) { + memcpy(burst, bi->burst + 3, 58); + memcpy(burst + 58, bi->burst + 87, 58); + } + + /* wait until complete set of bursts */ + if (bi->bid != 3) + return 0; + + /* fill up the burst buffer so that we have 8 bursts in there */ + if (OSMO_UNLIKELY((*mask & 0xff) != 0xff)) { + LOGL1SB(DL1P, LOGL_DEBUG, l1ts, bi, + "UL burst buffer is not filled up: mask=0x%02x != 0xff\n", + *mask); + return 0; /* TODO: send BFI */ + } + + /* TCH/F: speech and signalling frames are interleaved over 8 bursts, while + * CSD frames are interleaved over 22 bursts. Unless we're in CSD mode, + * decode only the last 8 bursts to avoid introducing additional delays. */ + switch (tch_mode) { + case GSM48_CMODE_SIGN: + case GSM48_CMODE_SPEECH_V1: /* FR */ + rc = gsm0503_tch_fr_decode(tch_data, BUFTAIL8(bursts_p), + 1, 0, &n_errors, &n_bits_total); + if (rc == GSM_FR_BYTES) /* only for valid *speech* frames */ + lchan_set_marker(osmo_fr_is_any_sid(tch_data), lchan); /* DTXu */ + break; + case GSM48_CMODE_SPEECH_EFR: /* EFR */ + rc = gsm0503_tch_fr_decode(tch_data, BUFTAIL8(bursts_p), + 1, 1, &n_errors, &n_bits_total); + if (rc == GSM_EFR_BYTES) /* only for valid *speech* frames */ + lchan_set_marker(osmo_efr_is_any_sid(tch_data), lchan); /* DTXu */ + break; + case GSM48_CMODE_SPEECH_AMR: /* AMR */ + /* the first FN 0,8,17 defines that CMI is included in frame, + * the first FN 4,13,21 defines that CMR is included in frame. + * NOTE: A frame ends 7 FN after start. + */ + amr_is_cmr = !sched_tchf_ul_amr_cmi_map[bi->fn % 26]; + + /* The AFS_ONSET frame itself does not result into an RTP frame + * since it only contains a recognition pattern that marks the + * end of the DTX interval. To mark the end of the DTX interval + * in the RTP stream as well, the voice frame after the + * AFS_ONSET frame is used. */ + if (chan_state->amr_last_dtx == AFS_ONSET) + lchan_set_marker(false, lchan); + + /* Store AMR payload in tch-data with an offset of 2 bytes, so + * that we can easily prepend/fill the RTP AMR header (struct + * amr_hdr) with osmo_amr_rtp_enc() later on. The amr variable + * is used far below to account for the decoded offset in case + * we receive an FACCH frame instead of a voice frame (we + * do not know this before we actually decode the frame) */ + amr = sizeof(struct amr_hdr); + rc = gsm0503_tch_afs_decode_dtx(tch_data + amr, BUFTAIL8(bursts_p), + amr_is_cmr, chan_state->codec, chan_state->codecs, &chan_state->ul_ft, + &chan_state->ul_cmr, &n_errors, &n_bits_total, &chan_state->amr_last_dtx); + + /* Tag all frames that are not regular AMR voice frames as + * SUB-Frames */ + if (chan_state->amr_last_dtx != AMR_OTHER) { + LOGL1SB(DL1P, LOGL_DEBUG, l1ts, bi, + "Received AMR DTX frame (rc=%d, BER %d/%d): %s\n", + rc, n_errors, n_bits_total, + gsm0503_amr_dtx_frame_name(chan_state->amr_last_dtx)); + is_sub = 1; + } + + /* The occurrence of the following frames indicates that we + * are either at the beginning or in the middle of a talk + * spurt. We update the SID status accordingly, but we do + * not want the marker to be set, since this must only + * happen when the talk spurt is over (see above) */ + switch (chan_state->amr_last_dtx) { + case AFS_SID_FIRST: + case AFS_SID_UPDATE: + case AFS_SID_UPDATE_CN: + lchan_set_marker(true, lchan); + lchan->rtp_tx_marker = false; + break; + } + + switch (chan_state->amr_last_dtx) { + case AFS_SID_FIRST: + case AFS_SID_UPDATE_CN: + meas_avg_mode = SCHED_MEAS_AVG_M_S8N4; + break; + case AFS_SID_UPDATE: + case AFS_ONSET: + meas_avg_mode = SCHED_MEAS_AVG_M_S4N4; + break; + } + + /* only good speech frames get rtp header */ + if (rc != GSM_MACBLOCK_LEN && rc >= 4) { + if (chan_state->amr_last_dtx == AMR_OTHER) { + ft = chan_state->codec[chan_state->ul_ft]; + } else { + /* SID frames will always get Frame Type Index 8 (AMR_SID) */ + ft = AMR_SID; + } + rc = osmo_amr_rtp_enc(tch_data, + chan_state->codec[chan_state->ul_cmr], + ft, AMR_GOOD); + } + + break; + /* CSD (TCH/F9.6): 12.0 kbit/s radio interface rate */ + case GSM48_CMODE_DATA_12k0: + /* FACCH/F does not steal TCH/F9.6 frames, but only disturbs some bits */ + decode_fr_facch(l1ts, bi); + rc = gsm0503_tch_fr96_decode(&tch_data[0], BUFPOS(bursts_p, 0), + &n_errors, &n_bits_total); + meas_avg_mode = SCHED_MEAS_AVG_M_S24N22; + break; + /* CSD (TCH/F4.8): 6.0 kbit/s radio interface rate */ + case GSM48_CMODE_DATA_6k0: + /* FACCH/F does not steal TCH/F4.8 frames, but only disturbs some bits */ + decode_fr_facch(l1ts, bi); + rc = gsm0503_tch_fr48_decode(&tch_data[0], BUFPOS(bursts_p, 0), + &n_errors, &n_bits_total); + meas_avg_mode = SCHED_MEAS_AVG_M_S24N22; + break; + /* CSD (TCH/F2.4): 3.6 kbit/s radio interface rate */ + case GSM48_CMODE_DATA_3k6: + /* TCH/F2.4 employs the same interleaving as TCH/FS (8 bursts), + * so FACCH/F *does* steal TCH/F2.4 frames completely. */ + if (decode_fr_facch(l1ts, bi) == GSM_MACBLOCK_LEN) + return 0; /* TODO: emit BFI */ + rc = gsm0503_tch_fr24_decode(&tch_data[0], BUFTAIL8(bursts_p), + &n_errors, &n_bits_total); + meas_avg_mode = SCHED_MEAS_AVG_M_S8N8; + break; + /* CSD (TCH/F14.4): 14.5 kbit/s radio interface rate */ + case GSM48_CMODE_DATA_14k5: + /* FACCH/F does not steal TCH/F14.4 frames, but only disturbs some bits */ + decode_fr_facch(l1ts, bi); + rc = gsm0503_tch_fr144_decode(&tch_data[0], BUFPOS(bursts_p, 0), + &n_errors, &n_bits_total); + meas_avg_mode = SCHED_MEAS_AVG_M_S24N22; + break; + default: + LOGL1SB(DL1P, LOGL_ERROR, l1ts, bi, + "TCH mode %u invalid, please fix!\n", + tch_mode); + return -EINVAL; + } + + ber10k = compute_ber10k(n_bits_total, n_errors); + + /* average measurements of the last N (depends on mode) bursts */ + trx_sched_meas_avg(chan_state, &meas_avg, meas_avg_mode); + /* meas_avg.fn now contains TDMA frame number of the first burst */ + fn_begin = meas_avg.fn; + + if (tch_mode == GSM48_CMODE_SPEECH_AMR) + trx_loop_amr_input(chan_state, &meas_avg); + + /* Check if the frame is bad */ + if (rc < 4) { + LOGL1SB(DL1P, LOGL_NOTICE, l1ts, bi, + BAD_DATA_MSG_FMT "\n", BAD_DATA_MSG_ARGS); + rc = 0; /* this is how we signal BFI to l1sap */ + } else if (rc == GSM_MACBLOCK_LEN) { /* FACCH/F */ + _sched_compose_ph_data_ind(l1ts, fn_begin, bi->chan, + &tch_data[amr], GSM_MACBLOCK_LEN, + ber10k, + meas_avg.rssi, + meas_avg.toa256, + meas_avg.ci_cb, + PRES_INFO_UNKNOWN); + + /* If we are in SPEECH mode we will generate a fake (BFI) TCH + * indication as well. This indication is needed by the higher + * layers, however we already have reported the measurement + * result for the current block together with the FACCH. + * To avoid reporting the same measurement result again with + * the fake (BFI) TCH indication we set meas_avg.rssi to zero. + * Doing so tells l1sap.c to ignore the measurement result. */ + meas_avg.rssi = 0; + rc = 0; + } + + if (rsl_cmode == RSL_CMOD_SPD_SIGN) + return 0; + + /* TCH or BFI */ + return _sched_compose_tch_ind(l1ts, fn_begin, bi->chan, + &tch_data[0], rc, + ber10k, + meas_avg.rssi, + meas_avg.toa256, + meas_avg.ci_cb, + is_sub); +} + +/* common section for generation of TCH bursts (TCH/H and TCH/F). + * FIXME: this function is over-complicated, refactor / get rid of it. */ +void tch_dl_dequeue(struct l1sched_ts *l1ts, struct trx_dl_burst_req *br, + struct msgb **msg_tch, struct msgb **msg_facch) +{ + struct msgb *msg1, *msg2; + struct l1sched_chan_state *chan_state = &l1ts->chan_state[br->chan]; + uint8_t rsl_cmode = chan_state->rsl_cmode; + uint8_t tch_mode = chan_state->tch_mode; + struct osmo_phsap_prim *l1sap; + + /* get frame and unlink from queue */ + msg1 = _sched_dequeue_prim(l1ts, br); + msg2 = _sched_dequeue_prim(l1ts, br); + if (msg1) { + l1sap = msgb_l1sap_prim(msg1); + if (l1sap->oph.primitive == PRIM_TCH) { + *msg_tch = msg1; + if (msg2) { + l1sap = msgb_l1sap_prim(msg2); + if (l1sap->oph.primitive == PRIM_TCH) { + LOGL1SB(DL1P, LOGL_FATAL, l1ts, br, "TCH twice, please FIX!\n"); + msgb_free(msg2); + } else + *msg_facch = msg2; + } + } else { + *msg_facch = msg1; + if (msg2) { + l1sap = msgb_l1sap_prim(msg2); + if (l1sap->oph.primitive != PRIM_TCH) { + LOGL1SB(DL1P, LOGL_FATAL, l1ts, br, "FACCH twice, please FIX!\n"); + msgb_free(msg2); + } else + *msg_tch = msg2; + } + } + } + + /* check validity of message */ + if (*msg_facch != NULL && msgb_l2len(*msg_facch) != GSM_MACBLOCK_LEN) { + LOGL1SB(DL1P, LOGL_FATAL, l1ts, br, "Prim has odd len=%u != %u\n", + msgb_l2len(*msg_facch), GSM_MACBLOCK_LEN); + /* free message */ + msgb_free(*msg_facch); + *msg_facch = NULL; + } + + /* check validity of message, get AMR ft and cmr */ + if (*msg_tch != NULL) { + int len; + uint8_t cmr_codec; + int ft, i; + enum osmo_amr_type ft_codec; + enum osmo_amr_quality bfi; + int8_t sti, cmi; + bool amr_is_cmr; + + if (OSMO_UNLIKELY(rsl_cmode == RSL_CMOD_SPD_SIGN)) { + LOGL1SB(DL1P, LOGL_NOTICE, l1ts, br, "Dropping a TCH frame, " + "because we are not in speech mode\n"); + goto free_bad_msg; + } + + switch (tch_mode) { + case GSM48_CMODE_SPEECH_V1: /* FR / HR */ + if (br->chan != TRXC_TCHF) /* HR */ + len = GSM_HR_BYTES; + else + len = GSM_FR_BYTES; + break; + case GSM48_CMODE_SPEECH_EFR: /* EFR */ + if (br->chan != TRXC_TCHF) + goto inval_mode2; + len = GSM_EFR_BYTES; + break; + case GSM48_CMODE_SPEECH_AMR: /* AMR */ + len = osmo_amr_rtp_dec(msgb_l2((*msg_tch)), msgb_l2len(*msg_tch), + &cmr_codec, &cmi, &ft_codec, + &bfi, &sti); + if (len < 0) { + LOGL1SB(DL1P, LOGL_ERROR, l1ts, br, "Cannot send invalid AMR payload\n"); + goto free_bad_msg; + } + ft = -1; + for (i = 0; i < chan_state->codecs; i++) { + if (chan_state->codec[i] == ft_codec) + ft = i; + } + if (ft < 0) { + LOGL1SB(DL1P, LOGL_ERROR, l1ts, br, + "Codec (FT = %d) of RTP frame not in list\n", ft_codec); + goto free_bad_msg; + } + if (br->chan == TRXC_TCHF) + amr_is_cmr = !sched_tchf_dl_amr_cmi_map[br->fn % 26]; + else /* TRXC_TCHH_0 or TRXC_TCHH_1 */ + amr_is_cmr = !sched_tchh_dl_amr_cmi_map[br->fn % 26]; + if (amr_is_cmr && chan_state->dl_ft != ft) { + LOGL1SB(DL1P, LOGL_NOTICE, l1ts, br, "Codec (FT = %d) " + " of RTP cannot be changed now, but in next frame\n", ft_codec); + goto free_bad_msg; + } + chan_state->dl_ft = ft; + if (bfi == AMR_BAD) { + LOGL1SB(DL1P, LOGL_NOTICE, l1ts, br, "Transmitting 'bad AMR frame'\n"); + goto free_bad_msg; + } + /* pull the AMR header, it's not being sent over Um */ + (*msg_tch)->l2h += sizeof(struct amr_hdr); + len -= sizeof(struct amr_hdr); + break; + case GSM48_CMODE_DATA_14k5: /* TCH/F14.4 */ + if (OSMO_UNLIKELY(br->chan != TRXC_TCHF)) + goto inval_mode2; + len = 290; + break; + case GSM48_CMODE_DATA_12k0: /* TCH/F9.6 */ + if (OSMO_UNLIKELY(br->chan != TRXC_TCHF)) + goto inval_mode2; + len = 4 * 60; + break; + case GSM48_CMODE_DATA_6k0: /* TCH/[FH]4.8 */ + if (br->chan == TRXC_TCHF) + len = 2 * 60; + else + len = 4 * 60; + break; + case GSM48_CMODE_DATA_3k6: /* TCH/[FH]2.4 */ + if (br->chan == TRXC_TCHF) + len = 2 * 36; + else + len = 4 * 36; + break; + default: +inval_mode2: + LOGL1SB(DL1P, LOGL_ERROR, l1ts, br, "TCH mode invalid, please fix!\n"); + goto free_bad_msg; + } + if (msgb_l2len(*msg_tch) != len) { + LOGL1SB(DL1P, LOGL_ERROR, l1ts, br, "Cannot send payload with " + "invalid length! (expecting %d, received %d)\n", + len, msgb_l2len(*msg_tch)); +free_bad_msg: + /* free message */ + msgb_free(*msg_tch); + *msg_tch = NULL; + } + } +} + +struct msgb *tch_dummy_msgb(size_t size, uint8_t pad) +{ + struct msgb *msg; + + msg = msgb_alloc(size, __func__); + OSMO_ASSERT(msg != NULL); + + msg->l2h = msgb_put(msg, size); + memset(msg->l2h, pad, size); + + return msg; +} + +/* obtain a to-be-transmitted TCH/F (Full Traffic Channel) burst */ +int tx_tchf_fn(struct l1sched_ts *l1ts, struct trx_dl_burst_req *br) +{ + struct l1sched_chan_state *chan_state = &l1ts->chan_state[br->chan]; + uint8_t tch_mode = chan_state->tch_mode; + ubit_t *burst, *bursts_p = chan_state->dl_bursts; + uint8_t *mask = &chan_state->dl_mask; + struct msgb *msg_facch = NULL; + struct msgb *msg_tch = NULL; + struct msgb *msg = NULL; + + /* send burst, if we already got a frame */ + if (br->bid > 0) { + if ((*mask & 0x01) != 0x01) + return -ENOMSG; + goto send_burst; + } + + *mask = *mask << 4; + + /* BURST BYPASS */ + + /* shift buffer by 4 bursts for interleaving */ + memmove(BUFPOS(bursts_p, 0), BUFPOS(bursts_p, 4), 20 * BPLEN); + memset(BUFPOS(bursts_p, 20), 0, 4 * BPLEN); + + /* dequeue a TCH and/or a FACCH message to be transmitted */ + tch_dl_dequeue(l1ts, br, &msg_tch, &msg_facch); + if (msg_tch == NULL && msg_facch == NULL) { + int rc; + + LOGL1SB(DL1P, LOGL_DEBUG, l1ts, br, "No TCH or FACCH prim for transmit.\n"); + /* - If the channel mode is TCH/FS or TCH/EFS, transmit a dummy + * speech block with inverted CRC3, designed to induce a BFI + * condition in the MS receiver. + * - If the channel mode is TCH/AFS, transmit a dummy speech + * block with inverted CRC6, designed to induce a BFI + * condition in the MS receiver. + * - If the channel mode is one of the CSD modes, transmit an + * idle frame as described in 3GPP TS 44.021, sections 8.1.6 + * and 10.2.3 (all data, status and E-bits set to binary '1'). + * - In all other channel modes, transmit dummy FACCH + * like we always did before. + */ + switch (tch_mode) { + case GSM48_CMODE_DATA_12k0: + case GSM48_CMODE_DATA_6k0: + case GSM48_CMODE_DATA_3k6: + case GSM48_CMODE_DATA_14k5: + break; /* see below */ + case GSM48_CMODE_SPEECH_V1: + case GSM48_CMODE_SPEECH_EFR: + rc = gsm0503_tch_fr_encode(BUFPOS(bursts_p, 0), NULL, 0, 1); + if (rc == 0) + goto send_burst; + /* fall-through */ + case GSM48_CMODE_SIGN: + default: + if (tch_mode == GSM48_CMODE_SPEECH_AMR) { + /* the first FN 4,13,21 defines that CMI is included in frame, + * the first FN 0,8,17 defines that CMR is included in frame. + */ + rc = gsm0503_tch_afs_encode(BUFPOS(bursts_p, 0), + NULL, 0, + !sched_tchf_dl_amr_cmi_map[br->fn % 26], + chan_state->codec, + chan_state->codecs, + chan_state->dl_ft, + chan_state->dl_cmr); + if (rc == 0) + goto send_burst; + } + + /* TODO: use randomized padding */ + msg_facch = tch_dummy_msgb(GSM_MACBLOCK_LEN, GSM_MACBLOCK_PADDING); + /* dummy LAPDm func=UI frame */ + msg_facch->l2h[0] = 0x03; + msg_facch->l2h[1] = 0x03; + msg_facch->l2h[2] = 0x01; + break; + } + } + + /* Unlike SACCH, FACCH has no dedicated slots on the multiframe layout. + * It's multiplexed together with TCH (speech or data) frames basically + * by replacing (stealing) their bits, either completely or partly. */ + msg = (msg_facch != NULL) ? msg_facch : msg_tch; + if (msg == msg_facch) + chan_state->dl_facch_bursts = 8; + + /* populate the buffer with bursts */ + switch (tch_mode) { + case GSM48_CMODE_SIGN: + case GSM48_CMODE_SPEECH_V1: + case GSM48_CMODE_SPEECH_EFR: + gsm0503_tch_fr_encode(BUFPOS(bursts_p, 0), msg->l2h, msgb_l2len(msg), 1); + break; + case GSM48_CMODE_SPEECH_AMR: + /* the first FN 4,13,21 defines that CMI is included in frame, + * the first FN 0,8,17 defines that CMR is included in frame. + */ + gsm0503_tch_afs_encode(BUFPOS(bursts_p, 0), + msgb_l2(msg), msgb_l2len(msg), + !sched_tchf_dl_amr_cmi_map[br->fn % 26], + chan_state->codec, + chan_state->codecs, + chan_state->dl_ft, + chan_state->dl_cmr); + break; + /* CSD (TCH/F9.6): 12.0 kbit/s radio interface rate */ + case GSM48_CMODE_DATA_12k0: + if (msg_tch == NULL) + msg_tch = tch_dummy_msgb(4 * 60, 0x01); + gsm0503_tch_fr96_encode(BUFPOS(bursts_p, 0), msgb_l2(msg_tch)); + if (msg_facch != NULL) + gsm0503_tch_fr_facch_encode(BUFPOS(bursts_p, 0), msgb_l2(msg_facch)); + break; + /* CSD (TCH/F4.8): 6.0 kbit/s radio interface rate */ + case GSM48_CMODE_DATA_6k0: + if (msg_tch == NULL) + msg_tch = tch_dummy_msgb(2 * 60, 0x01); + gsm0503_tch_fr48_encode(BUFPOS(bursts_p, 0), msgb_l2(msg_tch)); + if (msg_facch != NULL) + gsm0503_tch_fr_facch_encode(BUFPOS(bursts_p, 0), msgb_l2(msg_facch)); + break; + /* CSD (TCH/F2.4): 3.6 kbit/s radio interface rate */ + case GSM48_CMODE_DATA_3k6: + /* FACCH/F does steal a TCH/F2.4 frame completely */ + if (msg_facch != NULL) { + gsm0503_tch_fr_facch_encode(BUFPOS(bursts_p, 0), msgb_l2(msg_facch)); + } else { + if (msg_tch == NULL) + msg_tch = tch_dummy_msgb(2 * 36, 0x01); + gsm0503_tch_fr24_encode(BUFPOS(bursts_p, 0), msgb_l2(msg_tch)); + } + break; + /* CSD (TCH/F14.4): 14.5 kbit/s radio interface rate */ + case GSM48_CMODE_DATA_14k5: + if (msg_tch == NULL) + msg_tch = tch_dummy_msgb(290, 0x01); + gsm0503_tch_fr144_encode(BUFPOS(bursts_p, 0), msgb_l2(msg_tch)); + if (msg_facch != NULL) + gsm0503_tch_fr_facch_encode(BUFPOS(bursts_p, 0), msgb_l2(msg_facch)); + break; + default: + OSMO_ASSERT(0); + } + + /* free messages */ + msgb_free(msg_tch); + msgb_free(msg_facch); + +send_burst: + /* compose burst */ + burst = BUFPOS(bursts_p, br->bid); + memcpy(br->burst + 3, burst, 58); + memcpy(br->burst + 61, TRX_GMSK_NB_TSC(br), 26); + memcpy(br->burst + 87, burst + 58, 58); + + br->burst_len = GSM_BURST_LEN; + + if (chan_state->dl_facch_bursts > 0) { + chan_state->dl_facch_bursts--; + br->flags |= TRX_BR_F_FACCH; + } + + *mask |= (1 << br->bid); + + LOGL1SB(DL1P, LOGL_DEBUG, l1ts, br, "Transmitting burst=%u.\n", br->bid); + + return 0; +} diff --git a/src/osmo-bts-trx/sched_lchan_tchh.c b/src/osmo-bts-trx/sched_lchan_tchh.c new file mode 100644 index 00000000..66244cb0 --- /dev/null +++ b/src/osmo-bts-trx/sched_lchan_tchh.c @@ -0,0 +1,580 @@ +/* + * (C) 2013 by Andreas Eversberg <jolly@eversberg.eu> + * (C) 2015-2017 by Harald Welte <laforge@gnumonks.org> + * (C) 2020-2023 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 <stdint.h> +#include <errno.h> + +#include <osmocom/core/bits.h> +#include <osmocom/core/msgb.h> +#include <osmocom/core/utils.h> + +#include <osmocom/gsm/gsm0502.h> + +#include <osmocom/codec/codec.h> + +#include <osmocom/coding/gsm0503_coding.h> +#include <osmocom/coding/gsm0503_amr_dtx.h> + +#include <osmocom/netif/amr.h> + +#include <osmo-bts/bts.h> +#include <osmo-bts/l1sap.h> +#include <osmo-bts/logging.h> +#include <osmo-bts/scheduler.h> +#include <osmo-bts/scheduler_backend.h> +#include <osmo-bts/msg_utils.h> + +#include <sched_utils.h> +#include <amr_loop.h> + +/* 3GPP TS 45.009, table 3.2.1.3-{2,4}: AMR on Uplink TCH/H. + * + * +---+---+---+---+---+---+ + * | a | b | c | d | e | f | Burst 'a' received first + * +---+---+---+---+---+---+ + * ^^^^^^^^^^^^^^^^^^^^^^^ FACCH frame (bursts 'a' .. 'f') + * ^^^^^^^^^^^^^^^ Speech frame (bursts 'a' .. 'd') + * + * TDMA frame number of burst 'f' is always used as the table index. */ +static const uint8_t sched_tchh_ul_amr_cmi_map[26] = { + [10] = 1, /* TCH/H(0): a=0 / d=6 / f=10 */ + [19] = 1, /* TCH/H(0): a=8 / d=15 / f=19 */ + [2] = 1, /* TCH/H(0): a=17 / d=23 / f=2 */ + + [11] = 1, /* TCH/H(1): a=1 / d=7 / f=11 */ + [20] = 1, /* TCH/H(1): a=9 / d=16 / f=20 */ + [3] = 1, /* TCH/H(1): a=18 / d=24 / f=3 */ +}; + +/* TDMA frame number of burst 'a' should be used as the table index. + * This mapping is valid for both FACCH/H(0) and FACCH/H(1). */ +const uint8_t sched_tchh_dl_amr_cmi_map[26] = { + [4] = 1, /* TCH/H(0): a=4 */ + [13] = 1, /* TCH/H(0): a=13 */ + [21] = 1, /* TCH/H(0): a=21 */ + + [5] = 1, /* TCH/H(1): a=5 */ + [14] = 1, /* TCH/H(1): a=14 */ + [22] = 1, /* TCH/H(1): a=22 */ +}; + +/* 3GPP TS 45.002, table 1 in clause 7: Mapping tables. + * TDMA frame number of burst 'f' is always used as the table index. */ +static const uint8_t sched_tchh_ul_facch_map[26] = { + [10] = 1, /* FACCH/H(0): B0(0,2,4,6,8,10) */ + [11] = 1, /* FACCH/H(1): B0(1,3,5,7,9,11) */ + [19] = 1, /* FACCH/H(0): B1(8,10,13,15,17,19) */ + [20] = 1, /* FACCH/H(1): B1(9,11,14,16,18,20) */ + [2] = 1, /* FACCH/H(0): B2(17,19,21,23,0,2) */ + [3] = 1, /* FACCH/H(1): B2(18,20,22,24,1,3) */ +}; + +/* TDMA frame number of burst 'a' is used as the table index. */ +extern const uint8_t sched_tchh_dl_facch_map[26]; + +/* 3GPP TS 45.002, table 2 in clause 7: Mapping tables for TCH/H2.4 and TCH/H4.8. + * + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * | a | b | c | d | e | f | g | h | i | j | k | l | m | n | o | p | q | r | s | t | u | v | + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * + * TCH/H(0): B0(0,2,4,6,8,10,13,15,17,19,21,23,0,2,4,6,8,10,13,15,17,19) + * TCH/H(1): B0(1,3,5,7,9,11,14,16,18,20,22,24,1,3,5,7,9,11,14,16,18,20) + * TCH/H(0): B1(8,10,13,15,17,19,21,23,0,2,4,6,8,10,13,15,17,19,21,23,0,2) + * TCH/H(1): B1(9,11,14,16,18,20,22,24,1,3,5,7,9,11,14,16,18,20,22,24,1,3) + * TCH/H(0): B2(17,19,21,23,0,2,4,6,8,10,13,15,17,19,21,23,0,2,4,6,8,10) + * TCH/H(1): B2(18,20,22,24,1,3,5,7,9,11,14,16,18,20,22,24,1,3,5,7,9,11) + * + * TDMA frame number of burst 'v' % 26 is the table index. + * This mapping is valid for both TCH/H(0) and TCH/H(1). */ +static const uint8_t sched_tchh_ul_csd_map[26] = { + [19] = 1, /* TCH/H(0): B0(0 ... 19) */ + [20] = 1, /* TCH/H(1): B0(1 ... 20) */ + [2] = 1, /* TCH/H(0): B1(8 ... 2) */ + [3] = 1, /* TCH/H(1): B1(9 ... 3) */ + [10] = 1, /* TCH/H(0): B2(17 ... 10) */ + [11] = 1, /* TCH/H(1): B2(18 ... 11) */ +}; + +/* TDMA frame number of burst 'a' % 26 is the table index. + * This mapping is valid for both TCH/H(0) and TCH/H(1). */ +static const uint8_t sched_tchh_dl_csd_map[26] = { + [0] = 1, /* TCH/H(0): B0(0 ... 19) */ + [1] = 1, /* TCH/H(1): B0(1 ... 20) */ + [8] = 1, /* TCH/H(0): B1(8 ... 2) */ + [9] = 1, /* TCH/H(1): B1(9 ... 3) */ + [17] = 1, /* TCH/H(0): B2(17 ... 10) */ + [18] = 1, /* TCH/H(1): B2(18 ... 11) */ +}; + +static int decode_hr_facch(struct l1sched_ts *l1ts, + const struct trx_ul_burst_ind *bi) +{ + struct l1sched_chan_state *chan_state = &l1ts->chan_state[bi->chan]; + const sbit_t *bursts_p = chan_state->ul_bursts; + struct l1sched_meas_set meas_avg; + uint8_t data[GSM_MACBLOCK_LEN]; + int n_errors, n_bits_total; + int rc; + + rc = gsm0503_tch_hr_facch_decode(&data[0], BUFTAIL8(bursts_p), + &n_errors, &n_bits_total); + if (rc != GSM_MACBLOCK_LEN) + return rc; + + /* average measurements of the last 6 bursts, obtain TDMA Fn of the first burst */ + trx_sched_meas_avg(chan_state, &meas_avg, SCHED_MEAS_AVG_M_S6N6); + + _sched_compose_ph_data_ind(l1ts, meas_avg.fn, bi->chan, + &data[0], GSM_MACBLOCK_LEN, + compute_ber10k(n_bits_total, n_errors), + meas_avg.rssi, + meas_avg.toa256, + meas_avg.ci_cb, + PRES_INFO_UNKNOWN); + return GSM_MACBLOCK_LEN; +} + +/* Process a single Uplink TCH/H burst received by the PHY. + * This function is visualized in file 'doc/trx_sched_tch.txt'. */ +int rx_tchh_fn(struct l1sched_ts *l1ts, const struct trx_ul_burst_ind *bi) +{ + struct l1sched_chan_state *chan_state = &l1ts->chan_state[bi->chan]; + struct gsm_lchan *lchan = chan_state->lchan; + sbit_t *burst, *bursts_p = chan_state->ul_bursts; + uint32_t *mask = &chan_state->ul_mask; + uint8_t rsl_cmode = chan_state->rsl_cmode; + uint8_t tch_mode = chan_state->tch_mode; + uint8_t tch_data[240]; /* large enough to hold 240 unpacked bits for CSD */ + int rc = 0; /* initialize to make gcc happy */ + int amr = 0; + int n_errors = 0; + int n_bits_total = 0; + enum sched_meas_avg_mode meas_avg_mode = SCHED_MEAS_AVG_M_S6N4; + struct l1sched_meas_set meas_avg; + unsigned int fn_begin; + uint16_t ber10k = 0; + uint8_t is_sub = 0; + uint8_t ft; + bool fn_is_cmi; + + /* If handover RACH detection is turned on, treat this burst as an Access Burst. + * Handle NOPE.ind as usually to ensure proper Uplink measurement reporting. */ + if (chan_state->ho_rach_detect == 1 && ~bi->flags & TRX_BI_F_NOPE_IND) + return rx_rach_fn(l1ts, bi); + + LOGL1SB(DL1P, LOGL_DEBUG, l1ts, bi, "Received TCH/H, bid=%u\n", bi->bid); + + /* shift the buffer by 2 bursts leftwards */ + if (bi->bid == 0) { + memmove(BUFPOS(bursts_p, 0), BUFPOS(bursts_p, 2), 20 * BPLEN); + memset(BUFPOS(bursts_p, 20), 0, 2 * BPLEN); + *mask = *mask << 2; + } + + /* update mask */ + *mask |= (1 << bi->bid); + + /* store measurements */ + trx_sched_meas_push(chan_state, bi); + + /* copy burst to end of buffer of 24 bursts */ + burst = BUFPOS(bursts_p, 20 + bi->bid); + if (bi->burst_len > 0) { + memcpy(burst, bi->burst + 3, 58); + memcpy(burst + 58, bi->burst + 87, 58); + } + + /* wait until complete set of bursts */ + if (bi->bid != 1) + return 0; + + /* fill up the burst buffer so that we have 6 bursts in there */ + if (OSMO_UNLIKELY((*mask & 0x3f) != 0x3f)) { + LOGL1SB(DL1P, LOGL_DEBUG, l1ts, bi, + "UL burst buffer is not filled up: mask=0x%02x != 0x3f\n", + *mask); + return 0; /* TODO: send BFI */ + } + + /* skip decoding of the last 4 bursts of FACCH/H */ + if (chan_state->ul_ongoing_facch) { + chan_state->ul_ongoing_facch = 0; + /* we have already sent the first BFI when a FACCH/H frame + * was decoded (see below), now send the second one. */ + trx_sched_meas_avg(chan_state, &meas_avg, meas_avg_mode); + /* meas_avg.fn now contains TDMA frame number of the first burst */ + fn_begin = meas_avg.fn; + goto bfi; + } + + /* TCH/H: speech and signalling frames are interleaved over 4 and 6 bursts, + * respectively, while CSD frames are interleaved over 22 bursts. Unless + * we're in CSD mode, decode only the last 6 bursts to avoid introducing + * additional delays. */ + switch (tch_mode) { + case GSM48_CMODE_SIGN: + meas_avg_mode = SCHED_MEAS_AVG_M_S6N6; + /* fall-through */ + case GSM48_CMODE_SPEECH_V1: /* HR or signalling */ + rc = gsm0503_tch_hr_decode2(tch_data, BUFTAIL8(bursts_p), + !sched_tchh_ul_facch_map[bi->fn % 26], + &n_errors, &n_bits_total); + if (rc == GSM_HR_BYTES) { /* only for valid *speech* frames */ + bool is_sid = osmo_hr_check_sid(tch_data, GSM_HR_BYTES); + lchan_set_marker(is_sid, lchan); /* DTXu */ + } + break; + case GSM48_CMODE_SPEECH_AMR: /* AMR */ + /* the first FN 0,8,17 or 1,9,18 defines that CMI is included + * in frame, the first FN 4,13,21 or 5,14,22 defines that CMR + * is included in frame. + */ + + /* See comment in function rx_tchf_fn() */ + switch (chan_state->amr_last_dtx) { + case AHS_ONSET: + case AHS_SID_FIRST_INH: + case AHS_SID_UPDATE_INH: + lchan_set_marker(false, lchan); + break; + } + + fn_is_cmi = sched_tchh_ul_amr_cmi_map[bi->fn % 26]; + + /* See comment in function rx_tchf_fn() */ + amr = sizeof(struct amr_hdr); + rc = gsm0503_tch_ahs_decode_dtx(tch_data + amr, BUFTAIL8(bursts_p), + !sched_tchh_ul_facch_map[bi->fn % 26], + !fn_is_cmi, chan_state->codec, + chan_state->codecs, &chan_state->ul_ft, + &chan_state->ul_cmr, &n_errors, &n_bits_total, + &chan_state->amr_last_dtx); + + /* Tag all frames that are not regular AMR voice frames + as SUB-Frames */ + if (chan_state->amr_last_dtx != AMR_OTHER) { + LOGL1SB(DL1P, LOGL_DEBUG, l1ts, bi, + "Received AMR DTX frame (rc=%d, BER %d/%d): %s\n", + rc, n_errors, n_bits_total, + gsm0503_amr_dtx_frame_name(chan_state->amr_last_dtx)); + is_sub = 1; + } + + /* See comment in function rx_tchf_fn() */ + switch (chan_state->amr_last_dtx) { + case AHS_SID_FIRST_P1: + case AHS_SID_FIRST_P2: + case AHS_SID_UPDATE: + case AHS_SID_UPDATE_CN: + lchan_set_marker(true, lchan); + lchan->rtp_tx_marker = false; + break; + } + + switch (chan_state->amr_last_dtx) { + case AHS_SID_FIRST_P1: + case AHS_SID_FIRST_P2: + case AHS_SID_UPDATE: + case AHS_SID_UPDATE_CN: + case AHS_SID_FIRST_INH: + case AHS_SID_UPDATE_INH: + meas_avg_mode = SCHED_MEAS_AVG_M_S6N2; + break; + case AHS_ONSET: + meas_avg_mode = SCHED_MEAS_AVG_M_S4N2; + break; + } + + /* only good speech frames get rtp header */ + if (rc != GSM_MACBLOCK_LEN && rc >= 4) { + if (chan_state->amr_last_dtx == AMR_OTHER) { + ft = chan_state->codec[chan_state->ul_ft]; + } else { + /* SID frames will always get Frame Type Index 8 (AMR_SID) */ + ft = AMR_SID; + } + rc = osmo_amr_rtp_enc(tch_data, + chan_state->codec[chan_state->ul_cmr], + ft, AMR_GOOD); + } + + break; + /* CSD (TCH/H4.8): 6.0 kbit/s radio interface rate */ + case GSM48_CMODE_DATA_6k0: + if (!sched_tchh_ul_csd_map[bi->fn % 26]) + return 0; /* CSD: skip decoding attempt, need 2 more bursts */ + /* FACCH/H does not steal TCH/H4.8 frames, but only disturbs some bits */ + decode_hr_facch(l1ts, bi); + rc = gsm0503_tch_hr48_decode(&tch_data[0], BUFPOS(bursts_p, 0), + &n_errors, &n_bits_total); + meas_avg_mode = SCHED_MEAS_AVG_M_S22N22; + break; + /* CSD (TCH/H2.4): 3.6 kbit/s radio interface rate */ + case GSM48_CMODE_DATA_3k6: + if (!sched_tchh_ul_csd_map[bi->fn % 26]) + return 0; /* CSD: skip decoding attempt, need 2 more bursts */ + /* FACCH/H does not steal TCH/H2.4 frames, but only disturbs some bits */ + decode_hr_facch(l1ts, bi); + rc = gsm0503_tch_hr24_decode(&tch_data[0], BUFPOS(bursts_p, 0), + &n_errors, &n_bits_total); + meas_avg_mode = SCHED_MEAS_AVG_M_S22N22; + break; + default: + LOGL1SB(DL1P, LOGL_ERROR, l1ts, bi, + "TCH mode %u invalid, please fix!\n", + tch_mode); + return -EINVAL; + } + + ber10k = compute_ber10k(n_bits_total, n_errors); + + /* average measurements of the last N (depends on mode) bursts */ + trx_sched_meas_avg(chan_state, &meas_avg, meas_avg_mode); + /* meas_avg.fn now contains TDMA frame number of the first burst */ + fn_begin = meas_avg.fn; + + if (tch_mode == GSM48_CMODE_SPEECH_AMR) + trx_loop_amr_input(chan_state, &meas_avg); + + /* Check if the frame is bad */ + if (rc < 4) { + LOGL1SB(DL1P, LOGL_NOTICE, l1ts, bi, + BAD_DATA_MSG_FMT "\n", BAD_DATA_MSG_ARGS); + rc = 0; /* this is how we signal BFI to l1sap */ + } else if (rc == GSM_MACBLOCK_LEN) { /* FACCH */ + chan_state->ul_ongoing_facch = 1; + /* In order to provide an even stream of measurement reports in *speech* + * mode, here we intentionally invalidate RSSI for FACCH, so that this + * report gets dropped in process_l1sap_meas_data(). The averaged results + * will be sent with the first (see below) and second (see above) BFIs. */ + _sched_compose_ph_data_ind(l1ts, fn_begin, bi->chan, + &tch_data[amr], GSM_MACBLOCK_LEN, + ber10k, + tch_mode == GSM48_CMODE_SIGN ? meas_avg.rssi : 0, + meas_avg.toa256, + meas_avg.ci_cb, + PRES_INFO_UNKNOWN); + ber10k = 0; +bfi: + /* A FACCH/H frame replaces two speech frames, so we need to send two BFIs. + * One is sent here, another will be sent two bursts later (see above). */ + rc = 0; + } + + if (rsl_cmode == RSL_CMOD_SPD_SIGN) + return 0; + + /* TCH or BFI */ + return _sched_compose_tch_ind(l1ts, fn_begin, bi->chan, + &tch_data[0], rc, + ber10k, + meas_avg.rssi, + meas_avg.toa256, + meas_avg.ci_cb, + is_sub); +} + +/* common section for generation of TCH bursts (TCH/H and TCH/F). + * FIXME: this function is over-complicated, refactor / get rid of it. */ +extern void tch_dl_dequeue(struct l1sched_ts *l1ts, const struct trx_dl_burst_req *br, + struct msgb **msg_tch, struct msgb **msg_facch); + +struct msgb *tch_dummy_msgb(size_t size, uint8_t pad); + +/* obtain a to-be-transmitted TCH/H (Half Traffic Channel) burst */ +int tx_tchh_fn(struct l1sched_ts *l1ts, struct trx_dl_burst_req *br) +{ + struct l1sched_chan_state *chan_state = &l1ts->chan_state[br->chan]; + uint8_t tch_mode = chan_state->tch_mode; + ubit_t *burst, *bursts_p = chan_state->dl_bursts; + uint8_t *mask = &chan_state->dl_mask; + struct msgb *msg_facch = NULL; + struct msgb *msg_tch = NULL; + struct msgb *msg = NULL; + + /* send burst, if we already got a frame */ + if (br->bid > 0) { + if ((*mask & 0x01) != 0x01) + return -ENOMSG; + goto send_burst; + } + + *mask = *mask << 2; + + /* BURST BYPASS */ + + /* shift buffer by 2 bursts for interleaving */ + memmove(BUFPOS(bursts_p, 0), BUFPOS(bursts_p, 2), 20 * BPLEN); + memset(BUFPOS(bursts_p, 20), 0, 2 * BPLEN); + + /* for half-rate CSD we dequeue every 4th burst */ + if (chan_state->rsl_cmode == RSL_CMOD_SPD_DATA) { + if (!sched_tchh_dl_csd_map[br->fn % 26]) + goto send_burst; + } + + /* dequeue a TCH and/or a FACCH message to be transmitted */ + tch_dl_dequeue(l1ts, br, &msg_tch, &msg_facch); + + /* if we're sending 2 middle bursts of FACCH/H */ + if (chan_state->dl_ongoing_facch) { + /* FACCH/H shall not be scheduled at wrong FNs */ + OSMO_ASSERT(msg_facch == NULL); + msgb_free(msg_tch); /* drop 2nd speech frame */ + chan_state->dl_ongoing_facch = 0; + goto send_burst; + } + + /* no message at all, send a dummy L2 frame on FACCH */ + if (msg_tch == NULL && msg_facch == NULL) { + int rc; + + LOGL1SB(DL1P, LOGL_INFO, l1ts, br, "No TCH or FACCH prim for transmit.\n"); + /* - If the channel mode is TCH/HS, transmit a dummy speech block + * with inverted CRC3, designed to induce a BFI condition in + * the MS receiver. + * - If the channel mode is TCH/AHS, transmit a dummy speech + * block with inverted CRC6, designed to induce a BFI + * condition in the MS receiver. + * - If the channel mode is one of the CSD modes, transmit an + * idle frame as described in 3GPP TS 44.021, sections 8.1.6 + * and 10.2.3 (all data, status and E-bits set to binary '1'). + * - In all other channel modes, transmit dummy FACCH + * like we always did before. + */ + switch (tch_mode) { + case GSM48_CMODE_DATA_6k0: + case GSM48_CMODE_DATA_3k6: + break; /* see below */ + case GSM48_CMODE_SPEECH_V1: + rc = gsm0503_tch_hr_encode(BUFPOS(bursts_p, 0), NULL, 0); + if (rc == 0) + goto send_burst; + /* fall-through */ + case GSM48_CMODE_SIGN: + default: + if (tch_mode == GSM48_CMODE_SPEECH_AMR) { + /* the first FN 4,13,21 or 5,14,22 defines that CMI is included + * in frame, the first FN 0,8,17 or 1,9,18 defines that CMR is + * included in frame. */ + rc = gsm0503_tch_ahs_encode(BUFPOS(bursts_p, 0), + NULL, 0, + !sched_tchh_dl_amr_cmi_map[br->fn % 26], + chan_state->codec, + chan_state->codecs, + chan_state->dl_ft, + chan_state->dl_cmr); + if (rc == 0) + goto send_burst; + } + + /* FACCH/H can only be scheduled at specific TDMA offset */ + if (!sched_tchh_dl_facch_map[br->fn % 26]) { + /* FACCH/H is not allowed, send half-filled bursts with even numbered + * bits contaning 232 encoded bits of the previous L2 frame, and 232 + * odd numbered bits all set to 0. */ + goto send_burst; + } + + /* TODO: use randomized padding */ + msg_facch = tch_dummy_msgb(GSM_MACBLOCK_LEN, GSM_MACBLOCK_PADDING); + /* dummy LAPDm func=UI frame */ + msg_facch->l2h[0] = 0x03; + msg_facch->l2h[1] = 0x03; + msg_facch->l2h[2] = 0x01; + break; + } + } + + /* Unlike SACCH, FACCH has no dedicated slots on the multiframe layout. + * It's multiplexed together with TCH (speech or data) frames basically + * by replacing (stealing) their bits, either completely or partly. */ + msg = (msg_facch != NULL) ? msg_facch : msg_tch; + if (msg == msg_facch) { + if (chan_state->rsl_cmode != RSL_CMOD_SPD_DATA) + chan_state->dl_ongoing_facch = 1; + chan_state->dl_facch_bursts = 6; + } + + /* populate the buffer with bursts */ + switch (tch_mode) { + case GSM48_CMODE_SIGN: + case GSM48_CMODE_SPEECH_V1: + gsm0503_tch_hr_encode(BUFPOS(bursts_p, 0), msg->l2h, msgb_l2len(msg)); + break; + case GSM48_CMODE_SPEECH_AMR: + /* the first FN 4,13,21 or 5,14,22 defines that CMI is included + * in frame, the first FN 0,8,17 or 1,9,18 defines that CMR is + * included in frame. */ + gsm0503_tch_ahs_encode(BUFPOS(bursts_p, 0), + msgb_l2(msg), msgb_l2len(msg), + !sched_tchh_dl_amr_cmi_map[br->fn % 26], + chan_state->codec, + chan_state->codecs, + chan_state->dl_ft, + chan_state->dl_cmr); + break; + /* CSD (TCH/H4.8): 6.0 kbit/s radio interface rate */ + case GSM48_CMODE_DATA_6k0: + if (msg_tch == NULL) + msg_tch = tch_dummy_msgb(4 * 60, 0x01); + gsm0503_tch_hr48_encode(BUFPOS(bursts_p, 0), msgb_l2(msg_tch)); + if (msg_facch != NULL) + gsm0503_tch_hr_facch_encode(BUFPOS(bursts_p, 0), msgb_l2(msg_facch)); + break; + /* CSD (TCH/H2.4): 3.6 kbit/s radio interface rate */ + case GSM48_CMODE_DATA_3k6: + if (msg_tch == NULL) + msg_tch = tch_dummy_msgb(4 * 36, 0x01); + gsm0503_tch_hr24_encode(BUFPOS(bursts_p, 0), msgb_l2(msg_tch)); + if (msg_facch != NULL) + gsm0503_tch_hr_facch_encode(BUFPOS(bursts_p, 0), msgb_l2(msg_facch)); + break; + default: + OSMO_ASSERT(0); + } + + /* free messages */ + msgb_free(msg_tch); + msgb_free(msg_facch); + +send_burst: + /* compose burst */ + burst = BUFPOS(bursts_p, br->bid); + memcpy(br->burst + 3, burst, 58); + memcpy(br->burst + 61, TRX_GMSK_NB_TSC(br), 26); + memcpy(br->burst + 87, burst + 58, 58); + + br->burst_len = GSM_BURST_LEN; + + if (chan_state->dl_facch_bursts > 0) { + chan_state->dl_facch_bursts--; + br->flags |= TRX_BR_F_FACCH; + } + + *mask |= (1 << br->bid); + + LOGL1SB(DL1P, LOGL_DEBUG, l1ts, br, "Transmitting burst=%u.\n", br->bid); + + return 0; +} diff --git a/src/osmo-bts-trx/sched_lchan_xcch.c b/src/osmo-bts-trx/sched_lchan_xcch.c new file mode 100644 index 00000000..0580d33b --- /dev/null +++ b/src/osmo-bts-trx/sched_lchan_xcch.c @@ -0,0 +1,205 @@ +/* + * (C) 2013 by Andreas Eversberg <jolly@eversberg.eu> + * (C) 2015-2017 by Harald Welte <laforge@gnumonks.org> + * Contributions 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 <stdint.h> +#include <errno.h> + +#include <osmocom/core/bits.h> +#include <osmocom/core/utils.h> +#include <osmocom/coding/gsm0503_coding.h> + +#include <osmo-bts/bts.h> +#include <osmo-bts/l1sap.h> +#include <osmo-bts/logging.h> +#include <osmo-bts/scheduler.h> +#include <osmo-bts/scheduler_backend.h> + +#include <sched_utils.h> + +/* Add two arrays of sbits */ +static void add_sbits(sbit_t *current, const sbit_t *previous) +{ + for (unsigned int i = 0; i < BPLEN * 4; i++) + current[i] = current[i] / 2 + previous[i] / 2; +} + +/*! \brief a single (SDCCH/SACCH) burst was received by the PHY, process it */ +int rx_data_fn(struct l1sched_ts *l1ts, const struct trx_ul_burst_ind *bi) +{ + struct l1sched_chan_state *chan_state = &l1ts->chan_state[bi->chan]; + sbit_t *burst, *bursts_p = chan_state->ul_bursts; + uint32_t *first_fn = &chan_state->ul_first_fn; + uint32_t *mask = &chan_state->ul_mask; + uint8_t l2[GSM_MACBLOCK_LEN], l2_len; + struct l1sched_meas_set meas_avg; + int n_errors = 0; + int n_bits_total = 0; + uint16_t ber10k; + int rc; + struct gsm_lchan *lchan = chan_state->lchan; + bool rep_sacch = L1SAP_IS_LINK_SACCH(trx_chan_desc[bi->chan].link_id) && lchan->rep_acch.ul_sacch_active; + + /* If handover RACH detection is turned on, treat this burst as an Access Burst. + * Handle NOPE.ind as usually to ensure proper Uplink measurement reporting. */ + if (chan_state->ho_rach_detect == 1 && ~bi->flags & TRX_BI_F_NOPE_IND) + return rx_rach_fn(l1ts, bi); + + LOGL1SB(DL1P, LOGL_DEBUG, l1ts, bi, "Received Data, bid=%u\n", bi->bid); + + /* clear burst & store frame number of first burst */ + if (bi->bid == 0) { + if (rep_sacch) /* Keep a copy to ease decoding in the next repetition pass */ + memcpy(BUFPOS(bursts_p, 4), BUFPOS(bursts_p, 0), BPLEN * 4); + memset(BUFPOS(bursts_p, 0), 0, BPLEN * 4); + *mask = 0x0; + *first_fn = bi->fn; + } + + /* update mask */ + *mask |= (1 << bi->bid); + + /* store measurements */ + trx_sched_meas_push(chan_state, bi); + + /* Copy burst to buffer of 4 bursts. If the burst indication contains + * no data, ensure that the buffer does not stay uninitialized */ + burst = bursts_p + bi->bid * 116; + if (bi->burst_len > 0) { + memcpy(burst, bi->burst + 3, 58); + memcpy(burst + 58, bi->burst + 87, 58); + } + + /* wait until complete set of bursts */ + if (bi->bid != 3) + return 0; + + /* average measurements of the last 4 bursts */ + trx_sched_meas_avg(chan_state, &meas_avg, SCHED_MEAS_AVG_M_S4N4); + + /* check for complete set of bursts */ + if ((*mask & 0xf) != 0xf) { + LOGL1SB(DL1P, LOGL_NOTICE, l1ts, bi, "Received incomplete data (%u/%u)\n", + bi->fn % l1ts->mf_period, l1ts->mf_period); + + /* we require first burst to have correct FN */ + if (!(*mask & 0x1)) { + *mask = 0x0; + return 0; + } + } + *mask = 0x0; + + /* decode */ + rc = gsm0503_xcch_decode(l2, BUFPOS(bursts_p, 0), &n_errors, &n_bits_total); + if (rc) { + LOGL1SB(DL1P, LOGL_NOTICE, l1ts, bi, + BAD_DATA_MSG_FMT "\n", BAD_DATA_MSG_ARGS); + l2_len = 0; + + /* When SACCH Repetition is active, we may try to decode the + * current SACCH block by including the information from the + * information from the previous SACCH block. See also: + * 3GPP TS 44.006, section 11.2 */ + if (rep_sacch) { + add_sbits(BUFPOS(bursts_p, 0), BUFPOS(bursts_p, 4)); + rc = gsm0503_xcch_decode(l2, BUFPOS(bursts_p, 0), &n_errors, &n_bits_total); + if (rc) { + LOGL1SB(DL1P, LOGL_NOTICE, l1ts, bi, + "Combining current SACCH block with previous SACCH block also yields bad data (%u/%u)\n", + bi->fn % l1ts->mf_period, l1ts->mf_period); + } else { + LOGL1SB(DL1P, LOGL_DEBUG, l1ts, bi, + "Combining current SACCH block with previous SACCH block yields good data (%u/%u)\n", + bi->fn % l1ts->mf_period, l1ts->mf_period); + l2_len = GSM_MACBLOCK_LEN; + } + } + } else + l2_len = GSM_MACBLOCK_LEN; + + ber10k = compute_ber10k(n_bits_total, n_errors); + + return _sched_compose_ph_data_ind(l1ts, *first_fn, bi->chan, + &l2[0], l2_len, + ber10k, + meas_avg.rssi, + meas_avg.toa256, + meas_avg.ci_cb, + PRES_INFO_UNKNOWN); +} + +/* obtain a to-be-transmitted xCCH (e.g SACCH or SDCCH) burst */ +int tx_data_fn(struct l1sched_ts *l1ts, struct trx_dl_burst_req *br) +{ + struct msgb *msg = NULL; /* make GCC happy */ + struct l1sched_chan_state *chan_state = &l1ts->chan_state[br->chan]; + ubit_t *burst, *bursts_p = chan_state->dl_bursts; + uint8_t *mask = &chan_state->dl_mask; + + /* send burst, if we already got a frame */ + if (br->bid > 0) { + if ((*mask & 0x01) != 0x01) + return -ENOMSG; + goto send_burst; + } + + *mask = *mask << 4; + + /* get mac block from queue */ + msg = _sched_dequeue_prim(l1ts, br); + if (msg == NULL) { + LOGL1SB(DL1P, LOGL_INFO, l1ts, br, "No prim for transmit.\n"); + return -ENODEV; + } + + /* check validity of message */ + if (msgb_l2len(msg) != GSM_MACBLOCK_LEN) { + LOGL1SB(DL1P, LOGL_FATAL, l1ts, br, "Prim has odd len=%u != %u\n", + msgb_l2len(msg), GSM_MACBLOCK_LEN); + /* free message */ + msgb_free(msg); + return -EINVAL; + } + + /* BURST BYPASS */ + + /* encode bursts */ + gsm0503_xcch_encode(bursts_p, msg->l2h); + + /* free message */ + msgb_free(msg); + +send_burst: + /* compose burst */ + burst = bursts_p + br->bid * 116; + memcpy(br->burst + 3, burst, 58); + memcpy(br->burst + 61, TRX_GMSK_NB_TSC(br), 26); + memcpy(br->burst + 87, burst + 58, 58); + + br->burst_len = GSM_BURST_LEN; + + *mask |= (1 << br->bid); + + LOGL1SB(DL1P, LOGL_DEBUG, l1ts, br, "Transmitting burst=%u.\n", br->bid); + + return 0; +} diff --git a/src/osmo-bts-trx/sched_utils.h b/src/osmo-bts-trx/sched_utils.h new file mode 100644 index 00000000..a88e06ee --- /dev/null +++ b/src/osmo-bts-trx/sched_utils.h @@ -0,0 +1,51 @@ +/* Auxiliary scheduler utilities. + * + * (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/>. + * + */ + +#pragma once + +#include <stdint.h> + +/* Burst Payload LENgth (short alias) */ +#define BPLEN GSM_NBITS_NB_GMSK_PAYLOAD + +/* Burst BUFfer capacity (in BPLEN units). Why 24? Because CSD frames + * are interleaved over 22 bursts, but on TCH/F we decode every 4th burst, + * so it must be a multiple of 4. See also 'doc/trx_sched_tch.txt'. */ +#define BUFMAX 24 + +/* Burst BUFfer position macros */ +#define BUFPOS(buf, n) &buf[(n) * BPLEN] +#define BUFTAIL8(buf) BUFPOS(buf, (BUFMAX - 8)) + +extern void *tall_bts_ctx; + +#define BAD_DATA_MSG_FMT "Received bad data (rc=%d, BER %d/%d) ending at fn=%u/%u" +#define BAD_DATA_MSG_ARGS \ + rc, n_errors, n_bits_total, bi->fn % l1ts->mf_period, l1ts->mf_period + +/* Compute the bit error rate in 1/10000 units */ +static inline uint16_t compute_ber10k(int n_bits_total, int n_errors) +{ + if (n_bits_total == 0) + return 10000; + else + return 10000 * n_errors / n_bits_total; +} diff --git a/src/osmo-bts-trx/scheduler_trx.c b/src/osmo-bts-trx/scheduler_trx.c index 45fc7012..00143abe 100644 --- a/src/osmo-bts-trx/scheduler_trx.c +++ b/src/osmo-bts-trx/scheduler_trx.c @@ -3,6 +3,7 @@ /* (C) 2013 by Andreas Eversberg <jolly@eversberg.eu> * (C) 2015 by Alexander Chemeris <Alexander.Chemeris@fairwaves.co> * (C) 2015-2017 by Harald Welte <laforge@gnumonks.org> + * (C) 2020-2021 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> * * All Rights Reserved * @@ -14,7 +15,7 @@ * 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. + * 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/>. @@ -32,1598 +33,345 @@ #include <osmocom/core/msgb.h> #include <osmocom/core/talloc.h> #include <osmocom/core/timer_compat.h> -#include <osmocom/codec/codec.h> -#include <osmocom/codec/ecu.h> #include <osmocom/core/bits.h> #include <osmocom/gsm/a5.h> -#include <osmocom/coding/gsm0503_coding.h> +#include <osmocom/gsm/gsm0502.h> + #include <osmo-bts/gsm_data.h> #include <osmo-bts/logging.h> #include <osmo-bts/rsl.h> #include <osmo-bts/bts.h> #include <osmo-bts/l1sap.h> -#include <osmo-bts/msg_utils.h> #include <osmo-bts/scheduler.h> #include <osmo-bts/scheduler_backend.h> +#include <osmo-bts/pcu_if.h> #include "l1_if.h" #include "trx_if.h" -#include "loops.h" - -extern void *tall_bts_ctx; - -/* Maximum size of a EGPRS message in bytes */ -#define EGPRS_0503_MAX_BYTES 155 - - -/* Compute the bit error rate in 1/10000 units */ -static inline uint16_t compute_ber10k(int n_bits_total, int n_errors) -{ - if (n_bits_total == 0) - return 10000; - else - return 10000 * n_errors / n_bits_total; -} - -/* - * TX on downlink - */ -/* an IDLE burst returns nothing. on C0 it is replaced by dummy burst */ -ubit_t *tx_idle_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, - enum trx_chan_type chan, uint8_t bid, uint16_t *nbits) -{ - LOGL1S(DL1P, LOGL_DEBUG, l1t, tn, chan, fn, "Transmitting IDLE\n"); - - if (nbits) - *nbits = GSM_BURST_LEN; - - return NULL; -} +#include "btsconfig.h" -/* obtain a to-be-transmitted FCCH (frequency correction channel) burst */ -ubit_t *tx_fcch_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, - enum trx_chan_type chan, uint8_t bid, uint16_t *nbits) -{ - LOGL1S(DL1P, LOGL_DEBUG, l1t, tn, chan, fn, "Transmitting FCCH\n"); - - if (nbits) - *nbits = GSM_BURST_LEN; +#ifdef HAVE_SYSTEMTAP +/* include the generated probes header and put markers in code */ +#include "probes.h" +#define TRACE(probe) probe +#define TRACE_ENABLED(probe) probe ## _ENABLED() +#else +/* Wrap the probe to allow it to be removed when no systemtap available */ +#define TRACE(probe) +#define TRACE_ENABLED(probe) (0) +#endif /* HAVE_SYSTEMTAP */ - /* BURST BYPASS */ +#define SCHED_FH_PARAMS_FMT "hsn=%u, maio=%u, ma_len=%u" +#define SCHED_FH_PARAMS_VALS(ts) \ + (ts)->hopping.hsn, (ts)->hopping.maio, (ts)->hopping.arfcn_num - return (ubit_t *) _sched_fcch_burst; -} - -/* obtain a to-be-transmitted SCH (synchronization channel) burst */ -ubit_t *tx_sch_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, - enum trx_chan_type chan, uint8_t bid, uint16_t *nbits) +static void lchan_report_interf_meas(const struct gsm_lchan *lchan) { - static ubit_t bits[GSM_BURST_LEN], burst[78]; - uint8_t sb_info[4]; - struct gsm_time t; - uint8_t t3p, bsic; - - LOGL1S(DL1P, LOGL_DEBUG, l1t, tn, chan, fn, "Transmitting SCH\n"); - - /* BURST BYPASS */ - - /* create SB info from GSM time and BSIC */ - gsm_fn2gsmtime(&t, fn); - t3p = t.t3 / 10; - bsic = l1t->trx->bts->bsic; - sb_info[0] = - ((bsic & 0x3f) << 2) | - ((t.t1 & 0x600) >> 9); - sb_info[1] = - ((t.t1 & 0x1fe) >> 1); - sb_info[2] = - ((t.t1 & 0x001) << 7) | - ((t.t2 & 0x1f) << 2) | - ((t3p & 0x6) >> 1); - sb_info[3] = - (t3p & 0x1); - - /* encode bursts */ - gsm0503_sch_encode(burst, sb_info); - - /* compose burst */ - memset(bits, 0, 3); - memcpy(bits + 3, burst, 39); - memcpy(bits + 42, _sched_sch_train, 64); - memcpy(bits + 106, burst + 39, 39); - memset(bits + 145, 0, 3); - - if (nbits) - *nbits = GSM_BURST_LEN; - - return bits; -} - -/* obtain a to-be-transmitted data (SACCH/SDCCH) burst */ -ubit_t *tx_data_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, - enum trx_chan_type chan, uint8_t bid, uint16_t *nbits) -{ - struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn); - struct gsm_bts_trx_ts *ts = &l1t->trx->ts[tn]; - uint8_t link_id = trx_chan_desc[chan].link_id; - uint8_t chan_nr = trx_chan_desc[chan].chan_nr | tn; - struct msgb *msg = NULL; /* make GCC happy */ - ubit_t *burst, **bursts_p = &l1ts->chan_state[chan].dl_bursts; - static ubit_t bits[GSM_BURST_LEN]; - - /* send burst, if we already got a frame */ - if (bid > 0) { - if (!*bursts_p) - return NULL; - goto send_burst; - } - - /* send clock information to loops process */ - if (L1SAP_IS_LINK_SACCH(link_id)) - trx_loop_sacch_clock(l1t, chan_nr, &l1ts->chan_state[chan]); - - /* get mac block from queue */ - msg = _sched_dequeue_prim(l1t, tn, fn, chan); - if (msg) - goto got_msg; - - LOGL1S(DL1P, LOGL_INFO, l1t, tn, chan, fn, "No prim for transmit.\n"); - -no_msg: - /* free burst memory */ - if (*bursts_p) { - talloc_free(*bursts_p); - *bursts_p = NULL; - } - return NULL; - -got_msg: - /* check validity of message */ - if (msgb_l2len(msg) != GSM_MACBLOCK_LEN) { - LOGL1S(DL1P, LOGL_FATAL, l1t, tn, chan, fn, "Prim not 23 bytes, please FIX! " - "(len=%d)\n", msgb_l2len(msg)); - /* free message */ - msgb_free(msg); - goto no_msg; - } - - /* BURST BYPASS */ - - /* handle loss detection of SACCH */ - if (L1SAP_IS_LINK_SACCH(trx_chan_desc[chan].link_id)) { - /* count and send BFI */ - if (++(l1ts->chan_state[chan].lost_frames) > 1) { - /* TODO: Should we pass old TOA here? Otherwise we risk - * unnecessary decreasing TA */ - - /* Send uplink measurement information to L2 */ - l1if_process_meas_res(l1t->trx, tn, fn, trx_chan_desc[chan].chan_nr | tn, - 456, 456, -110, 0); - /* FIXME: use actual values for BER etc */ - _sched_compose_ph_data_ind(l1t, tn, 0, chan, NULL, 0, - -110, 0, 0, 10000, - PRES_INFO_INVALID); + const struct gsm_bts_trx_ts *ts = lchan->ts; + const struct l1sched_ts *l1ts = ts->priv; + enum trx_chan_type dcch, acch; + int interf_avg; + + /* We're not interested in active CS channels */ + if (lchan->state == LCHAN_S_ACTIVE) { + if (lchan->type != GSM_LCHAN_PDTCH) + return; + } + + switch (lchan->type) { + case GSM_LCHAN_SDCCH: + if (ts->pchan == GSM_PCHAN_CCCH_SDCCH4 || + ts->pchan == GSM_PCHAN_CCCH_SDCCH4_CBCH) { + dcch = TRXC_SDCCH4_0 + lchan->nr; + acch = TRXC_SACCH4_0 + lchan->nr; + } else { /* SDCCH/8 otherwise */ + dcch = TRXC_SDCCH8_0 + lchan->nr; + acch = TRXC_SACCH8_0 + lchan->nr; } + break; + case GSM_LCHAN_TCH_F: + dcch = TRXC_TCHF; + acch = TRXC_SACCHTF; + break; + case GSM_LCHAN_TCH_H: + dcch = TRXC_TCHH_0 + lchan->nr; + acch = TRXC_SACCHTH_0 + lchan->nr; + break; + case GSM_LCHAN_PDTCH: + /* We use idle TDMA frames on PDCH */ + dcch = TRXC_IDLE; + acch = TRXC_IDLE; + break; + default: + /* Skip other lchan types */ + return; } - /* allocate burst memory, if not already */ - if (!*bursts_p) { - *bursts_p = talloc_zero_size(tall_bts_ctx, 464); - if (!*bursts_p) - return NULL; - } - - /* encode bursts */ - gsm0503_xcch_encode(*bursts_p, msg->l2h); - - /* free message */ - msgb_free(msg); - -send_burst: - /* compose burst */ - burst = *bursts_p + bid * 116; - memset(bits, 0, 3); - memcpy(bits + 3, burst, 58); - memcpy(bits + 61, _sched_tsc[gsm_ts_tsc(ts)], 26); - memcpy(bits + 87, burst + 58, 58); - memset(bits + 145, 0, 3); - - if (nbits) - *nbits = GSM_BURST_LEN; - - LOGL1S(DL1P, LOGL_DEBUG, l1t, tn, chan, fn, "Transmitting burst=%u.\n", bid); - - return bits; -} - -/* obtain a to-be-transmitted PDTCH (packet data) burst */ -ubit_t *tx_pdtch_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, - enum trx_chan_type chan, uint8_t bid, uint16_t *nbits) -{ - struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn); - struct gsm_bts_trx_ts *ts = &l1t->trx->ts[tn]; - struct msgb *msg = NULL; /* make GCC happy */ - ubit_t *burst, **bursts_p = &l1ts->chan_state[chan].dl_bursts; - enum trx_burst_type *burst_type = &l1ts->chan_state[chan].dl_burst_type; - static ubit_t bits[EGPRS_BURST_LEN]; - int rc = 0; - - /* send burst, if we already got a frame */ - if (bid > 0) { - if (!*bursts_p) - return NULL; - goto send_burst; - } - - /* get mac block from queue */ - msg = _sched_dequeue_prim(l1t, tn, fn, chan); - if (msg) - goto got_msg; - - LOGL1S(DL1P, LOGL_INFO, l1t, tn, chan, fn, "No prim for transmit.\n"); - -no_msg: - /* free burst memory */ - if (*bursts_p) { - talloc_free(*bursts_p); - *bursts_p = NULL; - } - return NULL; - -got_msg: - /* BURST BYPASS */ - - /* allocate burst memory, if not already */ - if (!*bursts_p) { - *bursts_p = talloc_zero_size(tall_bts_ctx, - GSM0503_EGPRS_BURSTS_NBITS); - if (!*bursts_p) - return NULL; - } - - /* encode bursts */ - rc = gsm0503_pdtch_egprs_encode(*bursts_p, msg->l2h, msg->tail - msg->l2h); - if (rc < 0) - rc = gsm0503_pdtch_encode(*bursts_p, msg->l2h, msg->tail - msg->l2h); - - /* check validity of message */ - if (rc < 0) { - LOGL1S(DL1P, LOGL_FATAL, l1t, tn, chan, fn, "Prim invalid length, please FIX! " - "(len=%ld)\n", msg->tail - msg->l2h); - /* free message */ - msgb_free(msg); - goto no_msg; - } else if (rc == GSM0503_EGPRS_BURSTS_NBITS) { - *burst_type = TRX_BURST_8PSK; - } else { - *burst_type = TRX_BURST_GMSK; - } - - /* free message */ - msgb_free(msg); - -send_burst: - /* compose burst */ - if (*burst_type == TRX_BURST_8PSK) { - burst = *bursts_p + bid * 348; - memset(bits, 1, 9); - memcpy(bits + 9, burst, 174); - memcpy(bits + 183, _sched_egprs_tsc[gsm_ts_tsc(ts)], 78); - memcpy(bits + 261, burst + 174, 174); - memset(bits + 435, 1, 9); - - if (nbits) - *nbits = EGPRS_BURST_LEN; - } else { - burst = *bursts_p + bid * 116; - memset(bits, 0, 3); - memcpy(bits + 3, burst, 58); - memcpy(bits + 61, _sched_tsc[gsm_ts_tsc(ts)], 26); - memcpy(bits + 87, burst + 58, 58); - memset(bits + 145, 0, 3); - - if (nbits) - *nbits = GSM_BURST_LEN; - } + OSMO_ASSERT(dcch < ARRAY_SIZE(l1ts->chan_state)); + OSMO_ASSERT(acch < ARRAY_SIZE(l1ts->chan_state)); - LOGL1S(DL1P, LOGL_DEBUG, l1t, tn, chan, fn, "Transmitting burst=%u.\n", bid); + interf_avg = (l1ts->chan_state[dcch].meas.interf_avg + + l1ts->chan_state[acch].meas.interf_avg) / 2; - return bits; + gsm_lchan_interf_meas_push((struct gsm_lchan *) lchan, interf_avg); } -/* determine if the FN is transmitting a CMR (1) or not (0) */ -static inline int fn_is_codec_mode_request(uint32_t fn) +static void bts_report_interf_meas(const struct gsm_bts *bts) { - return (((fn + 4) % 26) >> 2) & 1; -} + const struct gsm_bts_trx *trx; + unsigned int tn, ln; -/* common section for generation of TCH bursts (TCH/H and TCH/F) */ -static void tx_tch_common(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, - enum trx_chan_type chan, uint8_t bid, struct msgb **_msg_tch, - struct msgb **_msg_facch) -{ - struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn); - struct msgb *msg1, *msg2, *msg_tch = NULL, *msg_facch = NULL; - struct l1sched_chan_state *chan_state = &l1ts->chan_state[chan]; - uint8_t rsl_cmode = chan_state->rsl_cmode; - uint8_t tch_mode = chan_state->tch_mode; - struct osmo_phsap_prim *l1sap; - - /* handle loss detection of received TCH frames */ - if (rsl_cmode == RSL_CMOD_SPD_SPEECH - && ++(chan_state->lost_frames) > 5) { - uint8_t tch_data[GSM_FR_BYTES]; - int len; - - LOGL1S(DL1P, LOGL_NOTICE, l1t, tn, chan, fn, - "Missing TCH bursts detected, sending BFI\n"); - - /* indicate bad frame */ - switch (tch_mode) { - case GSM48_CMODE_SPEECH_V1: /* FR / HR */ - if (chan != TRXC_TCHF) { /* HR */ - tch_data[0] = 0x70; /* F = 0, FT = 111 */ - memset(tch_data + 1, 0, 14); - len = 15; - break; - } - memset(tch_data, 0, GSM_FR_BYTES); - len = GSM_FR_BYTES; - break; - case GSM48_CMODE_SPEECH_EFR: /* EFR */ - if (chan != TRXC_TCHF) - goto inval_mode1; - memset(tch_data, 0, GSM_EFR_BYTES); - len = GSM_EFR_BYTES; - break; - case GSM48_CMODE_SPEECH_AMR: /* AMR */ - len = osmo_amr_rtp_enc(tch_data, - chan_state->codec[chan_state->dl_cmr], - chan_state->codec[chan_state->dl_ft], AMR_BAD); - if (len < 2) { - LOGL1S(DL1P, LOGL_ERROR, l1t, tn, chan, fn, - "Failed to encode AMR_BAD frame (rc=%d), " - "not sending BFI\n", len); - return; - } - memset(tch_data + 2, 0, len - 2); - break; - default: -inval_mode1: - LOGL1S(DL1P, LOGL_ERROR, l1t, tn, chan, fn, "TCH mode invalid, please fix!\n"); - len = 0; - } - if (len) - _sched_compose_tch_ind(l1t, tn, fn, chan, tch_data, len); - } + llist_for_each_entry(trx, &bts->trx_list, list) { + /* Skip pushing interf_meas for disabled TRX */ + if (trx->mo.nm_state.operational != NM_OPSTATE_ENABLED || + trx->bb_transc.mo.nm_state.operational != NM_OPSTATE_ENABLED) + continue; - /* get frame and unlink from queue */ - msg1 = _sched_dequeue_prim(l1t, tn, fn, chan); - msg2 = _sched_dequeue_prim(l1t, tn, fn, chan); - if (msg1) { - l1sap = msgb_l1sap_prim(msg1); - if (l1sap->oph.primitive == PRIM_TCH) { - msg_tch = msg1; - if (msg2) { - l1sap = msgb_l1sap_prim(msg2); - if (l1sap->oph.primitive == PRIM_TCH) { - LOGL1S(DL1P, LOGL_FATAL, l1t, tn, chan, fn, - "TCH twice, please FIX!\n"); - msgb_free(msg2); - } else - msg_facch = msg2; - } - } else { - msg_facch = msg1; - if (msg2) { - l1sap = msgb_l1sap_prim(msg2); - if (l1sap->oph.primitive != PRIM_TCH) { - LOGL1S(DL1P, LOGL_FATAL, l1t, tn, chan, fn, - "FACCH twice, please FIX!\n"); - msgb_free(msg2); - } else - msg_tch = msg2; - } + for (tn = 0; tn < ARRAY_SIZE(trx->ts); tn++) { + const struct gsm_bts_trx_ts *ts = &trx->ts[tn]; + for (ln = 0; ln < ARRAY_SIZE(ts->lchan); ln++) + lchan_report_interf_meas(&ts->lchan[ln]); } - } else if (msg2) { - l1sap = msgb_l1sap_prim(msg2); - if (l1sap->oph.primitive == PRIM_TCH) - msg_tch = msg2; - else - msg_facch = msg2; } - - /* check validity of message */ - if (msg_facch && msgb_l2len(msg_facch) != GSM_MACBLOCK_LEN) { - LOGL1S(DL1P, LOGL_FATAL, l1t, tn, chan, fn, "Prim not 23 bytes, please FIX! " - "(len=%d)\n", msgb_l2len(msg_facch)); - /* free message */ - msgb_free(msg_facch); - msg_facch = NULL; - } - - /* check validity of message, get AMR ft and cmr */ - if (!msg_facch && msg_tch) { - int len; - uint8_t cmr_codec; - int cmr, ft, i; - enum osmo_amr_type ft_codec; - enum osmo_amr_quality bfi; - int8_t sti, cmi; - - if (rsl_cmode != RSL_CMOD_SPD_SPEECH) { - LOGL1S(DL1P, LOGL_NOTICE, l1t, tn, chan, fn, "Dropping speech frame, " - "because we are not in speech mode\n"); - goto free_bad_msg; - } - - switch (tch_mode) { - case GSM48_CMODE_SPEECH_V1: /* FR / HR */ - if (chan != TRXC_TCHF) /* HR */ - len = 15; - else - len = GSM_FR_BYTES; - break; - case GSM48_CMODE_SPEECH_EFR: /* EFR */ - if (chan != TRXC_TCHF) - goto inval_mode2; - len = GSM_EFR_BYTES; - break; - case GSM48_CMODE_SPEECH_AMR: /* AMR */ - len = osmo_amr_rtp_dec(msg_tch->l2h, msgb_l2len(msg_tch), - &cmr_codec, &cmi, &ft_codec, - &bfi, &sti); - cmr = -1; - ft = -1; - for (i = 0; i < chan_state->codecs; i++) { - if (chan_state->codec[i] == cmr_codec) - cmr = i; - if (chan_state->codec[i] == ft_codec) - ft = i; - } - if (cmr >= 0) { /* new request */ - chan_state->dl_cmr = cmr; - /* disable AMR loop */ - trx_loop_amr_set(chan_state, 0); - } else { - /* enable AMR loop */ - trx_loop_amr_set(chan_state, 1); - } - if (ft < 0) { - LOGL1S(DL1P, LOGL_ERROR, l1t, tn, chan, fn, - "Codec (FT = %d) of RTP frame not in list\n", ft_codec); - goto free_bad_msg; - } - if (fn_is_codec_mode_request(fn) && chan_state->dl_ft != ft) { - LOGL1S(DL1P, LOGL_NOTICE, l1t, tn, chan, fn, "Codec (FT = %d) " - " of RTP cannot be changed now, but in next frame\n", ft_codec); - goto free_bad_msg; - } - chan_state->dl_ft = ft; - if (bfi == AMR_BAD) { - LOGL1S(DL1P, LOGL_NOTICE, l1t, tn, chan, fn, - "Transmitting 'bad AMR frame'\n"); - goto free_bad_msg; - } - break; - default: -inval_mode2: - LOGL1S(DL1P, LOGL_ERROR, l1t, tn, chan, fn, "TCH mode invalid, please fix!\n"); - goto free_bad_msg; - } - if (len < 0) { - LOGL1S(DL1P, LOGL_ERROR, l1t, tn, chan, fn, "Cannot send invalid AMR payload\n"); - goto free_bad_msg; - } - if (msgb_l2len(msg_tch) != len) { - LOGL1S(DL1P, LOGL_ERROR, l1t, tn, chan, fn, "Cannot send payload with " - "invalid length! (expecting %d, received %d)\n", - len, msgb_l2len(msg_tch)); -free_bad_msg: - /* free message */ - msgb_free(msg_tch); - msg_tch = NULL; - goto send_frame; - } - } - -send_frame: - *_msg_tch = msg_tch; - *_msg_facch = msg_facch; } -/* obtain a to-be-transmitted TCH/F (Full Traffic Channel) burst */ -ubit_t *tx_tchf_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, - enum trx_chan_type chan, uint8_t bid, uint16_t *nbits) +/* Find a route (PHY instance) for a given Downlink burst request */ +static struct phy_instance *dlfh_route_br(const struct trx_dl_burst_req *br, + struct gsm_bts_trx_ts *ts) { - struct msgb *msg_tch = NULL, *msg_facch = NULL; - struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn); - struct gsm_bts_trx_ts *ts = &l1t->trx->ts[tn]; - struct l1sched_chan_state *chan_state = &l1ts->chan_state[chan]; - uint8_t tch_mode = chan_state->tch_mode; - ubit_t *burst, **bursts_p = &chan_state->dl_bursts; - static ubit_t bits[GSM_BURST_LEN]; - - /* send burst, if we already got a frame */ - if (bid > 0) { - if (!*bursts_p) - return NULL; - goto send_burst; - } - - tx_tch_common(l1t, tn, fn, chan, bid, &msg_tch, &msg_facch); - - /* BURST BYPASS */ - - /* allocate burst memory, if not already, - * otherwise shift buffer by 4 bursts for interleaving */ - if (!*bursts_p) { - *bursts_p = talloc_zero_size(tall_bts_ctx, 928); - if (!*bursts_p) - return NULL; - } else { - memcpy(*bursts_p, *bursts_p + 464, 464); - memset(*bursts_p + 464, 0, 464); - } - - /* no message at all */ - if (!msg_tch && !msg_facch) { - LOGL1S(DL1P, LOGL_INFO, l1t, tn, chan, fn, "No TCH or FACCH prim for transmit.\n"); - goto send_burst; - } - - /* encode bursts (prioritize FACCH) */ - if (msg_facch) - gsm0503_tch_fr_encode(*bursts_p, msg_facch->l2h, msgb_l2len(msg_facch), - 1); - else if (tch_mode == GSM48_CMODE_SPEECH_AMR) - /* the first FN 4,13,21 defines that CMI is included in frame, - * the first FN 0,8,17 defines that CMR is included in frame. - */ - gsm0503_tch_afs_encode(*bursts_p, msg_tch->l2h + 2, - msgb_l2len(msg_tch) - 2, fn_is_codec_mode_request(fn), - chan_state->codec, chan_state->codecs, - chan_state->dl_ft, - chan_state->dl_cmr); - else - gsm0503_tch_fr_encode(*bursts_p, msg_tch->l2h, msgb_l2len(msg_tch), 1); - - /* free message */ - if (msg_tch) - msgb_free(msg_tch); - if (msg_facch) - msgb_free(msg_facch); - -send_burst: - /* compose burst */ - burst = *bursts_p + bid * 116; - memset(bits, 0, 3); - memcpy(bits + 3, burst, 58); - memcpy(bits + 61, _sched_tsc[gsm_ts_tsc(ts)], 26); - memcpy(bits + 87, burst + 58, 58); - memset(bits + 145, 0, 3); + const struct gsm_bts_trx *trx; + struct gsm_time time; + uint16_t idx; - if (nbits) - *nbits = GSM_BURST_LEN; + gsm_fn2gsmtime(&time, br->fn); - LOGL1S(DL1P, LOGL_DEBUG, l1t, tn, chan, fn, "Transmitting burst=%u.\n", bid); + /* Check the "cache" first, so we eliminate frequent lookups */ + idx = gsm0502_hop_seq_gen(&time, SCHED_FH_PARAMS_VALS(ts), NULL); + if (ts->fh_trx_list[idx] != NULL) + return ts->fh_trx_list[idx]->pinst; - return bits; -} - -/* obtain a to-be-transmitted TCH/H (Half Traffic Channel) burst */ -ubit_t *tx_tchh_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, - enum trx_chan_type chan, uint8_t bid, uint16_t *nbits) -{ - struct msgb *msg_tch = NULL, *msg_facch = NULL; - struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn); - struct gsm_bts_trx_ts *ts = &l1t->trx->ts[tn]; - struct l1sched_chan_state *chan_state = &l1ts->chan_state[chan]; - uint8_t tch_mode = chan_state->tch_mode; - ubit_t *burst, **bursts_p = &chan_state->dl_bursts; - static ubit_t bits[GSM_BURST_LEN]; - - /* send burst, if we already got a frame */ - if (bid > 0) { - if (!*bursts_p) - return NULL; - goto send_burst; - } - - /* get TCH and/or FACCH */ - tx_tch_common(l1t, tn, fn, chan, bid, &msg_tch, &msg_facch); + struct bts_trx_priv *priv = (struct bts_trx_priv *) ts->trx->bts->model_priv; - /* check for FACCH alignment */ - if (msg_facch && ((((fn + 4) % 26) >> 2) & 1)) { - LOGL1S(DL1P, LOGL_ERROR, l1t, tn, chan, fn, "Cannot transmit FACCH starting on " - "even frames, please fix RTS!\n"); - msgb_free(msg_facch); - msg_facch = NULL; - } - - /* BURST BYPASS */ - - /* allocate burst memory, if not already, - * otherwise shift buffer by 2 bursts for interleaving */ - if (!*bursts_p) { - *bursts_p = talloc_zero_size(tall_bts_ctx, 696); - if (!*bursts_p) - return NULL; - } else { - memcpy(*bursts_p, *bursts_p + 232, 232); - if (chan_state->dl_ongoing_facch) { - memcpy(*bursts_p + 232, *bursts_p + 464, 232); - memset(*bursts_p + 464, 0, 232); - } else { - memset(*bursts_p + 232, 0, 232); + /* The "cache" may not be filled yet, lookup the transceiver */ + llist_for_each_entry(trx, &ts->trx->bts->trx_list, list) { + if (trx->arfcn == ts->hopping.arfcn_list[idx]) { + rate_ctr_inc2(priv->ctrs, BTSTRX_CTR_SCHED_DL_FH_CACHE_MISS); + ts->fh_trx_list[idx] = trx; + return trx->pinst; } } - /* no message at all */ - if (!msg_tch && !msg_facch && !chan_state->dl_ongoing_facch) { - LOGL1S(DL1P, LOGL_INFO, l1t, tn, chan, fn, "No TCH or FACCH prim for transmit.\n"); - goto send_burst; - } - - /* encode bursts (prioritize FACCH) */ - if (msg_facch) { - gsm0503_tch_hr_encode(*bursts_p, msg_facch->l2h, msgb_l2len(msg_facch)); - chan_state->dl_ongoing_facch = 1; /* first of two TCH frames */ - } else if (chan_state->dl_ongoing_facch) /* second of two TCH frames */ - chan_state->dl_ongoing_facch = 0; /* we are done with FACCH */ - else if (tch_mode == GSM48_CMODE_SPEECH_AMR) - /* the first FN 4,13,21 or 5,14,22 defines that CMI is included - * in frame, the first FN 0,8,17 or 1,9,18 defines that CMR is - * included in frame. */ - gsm0503_tch_ahs_encode(*bursts_p, msg_tch->l2h + 2, - msgb_l2len(msg_tch) - 2, fn_is_codec_mode_request(fn), - chan_state->codec, chan_state->codecs, - chan_state->dl_ft, - chan_state->dl_cmr); - else - gsm0503_tch_hr_encode(*bursts_p, msg_tch->l2h, msgb_l2len(msg_tch)); + LOGPTRX(ts->trx, DL1C, LOGL_FATAL, "Failed to find the transceiver (RF carrier) " + "for a Downlink burst (fn=%u, tn=%u, " SCHED_FH_PARAMS_FMT ")\n", + br->fn, br->tn, SCHED_FH_PARAMS_VALS(ts)); - /* free message */ - if (msg_tch) - msgb_free(msg_tch); - if (msg_facch) - msgb_free(msg_facch); + rate_ctr_inc2(priv->ctrs, BTSTRX_CTR_SCHED_DL_FH_NO_CARRIER); -send_burst: - /* compose burst */ - burst = *bursts_p + bid * 116; - memset(bits, 0, 3); - memcpy(bits + 3, burst, 58); - memcpy(bits + 61, _sched_tsc[gsm_ts_tsc(ts)], 26); - memcpy(bits + 87, burst + 58, 58); - memset(bits + 145, 0, 3); - - if (nbits) - *nbits = GSM_BURST_LEN; - - LOGL1S(DL1P, LOGL_DEBUG, l1t, tn, chan, fn, "Transmitting burst=%u.\n", bid); - - return bits; + return NULL; } +static void bts_sched_init_buffers(struct gsm_bts *bts, const uint32_t fn) +{ + struct gsm_bts_trx *trx; + uint8_t tn; -/* - * RX on uplink (indication to upper layer) - */ - -/* 3GPP TS 05.02, section 5.2.7 */ -#define RACH_EXT_TAIL_LEN 8 -#define RACH_SYNCH_SEQ_LEN 41 - -enum rach_synch_seq_t { - RACH_SYNCH_SEQ_UNKNOWN = -1, - RACH_SYNCH_SEQ_TS0, /* GSM, GMSK (default) */ - RACH_SYNCH_SEQ_TS1, /* EGPRS, 8-PSK */ - RACH_SYNCH_SEQ_TS2, /* EGPRS, GMSK */ - RACH_SYNCH_SEQ_NUM -}; + llist_for_each_entry(trx, &bts->trx_list, list) { + struct phy_instance *pinst = trx->pinst; + const struct phy_link *plink = pinst->phy_link; -static struct value_string rach_synch_seq_names[] = { - { RACH_SYNCH_SEQ_UNKNOWN, "UNKNOWN" }, - { RACH_SYNCH_SEQ_TS0, "TS0: GSM, GMSK" }, - { RACH_SYNCH_SEQ_TS1, "TS1: EGPRS, 8-PSK" }, - { RACH_SYNCH_SEQ_TS2, "TS2: EGPRS, GMSK" }, - { 0, NULL }, -}; + /* Advance frame number, so the PHY has more time to process bursts */ + const uint32_t sched_fn = GSM_TDMA_FN_SUM(fn, plink->u.osmotrx.clock_advance); -static enum rach_synch_seq_t rach_get_synch_seq(sbit_t *bits, int *best_score) -{ - sbit_t *synch_seq_burst = bits + RACH_EXT_TAIL_LEN; - enum rach_synch_seq_t seq = RACH_SYNCH_SEQ_TS0; - int score[RACH_SYNCH_SEQ_NUM] = { 0 }; - int max_score = INT_MIN; - int i, j; - - /* 3GPP TS 05.02, section 5.2.7 "Access burst (AB)", synch. sequence bits */ - static const char synch_seq_ref[RACH_SYNCH_SEQ_NUM][RACH_SYNCH_SEQ_LEN] = { - [RACH_SYNCH_SEQ_TS0] = "01001011011111111001100110101010001111000", - [RACH_SYNCH_SEQ_TS1] = "01010100111110001000011000101111001001101", - [RACH_SYNCH_SEQ_TS2] = "11101111001001110101011000001101101110111", - }; + for (tn = 0; tn < ARRAY_SIZE(trx->ts); tn++) { + struct trx_dl_burst_req *br = &pinst->u.osmotrx.br[tn]; - /* Get a multiplier for j-th bit of i-th synch. sequence */ -#define RACH_SYNCH_SEQ_MULT \ - (synch_seq_ref[i][j] == '1' ? -1 : 1) - - /* For each synch. sequence, count the bit match score. Since we deal with - * soft-bits (-127...127), we sum the absolute values of matching ones, - * and subtract the absolute values of different ones, so the resulting - * score is more accurate than it could be with hard-bits. */ - for (i = 0; i < RACH_SYNCH_SEQ_NUM; i++) { - for (j = 0; j < RACH_SYNCH_SEQ_LEN; j++) - score[i] += RACH_SYNCH_SEQ_MULT * synch_seq_burst[j]; - - /* Keep the maximum value updated */ - if (score[i] > max_score) { - max_score = score[i]; - seq = i; + *br = (struct trx_dl_burst_req) { + .trx_num = trx->nr, + .fn = sched_fn, + .tn = tn, + }; } } - /* Calculate an approximate level of our confidence */ - if (best_score != NULL) - *best_score = max_score; + /* Initialize all timeslots on C0/TRX0 with dummy burst */ + for (tn = 0; tn < ARRAY_SIZE(trx->ts); tn++) { + struct phy_instance *pinst = bts->c0->pinst; + struct trx_dl_burst_req *br = &pinst->u.osmotrx.br[tn]; + const struct gsm_bts_trx_ts *ts = &bts->c0->ts[tn]; - /* At least 1/3 of a synch. sequence shall match */ - if (max_score < (127 * RACH_SYNCH_SEQ_LEN / 3)) - return RACH_SYNCH_SEQ_UNKNOWN; + memcpy(br->burst, _sched_dummy_burst, GSM_BURST_LEN); + br->burst_len = GSM_BURST_LEN; - return seq; + /* BCCH carrier power reduction for this timeslot */ + br->att = ts->c0_power_red_db; + } } -int rx_rach_fn(struct l1sched_trx *l1t, enum trx_chan_type chan, - uint8_t bid, const struct trx_ul_burst_ind *bi) +static void bts_sched_flush_buffers(struct gsm_bts *bts) { - struct osmo_phsap_prim l1sap; - int n_errors, n_bits_total; - uint16_t ra11; - uint8_t ra; - int rc; - - /* TSC (Training Sequence Code) is an optional parameter of the UL burst - * indication. We need this information in order to decide whether an - * Access Burst is 11-bit encoded or not (see OS#1854). If this information - * is absent, we try to correlate the received synch. sequence with the - * known ones (3GPP TS 05.02, section 5.2.7), and fall-back to the default - * TS0 if it fails. */ - enum rach_synch_seq_t synch_seq = RACH_SYNCH_SEQ_TS0; - int best_score = 127 * RACH_SYNCH_SEQ_LEN; - - /* If chan != TRXC_RACH, this is a handover RACH, which is always encoded - * as 8-bit and should contain the generic training sequence (TS0). */ - if (chan == TRXC_RACH) { - if (bi->flags & TRX_BI_F_TS_INFO) - synch_seq = (enum rach_synch_seq_t) bi->tsc; - else - synch_seq = rach_get_synch_seq((sbit_t *) bi->burst, &best_score); - } + const struct gsm_bts_trx *trx; + unsigned int tn; - LOGL1S(DL1P, LOGL_DEBUG, l1t, bi->tn, chan, bi->fn, - "Received%s RACH (%s): rssi=%d toa256=%d", - (chan != TRXC_RACH) ? " handover" : "", - get_value_string(rach_synch_seq_names, synch_seq), - bi->rssi, bi->toa256); - if (bi->flags & TRX_BI_F_CI_CB) - LOGPC(DL1P, LOGL_DEBUG, " C/I=%d cB", bi->ci_cb); - else - LOGPC(DL1P, LOGL_DEBUG, " match=%.1f%%", - best_score * 100.0 / (127 * RACH_SYNCH_SEQ_LEN)); - LOGPC(DL1P, LOGL_DEBUG, "\n"); - - /* Compose a new L1SAP primitive */ - memset(&l1sap, 0x00, sizeof(l1sap)); - osmo_prim_init(&l1sap.oph, SAP_GSM_PH, PRIM_PH_RACH, PRIM_OP_INDICATION, NULL); - l1sap.u.rach_ind.chan_nr = trx_chan_desc[chan].chan_nr | bi->tn; - l1sap.u.rach_ind.acc_delay = (bi->toa256 >= 0) ? bi->toa256 / 256 : 0; - l1sap.u.rach_ind.acc_delay_256bits = bi->toa256; - l1sap.u.rach_ind.rssi = bi->rssi; - l1sap.u.rach_ind.fn = bi->fn; - - /* Link quality is defined by C/I (Carrier-to-Interference ratio), - * which has optional presence. If it's absent, report the - * minimum acceptable value to pass L1SAP checks. */ - if (bi->flags & TRX_BI_F_CI_CB) - l1sap.u.rach_ind.lqual_cb = bi->ci_cb; - else - l1sap.u.rach_ind.lqual_cb = l1t->trx->bts->min_qual_rach; - - /* Decode RACH depending on its synch. sequence */ - switch (synch_seq) { - case RACH_SYNCH_SEQ_TS1: - case RACH_SYNCH_SEQ_TS2: - rc = gsm0503_rach_ext_decode_ber(&ra11, bi->burst + RACH_EXT_TAIL_LEN + RACH_SYNCH_SEQ_LEN, - l1t->trx->bts->bsic, &n_errors, &n_bits_total); - if (rc) { - LOGL1S(DL1P, LOGL_DEBUG, l1t, bi->tn, chan, bi->fn, - "Received bad Access Burst\n"); - return 0; - } - - if (synch_seq == RACH_SYNCH_SEQ_TS1) - l1sap.u.rach_ind.burst_type = GSM_L1_BURST_TYPE_ACCESS_1; - else - l1sap.u.rach_ind.burst_type = GSM_L1_BURST_TYPE_ACCESS_2; - - l1sap.u.rach_ind.is_11bit = 1; - l1sap.u.rach_ind.ra = ra11; - break; + llist_for_each_entry(trx, &bts->trx_list, list) { + const struct phy_instance *pinst = trx->pinst; + struct trx_l1h *l1h = pinst->u.osmotrx.hdl; - case RACH_SYNCH_SEQ_TS0: - default: - /* Fall-back to the default TS0 if needed */ - if (synch_seq != RACH_SYNCH_SEQ_TS0) { - LOGL1S(DL1P, LOGL_DEBUG, l1t, bi->tn, chan, bi->fn, - "Falling-back to the default TS0\n"); - synch_seq = RACH_SYNCH_SEQ_TS0; - } + for (tn = 0; tn < TRX_NR_TS; tn++) { + const struct trx_dl_burst_req *br; - rc = gsm0503_rach_decode_ber(&ra, bi->burst + RACH_EXT_TAIL_LEN + RACH_SYNCH_SEQ_LEN, - l1t->trx->bts->bsic, &n_errors, &n_bits_total); - if (rc) { - LOGL1S(DL1P, LOGL_DEBUG, l1t, bi->tn, chan, bi->fn, - "Received bad Access Burst\n"); - return 0; + br = &pinst->u.osmotrx.br[tn]; + if (!br->burst_len) + continue; + trx_if_send_burst(l1h, br); } - l1sap.u.rach_ind.burst_type = GSM_L1_BURST_TYPE_ACCESS_0; - l1sap.u.rach_ind.is_11bit = 0; - l1sap.u.rach_ind.ra = ra; - break; + /* Batch all timeslots into a single TRXD PDU */ + trx_if_send_burst(l1h, NULL); } - - l1sap.u.rach_ind.ber10k = compute_ber10k(n_bits_total, n_errors); - - /* forward primitive */ - l1sap_up(l1t->trx, &l1sap); - - return 0; } -/*! \brief a single (SDCCH/SACCH) burst was received by the PHY, process it */ -int rx_data_fn(struct l1sched_trx *l1t, enum trx_chan_type chan, - uint8_t bid, const struct trx_ul_burst_ind *bi) +/* schedule one frame for a shadow timeslot, merge bursts */ +static void _sched_dl_shadow_burst(const struct gsm_bts_trx_ts *ts, + struct trx_dl_burst_req *br) { - struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, bi->tn); - struct l1sched_chan_state *chan_state = &l1ts->chan_state[chan]; - sbit_t *burst, **bursts_p = &chan_state->ul_bursts; - uint32_t *first_fn = &chan_state->ul_first_fn; - uint8_t *mask = &chan_state->ul_mask; - float *rssi_sum = &chan_state->rssi_sum; - uint8_t *rssi_num = &chan_state->rssi_num; - int32_t *toa256_sum = &chan_state->toa256_sum; - uint8_t *toa_num = &chan_state->toa_num; - int32_t *ci_cb_sum = &chan_state->ci_cb_sum; - uint8_t *ci_cb_num = &chan_state->ci_cb_num; - uint8_t l2[GSM_MACBLOCK_LEN], l2_len; - int n_errors, n_bits_total; - int16_t lqual_cb; - uint16_t ber10k; - int rc; - - /* handle RACH, if handover RACH detection is turned on */ - if (chan_state->ho_rach_detect == 1) - return rx_rach_fn(l1t, chan, bid, bi); - - LOGL1S(DL1P, LOGL_DEBUG, l1t, bi->tn, chan, bi->fn, - "Received Data, bid=%u\n", bid); - - /* allocate burst memory, if not already */ - if (!*bursts_p) { - *bursts_p = talloc_zero_size(tall_bts_ctx, 464); - if (!*bursts_p) - return -ENOMEM; - } - - /* clear burst & store frame number of first burst */ - if (bid == 0) { - memset(*bursts_p, 0, 464); - *mask = 0x0; - *first_fn = bi->fn; - *rssi_sum = 0; - *rssi_num = 0; - *toa256_sum = 0; - *toa_num = 0; - *ci_cb_sum = 0; - *ci_cb_num = 0; - } - - /* update mask + RSSI */ - *mask |= (1 << bid); - *rssi_sum += bi->rssi; - (*rssi_num)++; - *toa256_sum += bi->toa256; - (*toa_num)++; - - /* C/I: Carrier-to-Interference ratio (in centiBels) */ - if (bi->flags & TRX_BI_F_CI_CB) { - *ci_cb_sum += bi->ci_cb; - (*ci_cb_num)++; - } - - /* copy burst to buffer of 4 bursts */ - burst = *bursts_p + bid * 116; - memcpy(burst, bi->burst + 3, 58); - memcpy(burst + 58, bi->burst + 87, 58); - - /* send burst information to loops process */ - if (L1SAP_IS_LINK_SACCH(trx_chan_desc[chan].link_id)) { - trx_loop_sacch_input(l1t, trx_chan_desc[chan].chan_nr | bi->tn, - chan_state, bi->rssi, bi->toa256); - } - - /* wait until complete set of bursts */ - if (bid != 3) - return 0; + struct l1sched_ts *l1ts = ts->priv; + + /* For the shadow timeslots, physical channel type can be either + * GSM_PCHAN_TCH_{F,H} or GSM_PCHAN_NONE. Even if the primary + * timeslot is a dynamic timeslot, it's always a concrete value. */ + if (ts->pchan == GSM_PCHAN_NONE) + return; + + struct trx_dl_burst_req sbr = { + .trx_num = br->trx_num, + .fn = br->fn, + .tn = br->tn, + }; - /* check for complete set of bursts */ - if ((*mask & 0xf) != 0xf) { - LOGL1S(DL1P, LOGL_NOTICE, l1t, bi->tn, chan, bi->fn, - "Received incomplete data (%u/%u)\n", - *first_fn, (*first_fn) % l1ts->mf_period); + _sched_dl_burst(l1ts, &sbr); - /* we require first burst to have correct FN */ - if (!(*mask & 0x1)) { - *mask = 0x0; - return 0; - } + if (br->burst_len != 0 && sbr.burst_len != 0) { /* Both present */ + memcpy(br->burst + GSM_BURST_LEN, sbr.burst, GSM_BURST_LEN); + br->burst_len = 2 * GSM_BURST_LEN; + br->mod = TRX_MOD_T_AQPSK; + /* FIXME: SCPIR is hard-coded to 0 */ + } else if (br->burst_len == 0) { + /* No primary burst, send shadow burst alone */ + memcpy(br, &sbr, sizeof(sbr)); + } else if (sbr.burst_len == 0) { + /* No shadow burst, send primary burst alone */ + return; } - *mask = 0x0; - - /* decode */ - rc = gsm0503_xcch_decode(l2, *bursts_p, &n_errors, &n_bits_total); - if (rc) { - LOGL1S(DL1P, LOGL_NOTICE, l1t, bi->tn, chan, bi->fn, - "Received bad data (%u/%u)\n", - *first_fn, (*first_fn) % l1ts->mf_period); - l2_len = 0; - } else - l2_len = GSM_MACBLOCK_LEN; - - /* Send uplink measurement information to L2 */ - l1if_process_meas_res(l1t->trx, bi->tn, *first_fn, - trx_chan_desc[chan].chan_nr | bi->tn, - n_errors, n_bits_total, - *rssi_sum / *rssi_num, - *toa256_sum / *toa_num); - lqual_cb = *ci_cb_num ? (*ci_cb_sum / *ci_cb_num) : 0; - ber10k = compute_ber10k(n_bits_total, n_errors); - return _sched_compose_ph_data_ind(l1t, bi->tn, *first_fn, - chan, l2, l2_len, - *rssi_sum / *rssi_num, - *toa256_sum / *toa_num, - lqual_cb, ber10k, - PRES_INFO_UNKNOWN); } -/*! \brief a single PDTCH burst was received by the PHY, process it */ -int rx_pdtch_fn(struct l1sched_trx *l1t, enum trx_chan_type chan, - uint8_t bid, const struct trx_ul_burst_ind *bi) +/* schedule all frames of all TRX for given FN */ +static void bts_sched_fn(struct gsm_bts *bts, const uint32_t fn) { - struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, bi->tn); - struct l1sched_chan_state *chan_state = &l1ts->chan_state[chan]; - sbit_t *burst, **bursts_p = &chan_state->ul_bursts; - uint32_t *first_fn = &chan_state->ul_first_fn; - uint8_t *mask = &chan_state->ul_mask; - float *rssi_sum = &chan_state->rssi_sum; - uint8_t *rssi_num = &chan_state->rssi_num; - int32_t *toa256_sum = &chan_state->toa256_sum; - uint8_t *toa_num = &chan_state->toa_num; - int32_t *ci_cb_sum = &chan_state->ci_cb_sum; - uint8_t *ci_cb_num = &chan_state->ci_cb_num; - uint8_t l2[EGPRS_0503_MAX_BYTES]; - int n_errors, n_bursts_bits, n_bits_total; - int16_t lqual_cb; - uint16_t ber10k; - int rc; - - LOGL1S(DL1P, LOGL_DEBUG, l1t, bi->tn, chan, bi->fn, - "Received PDTCH bid=%u\n", bid); - - /* allocate burst memory, if not already */ - if (!*bursts_p) { - *bursts_p = talloc_zero_size(tall_bts_ctx, - GSM0503_EGPRS_BURSTS_NBITS); - if (!*bursts_p) - return -ENOMEM; - } - - /* clear burst */ - if (bid == 0) { - memset(*bursts_p, 0, GSM0503_EGPRS_BURSTS_NBITS); - *mask = 0x0; - *first_fn = bi->fn; - *rssi_sum = 0; - *rssi_num = 0; - *toa256_sum = 0; - *toa_num = 0; - *ci_cb_sum = 0; - *ci_cb_num = 0; - } - - /* update mask + rssi */ - *mask |= (1 << bid); - *rssi_sum += bi->rssi; - (*rssi_num)++; - *toa256_sum += bi->toa256; - (*toa_num)++; - - /* C/I: Carrier-to-Interference ratio (in centiBels) */ - if (bi->flags & TRX_BI_F_CI_CB) { - *ci_cb_sum += bi->ci_cb; - (*ci_cb_num)++; - } - - /* copy burst to buffer of 4 bursts */ - if (bi->burst_len == EGPRS_BURST_LEN) { - burst = *bursts_p + bid * 348; - memcpy(burst, bi->burst + 9, 174); - memcpy(burst + 174, bi->burst + 261, 174); - n_bursts_bits = GSM0503_EGPRS_BURSTS_NBITS; - } else { - burst = *bursts_p + bid * 116; - memcpy(burst, bi->burst + 3, 58); - memcpy(burst + 58, bi->burst + 87, 58); - n_bursts_bits = GSM0503_GPRS_BURSTS_NBITS; - } - - /* wait until complete set of bursts */ - if (bid != 3) - return 0; - - /* check for complete set of bursts */ - if ((*mask & 0xf) != 0xf) { - LOGL1S(DL1P, LOGL_DEBUG, l1t, bi->tn, chan, bi->fn, - "Received incomplete frame (%u/%u)\n", - bi->fn % l1ts->mf_period, l1ts->mf_period); - } - *mask = 0x0; - - /* - * Attempt to decode EGPRS bursts first. For 8-PSK EGPRS this is all we - * do. Attempt GPRS decoding on EGPRS failure. If the burst is GPRS, - * then we incur decoding overhead of 31 bits on the Type 3 EGPRS - * header, which is tolerable. - */ - rc = gsm0503_pdtch_egprs_decode(l2, *bursts_p, n_bursts_bits, - NULL, &n_errors, &n_bits_total); - - if ((bi->burst_len == GSM_BURST_LEN) && (rc < 0)) { - rc = gsm0503_pdtch_decode(l2, *bursts_p, NULL, - &n_errors, &n_bits_total); - } - - - /* Send uplink measurement information to L2 */ - l1if_process_meas_res(l1t->trx, bi->tn, *first_fn, - trx_chan_desc[chan].chan_nr | bi->tn, - n_errors, n_bits_total, - *rssi_sum / *rssi_num, - *toa256_sum / *toa_num); - - if (rc <= 0) { - LOGL1S(DL1P, LOGL_DEBUG, l1t, bi->tn, chan, bi->fn, - "Received bad PDTCH (%u/%u)\n", - bi->fn % l1ts->mf_period, l1ts->mf_period); - return 0; - } - - lqual_cb = *ci_cb_num ? (*ci_cb_sum / *ci_cb_num) : 0; - ber10k = compute_ber10k(n_bits_total, n_errors); - return _sched_compose_ph_data_ind(l1t, bi->tn, - *first_fn, chan, l2, rc, - *rssi_sum / *rssi_num, - *toa256_sum / *toa_num, - lqual_cb, ber10k, - PRES_INFO_BOTH); -} + struct gsm_bts_trx *trx; + unsigned int tn; -/*! \brief a single TCH/F burst was received by the PHY, process it */ -int rx_tchf_fn(struct l1sched_trx *l1t, enum trx_chan_type chan, - uint8_t bid, const struct trx_ul_burst_ind *bi) -{ - struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, bi->tn); - struct l1sched_chan_state *chan_state = &l1ts->chan_state[chan]; - sbit_t *burst, **bursts_p = &chan_state->ul_bursts; - uint32_t *first_fn = &chan_state->ul_first_fn; - uint8_t *mask = &chan_state->ul_mask; - uint8_t rsl_cmode = chan_state->rsl_cmode; - uint8_t tch_mode = chan_state->tch_mode; - uint8_t tch_data[128]; /* just to be safe */ - int rc, amr = 0; - int n_errors, n_bits_total; - bool bfi_flag = false; - struct gsm_lchan *lchan = - get_lchan_by_chan_nr(l1t->trx, trx_chan_desc[chan].chan_nr | bi->tn); - - /* handle rach, if handover rach detection is turned on */ - if (chan_state->ho_rach_detect == 1) - return rx_rach_fn(l1t, chan, bid, bi); - - LOGL1S(DL1P, LOGL_DEBUG, l1t, bi->tn, chan, bi->fn, - "Received TCH/F, bid=%u\n", bid); - - /* allocate burst memory, if not already */ - if (!*bursts_p) { - *bursts_p = talloc_zero_size(tall_bts_ctx, 928); - if (!*bursts_p) - return -ENOMEM; - } + /* Report interference measurements */ + if (fn % 104 == 0) /* SACCH period */ + bts_report_interf_meas(bts); - /* clear burst */ - if (bid == 0) { - memset(*bursts_p + 464, 0, 464); - *mask = 0x0; - *first_fn = bi->fn; - } + /* send time indication */ + l1if_mph_time_ind(bts, fn); - /* update mask */ - *mask |= (1 << bid); + /* Initialize Downlink burst buffers */ + bts_sched_init_buffers(bts, fn); - /* copy burst to end of buffer of 8 bursts */ - burst = *bursts_p + bid * 116 + 464; - memcpy(burst, bi->burst + 3, 58); - memcpy(burst + 58, bi->burst + 87, 58); + /* Populate Downlink burst buffers for each TRX/TS */ + llist_for_each_entry(trx, &bts->trx_list, list) { + const struct phy_link *plink = trx->pinst->phy_link; + struct trx_l1h *l1h = trx->pinst->u.osmotrx.hdl; - /* wait until complete set of bursts */ - if (bid != 3) - return 0; + /* we don't schedule, if power is off */ + if (!trx_if_powered(l1h)) + continue; - /* check for complete set of bursts */ - if ((*mask & 0xf) != 0xf) { - LOGL1S(DL1P, LOGL_NOTICE, l1t, bi->tn, chan, bi->fn, - "Received incomplete frame (%u/%u)\n", - bi->fn % l1ts->mf_period, l1ts->mf_period); - } - *mask = 0x0; - - /* decode - * also shift buffer by 4 bursts for interleaving */ - switch ((rsl_cmode != RSL_CMOD_SPD_SPEECH) ? GSM48_CMODE_SPEECH_V1 - : tch_mode) { - case GSM48_CMODE_SPEECH_V1: /* FR */ - rc = gsm0503_tch_fr_decode(tch_data, *bursts_p, 1, 0, &n_errors, &n_bits_total); - if (rc >= 0) - lchan_set_marker(osmo_fr_check_sid(tch_data, rc), lchan); /* DTXu */ - break; - case GSM48_CMODE_SPEECH_EFR: /* EFR */ - rc = gsm0503_tch_fr_decode(tch_data, *bursts_p, 1, 1, &n_errors, &n_bits_total); - break; - case GSM48_CMODE_SPEECH_AMR: /* AMR */ - /* the first FN 0,8,17 defines that CMI is included in frame, - * the first FN 4,13,21 defines that CMR is included in frame. - * NOTE: A frame ends 7 FN after start. - */ - rc = gsm0503_tch_afs_decode(tch_data + 2, *bursts_p, - (((bi->fn + 26 - 7) % 26) >> 2) & 1, chan_state->codec, - chan_state->codecs, &chan_state->ul_ft, - &chan_state->ul_cmr, &n_errors, &n_bits_total); - if (rc) - trx_loop_amr_input(l1t, - trx_chan_desc[chan].chan_nr | bi->tn, chan_state, - (float)n_errors/(float)n_bits_total); - amr = 2; /* we store tch_data + 2 header bytes */ - /* only good speech frames get rtp header */ - if (rc != GSM_MACBLOCK_LEN && rc >= 4) { - rc = osmo_amr_rtp_enc(tch_data, - chan_state->codec[chan_state->ul_cmr], - chan_state->codec[chan_state->ul_ft], AMR_GOOD); - } - break; - default: - LOGL1S(DL1P, LOGL_ERROR, l1t, bi->tn, chan, bi->fn, - "TCH mode %u invalid, please fix!\n", - tch_mode); - return -EINVAL; - } - memcpy(*bursts_p, *bursts_p + 464, 464); - - /* Send uplink measurement information to L2 */ - l1if_process_meas_res(l1t->trx, bi->tn, *first_fn, - trx_chan_desc[chan].chan_nr | bi->tn, - n_errors, n_bits_total, - bi->rssi, bi->toa256); - - /* Check if the frame is bad */ - if (rc < 0) { - LOGL1S(DL1P, LOGL_NOTICE, l1t, bi->tn, chan, bi->fn, - "Received bad data (%u/%u)\n", - bi->fn % l1ts->mf_period, l1ts->mf_period); - bfi_flag = true; - } else if (rc < 4) { - LOGL1S(DL1P, LOGL_NOTICE, l1t, bi->tn, chan, bi->fn, - "Received bad data (%u/%u) with invalid codec mode %d\n", - bi->fn % l1ts->mf_period, l1ts->mf_period, rc); - bfi_flag = true; - } + /* process every TS of TRX */ + for (tn = 0; tn < ARRAY_SIZE(trx->ts); tn++) { + struct phy_instance *pinst = trx->pinst; + struct gsm_bts_trx_ts *ts = &trx->ts[tn]; + struct l1sched_ts *l1ts = ts->priv; + struct trx_dl_burst_req *br; - if (rc != GSM_MACBLOCK_LEN && lchan->ecu_state) - osmo_ecu_frame_in(lchan->ecu_state, bfi_flag, tch_data, rc); - - if (bfi_flag) - goto bfi; - - /* FACCH */ - if (rc == GSM_MACBLOCK_LEN) { - uint16_t ber10k = compute_ber10k(n_bits_total, n_errors); - _sched_compose_ph_data_ind(l1t, bi->tn, - /* FIXME: this calculation is wrong */ - (bi->fn + GSM_HYPERFRAME - 7) % GSM_HYPERFRAME, chan, - tch_data + amr, GSM_MACBLOCK_LEN, - /* FIXME: AVG RSSI and ToA256 */ - bi->rssi, bi->toa256, - 0 /* FIXME: AVG C/I */, - ber10k, PRES_INFO_UNKNOWN); -bfi: - if (rsl_cmode == RSL_CMOD_SPD_SPEECH) { - /* indicate bad frame */ - if (lchan->tch.dtx.ul_sid) { - /* DTXu: pause in progress. Push empty payload to upper layers */ - rc = 0; - goto compose_l1sap; + /* ready-to-send */ + TRACE(OSMO_BTS_TRX_DL_RTS_START(trx->nr, tn, fn)); + _sched_rts(l1ts, GSM_TDMA_FN_SUM(fn, plink->u.osmotrx.clock_advance + + plink->u.osmotrx.rts_advance)); + TRACE(OSMO_BTS_TRX_DL_RTS_DONE(trx->nr, tn, fn)); + + /* pre-initialized buffer for the Downlink burst */ + br = &pinst->u.osmotrx.br[tn]; + + /* resolve PHY instance if freq. hopping is enabled */ + if (ts->hopping.enabled) { + pinst = dlfh_route_br(br, ts); + if (pinst == NULL) + continue; + /* simply use a different buffer */ + br = &pinst->u.osmotrx.br[tn]; } - /* If there is an ECU active on this channel, use its output */ - if (lchan->ecu_state) { - rc = osmo_ecu_frame_out(lchan->ecu_state, tch_data); - goto compose_l1sap; - } + /* get burst for the primary timeslot */ + _sched_dl_burst(l1ts, br); - switch (tch_mode) { - case GSM48_CMODE_SPEECH_V1: /* FR */ - memset(tch_data, 0, GSM_FR_BYTES); - tch_data[0] = 0xd0; - rc = GSM_FR_BYTES; - break; - case GSM48_CMODE_SPEECH_EFR: /* EFR */ - memset(tch_data, 0, GSM_EFR_BYTES); - tch_data[0] = 0xc0; - rc = GSM_EFR_BYTES; - break; - case GSM48_CMODE_SPEECH_AMR: /* AMR */ - rc = osmo_amr_rtp_enc(tch_data, - chan_state->codec[chan_state->dl_cmr], - chan_state->codec[chan_state->dl_ft], - AMR_BAD); - if (rc < 2) { - LOGL1S(DL1P, LOGL_ERROR, l1t, bi->tn, chan, bi->fn, - "Failed to encode AMR_BAD frame (rc=%d), " - "not sending BFI\n", rc); - return -EINVAL; - } - memset(tch_data + 2, 0, rc - 2); - break; - default: - LOGL1S(DL1P, LOGL_ERROR, l1t, bi->tn, chan, bi->fn, - "TCH mode %u invalid, please fix!\n", tch_mode); - return -EINVAL; - } + /* get burst for the shadow timeslot */ + _sched_dl_shadow_burst(ts->vamos.peer, br); } } - if (rsl_cmode != RSL_CMOD_SPD_SPEECH) - return 0; - - /* TCH or BFI */ -compose_l1sap: - return _sched_compose_tch_ind(l1t, bi->tn, - /* FIXME: this calculation is wrong */ - (bi->fn + GSM_HYPERFRAME - 7) % GSM_HYPERFRAME, chan, - tch_data, rc); + /* Send everything to the PHY */ + bts_sched_flush_buffers(bts); } -/*! \brief a single TCH/H burst was received by the PHY, process it */ -int rx_tchh_fn(struct l1sched_trx *l1t, enum trx_chan_type chan, - uint8_t bid, const struct trx_ul_burst_ind *bi) +/* Find a route (TRX instance) for a given Uplink burst indication */ +static struct gsm_bts_trx *ulfh_route_bi(const struct trx_ul_burst_ind *bi, + const struct gsm_bts_trx *src_trx) { - struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, bi->tn); - struct l1sched_chan_state *chan_state = &l1ts->chan_state[chan]; - sbit_t *burst, **bursts_p = &chan_state->ul_bursts; - uint32_t *first_fn = &chan_state->ul_first_fn; - uint8_t *mask = &chan_state->ul_mask; - uint8_t rsl_cmode = chan_state->rsl_cmode; - uint8_t tch_mode = chan_state->tch_mode; - uint8_t tch_data[128]; /* just to be safe */ - int rc, amr = 0; - int n_errors, n_bits_total; - bool bfi_flag = false; - struct gsm_lchan *lchan = - get_lchan_by_chan_nr(l1t->trx, trx_chan_desc[chan].chan_nr | bi->tn); - /* Note on FN-10: If we are at FN 10, we decoded an even aligned - * TCH/FACCH frame, because our burst buffer carries 6 bursts. - * Even FN ending at: 10,11,19,20,2,3 - */ - int fn_is_odd = (((bi->fn + 26 - 10) % 26) >> 2) & 1; - - /* handle RACH, if handover RACH detection is turned on */ - if (chan_state->ho_rach_detect == 1) - return rx_rach_fn(l1t, chan, bid, bi); - - LOGL1S(DL1P, LOGL_DEBUG, l1t, bi->tn, chan, bi->fn, - "Received TCH/H, bid=%u\n", bid); - - /* allocate burst memory, if not already */ - if (!*bursts_p) { - *bursts_p = talloc_zero_size(tall_bts_ctx, 696); - if (!*bursts_p) - return -ENOMEM; - } - - /* clear burst */ - if (bid == 0) { - memset(*bursts_p + 464, 0, 232); - *mask = 0x0; - *first_fn = bi->fn; - } - - /* update mask */ - *mask |= (1 << bid); - - /* copy burst to end of buffer of 6 bursts */ - burst = *bursts_p + bid * 116 + 464; - memcpy(burst, bi->burst + 3, 58); - memcpy(burst + 58, bi->burst + 87, 58); + struct gsm_bts_trx *trx; + struct gsm_time time; + uint16_t arfcn; - /* wait until complete set of bursts */ - if (bid != 1) - return 0; + gsm_fn2gsmtime(&time, bi->fn); - /* check for complete set of bursts */ - if ((*mask & 0x3) != 0x3) { - LOGL1S(DL1P, LOGL_NOTICE, l1t, bi->tn, chan, bi->fn, - "Received incomplete frame (%u/%u)\n", - bi->fn % l1ts->mf_period, l1ts->mf_period); - } - *mask = 0x0; - - /* skip second of two TCH frames of FACCH was received */ - if (chan_state->ul_ongoing_facch) { - chan_state->ul_ongoing_facch = 0; - memcpy(*bursts_p, *bursts_p + 232, 232); - memcpy(*bursts_p + 232, *bursts_p + 464, 232); - goto bfi; - } + llist_for_each_entry(trx, &src_trx->bts->trx_list, list) { + const struct gsm_bts_trx_ts *ts = &trx->ts[bi->tn]; + if (!ts->hopping.enabled) + continue; - /* decode - * also shift buffer by 4 bursts for interleaving */ - switch ((rsl_cmode != RSL_CMOD_SPD_SPEECH) ? GSM48_CMODE_SPEECH_V1 - : tch_mode) { - case GSM48_CMODE_SPEECH_V1: /* HR or signalling */ - /* Note on FN-10: If we are at FN 10, we decoded an even aligned - * TCH/FACCH frame, because our burst buffer carries 6 bursts. - * Even FN ending at: 10,11,19,20,2,3 - */ - rc = gsm0503_tch_hr_decode(tch_data, *bursts_p, - fn_is_odd, &n_errors, &n_bits_total); - if (rc >= 0) /* DTXu */ - lchan_set_marker(osmo_hr_check_sid(tch_data, rc), lchan); - break; - case GSM48_CMODE_SPEECH_AMR: /* AMR */ - /* the first FN 0,8,17 or 1,9,18 defines that CMI is included - * in frame, the first FN 4,13,21 or 5,14,22 defines that CMR - * is included in frame. - */ - rc = gsm0503_tch_ahs_decode(tch_data + 2, *bursts_p, - fn_is_odd, fn_is_odd, chan_state->codec, - chan_state->codecs, &chan_state->ul_ft, - &chan_state->ul_cmr, &n_errors, &n_bits_total); - if (rc) - trx_loop_amr_input(l1t, - trx_chan_desc[chan].chan_nr | bi->tn, chan_state, - (float)n_errors/(float)n_bits_total); - amr = 2; /* we store tch_data + 2 two */ - /* only good speech frames get rtp header */ - if (rc != GSM_MACBLOCK_LEN && rc >= 4) { - rc = osmo_amr_rtp_enc(tch_data, - chan_state->codec[chan_state->ul_cmr], - chan_state->codec[chan_state->ul_ft], AMR_GOOD); - } - break; - default: - LOGL1S(DL1P, LOGL_ERROR, l1t, bi->tn, chan, bi->fn, - "TCH mode %u invalid, please fix!\n", - tch_mode); - return -EINVAL; - } - memcpy(*bursts_p, *bursts_p + 232, 232); - memcpy(*bursts_p + 232, *bursts_p + 464, 232); - - /* Send uplink measurement information to L2 */ - l1if_process_meas_res(l1t->trx, bi->tn, - *first_fn /* FIXME: this is wrong */, - trx_chan_desc[chan].chan_nr | bi->tn, - n_errors, n_bits_total, bi->rssi, bi->toa256); - - /* Check if the frame is bad */ - if (rc < 0) { - LOGL1S(DL1P, LOGL_NOTICE, l1t, bi->tn, chan, bi->fn, - "Received bad data (%u/%u)\n", - bi->fn % l1ts->mf_period, l1ts->mf_period); - bfi_flag = true; - } else if (rc < 4) { - LOGL1S(DL1P, LOGL_NOTICE, l1t, bi->tn, chan, bi->fn, - "Received bad data (%u/%u) with invalid codec mode %d\n", - bi->fn % l1ts->mf_period, l1ts->mf_period, rc); - bfi_flag = true; + arfcn = gsm0502_hop_seq_gen(&time, SCHED_FH_PARAMS_VALS(ts), ts->hopping.arfcn_list); + if (src_trx->arfcn == arfcn) + return trx; } - if (rc != GSM_MACBLOCK_LEN && lchan->ecu_state) - osmo_ecu_frame_in(lchan->ecu_state, bfi_flag, tch_data, rc); - - if (bfi_flag) - goto bfi; - - /* FACCH */ - if (rc == GSM_MACBLOCK_LEN) { - chan_state->ul_ongoing_facch = 1; - uint16_t ber10k = compute_ber10k(n_bits_total, n_errors); - _sched_compose_ph_data_ind(l1t, bi->tn, - /* FIXME: what the hell is this?!? */ - (bi->fn + GSM_HYPERFRAME - 10 - ((bi->fn % 26) >= 19)) % GSM_HYPERFRAME, chan, - tch_data + amr, GSM_MACBLOCK_LEN, - /* FIXME: AVG both RSSI and ToA */ - bi->rssi, bi->toa256, - 0 /* FIXME: AVG C/I */, - ber10k, PRES_INFO_UNKNOWN); -bfi: - /* FIXME: a FACCH/H frame replaces two speech frames, - * so we actually need to send two bad frame indications! */ - if (rsl_cmode == RSL_CMOD_SPD_SPEECH) { - /* indicate bad frame */ - if (lchan->tch.dtx.ul_sid) { - /* DTXu: pause in progress. Push empty payload to upper layers */ - rc = 0; - goto compose_l1sap; - } + LOGPTRX(src_trx, DL1C, LOGL_DEBUG, "Failed to find the transceiver (RF carrier) " + "for an Uplink burst (fn=%u, tn=%u, " SCHED_FH_PARAMS_FMT ")\n", + bi->fn, bi->tn, SCHED_FH_PARAMS_VALS(&src_trx->ts[bi->tn])); - /* If there is an ECU active on this channel, use its output */ - if (lchan->ecu_state) { - rc = osmo_ecu_frame_out(lchan->ecu_state, tch_data); - goto compose_l1sap; - } + struct bts_trx_priv *priv = (struct bts_trx_priv *) src_trx->bts->model_priv; + rate_ctr_inc2(priv->ctrs, BTSTRX_CTR_SCHED_UL_FH_NO_CARRIER); - switch (tch_mode) { - case GSM48_CMODE_SPEECH_V1: /* HR */ - tch_data[0] = 0x70; /* F = 0, FT = 111 */ - memset(tch_data + 1, 0, 14); - rc = 15; - break; - case GSM48_CMODE_SPEECH_AMR: /* AMR */ - rc = osmo_amr_rtp_enc(tch_data, - chan_state->codec[chan_state->dl_cmr], - chan_state->codec[chan_state->dl_ft], - AMR_BAD); - if (rc < 2) { - LOGL1S(DL1P, LOGL_ERROR, l1t, bi->tn, chan, bi->fn, - "Failed to encode AMR_BAD frame (rc=%d), " - "not sending BFI\n", rc); - return -EINVAL; - } - memset(tch_data + 2, 0, rc - 2); - break; - default: - LOGL1S(DL1P, LOGL_ERROR, l1t, bi->tn, chan, bi->fn, - "TCH mode %u invalid, please fix!\n", tch_mode); - return -EINVAL; - } - } - } - - if (rsl_cmode != RSL_CMOD_SPD_SPEECH) - return 0; - -compose_l1sap: - /* TCH or BFI */ - /* Note on FN 19 or 20: If we received the last burst of a frame, - * it actually starts at FN 8 or 9. A burst starting there, overlaps - * with the slot 12, so an extra FN must be subtracted to get correct - * start of frame. - */ - return _sched_compose_tch_ind(l1t, bi->tn, - /* FIXME: what the hell is this?!? */ - (bi->fn + GSM_HYPERFRAME - 10 - ((bi->fn%26)==19) - ((bi->fn%26)==20)) % GSM_HYPERFRAME, - chan, tch_data, rc); + return NULL; } -/* schedule all frames of all TRX for given FN */ -static int trx_sched_fn(struct gsm_bts *bts, uint32_t fn) +/* Route a given Uplink burst indication to the scheduler depending on freq. hopping state */ +int trx_sched_route_burst_ind(const struct gsm_bts_trx *trx, struct trx_ul_burst_ind *bi) { - struct gsm_bts_trx *trx; - uint8_t tn; - const ubit_t *bits; - uint8_t gain; - uint16_t nbits = 0; - - /* send time indication */ - l1if_mph_time_ind(bts, fn); + /* no frequency hopping => nothing to do */ + if (!trx->ts[bi->tn].hopping.enabled) + return trx_sched_ul_burst(trx->ts[bi->tn].priv, bi); - /* process every TRX */ - llist_for_each_entry(trx, &bts->trx_list, list) { - struct phy_instance *pinst = trx_phy_instance(trx); - struct phy_link *plink = pinst->phy_link; - struct trx_l1h *l1h = pinst->u.osmotrx.hdl; - struct l1sched_trx *l1t = &l1h->l1s; - - /* advance frame number, so the transceiver has more - * time until it must be transmitted. */ - fn = (fn + plink->u.osmotrx.clock_advance) % GSM_HYPERFRAME; + trx = ulfh_route_bi(bi, trx); + if (trx == NULL) + return -ENODEV; - /* we don't schedule, if power is off */ - if (!trx_if_powered(l1h)) - continue; - - /* process every TS of TRX */ - for (tn = 0; tn < ARRAY_SIZE(l1t->ts); tn++) { - /* ready-to-send */ - _sched_rts(l1t, tn, - (fn + plink->u.osmotrx.rts_advance) % GSM_HYPERFRAME); - /* get burst for FN */ - bits = _sched_dl_burst(l1t, tn, fn, &nbits); - if (!bits) { - /* if no bits, send no burst */ - continue; - } else - gain = 0; - if (nbits) - trx_if_send_burst(l1h, tn, fn, gain, bits, nbits); - } - } - - return 0; + return trx_sched_ul_burst(trx->ts[bi->tn].priv, bi); } -/* - * TRX frame clock handling - * - * In a "normal" synchronous PHY layer, we would be polled every time - * the PHY needs data for a given frame number. However, the - * OpenBTS-inherited TRX protocol works differently: We (L1) must - * autonomously send burst data based on our own clock, and every so - * often (currently every ~ 216 frames), we get a clock indication from - * the TRX. - * - * We're using a MONOTONIC timerfd interval timer for the 4.615ms frame - * intervals, and then compute + send the 8 bursts for that frame. - * - * Upon receiving a clock indication from the TRX, we compensate - * accordingly: If we were transmitting too fast, we're delaying the - * next interval timer accordingly. If we were too slow, we immediately - * send burst data for the missing frame numbers. - */ - -/*! clock state of a given TRX */ -struct osmo_trx_clock_state { - /*! number of FN periods without TRX clock indication */ - uint32_t fn_without_clock_ind; - struct { - /*! last FN we processed based on FN period timer */ - uint32_t fn; - /*! time at which we last processed FN */ - struct timespec tv; - } last_fn_timer; - struct { - /*! last FN we received a clock indication for */ - uint32_t fn; - /*! time at which we received the last clock indication */ - struct timespec tv; - } last_clk_ind; - /*! Osmocom FD wrapper for timerfd */ - struct osmo_fd fn_timer_ofd; -}; - -/* TODO: This must go and become part of the phy_link */ -static struct osmo_trx_clock_state g_clk_s = { .fn_timer_ofd.fd = -1 }; - -/*! duration of a GSM frame in nano-seconds. (120ms/26) */ -#define FRAME_DURATION_nS 4615384 -/*! duration of a GSM frame in micro-seconds (120s/26) */ -#define FRAME_DURATION_uS (FRAME_DURATION_nS/1000) /*! maximum number of 'missed' frame periods we can tolerate of OS doesn't schedule us*/ #define MAX_FN_SKEW 50 /*! maximum number of frame periods we can tolerate without TRX Clock Indication*/ @@ -1641,9 +389,9 @@ static inline int64_t compute_elapsed_us(const struct timespec *last, const stru /*! compute the number of frame number intervals elapsed between \a last and \a now */ static inline int compute_elapsed_fn(const uint32_t last, const uint32_t now) { - int elapsed_fn = (now + GSM_HYPERFRAME - last) % GSM_HYPERFRAME; + int elapsed_fn = GSM_TDMA_FN_SUB(now, last); if (elapsed_fn >= 135774) - elapsed_fn -= GSM_HYPERFRAME; + elapsed_fn -= GSM_TDMA_HYPERFRAME; return elapsed_fn; } @@ -1654,22 +402,18 @@ static inline void normalize_timespec(struct timespec *ts) ts->tv_nsec = ts->tv_nsec % 1000000000; } -/*! Increment a GSM frame number modulo GSM_HYPERFRAME */ -#define INCREMENT_FN(fn) (fn) = (((fn) + 1) % GSM_HYPERFRAME) - -extern int quit; - /*! this is the timerfd-callback firing for every FN to be processed */ static int trx_fn_timer_cb(struct osmo_fd *ofd, unsigned int what) { struct gsm_bts *bts = ofd->data; - struct osmo_trx_clock_state *tcs = &g_clk_s; + struct bts_trx_priv *bts_trx = (struct bts_trx_priv *)bts->model_priv; + struct osmo_trx_clock_state *tcs = &bts_trx->clk_s; struct timespec tv_now; uint64_t expire_count; int64_t elapsed_us, error_us; int rc, i; - if (!(what & BSC_FD_READ)) + if (!(what & OSMO_FD_READ)) return 0; /* read from timerfd: number of expirations of periodic timer */ @@ -1680,7 +424,8 @@ static int trx_fn_timer_cb(struct osmo_fd *ofd, unsigned int what) if (expire_count > 1) { LOGP(DL1C, LOGL_NOTICE, "FN timer expire_count=%"PRIu64": We missed %"PRIu64" timers\n", - expire_count, expire_count-1); + expire_count, expire_count - 1); + rate_ctr_add(rate_ctr_group_get_ctr(bts_trx->ctrs, BTSTRX_CTR_SCHED_DL_MISS_FN), expire_count - 1); } /* check if transceiver is still alive */ @@ -1692,7 +437,7 @@ static int trx_fn_timer_cb(struct osmo_fd *ofd, unsigned int what) /* compute actual elapsed time and resulting OS scheduling error */ clock_gettime(CLOCK_MONOTONIC, &tv_now); elapsed_us = compute_elapsed_us(&tcs->last_fn_timer.tv, &tv_now); - error_us = elapsed_us - FRAME_DURATION_uS; + error_us = elapsed_us - GSM_TDMA_FN_DURATION_uS; #ifdef DEBUG_CLOCK printf("%s(): %09ld, elapsed_us=%05" PRId64 ", error_us=%-d: fn=%d\n", __func__, tv_now.tv_nsec, elapsed_us, error_us, tcs->last_fn_timer.fn+1); @@ -1700,45 +445,83 @@ static int trx_fn_timer_cb(struct osmo_fd *ofd, unsigned int what) tcs->last_fn_timer.tv = tv_now; /* if someone played with clock, or if the process stalled */ - if (elapsed_us > FRAME_DURATION_uS * MAX_FN_SKEW || elapsed_us < 0) { + if (elapsed_us > GSM_TDMA_FN_DURATION_uS * MAX_FN_SKEW || elapsed_us < 0) { LOGP(DL1C, LOGL_ERROR, "PC clock skew: elapsed_us=%" PRId64 ", error_us=%" PRId64 "\n", elapsed_us, error_us); goto no_clock; } - /* call trx_sched_fn() for all expired FN */ - for (i = 0; i < expire_count; i++) { - INCREMENT_FN(tcs->last_fn_timer.fn); - trx_sched_fn(bts, tcs->last_fn_timer.fn); - } + /* call bts_sched_fn() for all expired FN */ + for (i = 0; i < expire_count; i++) + bts_sched_fn(bts, GSM_TDMA_FN_INC(tcs->last_fn_timer.fn)); return 0; no_clock: osmo_timerfd_disable(&tcs->fn_timer_ofd); - transceiver_available = 0; - bts_shutdown(bts, "No clock from osmo-trx"); + return -1; +} +/*! \brief This is the cb of the initial timer set upon start. On timeout, it + * means it wasn't replaced and hence no CLOCK IND was received. */ +static int trx_start_noclockind_to_cb(struct osmo_fd *ofd, unsigned int what) +{ + struct gsm_bts *bts = ofd->data; + struct bts_trx_priv *bts_trx = (struct bts_trx_priv *)bts->model_priv; + struct osmo_trx_clock_state *tcs = &bts_trx->clk_s; + + osmo_fd_close(&tcs->fn_timer_ofd); /* Avoid being called again */ + bts_shutdown(bts, "No clock since TRX was started"); return -1; } +/*! \brief PHY informs us clock indications should start to be received */ +int trx_sched_clock_started(struct gsm_bts *bts) +{ + struct bts_trx_priv *bts_trx = (struct bts_trx_priv *)bts->model_priv; + struct osmo_trx_clock_state *tcs = &bts_trx->clk_s; + const struct timespec it_val = {3, 0}; + const struct timespec it_intval = {0, 0}; + + LOGP(DL1C, LOGL_NOTICE, "GSM clock started, waiting for clock indications\n"); + osmo_fd_close(&tcs->fn_timer_ofd); + memset(tcs, 0, sizeof(*tcs)); + tcs->fn_timer_ofd.fd = -1; + /* Set up timeout to shutdown BTS if no clock ind is received in a few + * seconds. Upon clock ind receival, fn_timer_ofd will be reused and + * timeout won't trigger. + */ + osmo_timerfd_setup(&tcs->fn_timer_ofd, trx_start_noclockind_to_cb, bts); + osmo_timerfd_schedule(&tcs->fn_timer_ofd, &it_val, &it_intval); + return 0; +} + +/*! \brief PHY informs us no more clock indications should be received anymore */ +int trx_sched_clock_stopped(struct gsm_bts *bts) +{ + struct bts_trx_priv *bts_trx = (struct bts_trx_priv *)bts->model_priv; + struct osmo_trx_clock_state *tcs = &bts_trx->clk_s; + + LOGP(DL1C, LOGL_NOTICE, "GSM clock stopped\n"); + osmo_fd_close(&tcs->fn_timer_ofd); + + return 0; +} + /*! reset clock with current fn and schedule it. Called when trx becomes * available or when max clock skew is reached */ static int trx_setup_clock(struct gsm_bts *bts, struct osmo_trx_clock_state *tcs, struct timespec *tv_now, const struct timespec *interval, uint32_t fn) { - tcs->last_fn_timer.fn = fn; - /* call trx cheduler function for new 'last' FN */ - trx_sched_fn(bts, tcs->last_fn_timer.fn); - /* schedule first FN clock timer */ osmo_timerfd_setup(&tcs->fn_timer_ofd, trx_fn_timer_cb, bts); osmo_timerfd_schedule(&tcs->fn_timer_ofd, NULL, interval); + tcs->last_fn_timer.fn = fn; tcs->last_fn_timer.tv = *tv_now; - tcs->last_clk_ind.tv = *tv_now; - tcs->last_clk_ind.fn = fn; + /* call trx scheduler function for new 'last' FN */ + bts_sched_fn(bts, tcs->last_fn_timer.fn); return 0; } @@ -1746,36 +529,19 @@ static int trx_setup_clock(struct gsm_bts *bts, struct osmo_trx_clock_state *tcs /*! called every time we receive a clock indication from TRX */ int trx_sched_clock(struct gsm_bts *bts, uint32_t fn) { - struct osmo_trx_clock_state *tcs = &g_clk_s; + struct bts_trx_priv *bts_trx = (struct bts_trx_priv *)bts->model_priv; + struct osmo_trx_clock_state *tcs = &bts_trx->clk_s; struct timespec tv_now; int elapsed_fn; int64_t elapsed_us, elapsed_us_since_clk, elapsed_fn_since_clk, error_us_since_clk; unsigned int fn_caught_up = 0; - const struct timespec interval = { .tv_sec = 0, .tv_nsec = FRAME_DURATION_nS }; - - if (quit) - return 0; + const struct timespec interval = { .tv_sec = 0, .tv_nsec = GSM_TDMA_FN_DURATION_nS }; /* reset lost counter */ tcs->fn_without_clock_ind = 0; clock_gettime(CLOCK_MONOTONIC, &tv_now); - /* clock becomes valid */ - if (!transceiver_available) { - LOGP(DL1C, LOGL_NOTICE, "initial GSM clock received: fn=%u\n", fn); - - transceiver_available = 1; - - /* start provisioning transceiver */ - l1if_provision_transceiver(bts); - - /* tell BSC */ - check_transceiver_availability(bts, 1); - - return trx_setup_clock(bts, tcs, &tv_now, &interval, fn); - } - /* calculate elapsed time +fn since last timer */ elapsed_us = compute_elapsed_us(&tcs->last_fn_timer.tv, &tv_now); elapsed_fn = compute_elapsed_fn(tcs->last_fn_timer.fn, fn); @@ -1792,7 +558,7 @@ int trx_sched_clock(struct gsm_bts *bts, uint32_t fn) elapsed_us_since_clk = compute_elapsed_us(&tcs->last_clk_ind.tv, &tv_now); elapsed_fn_since_clk = compute_elapsed_fn(tcs->last_clk_ind.fn, fn); /* error (delta) between local clock since last CLK and CLK based on FN clock at TRX */ - error_us_since_clk = elapsed_us_since_clk - (FRAME_DURATION_uS * elapsed_fn_since_clk); + error_us_since_clk = elapsed_us_since_clk - (GSM_TDMA_FN_DURATION_uS * elapsed_fn_since_clk); LOGP(DL1C, LOGL_INFO, "TRX Clock Ind: elapsed_us=%7"PRId64", " "elapsed_fn=%3"PRId64", error_us=%+5"PRId64"\n", elapsed_us_since_clk, elapsed_fn_since_clk, error_us_since_clk); @@ -1813,14 +579,14 @@ int trx_sched_clock(struct gsm_bts *bts, uint32_t fn) } LOGP(DL1C, LOGL_INFO, "GSM clock jitter: %" PRId64 "us (elapsed_fn=%d)\n", - elapsed_fn * FRAME_DURATION_uS - elapsed_us, elapsed_fn); + elapsed_fn * GSM_TDMA_FN_DURATION_uS - elapsed_us, elapsed_fn); /* too many frames have been processed already */ if (elapsed_fn < 0) { struct timespec first = interval; /* set clock to the time or last FN should have been * transmitted. */ - first.tv_nsec += (0 - elapsed_fn) * FRAME_DURATION_nS; + first.tv_nsec += (0 - elapsed_fn) * GSM_TDMA_FN_DURATION_nS; normalize_timespec(&first); LOGP(DL1C, LOGL_NOTICE, "We were %d FN faster than TRX, compensating\n", -elapsed_fn); /* set time to the time our next FN has to be transmitted */ @@ -1830,8 +596,7 @@ int trx_sched_clock(struct gsm_bts *bts, uint32_t fn) /* transmit what we still need to transmit */ while (fn != tcs->last_fn_timer.fn) { - INCREMENT_FN(tcs->last_fn_timer.fn); - trx_sched_fn(bts, tcs->last_fn_timer.fn); + bts_sched_fn(bts, GSM_TDMA_FN_INC(tcs->last_fn_timer.fn)); fn_caught_up++; } @@ -1843,9 +608,9 @@ int trx_sched_clock(struct gsm_bts *bts, uint32_t fn) return 0; } -void _sched_act_rach_det(struct l1sched_trx *l1t, uint8_t tn, uint8_t ss, int activate) +void _sched_act_rach_det(struct gsm_bts_trx *trx, uint8_t tn, uint8_t ss, int activate) { - struct phy_instance *pinst = trx_phy_instance(l1t->trx); + struct phy_instance *pinst = trx_phy_instance(trx); struct trx_l1h *l1h = pinst->u.osmotrx.hdl; if (activate) @@ -1853,3 +618,92 @@ void _sched_act_rach_det(struct l1sched_trx *l1t, uint8_t tn, uint8_t ss, int ac else trx_if_cmd_nohandover(l1h, tn, ss); } + +/* Add a set of UL burst measurements to the history */ +void trx_sched_meas_push(struct l1sched_chan_state *chan_state, + const struct trx_ul_burst_ind *bi) +{ + unsigned int hist_size = ARRAY_SIZE(chan_state->meas.buf); + unsigned int current = chan_state->meas.current; + + chan_state->meas.buf[current] = (struct l1sched_meas_set) { + .fn = bi->fn, + .ci_cb = (bi->flags & TRX_BI_F_CI_CB) ? bi->ci_cb : 0, + .toa256 = bi->toa256, + .rssi = bi->rssi, + }; + + chan_state->meas.current = (current + 1) % hist_size; +} + +/* Measurement averaging mode sets: [MODE] = { SHIFT, NUM } */ +static const uint8_t trx_sched_meas_modeset[][2] = { + [SCHED_MEAS_AVG_M_S24N22] = { 24, 22 }, + [SCHED_MEAS_AVG_M_S22N22] = { 22, 22 }, + [SCHED_MEAS_AVG_M_S4N4] = { 4, 4 }, + [SCHED_MEAS_AVG_M_S8N8] = { 8, 8 }, + [SCHED_MEAS_AVG_M_S6N4] = { 6, 4 }, + [SCHED_MEAS_AVG_M_S6N6] = { 6, 6 }, + [SCHED_MEAS_AVG_M_S8N4] = { 8, 4 }, + [SCHED_MEAS_AVG_M_S6N2] = { 6, 2 }, + [SCHED_MEAS_AVG_M_S4N2] = { 4, 2 }, +}; + +/* Calculate the AVG of n measurements from the history */ +void trx_sched_meas_avg(const struct l1sched_chan_state *chan_state, + struct l1sched_meas_set *avg, + enum sched_meas_avg_mode mode) +{ + unsigned int hist_size = ARRAY_SIZE(chan_state->meas.buf); + unsigned int current = chan_state->meas.current; + const struct l1sched_meas_set *set; + unsigned int pos, i; + + float rssi_sum = 0; + int toa256_sum = 0; + int ci_cb_sum = 0; + + const unsigned int shift = trx_sched_meas_modeset[mode][0]; + const unsigned int num = trx_sched_meas_modeset[mode][1]; + + /* Calculate the sum of n entries starting from pos */ + for (i = 0; i < num; i++) { + pos = (current + hist_size - shift + i) % hist_size; + set = &chan_state->meas.buf[pos]; + + rssi_sum += set->rssi; + toa256_sum += set->toa256; + ci_cb_sum += set->ci_cb; + } + + /* First sample contains TDMA frame number of the first burst */ + pos = (current + hist_size - shift) % hist_size; + set = &chan_state->meas.buf[pos]; + + /* Calculate the average for each value */ + *avg = (struct l1sched_meas_set) { + .fn = set->fn, /* first burst */ + .rssi = (rssi_sum / num), + .toa256 = (toa256_sum / num), + .ci_cb = (ci_cb_sum / num), + }; + + LOGP(DMEAS, LOGL_DEBUG, "%s%sMeasurement AVG (num=%u, shift=%u): " + "RSSI %f, ToA256 %d, C/I %d cB\n", + chan_state->lchan ? gsm_lchan_name(chan_state->lchan) : "", + chan_state->lchan ? " " : "", + num, shift, avg->rssi, avg->toa256, avg->ci_cb); +} + +/* Lookup TDMA frame number of the N-th sample in the history */ +uint32_t trx_sched_lookup_fn(const struct l1sched_chan_state *chan_state, + const unsigned int shift) +{ + const unsigned int hist_size = ARRAY_SIZE(chan_state->meas.buf); + const unsigned int current = chan_state->meas.current; + unsigned int pos; + + /* First sample contains TDMA frame number of the first burst */ + pos = (current + hist_size - shift) % hist_size; + return chan_state->meas.buf[pos].fn; +} diff --git a/src/osmo-bts-trx/trx_if.c b/src/osmo-bts-trx/trx_if.c index 6c6d5ad9..b6b20e94 100644 --- a/src/osmo-bts-trx/trx_if.c +++ b/src/osmo-bts-trx/trx_if.c @@ -8,6 +8,7 @@ * Copyright (C) 2013 Andreas Eversberg <jolly@eversberg.eu> * Copyright (C) 2016-2017 Harald Welte <laforge@gnumonks.org> * Copyright (C) 2019 Vadim Yanitskiy <axilirator@gmail.com> + * Copyright (C) 2021 sysmocom - s.f.m.c. GmbH <info@sysmocom.de> * * All Rights Reserved * @@ -39,6 +40,7 @@ #include <osmocom/core/timer.h> #include <osmocom/core/talloc.h> #include <osmocom/core/bits.h> +#include <osmocom/core/fsm.h> #include <osmo-bts/phy_link.h> #include <osmo-bts/logging.h> @@ -47,8 +49,20 @@ #include "l1_if.h" #include "trx_if.h" +#include "trx_provision_fsm.h" -int transceiver_available = 0; +#include "btsconfig.h" + +#ifdef HAVE_SYSTEMTAP +/* include the generated probes header and put markers in code */ +#include "probes.h" +#define TRACE(probe) probe +#define TRACE_ENABLED(probe) probe ## _ENABLED() +#else +/* Wrap the probe to allow it to be removed when no systemtap available */ +#define TRACE(probe) +#define TRACE_ENABLED(probe) (0) +#endif /* HAVE_SYSTEMTAP */ /* * socket helper functions @@ -95,15 +109,19 @@ static int trx_clk_read_cb(struct osmo_fd *ofd, unsigned int what) { struct phy_link *plink = ofd->data; struct phy_instance *pinst = phy_instance_by_num(plink, 0); - char buf[1500]; - int len; + char buf[TRXC_MSG_BUF_SIZE]; + ssize_t len; uint32_t fn; OSMO_ASSERT(pinst); len = recv(ofd->fd, buf, sizeof(buf) - 1, 0); - if (len <= 0) + if (len <= 0) { + strerror_r(errno, (char *)buf, sizeof(buf)); + LOGPPHI(pinst, DTRX, LOGL_ERROR, + "recv() failed on TRXD with rc=%zd (%s)\n", len, buf); return len; + } buf[len] = '\0'; if (!!strncmp(buf, "IND CLOCK ", 10)) { @@ -119,12 +137,16 @@ static int trx_clk_read_cb(struct osmo_fd *ofd, unsigned int what) LOGPPHI(pinst, DTRX, LOGL_INFO, "Clock indication: fn=%u\n", fn); - if (fn >= GSM_HYPERFRAME) { - fn %= GSM_HYPERFRAME; + if (fn >= GSM_TDMA_HYPERFRAME) { + fn %= GSM_TDMA_HYPERFRAME; LOGPPHI(pinst, DTRX, LOGL_ERROR, "Indicated clock's FN is not " "wrapping correctly, correcting to fn=%u\n", fn); } + if (!plink->u.osmotrx.powered) { + LOGPPHI(pinst, DTRX, LOGL_NOTICE, "Ignoring CLOCK IND %u, TRX not yet powered on\n", fn); + return 0; + } /* inform core TRX clock handling code that a FN has been received */ trx_sched_clock(pinst->trx->bts, fn); @@ -140,8 +162,9 @@ static int trx_clk_read_cb(struct osmo_fd *ofd, unsigned int what) static void trx_ctrl_send(struct trx_l1h *l1h) { struct trx_ctrl_msg *tcm; - char buf[1500]; + char buf[TRXC_MSG_BUF_SIZE]; int len; + ssize_t snd_len; /* get first command */ if (llist_empty(&l1h->trx_ctrl_list)) @@ -153,7 +176,12 @@ static void trx_ctrl_send(struct trx_l1h *l1h) LOGPPHI(l1h->phy_inst, DTRX, LOGL_DEBUG, "Sending control '%s'\n", buf); /* send command */ - send(l1h->trx_ofd_ctrl.fd, buf, len+1, 0); + snd_len = send(l1h->trx_ofd_ctrl.fd, buf, len+1, 0); + if (snd_len <= 0) { + strerror_r(errno, (char *)buf, sizeof(buf)); + LOGPPHI(l1h->phy_inst, DTRX, LOGL_ERROR, + "send() failed on TRXC with rc=%zd (%s)\n", snd_len, buf); + } /* start timer */ osmo_timer_schedule(&l1h->trx_ctrl_timer, 2, 0); @@ -179,36 +207,32 @@ void trx_if_init(struct trx_l1h *l1h) { l1h->trx_ctrl_timer.cb = trx_ctrl_timer_cb; l1h->trx_ctrl_timer.data = l1h; + + /* initialize ctrl queue */ + INIT_LLIST_HEAD(&l1h->trx_ctrl_list); + + l1h->trx_ofd_ctrl.fd = -1; + l1h->trx_ofd_data.fd = -1; } /*! Send a new TRX control command. * \param[inout] l1h TRX Layer1 handle to which to send command - * \param[in] criticial + * \param[in] critical * \param[in] cb callback function to be called when valid response is * received. Type of cb depends on type of message. * \param[in] cmd zero-terminated string containing command * \param[in] fmt Format string (+ variable list of arguments) * \returns 0 on success; negative on error * - * The new ocommand will be added to the end of the control command + * The new command will be added to the end of the control command * queue. */ -static int trx_ctrl_cmd_cb(struct trx_l1h *l1h, int critical, void *cb, const char *cmd, - const char *fmt, ...) +int trx_ctrl_cmd_cb(struct trx_l1h *l1h, int critical, void *cb, + const char *cmd, const char *fmt, ...) { struct trx_ctrl_msg *tcm; struct trx_ctrl_msg *prev = NULL; va_list ap; - int pending; - - if (!transceiver_available && - !(!strcmp(cmd, "POWEROFF") || !strcmp(cmd, "POWERON"))) { - LOGPPHI(l1h->phy_inst, DTRX, LOGL_ERROR, "CTRL %s ignored: No clock from " - "transceiver, please fix!\n", cmd); - return -EIO; - } - - pending = !llist_empty(&l1h->trx_ctrl_list); /* create message */ tcm = talloc_zero(tall_bts_ctx, struct trx_ctrl_msg); @@ -231,71 +255,60 @@ static int trx_ctrl_cmd_cb(struct trx_l1h *l1h, int critical, void *cb, const ch tcm->cb = cb; /* Avoid adding consecutive duplicate messages, eg: two consecutive POWEROFF */ - if(pending) + if (!llist_empty(&l1h->trx_ctrl_list)) prev = llist_entry(l1h->trx_ctrl_list.prev, struct trx_ctrl_msg, list); - - if (!pending || - !(strcmp(tcm->cmd, prev->cmd) == 0 && strcmp(tcm->params, prev->params) == 0)) { - LOGPPHI(l1h->phy_inst, DTRX, LOGL_INFO, "Enqueuing TRX control command 'CMD %s%s%s'\n", - tcm->cmd, tcm->params_len ? " ":"", tcm->params); - llist_add_tail(&tcm->list, &l1h->trx_ctrl_list); + if (prev != NULL && !strcmp(tcm->cmd, prev->cmd) + && !strcmp(tcm->params, prev->params)) { + LOGPPHI(l1h->phy_inst, DTRX, LOGL_DEBUG, + "Not sending duplicate command '%s'\n", tcm->cmd); + talloc_free(tcm); + return 0; } - /* send message, if we didn't already have pending messages */ - if (!pending) + LOGPPHI(l1h->phy_inst, DTRX, LOGL_INFO, "Enqueuing TRX control command 'CMD %s%s%s'\n", + tcm->cmd, tcm->params_len ? " " : "", tcm->params); + llist_add_tail(&tcm->list, &l1h->trx_ctrl_list); + + /* send message, if we didn't already have pending messages. + * If we are in the rx_rsp callback code path, skip sending, the + * callback will do so when returning to it. */ + if (prev == NULL && !l1h->in_trx_ctrl_read_cb) trx_ctrl_send(l1h); return 0; } -#define trx_ctrl_cmd(l1h, critical, cmd, fmt, ...) trx_ctrl_cmd_cb(l1h, critical, NULL, cmd, fmt, ##__VA_ARGS__) /*! Send "POWEROFF" command to TRX */ -int trx_if_cmd_poweroff(struct trx_l1h *l1h) +int trx_if_cmd_poweroff(struct trx_l1h *l1h, trx_if_cmd_poweronoff_cb *cb) { - struct phy_instance *pinst = l1h->phy_inst; - if (pinst->num == 0) - return trx_ctrl_cmd(l1h, 1, "POWEROFF", ""); - else - return 0; + return trx_ctrl_cmd_cb(l1h, 1, cb, "POWEROFF", ""); } /*! Send "POWERON" command to TRX */ -int trx_if_cmd_poweron(struct trx_l1h *l1h) +int trx_if_cmd_poweron(struct trx_l1h *l1h, trx_if_cmd_poweronoff_cb *cb) { - struct phy_instance *pinst = l1h->phy_inst; - if (pinst->num == 0) - return trx_ctrl_cmd(l1h, 1, "POWERON", ""); - else - return 0; + return trx_ctrl_cmd_cb(l1h, 1, cb, "POWERON", ""); } -/*! Send "SETFORMAT" command to TRX: change TRXD header format version */ -int trx_if_cmd_setformat(struct trx_l1h *l1h, uint8_t ver) +/*! Send "SETFORMAT" command to TRX: change TRXD PDU version */ +int trx_if_cmd_setformat(struct trx_l1h *l1h, uint8_t ver, trx_if_cmd_generic_cb *cb) { LOGPPHI(l1h->phy_inst, DTRX, LOGL_INFO, - "Requesting TRXD header format version %u\n", ver); + "Requesting TRXD PDU version %u\n", ver); - return trx_ctrl_cmd(l1h, 0, "SETFORMAT", "%u", ver); + return trx_ctrl_cmd_cb(l1h, 0, cb, "SETFORMAT", "%u", ver); } /*! Send "SETTSC" command to TRX */ -int trx_if_cmd_settsc(struct trx_l1h *l1h, uint8_t tsc) +int trx_if_cmd_settsc(struct trx_l1h *l1h, uint8_t tsc, trx_if_cmd_generic_cb *cb) { - struct phy_instance *pinst = l1h->phy_inst; - if (pinst->phy_link->u.osmotrx.use_legacy_setbsic) - return 0; - - return trx_ctrl_cmd(l1h, 1, "SETTSC", "%d", tsc); + return trx_ctrl_cmd_cb(l1h, 1, cb, "SETTSC", "%d", tsc); } /*! Send "SETBSIC" command to TRX */ -int trx_if_cmd_setbsic(struct trx_l1h *l1h, uint8_t bsic) +int trx_if_cmd_setbsic(struct trx_l1h *l1h, uint8_t bsic, trx_if_cmd_generic_cb *cb) { - struct phy_instance *pinst = l1h->phy_inst; - if (!pinst->phy_link->u.osmotrx.use_legacy_setbsic) - return 0; - - return trx_ctrl_cmd(l1h, 1, "SETBSIC", "%d", bsic); + return trx_ctrl_cmd_cb(l1h, 1, cb, "SETBSIC", "%d", bsic); } /*! Send "SETRXGAIN" command to TRX */ @@ -304,10 +317,16 @@ int trx_if_cmd_setrxgain(struct trx_l1h *l1h, int db) return trx_ctrl_cmd(l1h, 0, "SETRXGAIN", "%d", db); } +/*! Send "NOMTXPOWER" command to TRX */ +int trx_if_cmd_getnompower(struct trx_l1h *l1h, trx_if_cmd_getnompower_cb *cb) +{ + return trx_ctrl_cmd_cb(l1h, 1, cb, "NOMTXPOWER", ""); +} + /*! Send "SETPOWER" command to TRX */ -int trx_if_cmd_setpower(struct trx_l1h *l1h, int db) +int trx_if_cmd_setpower_att(struct trx_l1h *l1h, int power_att_db, trx_if_cmd_setpower_att_cb *cb) { - return trx_ctrl_cmd(l1h, 0, "SETPOWER", "%d", db); + return trx_ctrl_cmd_cb(l1h, 0, cb, "SETPOWER", "%d", power_att_db); } /*! Send "SETMAXDLY" command to TRX, i.e. maximum delay for RACH bursts */ @@ -322,14 +341,27 @@ int trx_if_cmd_setmaxdlynb(struct trx_l1h *l1h, int dly) return trx_ctrl_cmd(l1h, 0, "SETMAXDLYNB", "%d", dly); } -/*! Send "SETSLOT" command to TRX: Configure Channel Combination for TS */ -int trx_if_cmd_setslot(struct trx_l1h *l1h, uint8_t tn, uint8_t type, trx_if_cmd_setslot_cb *cb) +/*! Send "SETSLOT" command to TRX: Configure Channel Combination and TSC for TS */ +int trx_if_cmd_setslot(struct trx_l1h *l1h, uint8_t tn, + trx_if_cmd_setslot_cb *cb) { - return trx_ctrl_cmd_cb(l1h, 1, cb, "SETSLOT", "%d %d", tn, type); + const struct trx_config *cfg = &l1h->config; + const struct phy_instance *pinst = l1h->phy_inst; + + if (cfg->setslot[tn].tsc_valid && cfg->setslot[tn].tsc_val != BTS_TSC(pinst->trx->bts)) { + /* PHY is instructed to use a custom TSC */ + return trx_ctrl_cmd_cb(l1h, 1, cb, "SETSLOT", "%u %u C%u/S%u", + tn, cfg->setslot[tn].slottype, + cfg->setslot[tn].tsc_val, + cfg->setslot[tn].tsc_set); + } else { /* PHY is instructed to use the default TSC from 'SETTSC' */ + return trx_ctrl_cmd_cb(l1h, 1, cb, "SETSLOT", "%u %u", + tn, cfg->setslot[tn].slottype); + } } /*! Send "RXTUNE" command to TRX: Tune Receiver to given ARFCN */ -int trx_if_cmd_rxtune(struct trx_l1h *l1h, uint16_t arfcn) +int trx_if_cmd_rxtune(struct trx_l1h *l1h, uint16_t arfcn, trx_if_cmd_generic_cb *cb) { struct phy_instance *pinst = l1h->phy_inst; uint16_t freq10; @@ -344,11 +376,11 @@ int trx_if_cmd_rxtune(struct trx_l1h *l1h, uint16_t arfcn) return -ENOTSUP; } - return trx_ctrl_cmd(l1h, 1, "RXTUNE", "%d", freq10 * 100); + return trx_ctrl_cmd_cb(l1h, 1, cb, "RXTUNE", "%d", freq10 * 100); } /*! Send "TXTUNE" command to TRX: Tune Transmitter to given ARFCN */ -int trx_if_cmd_txtune(struct trx_l1h *l1h, uint16_t arfcn) +int trx_if_cmd_txtune(struct trx_l1h *l1h, uint16_t arfcn, trx_if_cmd_generic_cb *cb) { struct phy_instance *pinst = l1h->phy_inst; uint16_t freq10; @@ -363,7 +395,7 @@ int trx_if_cmd_txtune(struct trx_l1h *l1h, uint16_t arfcn) return -ENOTSUP; } - return trx_ctrl_cmd(l1h, 1, "TXTUNE", "%d", freq10 * 100); + return trx_ctrl_cmd_cb(l1h, 1, cb, "TXTUNE", "%d", freq10 * 100); } /*! Send "HANDOVER" command to TRX: Enable handover RACH Detection on timeslot/sub-slot */ @@ -378,6 +410,12 @@ int trx_if_cmd_nohandover(struct trx_l1h *l1h, uint8_t tn, uint8_t ss) return trx_ctrl_cmd(l1h, 1, "NOHANDOVER", "%d %d", tn, ss); } +/*! Send "RFMUTE" command to TRX: Mute or Unmute RF transmission */ +int trx_if_cmd_rfmute(struct trx_l1h *l1h, bool mute) +{ + return trx_ctrl_cmd(l1h, 0, "RFMUTE", mute ? "1" : "0"); +} + struct trx_ctrl_rsp { char cmd[50]; char params[100]; @@ -387,6 +425,7 @@ struct trx_ctrl_rsp { static int parse_rsp(const char *buf_in, size_t len_in, struct trx_ctrl_rsp *rsp) { + size_t nlen, plen; char *p, *k; if (strncmp(buf_in, "RSP ", 4)) @@ -396,14 +435,17 @@ static int parse_rsp(const char *buf_in, size_t len_in, struct trx_ctrl_rsp *rsp if (!(p = strchr(buf_in + 4, ' '))) goto parse_err; - if (p - buf_in >= sizeof(rsp->cmd)) { - LOGP(DTRX, LOGL_ERROR, "cmd buffer too small %lu >= %lu\n", - p - buf_in, sizeof(rsp->cmd)); + /* Calculate length of the name part */ + nlen = p - (buf_in + 4); + + if (nlen >= sizeof(rsp->cmd)) { + LOGP(DTRX, LOGL_ERROR, "TRXC command name part is too long: " + "%zu >= %zu\n", nlen, sizeof(rsp->cmd)); goto parse_err; } - rsp->cmd[0] = '\0'; - strncat(rsp->cmd, buf_in + 4, p - buf_in - 4); + memcpy(&rsp->cmd[0], buf_in + 4, nlen); + rsp->cmd[nlen] = '\0'; /* Now comes the status code of the response */ p++; @@ -417,18 +459,22 @@ static int parse_rsp(const char *buf_in, size_t len_in, struct trx_ctrl_rsp *rsp else k = p + strlen(p); - if (strlen(k) >= sizeof(rsp->params)) { - LOGP(DTRX, LOGL_ERROR, "params buffer too small %lu >= %lu\n", - strlen(k), sizeof(rsp->params)); + /* Calculate length of the parameters part */ + plen = strlen(k); + + if (plen >= sizeof(rsp->params)) { + LOGP(DTRX, LOGL_ERROR, "TRXC command parameters part is too long: " + "%zu >= %zu\n", plen, sizeof(rsp->params)); goto parse_err; } - rsp->params[0] = '\0'; - strcat(rsp->params, k); + + memcpy(&rsp->params[0], k, plen); + rsp->params[plen] = '\0'; + return 0; parse_err: - LOGP(DTRX, LOGL_NOTICE, "Unknown message on ctrl port: %s\n", - buf_in); + LOGP(DTRX, LOGL_NOTICE, "Unknown TRXC message: %s\n", buf_in); return -1; } @@ -448,6 +494,35 @@ static bool cmd_matches_rsp(struct trx_ctrl_msg *tcm, struct trx_ctrl_rsp *rsp) return true; } +static int trx_ctrl_rx_rsp_poweron(struct trx_l1h *l1h, struct trx_ctrl_rsp *rsp) +{ + trx_if_cmd_poweronoff_cb *cb = (trx_if_cmd_poweronoff_cb*) rsp->cb; + + if (rsp->status != 0) + LOGPPHI(l1h->phy_inst, DTRX, LOGL_NOTICE, + "transceiver rejected POWERON command (%d), re-trying in a few seconds\n", + rsp->status); + + if (cb) + cb(l1h, true, rsp->status); + + /* If TRX fails, try again after 5 sec */ + return rsp->status == 0 ? 0 : 5; +} + +static int trx_ctrl_rx_rsp_poweroff(struct trx_l1h *l1h, struct trx_ctrl_rsp *rsp) +{ + trx_if_cmd_poweronoff_cb *cb = (trx_if_cmd_poweronoff_cb*) rsp->cb; + + if (rsp->status == 0) { + if (cb) + cb(l1h, false, rsp->status); + return 0; + } + + return -EINVAL; +} + static int trx_ctrl_rx_rsp_setslot(struct trx_l1h *l1h, struct trx_ctrl_rsp *rsp) { trx_if_cmd_setslot_cb *cb = (trx_if_cmd_setslot_cb*) rsp->cb; @@ -471,7 +546,7 @@ static int trx_ctrl_rx_rsp_setslot(struct trx_l1h *l1h, struct trx_ctrl_rsp *rsp return rsp->status == 0 ? 0 : -EINVAL; } -/* TRXD header format negotiation handler. +/* TRXD PDU format negotiation handler. * * If the transceiver does not support the format negotiation, it would * reject SETFORMAT with 'RSP ERR 1'. If the requested version is not @@ -481,42 +556,72 @@ static int trx_ctrl_rx_rsp_setslot(struct trx_l1h *l1h, struct trx_ctrl_rsp *rsp static int trx_ctrl_rx_rsp_setformat(struct trx_l1h *l1h, struct trx_ctrl_rsp *rsp) { + trx_if_cmd_generic_cb *cb; + /* Old transceivers reject 'SETFORMAT' with 'RSP ERR 1' */ if (strcmp(rsp->cmd, "SETFORMAT") != 0) { LOGPPHI(l1h->phy_inst, DTRX, LOGL_NOTICE, "Transceiver rejected the format negotiation command, " - "using legacy TRXD header format version (0)\n"); - l1h->config.trxd_hdr_ver_use = 0; + "using legacy TRXD PDU version (0)\n"); + if (rsp->cb) { + cb = (trx_if_cmd_generic_cb*) rsp->cb; + cb(l1h, 0); + } return 0; } /* Status shall indicate a proper version supported by the transceiver */ - if (rsp->status < 0 || rsp->status > l1h->config.trxd_hdr_ver_req) { + if (rsp->status < 0 || rsp->status > l1h->config.trxd_pdu_ver_req) { LOGPPHI(l1h->phy_inst, DTRX, LOGL_ERROR, "Transceiver indicated an out of range " - "header format version %d (requested %u)\n", - rsp->status, l1h->config.trxd_hdr_ver_req); + "PDU version %d (requested %u)\n", + rsp->status, l1h->config.trxd_pdu_ver_req); return -EINVAL; } - /* Transceiver may suggest a lower version (than requested) */ - if (rsp->status == l1h->config.trxd_hdr_ver_req) { - l1h->config.trxd_hdr_ver_use = rsp->status; - LOGPPHI(l1h->phy_inst, DTRX, LOGL_INFO, - "Using TRXD header format version %u\n", - l1h->config.trxd_hdr_ver_use); - } else { - LOGPPHI(l1h->phy_inst, DTRX, LOGL_DEBUG, - "Transceiver suggests TRXD header version %u (requested %u)\n", - rsp->status, l1h->config.trxd_hdr_ver_req); - /* Send another SETFORMAT with suggested version */ - l1h->config.trxd_hdr_ver_req = rsp->status; - trx_if_cmd_setformat(l1h, rsp->status); + if (rsp->cb) { + cb = (trx_if_cmd_generic_cb*) rsp->cb; + cb(l1h, rsp->status); } return 0; } +static int trx_ctrl_rx_rsp_nomtxpower(struct trx_l1h *l1h, struct trx_ctrl_rsp *rsp) +{ + trx_if_cmd_getnompower_cb *cb = (trx_if_cmd_getnompower_cb*) rsp->cb; + struct phy_instance *pinst = l1h->phy_inst; + int nominal_power; + + if (rsp->status) + LOGPPHI(pinst, DTRX, LOGL_ERROR, "transceiver NOMTXPOWER failed " + "with status %d. If your transceiver doesn't support this " + "command, then please set the nominal transmit power manually " + "through VTY cmd 'nominal-tx-power'.\n", + rsp->status); + if (cb) { + sscanf(rsp->params, "%d", &nominal_power); + cb(l1h, nominal_power, rsp->status); + } + return 0; +} + +static int trx_ctrl_rx_rsp_setpower(struct trx_l1h *l1h, struct trx_ctrl_rsp *rsp) +{ + trx_if_cmd_setpower_att_cb *cb = (trx_if_cmd_setpower_att_cb*) rsp->cb; + struct phy_instance *pinst = l1h->phy_inst; + int power_att; + + if (rsp->status) + LOGPPHI(pinst, DTRX, LOGL_ERROR, "transceiver SETPOWER failed with status %d\n", + rsp->status); + if (cb) { + sscanf(rsp->params, "%d", &power_att); + cb(l1h, power_att, rsp->status); + } + return 0; +} + /* -EINVAL: unrecoverable error, exit BTS * N > 0: try sending originating command again after N seconds * 0: Done with response, get originating command out from send queue @@ -525,28 +630,28 @@ static int trx_ctrl_rx_rsp(struct trx_l1h *l1h, struct trx_ctrl_rsp *rsp, struct trx_ctrl_msg *tcm) { - struct phy_instance *pinst = l1h->phy_inst; + trx_if_cmd_generic_cb *cb; - /* If TRX fails, try again after 1 sec */ if (strcmp(rsp->cmd, "POWERON") == 0) { - if (rsp->status == 0) { - if (pinst->phy_link->state != PHY_LINK_CONNECTED) - phy_link_state_set(pinst->phy_link, PHY_LINK_CONNECTED); - return 0; - } else { - LOGPPHI(l1h->phy_inst, DTRX, LOGL_NOTICE, - "transceiver rejected POWERON command (%d), re-trying in a few seconds\n", - rsp->status); - if (pinst->phy_link->state != PHY_LINK_SHUTDOWN) - phy_link_state_set(pinst->phy_link, PHY_LINK_SHUTDOWN); - return 5; - } + return trx_ctrl_rx_rsp_poweron(l1h, rsp); + } else if (strcmp(rsp->cmd, "POWEROFF") == 0) { + return trx_ctrl_rx_rsp_poweroff(l1h, rsp); } else if (strcmp(rsp->cmd, "SETSLOT") == 0) { return trx_ctrl_rx_rsp_setslot(l1h, rsp); /* We may get 'RSP ERR 1' if 'SETFORMAT' is not supported, * so that's why we should use tcm instead of rsp. */ } else if (strcmp(tcm->cmd, "SETFORMAT") == 0) { return trx_ctrl_rx_rsp_setformat(l1h, rsp); + } else if (strcmp(tcm->cmd, "NOMTXPOWER") == 0) { + return trx_ctrl_rx_rsp_nomtxpower(l1h, rsp); + } else if (strcmp(tcm->cmd, "SETPOWER") == 0) { + return trx_ctrl_rx_rsp_setpower(l1h, rsp); + } + + /* Generic callback if available */ + if (rsp->cb) { + cb = (trx_if_cmd_generic_cb*) rsp->cb; + cb(l1h, rsp->status); } if (rsp->status) { @@ -565,10 +670,11 @@ static int trx_ctrl_read_cb(struct osmo_fd *ofd, unsigned int what) { struct trx_l1h *l1h = ofd->data; struct phy_instance *pinst = l1h->phy_inst; - char buf[1500]; + char buf[TRXC_MSG_BUF_SIZE]; struct trx_ctrl_rsp rsp; int len, rc; struct trx_ctrl_msg *tcm; + bool flushed; len = recv(ofd->fd, buf, sizeof(buf) - 1, 0); if (len <= 0) @@ -581,8 +687,7 @@ static int trx_ctrl_read_cb(struct osmo_fd *ofd, unsigned int what) LOGPPHI(l1h->phy_inst, DTRX, LOGL_INFO, "Response message: '%s'\n", buf); /* abort timer and send next message, if any */ - if (osmo_timer_pending(&l1h->trx_ctrl_timer)) - osmo_timer_del(&l1h->trx_ctrl_timer); + osmo_timer_del(&l1h->trx_ctrl_timer); /* get command for response message */ if (llist_empty(&l1h->trx_ctrl_list)) { @@ -619,21 +724,34 @@ static int trx_ctrl_read_cb(struct osmo_fd *ofd, unsigned int what) rsp.cb = tcm->cb; /* check for response code */ + l1h->in_trx_ctrl_read_cb = true; rc = trx_ctrl_rx_rsp(l1h, &rsp, tcm); + /* Reset state: */ + flushed = l1h->flushed_while_in_trx_ctrl_read_cb; + l1h->flushed_while_in_trx_ctrl_read_cb = false; + l1h->in_trx_ctrl_read_cb = false; + if (rc == -EINVAL) goto rsp_error; /* re-schedule last cmd in rc seconds time */ if (rc > 0) { - osmo_timer_schedule(&l1h->trx_ctrl_timer, rc, 0); + /* The queue may have been flushed in the trx_ctrl_rx_rsp(): */ + if (!llist_empty(&l1h->trx_ctrl_list)) + osmo_timer_schedule(&l1h->trx_ctrl_timer, rc, 0); return 0; } - /* remove command from list, save it to last_acked and removed previous last_acked */ - llist_del(&tcm->list); - talloc_free(l1h->last_acked); - l1h->last_acked = tcm; + if (!flushed) { + /* Remove command from list, save it to last_acked and removed + * previous last_acked */ + llist_del(&tcm->list); + talloc_free(l1h->last_acked); + l1h->last_acked = tcm; + } /* else: tcm was freed by trx_if_flush(), do not access it. */ + + /* Send next message waiting in the list: */ trx_ctrl_send(l1h); return 0; @@ -649,93 +767,119 @@ rsp_error: * TRX burst data socket */ -/* Maximum DATA message length (header + burst) */ -#define TRX_DATA_MSG_MAX_LEN 512 - -/* Common header length: 1/2 VER + 1/2 TDMA TN + 4 TDMA FN */ -#define TRX_CHDR_LEN (1 + 4) -/* Uplink v0 header length: 1 RSSI + 2 ToA256 */ -#define TRX_UL_V0HDR_LEN (TRX_CHDR_LEN + 1 + 2) -/* Uplink v1 header length: + 1 MTS + 2 C/I */ +/* Uplink TRXDv0 header length: TDMA TN + FN + RSSI + ToA256 */ +#define TRX_UL_V0HDR_LEN (1 + 4 + 1 + 2) +/* Uplink TRXDv1 header length: additional MTS + C/I */ #define TRX_UL_V1HDR_LEN (TRX_UL_V0HDR_LEN + 1 + 2) +/* Uplink TRXDv2 header length: TDMA TN + TRXN + MTS + RSSI + ToA256 + C/I */ +#define TRX_UL_V2HDR_LEN (1 + 1 + 1 + 1 + 2 + 2) + +/* Minimum Uplink TRXD header length for all PDU versions */ +static const uint8_t trx_data_rx_hdr_len[] = { + TRX_UL_V0HDR_LEN, /* TRXDv0 */ + TRX_UL_V1HDR_LEN, /* TRXDv1 */ + TRX_UL_V2HDR_LEN, /* TRXDv2 */ +}; -/* TRXD header dissector for version 0 */ -static int trx_data_handle_hdr_v0(struct trx_l1h *l1h, - struct trx_ul_burst_ind *bi, - const uint8_t *buf, size_t buf_len) -{ - /* Make sure we have enough data */ - if (buf_len < TRX_UL_V0HDR_LEN) { - LOGPPHI(l1h->phy_inst, DTRX, LOGL_ERROR, - "Short read on TRXD, missing version 0 header " - "(len=%zu vs expected %d)\n", buf_len, TRX_UL_V0HDR_LEN); - return -EIO; - } +static const uint8_t trx_data_mod_val[] = { + [TRX_MOD_T_GMSK] = 0x00, /* .00xx... */ + [TRX_MOD_T_8PSK] = 0x20, /* .010x... */ + [TRX_MOD_T_AQPSK] = 0x60, /* .11xx... */ +}; +/* Header dissector for TRXDv0 (and part of TRXDv1) */ +static inline void trx_data_handle_hdr_v0_part(struct trx_ul_burst_ind *bi, + const uint8_t *buf) +{ bi->tn = buf[0] & 0b111; bi->fn = osmo_load32be(buf + 1); bi->rssi = -(int8_t)buf[5]; bi->toa256 = (int16_t) osmo_load16be(buf + 6); - - if (bi->fn >= GSM_HYPERFRAME) { - LOGPPHI(l1h->phy_inst, DTRX, LOGL_ERROR, - "Illegal TDMA fn=%u\n", bi->fn); - return -EINVAL; - } - - return TRX_UL_V0HDR_LEN; } -/* TRXD header dissector for version 0x01 */ -static int trx_data_handle_hdr_v1(struct trx_l1h *l1h, +/* TRXD header dissector for version 0x00 */ +static int trx_data_handle_hdr_v0(struct phy_instance *phy_inst, struct trx_ul_burst_ind *bi, const uint8_t *buf, size_t buf_len) { - uint8_t mts; - int rc; + /* Parse TRXDv0 specific header part */ + trx_data_handle_hdr_v0_part(bi, buf); + buf_len -= TRX_UL_V0HDR_LEN; - /* Make sure we have enough data */ - if (buf_len < TRX_UL_V1HDR_LEN) { - LOGPPHI(l1h->phy_inst, DTRX, LOGL_ERROR, - "Short read on TRXD, missing version 1 header " - "(len=%zu vs expected %d)\n", buf_len, TRX_UL_V1HDR_LEN); - return -EIO; + /* Guess modulation and burst length by the rest octets. + * NOTE: a legacy transceiver may append two garbage bytes. */ + switch (buf_len) { + case EGPRS_BURST_LEN + 2: + case EGPRS_BURST_LEN: + bi->mod = TRX_MOD_T_8PSK; + break; + case GSM_BURST_LEN + 2: + case GSM_BURST_LEN: + bi->mod = TRX_MOD_T_GMSK; + break; + default: + LOGPPHI(phy_inst, DTRX, LOGL_NOTICE, + "Rx TRXD PDU with odd burst length %zu\n", buf_len); + return -EINVAL; } - /* Parse v0 specific part */ - rc = trx_data_handle_hdr_v0(l1h, bi, buf, buf_len); - if (rc < 0) - return rc; - - /* Move closer to the v1 specific part */ - buf_len -= rc; - buf += rc; + return TRX_UL_V0HDR_LEN; +} - /* IDLE / NOPE frame indication */ - if (buf[0] & (1 << 7)) { +/* Parser for MTS (Modulation and Training Sequence) */ +static inline int trx_data_parse_mts(struct phy_instance *phy_inst, + struct trx_ul_burst_ind *bi, + const uint8_t mts) +{ + if (mts & (1 << 7)) { bi->flags |= TRX_BI_F_NOPE_IND; - return TRX_UL_V1HDR_LEN; + return 0; } - /* Modulation info and TSC set */ - mts = (buf[0] >> 3) & 0b1111; - if ((mts & 0b1100) == 0x00) { - bi->bt = TRX_BURST_GMSK; - bi->tsc_set = mts & 0b11; - bi->flags |= TRX_BI_F_MOD_TYPE; - } else if ((mts & 0b0100) == 0b0100) { - bi->bt = TRX_BURST_8PSK; - bi->tsc_set = mts & 0b1; - bi->flags |= TRX_BI_F_MOD_TYPE; + /* | 7 6 5 4 3 2 1 0 | Bitmask / description + * | . 0 0 X X . . . | GMSK, 4 TSC sets (0..3) + * | . 0 1 0 X . . . | 8-PSK, 2 TSC sets (0..1) + * | . 0 1 1 0 . . . | GMSK, Access Burst */ + if ((mts >> 5) == 0x00) { + bi->mod = TRX_MOD_T_GMSK; + bi->tsc_set = (mts >> 3) & 0x03; + } else if ((mts >> 4) == 0x02) { + bi->mod = TRX_MOD_T_8PSK; + bi->tsc_set = (mts >> 3) & 0x01; + } else if ((mts >> 3) == 0x06) { + bi->flags |= TRX_BI_F_ACCESS_BURST; + bi->mod = TRX_MOD_T_GMSK; + bi->tsc_set = 0; } else { - LOGPPHI(l1h->phy_inst, DTRX, LOGL_ERROR, - "Indicated modulation 0x%02x is not supported\n", mts & 0b1110); + LOGPPHI(phy_inst, DTRX, LOGL_ERROR, + "Rx TRXD PDU with unknown or not supported " + "modulation (MTS=0x%02x)\n", mts); return -ENOTSUP; } /* Training Sequence Code */ - bi->tsc = buf[0] & 0b111; - bi->flags |= TRX_BI_F_TS_INFO; + bi->tsc = mts & 0x07; + + bi->flags |= (TRX_BI_F_MOD_TYPE | TRX_BI_F_TS_INFO); + + return 0; +} + +/* TRXD header dissector for version 0x01 */ +static int trx_data_handle_hdr_v1(struct phy_instance *phy_inst, + struct trx_ul_burst_ind *bi, + const uint8_t *buf, size_t buf_len) +{ + int rc; + + /* Parse TRXDv0 specific header part */ + trx_data_handle_hdr_v0_part(bi, buf); + buf += TRX_UL_V0HDR_LEN; + + /* MTS (Modulation and Training Sequence) */ + rc = trx_data_parse_mts(phy_inst, bi, buf[0]); + if (OSMO_UNLIKELY(rc < 0)) + return rc; /* C/I: Carrier-to-Interference ratio (in centiBels) */ bi->ci_cb = (int16_t) osmo_load16be(buf + 1); @@ -744,64 +888,82 @@ static int trx_data_handle_hdr_v1(struct trx_l1h *l1h, return TRX_UL_V1HDR_LEN; } -/* TRXD burst handler for header version 0 */ -static int trx_data_handle_burst_v0(struct trx_l1h *l1h, - struct trx_ul_burst_ind *bi, - const uint8_t *buf, size_t buf_len) +/* TRXD header dissector for version 0x01 */ +static int trx_data_handle_pdu_v2(struct phy_instance *phy_inst, + struct trx_ul_burst_ind *bi, + const uint8_t *buf, size_t buf_len) { - size_t i; + int rc; - /* Verify burst length */ - switch (buf_len) { - /* Legacy transceivers append two padding bytes */ - case EGPRS_BURST_LEN + 2: - case GSM_BURST_LEN + 2: - bi->burst_len = buf_len - 2; - break; - case EGPRS_BURST_LEN: - case GSM_BURST_LEN: - bi->burst_len = buf_len; - break; + /* TDMA timeslot number (other bits are RFU) */ + bi->tn = buf[0] & 0x07; - default: - LOGPPHI(l1h->phy_inst, DTRX, LOGL_NOTICE, - "Rx TRXD message with odd burst length %zu\n", buf_len); - return -EINVAL; - } + if (buf[1] & (1 << 7)) /* BATCH.ind */ + bi->flags |= TRX_BI_F_BATCH_IND; + if (buf[1] & (1 << 6)) /* VAMOS.ind */ + bi->flags |= TRX_BI_F_SHADOW_IND; - /* Convert unsigned soft-bits [254..0] to soft-bits [-127..127] */ - for (i = 0; i < bi->burst_len; i++) { - if (buf[i] == 255) - bi->burst[i] = -127; - else - bi->burst[i] = 127 - buf[i]; + /* TRX (RF channel) number */ + bi->trx_num = buf[1] & 0x3f; + bi->flags |= TRX_BI_F_TRX_NUM; + + /* MTS (Modulation and Training Sequence) */ + rc = trx_data_parse_mts(phy_inst, bi, buf[2]); + if (OSMO_UNLIKELY(rc < 0)) + return rc; + + bi->rssi = -(int8_t)buf[3]; + bi->toa256 = (int16_t) osmo_load16be(&buf[4]); + bi->ci_cb = (int16_t) osmo_load16be(&buf[6]); + bi->flags |= TRX_BI_F_CI_CB; + + /* TDMA frame number is absent in batched PDUs */ + if (bi->_num_pdus == 0) { + if (OSMO_UNLIKELY(buf_len < sizeof(bi->fn) + TRX_UL_V2HDR_LEN)) { + LOGPPHI(phy_inst, DTRX, LOGL_ERROR, + "Rx malformed TRXDv2 PDU: not enough bytes " + "to parse TDMA frame number\n"); + return -EINVAL; + } + + bi->fn = osmo_load32be(buf + TRX_UL_V2HDR_LEN); + return TRX_UL_V2HDR_LEN + sizeof(bi->fn); } - return 0; + return TRX_UL_V2HDR_LEN; } -/* TRXD burst handler for header version 1 */ -static int trx_data_handle_burst_v1(struct trx_l1h *l1h, - struct trx_ul_burst_ind *bi, - const uint8_t *buf, size_t buf_len) +/* TRXD burst handler (version independent) */ +static int trx_data_handle_burst(struct trx_ul_burst_ind *bi, + const uint8_t *buf, size_t buf_len) { + size_t i; + + /* NOPE.ind contains no burst */ + if (bi->flags & TRX_BI_F_NOPE_IND) { + bi->burst_len = 0; + return 0; + } + /* Modulation types defined in 3GPP TS 45.002 */ static const size_t bl[] = { - [TRX_BURST_GMSK] = 148, /* 1 bit per symbol */ - [TRX_BURST_8PSK] = 444, /* 3 bits per symbol */ + [TRX_MOD_T_GMSK] = 148, /* 1 bit per symbol */ + [TRX_MOD_T_8PSK] = 444, /* 3 bits per symbol */ }; - /* Verify burst length */ - if (bl[bi->bt] != buf_len) { - LOGPPHI(l1h->phy_inst, DTRX, LOGL_NOTICE, - "Rx TRXD message with odd burst length %zu, " - "expected %zu\n", buf_len, bl[bi->bt]); + bi->burst_len = bl[bi->mod]; + if (OSMO_UNLIKELY(buf_len < bi->burst_len)) return -EINVAL; + + /* Convert unsigned soft-bits [254..0] to soft-bits [-127..127] */ + for (i = 0; i < bi->burst_len; i++) { + if (buf[i] == 255) + bi->burst[i] = -127; + else + bi->burst[i] = 127 - buf[i]; } - /* The burst format is the same as for version 0. - * NOTE: other modulation types to be handled separately. */ - return trx_data_handle_burst_v0(l1h, bi, buf, buf_len); + return 0; } static const char *trx_data_desc_msg(const struct trx_ul_burst_ind *bi) @@ -811,8 +973,8 @@ static const char *trx_data_desc_msg(const struct trx_ul_burst_ind *bi) /* Modulation types defined in 3GPP TS 45.002 */ static const char *mod_names[] = { - [TRX_BURST_GMSK] = "GMSK", - [TRX_BURST_8PSK] = "8-PSK", + [TRX_MOD_T_GMSK] = "GMSK", + [TRX_MOD_T_8PSK] = "8-PSK", }; /* Initialize the string buffer */ @@ -821,279 +983,236 @@ static const char *trx_data_desc_msg(const struct trx_ul_burst_ind *bi) /* Common TDMA parameters */ OSMO_STRBUF_PRINTF(sb, "tn=%u fn=%u", bi->tn, bi->fn); - /* Nothing else to print for NOPE.ind */ - if (bi->flags & TRX_BI_F_NOPE_IND) - return buf; + /* TRX (RF channel number) */ + if (bi->flags & TRX_BI_F_TRX_NUM) + OSMO_STRBUF_PRINTF(sb, " trx_num=%u", bi->trx_num); /* RSSI and ToA256 */ OSMO_STRBUF_PRINTF(sb, " rssi=%d toa256=%d", bi->rssi, bi->toa256); + /* C/I: Carrier-to-Interference ratio (in centiBels) */ + if (bi->flags & TRX_BI_F_CI_CB) + OSMO_STRBUF_PRINTF(sb, " C/I=%d cB", bi->ci_cb); + + /* Nothing else to print for NOPE.ind */ + if (bi->flags & TRX_BI_F_NOPE_IND) + return buf; + /* Modulation and TSC set */ if (bi->flags & TRX_BI_F_MOD_TYPE) - OSMO_STRBUF_PRINTF(sb, " mod=%s", mod_names[bi->bt]); + OSMO_STRBUF_PRINTF(sb, " mod=%s", mod_names[bi->mod]); /* Training Sequence Code */ if (bi->flags & TRX_BI_F_TS_INFO) OSMO_STRBUF_PRINTF(sb, " set=%u tsc=%u", bi->tsc_set, bi->tsc); - /* C/I: Carrier-to-Interference ratio (in centiBels) */ - if (bi->flags & TRX_BI_F_CI_CB) - OSMO_STRBUF_PRINTF(sb, " C/I=%d cB", bi->ci_cb); - /* Burst length */ OSMO_STRBUF_PRINTF(sb, " burst_len=%zu", bi->burst_len); return buf; } -/* Parse TRXD message from transceiver, compose an UL burst indication. - * - * This message contains a demodulated Uplink burst with fixed-size - * header preceding the burst bits. The header consists of the common - * and message specific part. - * - * +---------------+-----------------+------------+ - * | common header | specific header | burst bits | - * +---------------+-----------------+------------+ - * - * Common header is the same as for Downlink message: - * - * +-----------------+----------------+-------------------+ - * | VER (1/2 octet) | TN (1/2 octet) | FN (4 octets, BE) | - * +-----------------+----------------+-------------------+ - * - * and among with TDMA parameters, contains the version indicator: - * - * +-----------------+------------------------+ - * | 7 6 5 4 3 2 1 0 | bit numbers | - * +-----------------+------------------------+ - * | X X X X . . . . | header version (0..15) | - * +-----------------+------------------------+ - * | . . . . . X X X | TDMA TN (0..7) | - * +-----------------+------------------------+ - * | . . . . X . . . | RESERVED (0) | - * +-----------------+------------------------+ - * - * which is encoded in 4 MSB bits of the first octet, which used to be - * zero-initialized due to the value range of TDMA TN. Therefore, the - * old header format has implicit version 0x00. - * - * The message specific header has the following structure: - * - * == Version 0x00 - * - * +------+-----+--------------------+ - * | RSSI | ToA | soft-bits (254..0) | - * +------+-----+--------------------+ - * - * == Version 0x01 - * - * +------+-----+-----+-----+--------------------+ - * | RSSI | ToA | MTS | C/I | soft-bits (254..0) | - * +------+-----+-----+-----+--------------------+ - * - * where: - * - * - RSSI (1 octet) - Received Signal Strength Indication - * encoded without the negative sign. - * - ToA (2 octets) - Timing of Arrival in units of 1/256 - * of symbol (big endian). - * - MTS (1 octet) - Modulation and Training Sequence info. - * - C/I (2 octets) - Carrier-to-Interference ratio (big endian). - * - * == Coding of MTS: Modulation and Training Sequence info - * - * 3GPP TS 45.002 version 15.1.0 defines several modulation types, - * and a few sets of training sequences for each type. The most - * common are GMSK and 8-PSK (which is used in EDGE). - * - * +-----------------+---------------------------------------+ - * | 7 6 5 4 3 2 1 0 | bit numbers (value range) | - * +-----------------+---------------------------------------+ - * | . . . . . X X X | Training Sequence Code (0..7) | - * +-----------------+---------------------------------------+ - * | . X X X X . . . | Modulation, TS set number (see below) | - * +-----------------+---------------------------------------+ - * | X . . . . . . . | IDLE / nope frame indication (0 or 1) | - * +-----------------+---------------------------------------+ - * - * The bit number 7 (MSB) is set to high when either nothing has been - * detected, or during IDLE frames, so we can deliver noise levels, - * and avoid clock gaps on the L1 side. Other bits are ignored, - * and should be set to low (0) in this case. L16 shall be set to 0x00. - * - * == Coding of modulation and TS set number - * - * GMSK has 4 sets of training sequences (see tables 5.2.3a-d), - * while 8-PSK (see tables 5.2.3f-g) and the others have 2 sets. - * Access and Synchronization bursts also have several synch. - * sequences. - * - * +-----------------+---------------------------------------+ - * | 7 6 5 4 3 2 1 0 | bit numbers (value range) | - * +-----------------+---------------------------------------+ - * | . 0 0 X X . . . | GMSK, 4 TS sets (0..3) | - * +-----------------+---------------------------------------+ - * | . 0 1 0 X . . . | 8-PSK, 2 TS sets (0..1) | - * +-----------------+---------------------------------------+ - * | . 0 1 1 X . . . | AQPSK, 2 TS sets (0..1) | - * +-----------------+---------------------------------------+ - * | . 1 0 0 X . . . | 16QAM, 2 TS sets (0..1) | - * +-----------------+---------------------------------------+ - * | . 1 0 1 X . . . | 32QAM, 2 TS sets (0..1) | - * +-----------------+---------------------------------------+ - * | . 1 1 1 X . . . | RESERVED (0) | - * +-----------------+---------------------------------------+ - * - * NOTE: we only support GMSK and 8-PSK. - * - * == C/I: Carrier-to-Interference ratio - * - * The C/I value can be computed from the training sequence of each - * burst, where we can compare the "ideal" training sequence with - * the actual training sequence and then express that in centiBels. - * - * == Coding of the burst bits - * - * Unlike to be transmitted bursts, the received bursts are designated - * using the soft-bits notation, so the receiver can indicate its - * assurance from 0 to -127 that a given bit is 1, and from 0 to +127 - * that a given bit is 0. - * - * Each soft-bit (-127..127) of the burst is encoded as an unsigned - * value in range (254..0) respectively using the constant shift. - * - */ +/* TRXD buffer used by Rx/Tx handlers */ +static uint8_t trx_data_buf[TRXD_MSG_BUF_SIZE]; + +/* Parse TRXD message from transceiver, compose an UL burst indication. */ static int trx_data_read_cb(struct osmo_fd *ofd, unsigned int what) { + const uint8_t *buf = &trx_data_buf[0]; struct trx_l1h *l1h = ofd->data; - uint8_t buf[TRX_DATA_MSG_MAX_LEN]; struct trx_ul_burst_ind bi; ssize_t hdr_len, buf_len; - uint8_t hdr_ver; - int rc; + uint8_t pdu_ver; - buf_len = recv(ofd->fd, buf, sizeof(buf), 0); - if (buf_len <= 0) { + buf_len = recv(ofd->fd, trx_data_buf, sizeof(trx_data_buf), 0); + if (OSMO_UNLIKELY(buf_len <= 0)) { + strerror_r(errno, (char *) trx_data_buf, sizeof(trx_data_buf)); LOGPPHI(l1h->phy_inst, DTRX, LOGL_ERROR, - "recv() failed on TRXD with rc=%zd\n", buf_len); + "recv() failed on TRXD with rc=%zd (%s)\n", + buf_len, trx_data_buf); return buf_len; } - /* Pre-clean (initialize) the flags */ - bi.flags = 0x00; + /* Parse PDU version first */ + pdu_ver = buf[0] >> 4; - /* Parse the header depending on its version */ - hdr_ver = buf[0] >> 4; - switch (hdr_ver) { - case 0: - /* Legacy protocol has no version indicator */ - hdr_len = trx_data_handle_hdr_v0(l1h, &bi, buf, buf_len); - break; - case 1: - hdr_len = trx_data_handle_hdr_v1(l1h, &bi, buf, buf_len); - break; - default: + /* Make sure that PDU version matches our expectations */ + if (OSMO_UNLIKELY(pdu_ver != l1h->config.trxd_pdu_ver_use)) { LOGPPHI(l1h->phy_inst, DTRX, LOGL_ERROR, - "TRXD header version %u is not supported\n", hdr_ver); - return -ENOTSUP; + "Rx TRXD PDU with unexpected version %u (expected %u)\n", + pdu_ver, l1h->config.trxd_pdu_ver_use); + return -EIO; } - /* Header parsing error */ - if (hdr_len < 0) - return hdr_len; + /* We're about to parse the first PDU */ + bi._num_pdus = 0; - /* TODO: we can use NOPE indications to get noise levels on IDLE - * TDMA frames, and properly drive scheduler if nothing has been - * detected on non-IDLE channels. */ - if (bi.flags & TRX_BI_F_NOPE_IND) { - LOGPPHI(l1h->phy_inst, DTRX, LOGL_NOTICE, - "IDLE / NOPE indications are not (yet) supported\n"); - return -ENOTSUP; - } + /* Starting from TRXDv2, there can be batched PDUs */ + do { + /* (Re)initialize the flags */ + bi.flags = 0x00; - /* We're done with the header now */ - buf_len -= hdr_len; + /* Make sure that we have enough bytes to parse the header */ + if (OSMO_UNLIKELY(buf_len < trx_data_rx_hdr_len[pdu_ver])) { + LOGPPHI(l1h->phy_inst, DTRX, LOGL_ERROR, + "Rx malformed TRXDv%u PDU: len=%zd < expected %u\n", + pdu_ver, buf_len, trx_data_rx_hdr_len[pdu_ver]); + return -EINVAL; + } - /* Handle burst bits */ - switch (hdr_ver) { - case 0: - rc = trx_data_handle_burst_v0(l1h, &bi, buf + hdr_len, buf_len); - break; - case 1: - rc = trx_data_handle_burst_v1(l1h, &bi, buf + hdr_len, buf_len); - break; - default: - /* Shall not happen, just to make GCC happy */ - OSMO_ASSERT(0); - } + /* Parse header depending on the PDU version */ + switch (pdu_ver) { + case 0: /* TRXDv0 */ + hdr_len = trx_data_handle_hdr_v0(l1h->phy_inst, &bi, buf, buf_len); + break; + case 1: /* TRXDv1 */ + hdr_len = trx_data_handle_hdr_v1(l1h->phy_inst, &bi, buf, buf_len); + break; + case 2: /* TRXDv2 */ + hdr_len = trx_data_handle_pdu_v2(l1h->phy_inst, &bi, buf, buf_len); + break; + default: + /* Shall not happen */ + OSMO_ASSERT(0); + } - /* Burst parsing error */ - if (rc < 0) - return rc; + /* Header parsing error */ + if (OSMO_UNLIKELY(hdr_len < 0)) + return hdr_len; - /* Print header & burst info */ - LOGPPHI(l1h->phy_inst, DTRX, LOGL_DEBUG, "Rx %s (hdr_ver=%u): %s\n", - (bi.flags & TRX_BI_F_NOPE_IND) ? "NOPE.ind" : "UL burst", - hdr_ver, trx_data_desc_msg(&bi)); + if (OSMO_UNLIKELY(bi.fn >= GSM_TDMA_HYPERFRAME)) { + LOGPPHI(l1h->phy_inst, DTRX, LOGL_ERROR, + "Rx malformed TRXDv%u PDU: illegal TDMA fn=%u\n", + pdu_ver, bi.fn); + return -EINVAL; + } + + /* We're done with the header now */ + buf_len -= hdr_len; + buf += hdr_len; + + /* Calculate burst length and parse it (if present) */ + if (OSMO_UNLIKELY(trx_data_handle_burst(&bi, buf, buf_len) != 0)) { + LOGPPHI(l1h->phy_inst, DTRX, LOGL_ERROR, + "Rx malformed TRXDv%u PDU: odd burst length=%zd\n", + pdu_ver, buf_len); + return -EINVAL; + } - /* feed received burst into scheduler code */ - trx_sched_ul_burst(&l1h->l1s, &bi); + /* We're done with the burst bits now */ + buf_len -= bi.burst_len; + buf += bi.burst_len; + + /* Print header & burst info */ + LOGPPHI(l1h->phy_inst, DTRX, LOGL_DEBUG, "Rx %s (pdu_ver=%u): %s\n", + (bi.flags & TRX_BI_F_NOPE_IND) ? "NOPE.ind" : "UL burst", + pdu_ver, trx_data_desc_msg(&bi)); + + /* Number of processed PDUs */ + bi._num_pdus++; + + /* feed received burst into scheduler code */ + TRACE(OSMO_BTS_TRX_UL_DATA_START(l1h->phy_inst->trx->nr, bi.tn, bi.fn)); + trx_sched_route_burst_ind(l1h->phy_inst->trx, &bi); + TRACE(OSMO_BTS_TRX_UL_DATA_DONE(l1h->phy_inst->trx->nr, bi.tn, bi.fn)); + } while (bi.flags & TRX_BI_F_BATCH_IND); return 0; } /*! Send burst data for given FN/timeslot to TRX * \param[inout] l1h TRX Layer1 handle referring to TX - * \param[in] tn Timeslot Number (0..7) - * \param[in] fn GSM Frame Number - * \param[in] pwr Transmit Power to use - * \param[in] bits Unpacked bits to be transmitted - * \param[in] nbits Number of \a bits + * \param[in] br Downlink burst request structure * \returns 0 on success; negative on error */ -int trx_if_send_burst(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, uint8_t pwr, - const ubit_t *bits, uint16_t nbits) +int trx_if_send_burst(struct trx_l1h *l1h, const struct trx_dl_burst_req *br) { - uint8_t hdr_ver = l1h->config.trxd_hdr_ver_use; - uint8_t buf[TRX_DATA_MSG_MAX_LEN]; - - if ((nbits != GSM_BURST_LEN) && (nbits != EGPRS_BURST_LEN)) { - LOGPPHI(l1h->phy_inst, DTRX, LOGL_ERROR, "Tx burst length %u invalid\n", nbits); - return -1; + uint8_t pdu_ver = l1h->config.trxd_pdu_ver_use; + static uint8_t *buf = &trx_data_buf[0]; + static uint8_t *last_pdu = NULL; + static unsigned int pdu_num = 0; + ssize_t snd_len, buf_len; + + /* Make sure that the PHY is powered on */ + if (OSMO_UNLIKELY(!trx_if_powered(l1h))) { + LOGPPHI(l1h->phy_inst, DTRX, LOGL_ERROR, + "Ignoring Tx data, transceiver is powered off\n"); + return -ENODEV; } - LOGPPHI(l1h->phy_inst, DTRX, LOGL_DEBUG, - "Tx burst (hdr_ver=%u): tn=%u fn=%u pwr=%u\n", - hdr_ver, tn, fn, pwr); + /* Burst batching breaker */ + if (br == NULL) { + if (pdu_num > 0) + goto sendall; + return -ENOMSG; + } - switch (hdr_ver) { - case 0: - case 1: - /* Both versions have the same header format */ + /* Pointer to the last encoded PDU */ + last_pdu = &buf[0]; + + switch (pdu_ver) { + /* Both versions have the same PDU format */ + case 0: /* TRXDv0 */ + case 1: /* TRXDv1 */ + buf[0] = ((pdu_ver & 0x0f) << 4) | br->tn; + osmo_store32be(br->fn, buf + 1); + buf[5] = br->att; + buf += 6; + break; + case 2: /* TRXDv2 */ + buf[0] = br->tn; + /* BATCH.ind will be unset in the last PDU */ + buf[1] = (br->trx_num & 0x3f) | (1 << 7); + buf[2] = trx_data_mod_val[br->mod] + | (br->tsc_set << 3) + | (br->tsc & 0x07); + buf[3] = br->att; + buf[4] = (uint8_t) br->scpir; + buf[5] = buf[6] = buf[7] = 0x00; /* Spare */ + /* Some fields are not present in batched PDUs */ + if (pdu_num == 0) { + buf[0] |= (pdu_ver & 0x0f) << 4; + osmo_store32be(br->fn, buf + 8); + buf += 4; + } + buf += 8; break; - default: - LOGPPHI(l1h->phy_inst, DTRX, LOGL_ERROR, - "Requested TRXD header version %u is not supported\n", hdr_ver); - return -ENOTSUP; + /* Shall not happen */ + OSMO_ASSERT(0); } - buf[0] = ((hdr_ver & 0x0f) << 4) | tn; - buf[1] = (fn >> 24) & 0xff; - buf[2] = (fn >> 16) & 0xff; - buf[3] = (fn >> 8) & 0xff; - buf[4] = (fn >> 0) & 0xff; - buf[5] = pwr; - /* copy ubits {0,1} */ - memcpy(buf + 6, bits, nbits); + memcpy(buf, br->burst, br->burst_len); + buf += br->burst_len; + + /* One more PDU in the buffer */ + pdu_num++; + + /* TRXDv2: wait for the batching breaker */ + if (pdu_ver >= 2) + return 0; + +sendall: + LOGPPHI(l1h->phy_inst, DTRX, LOGL_DEBUG, + "Tx TRXDv%u datagram with %u PDU(s)\n", + pdu_ver, pdu_num); + + /* TRXDv2: unset BATCH.ind in the last PDU */ + if (pdu_ver >= 2) + last_pdu[1] &= ~(1 << 7); + + buf_len = buf - &trx_data_buf[0]; + buf = &trx_data_buf[0]; + pdu_num = 0; - /* we must be sure that we have clock, and we have sent all control - * data */ - if (transceiver_available && llist_empty(&l1h->trx_ctrl_list)) { - send(l1h->trx_ofd_data.fd, buf, nbits + 6, 0); - } else - LOGPPHI(l1h->phy_inst, DTRX, LOGL_ERROR, "Ignoring TX data, transceiver offline.\n"); + snd_len = send(l1h->trx_ofd_data.fd, trx_data_buf, buf_len, 0); + if (OSMO_UNLIKELY(snd_len <= 0)) { + strerror_r(errno, (char *) trx_data_buf, sizeof(trx_data_buf)); + LOGPPHI(l1h->phy_inst, DTRX, LOGL_ERROR, + "send() failed on TRXD with rc=%zd (%s)\n", + snd_len, trx_data_buf); + return -2; + } return 0; } @@ -1116,12 +1235,21 @@ void trx_if_flush(struct trx_l1h *l1h) talloc_free(tcm); } talloc_free(l1h->last_acked); + l1h->last_acked = NULL; + + /* Tx queue is now empty, so there's no point in keeping the retrans timer armed: */ + osmo_timer_del(&l1h->trx_ctrl_timer); + + /* If we are in read_cb, signal to the returning code path that we freed the list. */ + if (l1h->in_trx_ctrl_read_cb) + l1h->flushed_while_in_trx_ctrl_read_cb = true; } /*! close the TRX for given handle (data + control socket) */ void trx_if_close(struct trx_l1h *l1h) { - LOGPPHI(l1h->phy_inst, DTRX, LOGL_NOTICE, "Close transceiver\n"); + LOGPPHI(l1h->phy_inst, DTRX, LOGL_NOTICE, "Closing TRXC/TRXD connections to %s\n", + l1h->phy_inst->phy_link->u.osmotrx.remote_ip); trx_if_flush(l1h); @@ -1131,7 +1259,7 @@ void trx_if_close(struct trx_l1h *l1h) } /*! compute UDP port number used for TRX protocol */ -static uint16_t compute_port(struct phy_instance *pinst, int remote, int is_data) +static uint16_t compute_port(struct phy_instance *pinst, bool remote, bool is_data) { struct phy_link *plink = pinst->phy_link; uint16_t inc = 1; @@ -1145,46 +1273,32 @@ static uint16_t compute_port(struct phy_instance *pinst, int remote, int is_data return plink->u.osmotrx.base_port_local + (pinst->num << 1) + inc; } -/*! open a TRX interface. creates contro + data sockets */ +/*! open a TRX interface. creates control + data sockets */ static int trx_if_open(struct trx_l1h *l1h) { struct phy_instance *pinst = l1h->phy_inst; struct phy_link *plink = pinst->phy_link; int rc; - LOGPPHI(l1h->phy_inst, DTRX, LOGL_NOTICE, "Open transceiver\n"); - - /* initialize ctrl queue */ - INIT_LLIST_HEAD(&l1h->trx_ctrl_list); + LOGPPHI(pinst, DTRX, LOGL_NOTICE, "Opening TRXC/TRXD connections to %s\n", plink->u.osmotrx.remote_ip); /* open sockets */ rc = trx_udp_open(l1h, &l1h->trx_ofd_ctrl, plink->u.osmotrx.local_ip, - compute_port(pinst, 0, 0), + compute_port(pinst, false, false), plink->u.osmotrx.remote_ip, - compute_port(pinst, 1, 0), trx_ctrl_read_cb); + compute_port(pinst, true, false), trx_ctrl_read_cb); if (rc < 0) - goto err; + return rc; rc = trx_udp_open(l1h, &l1h->trx_ofd_data, plink->u.osmotrx.local_ip, - compute_port(pinst, 0, 1), + compute_port(pinst, false, true), plink->u.osmotrx.remote_ip, - compute_port(pinst, 1, 1), trx_data_read_cb); + compute_port(pinst, true, true), trx_data_read_cb); if (rc < 0) - goto err; - - /* enable all slots */ - l1h->config.slotmask = 0xff; - - /* FIXME: why was this only for TRX0 ? */ - //if (l1h->trx->nr == 0) - trx_if_cmd_poweroff(l1h); + return rc; return 0; - -err: - trx_if_close(l1h); - return rc; } /*! close the control + burst data sockets for one phy_instance */ @@ -1193,7 +1307,8 @@ static void trx_phy_inst_close(struct phy_instance *pinst) struct trx_l1h *l1h = pinst->u.osmotrx.hdl; trx_if_close(l1h); - trx_sched_exit(&l1h->l1s); + if (pinst->trx) + trx_sched_clean(pinst->trx); } /*! open the control + burst data sockets for one phy_instance */ @@ -1206,11 +1321,9 @@ static int trx_phy_inst_open(struct phy_instance *pinst) if (!l1h) return -EINVAL; - rc = trx_sched_init(&l1h->l1s, pinst->trx); - if (rc < 0) { - LOGPPHI(l1h->phy_inst, DL1C, LOGL_FATAL, "Cannot initialize scheduler\n"); - return -EIO; - } + /* PHY instance may be not associated with a TRX instance */ + if (pinst->trx != NULL) + trx_sched_init(pinst->trx); rc = trx_if_open(l1h); if (rc < 0) { @@ -1244,11 +1357,12 @@ int bts_model_phy_link_open(struct phy_link *plink) /* open the individual instances with their ctrl+data sockets */ llist_for_each_entry(pinst, &plink->instances, list) { + struct trx_l1h *l1h = pinst->u.osmotrx.hdl; if (trx_phy_inst_open(pinst) < 0) goto cleanup; + osmo_fsm_inst_dispatch(l1h->provision_fi, TRX_PROV_EV_OPEN, NULL); } - /* FIXME: is there better way to check/report TRX availability? */ - transceiver_available = 1; + return 0; cleanup: @@ -1263,8 +1377,27 @@ cleanup: return -1; } +/*! close the PHY link using TRX protocol */ +int bts_model_phy_link_close(struct phy_link *plink) +{ + bool clock_stopped = false; + struct phy_instance *pinst; + llist_for_each_entry(pinst, &plink->instances, list) { + if (!clock_stopped) { + clock_stopped = true; + trx_sched_clock_stopped(pinst->trx->bts); + } + trx_phy_inst_close(pinst); + } + trx_udp_close(&plink->u.osmotrx.trx_ofd_clk); + phy_link_state_set(plink, PHY_LINK_SHUTDOWN); + return 0; +} + /*! determine if the TRX for given handle is powered up */ int trx_if_powered(struct trx_l1h *l1h) { - return l1h->config.poweron; + struct phy_instance *pinst = l1h->phy_inst; + struct phy_link *plink = pinst->phy_link; + return plink->u.osmotrx.powered; } diff --git a/src/osmo-bts-trx/trx_if.h b/src/osmo-bts-trx/trx_if.h index dda7116e..3a22a1b4 100644 --- a/src/osmo-bts-trx/trx_if.h +++ b/src/osmo-bts-trx/trx_if.h @@ -1,8 +1,11 @@ -#ifndef TRX_IF_H -#define TRX_IF_H +#pragma once -extern int transceiver_available; +/* TRXC read/send buffer size */ +#define TRXC_MSG_BUF_SIZE 1500 +/* TRXD read/send buffer size (max. lo MTU) */ +#define TRXD_MSG_BUF_SIZE 65536 +struct trx_dl_burst_req; struct trx_l1h; struct trx_ctrl_msg { @@ -15,30 +18,37 @@ struct trx_ctrl_msg { void *cb; }; +typedef void trx_if_cmd_generic_cb(struct trx_l1h *l1h, int rc); +typedef void trx_if_cmd_poweronoff_cb(struct trx_l1h *l1h, bool poweronoff, int rc); typedef void trx_if_cmd_setslot_cb(struct trx_l1h *l1h, uint8_t tn, uint8_t type, int rc); +typedef void trx_if_cmd_getnompower_cb(struct trx_l1h *l1h, int nominal_power, int rc); +typedef void trx_if_cmd_setpower_att_cb(struct trx_l1h *l1h, int power_att_db, int rc); void trx_if_init(struct trx_l1h *l1h); -int trx_if_cmd_poweroff(struct trx_l1h *l1h); -int trx_if_cmd_poweron(struct trx_l1h *l1h); -int trx_if_cmd_settsc(struct trx_l1h *l1h, uint8_t tsc); -int trx_if_cmd_setbsic(struct trx_l1h *l1h, uint8_t bsic); +int trx_if_cmd_poweroff(struct trx_l1h *l1h, trx_if_cmd_poweronoff_cb *cb); +int trx_if_cmd_poweron(struct trx_l1h *l1h, trx_if_cmd_poweronoff_cb *cb); +int trx_if_cmd_settsc(struct trx_l1h *l1h, uint8_t tsc, trx_if_cmd_generic_cb *cb); +int trx_if_cmd_setbsic(struct trx_l1h *l1h, uint8_t bsic, trx_if_cmd_generic_cb *cb); int trx_if_cmd_setrxgain(struct trx_l1h *l1h, int db); -int trx_if_cmd_setpower(struct trx_l1h *l1h, int db); +int trx_if_cmd_getnompower(struct trx_l1h *l1h, trx_if_cmd_getnompower_cb *cb); +int trx_if_cmd_setpower_att(struct trx_l1h *l1h, int power_att_db, trx_if_cmd_setpower_att_cb *cb); int trx_if_cmd_setmaxdly(struct trx_l1h *l1h, int dly); int trx_if_cmd_setmaxdlynb(struct trx_l1h *l1h, int dly); -int trx_if_cmd_setslot(struct trx_l1h *l1h, uint8_t tn, uint8_t type, trx_if_cmd_setslot_cb *cb); -int trx_if_cmd_rxtune(struct trx_l1h *l1h, uint16_t arfcn); -int trx_if_cmd_txtune(struct trx_l1h *l1h, uint16_t arfcn); +int trx_if_cmd_setslot(struct trx_l1h *l1h, uint8_t tn, trx_if_cmd_setslot_cb *cb); +int trx_if_cmd_rxtune(struct trx_l1h *l1h, uint16_t arfcn, trx_if_cmd_generic_cb *cb); +int trx_if_cmd_txtune(struct trx_l1h *l1h, uint16_t arfcn, trx_if_cmd_generic_cb *cb); int trx_if_cmd_handover(struct trx_l1h *l1h, uint8_t tn, uint8_t ss); int trx_if_cmd_nohandover(struct trx_l1h *l1h, uint8_t tn, uint8_t ss); -int trx_if_send_burst(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, uint8_t pwr, - const ubit_t *bits, uint16_t nbits); +int trx_if_cmd_rfmute(struct trx_l1h *l1h, bool mute); +int trx_if_send_burst(struct trx_l1h *l1h, const struct trx_dl_burst_req *br); int trx_if_powered(struct trx_l1h *l1h); -/* The latest supported TRXD header format version */ -#define TRX_DATA_FORMAT_VER 1 +/* The latest supported TRXD PDU version */ +#define TRX_DATA_PDU_VER 2 /* Format negotiation command */ -int trx_if_cmd_setformat(struct trx_l1h *l1h, uint8_t ver); +int trx_if_cmd_setformat(struct trx_l1h *l1h, uint8_t ver, trx_if_cmd_generic_cb *cb); -#endif /* TRX_IF_H */ +int trx_ctrl_cmd_cb(struct trx_l1h *l1h, int critical, void *cb, + const char *cmd, const char *fmt, ...); +#define trx_ctrl_cmd(l1h, critical, cmd, fmt, ...) trx_ctrl_cmd_cb(l1h, critical, NULL, cmd, fmt, ##__VA_ARGS__) diff --git a/src/osmo-bts-trx/trx_provision_fsm.c b/src/osmo-bts-trx/trx_provision_fsm.c new file mode 100644 index 00000000..5ca23e31 --- /dev/null +++ b/src/osmo-bts-trx/trx_provision_fsm.c @@ -0,0 +1,740 @@ +/* TRX provision FSM */ + +/* (C) 2020 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> + * Author: Pau Espin Pedrol <pespin@sysmocom.de> + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU 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 <inttypes.h> + +#include <osmocom/core/fsm.h> +#include <osmocom/core/tdef.h> +#include <osmocom/gsm/protocol/gsm_12_21.h> + +#include <osmo-bts/logging.h> +#include <osmo-bts/gsm_data.h> +#include <osmo-bts/bts_model.h> +#include <osmo-bts/bts.h> +#include <osmo-bts/rsl.h> +#include <osmo-bts/nm_common_fsm.h> +#include <osmo-bts/signal.h> + +#include "l1_if.h" +#include "trx_provision_fsm.h" + +#define X(s) (1 << (s)) + +#define trx_prov_fsm_state_chg(fi, NEXT_STATE) \ + osmo_fsm_inst_state_chg(fi, NEXT_STATE, 0, 0) + +static void l1if_poweronoff_cb(struct trx_l1h *l1h, bool poweronoff, int rc) +{ + struct phy_instance *pinst = l1h->phy_inst; + struct phy_link *plink = pinst->phy_link; + + plink->u.osmotrx.powered = poweronoff; + + if (poweronoff) { + plink->u.osmotrx.poweron_sent = false; + osmo_fsm_inst_dispatch(l1h->provision_fi, TRX_PROV_EV_POWERON_CNF, (void*)(intptr_t)rc); + } else { + plink->u.osmotrx.poweroff_sent = false; + osmo_fsm_inst_dispatch(l1h->provision_fi, TRX_PROV_EV_POWEROFF_CNF, (void*)(intptr_t)rc); + } +} + + +void l1if_rxtune_cb(struct trx_l1h *l1h, int rc) +{ + osmo_fsm_inst_dispatch(l1h->provision_fi, TRX_PROV_EV_RXTUNE_CNF, (void*)(intptr_t)rc); +} + +void l1if_txtune_cb(struct trx_l1h *l1h, int rc) +{ + osmo_fsm_inst_dispatch(l1h->provision_fi, TRX_PROV_EV_TXTUNE_CNF, (void*)(intptr_t)rc); +} + +void l1if_settsc_cb(struct trx_l1h *l1h, int rc) +{ + osmo_fsm_inst_dispatch(l1h->provision_fi, TRX_PROV_EV_SETTSC_CNF, (void*)(intptr_t)rc); +} + +void l1if_setbsic_cb(struct trx_l1h *l1h, int rc) +{ + osmo_fsm_inst_dispatch(l1h->provision_fi, TRX_PROV_EV_SETBSIC_CNF, (void*)(intptr_t)rc); +} + +static void l1if_getnompower_cb(struct trx_l1h *l1h, int nominal_power, int rc) +{ + struct phy_instance *pinst = l1h->phy_inst; + LOGPPHI(pinst, DL1C, LOGL_DEBUG, "l1if_getnompower_cb(nominal_power=%d, rc=%d)\n", nominal_power, rc); + osmo_fsm_inst_dispatch(l1h->provision_fi, TRX_PROV_EV_NOMTXPOWER_CNF, (void*)(intptr_t)nominal_power); +} + +void l1if_setformat_cb(struct trx_l1h *l1h, int rc) +{ + osmo_fsm_inst_dispatch(l1h->provision_fi, TRX_PROV_EV_SETFORMAT_CNF, (void*)(intptr_t)rc); +} + +/* + * transceiver provisioning + */ + +static void trx_provision_reset(struct trx_l1h *l1h) +{ + struct phy_instance *pinst = l1h->phy_inst; + uint8_t tn; + + l1h->config.trxd_pdu_ver_req = pinst->phy_link->u.osmotrx.trxd_pdu_ver_max; + l1h->config.trxd_pdu_ver_use = 0; + l1h->config.setformat_sent = false; + l1h->config.setformat_acked = false; + + l1h->config.enabled = false; + l1h->config.arfcn_valid = false; + l1h->config.arfcn = 0; + l1h->config.rxtune_sent = false; + l1h->config.rxtune_acked = false; + l1h->config.txtune_sent = false; + l1h->config.txtune_acked = false; + + l1h->config.tsc_valid = false; + l1h->config.tsc = 0; + l1h->config.tsc_sent = false; + l1h->config.tsc_acked = false; + + l1h->config.bsic_valid = false; + l1h->config.bsic = 0; + l1h->config.bsic_sent = false; + l1h->config.bsic_acked = false; + + l1h->config.rxgain_sent = false; + + l1h->config.nomtxpower_sent = false; + l1h->config.nomtxpower_acked = false; + + l1h->config.maxdly_sent = false; + + l1h->config.maxdlynb_sent = false; + + for (tn = 0; tn < TRX_NR_TS; tn++) { + l1h->config.setslot_valid[tn] = false; + l1h->config.setslot_sent[tn] = false; + l1h->config.setslot[tn].slottype = 0; + l1h->config.setslot[tn].tsc_set = 0; + l1h->config.setslot[tn].tsc_val = 0; + l1h->config.setslot[tn].tsc_valid = 0; + } +} +int l1if_provision_transceiver_trx(struct trx_l1h *l1h) +{ + struct phy_instance *pinst = l1h->phy_inst; + struct phy_link *plink = pinst->phy_link; + + /* During setup, pinst may still not be associated to a TRX nr */ + if (!pinst->trx) { + LOGPPHI(pinst, DL1C, LOGL_INFO, + "Delaying provision, TRX not yet assigned to phy instance\n"); + return -EIO; + } + + if (phy_link_state_get(plink) == PHY_LINK_SHUTDOWN) { + LOGPPHI(pinst, DL1C, LOGL_INFO, + "Delaying provision, TRX not yet available\n"); + return -EIO; + } + + /* before power on */ + if (l1h->config.arfcn_valid) { + if (!l1h->config.rxtune_sent) { + trx_if_cmd_rxtune(l1h, l1h->config.arfcn, l1if_rxtune_cb); + l1h->config.rxtune_sent = true; + l1h->config.rxtune_acked = false; + } + if (!l1h->config.txtune_sent) { + trx_if_cmd_txtune(l1h, l1h->config.arfcn, l1if_txtune_cb); + l1h->config.txtune_sent = true; + l1h->config.txtune_acked = false; + } + if (l1h->config.txtune_acked) { + /* After TXTUNE is sent to TRX, get the tx nominal power + * (which may vary precisly on band/arfcn. Avoid sending + * it if we are forced by VTY to use a specific nominal + * power (because TRX may not support the command or + * provide broken values) */ + if (!l1h->config.nominal_power_set_by_vty && !l1h->config.nomtxpower_sent) { + trx_if_cmd_getnompower(l1h, l1if_getnompower_cb); + l1h->config.nomtxpower_sent = true; + l1h->config.nomtxpower_acked = false; + } + } + } + if (!pinst->phy_link->u.osmotrx.use_legacy_setbsic && + l1h->config.tsc_valid && !l1h->config.tsc_sent) { + trx_if_cmd_settsc(l1h, l1h->config.tsc, l1if_settsc_cb); + l1h->config.tsc_sent = true; + l1h->config.tsc_acked = false; + } + if (pinst->phy_link->u.osmotrx.use_legacy_setbsic && + l1h->config.bsic_valid && !l1h->config.bsic_sent) { + trx_if_cmd_setbsic(l1h, l1h->config.bsic, l1if_setbsic_cb); + l1h->config.bsic_sent = true; + l1h->config.bsic_acked = false; + } + + /* Ask transceiver to use the newest TRXD PDU version if not using it yet */ + if (!l1h->config.setformat_sent) { + l1h->config.setformat_sent = true; + if (plink->u.osmotrx.trxd_pdu_ver_max == 0) { + LOGPPHI(pinst, DL1C, LOGL_INFO, + "No need to negotiate max TRXD version 0"); + l1h->config.trxd_pdu_ver_use = 0; + l1h->config.setformat_acked = true; + } else { + trx_if_cmd_setformat(l1h, l1h->config.trxd_pdu_ver_req, l1if_setformat_cb); + l1h->config.setformat_acked = false; + } + } + return 0; +} + +static void l1if_setslot_cb(struct trx_l1h *l1h, uint8_t tn, uint8_t type, int rc) +{ + struct phy_instance *pinst = l1h->phy_inst; + struct gsm_bts_trx *trx = pinst->trx; + struct gsm_bts_trx_ts *ts; + enum gsm_phys_chan_config pchan; + + if (tn >= TRX_NR_TS) { + LOGPPHI(pinst, DL1C, LOGL_ERROR, "transceiver SETSLOT invalid param TN (%" PRIu8 ")\n", + tn); + return; + } + + pchan = transceiver_chan_type_2_pchan(type); + if (pchan == GSM_PCHAN_UNKNOWN) { + LOGPPHI(pinst, DL1C, LOGL_ERROR, "transceiver SETSLOT invalid param TS_TYPE (%" PRIu8 ")\n", + type); + return; + } + + ts = &trx->ts[tn]; + LOGPPHI(pinst, DL1C, LOGL_DEBUG, "%s l1if_setslot_cb(as_pchan=%s)," + " calling cb_ts_connected(rc=%d)\n", + gsm_ts_name(ts), gsm_pchan_name(pchan), rc); + cb_ts_connected(ts, rc); +} + +static void update_ts_data(struct trx_l1h *l1h, struct trx_prov_ev_cfg_ts_data *data) +{ + l1h->config.setslot[data->tn].slottype = data->slottype; + l1h->config.setslot[data->tn].tsc_set = data->tsc_set; + l1h->config.setslot[data->tn].tsc_val = data->tsc_val; + l1h->config.setslot[data->tn].tsc_valid = data->tsc_valid; + + l1h->config.setslot_valid[data->tn] = true; + l1h->config.setslot_sent[data->tn] = false; +} + +/* Whether a given TRX is fully configured */ +static bool trx_is_provisioned(struct trx_l1h *l1h) +{ + struct phy_instance *pinst = l1h->phy_inst; + if (l1h->config.rxtune_acked && l1h->config.txtune_acked && + (l1h->config.bsic_acked || !pinst->phy_link->u.osmotrx.use_legacy_setbsic) && + (l1h->config.tsc_acked || pinst->phy_link->u.osmotrx.use_legacy_setbsic) && + (l1h->config.nomtxpower_acked || l1h->config.nominal_power_set_by_vty) && + (l1h->config.setformat_acked)) { + return true; + } + return false; +} + +/* Whether a given TRX is fully configured and can be powered on */ +static bool trx_is_provisioned_and_enabled(struct trx_l1h *l1h) +{ + return l1h->config.enabled && trx_is_provisioned(l1h); +} + +static void trx_signal_ready_trx0(struct trx_l1h *l1h) +{ + struct phy_instance *pinst = l1h->phy_inst; + struct phy_instance *pinst_it; + + llist_for_each_entry(pinst_it, &pinst->phy_link->instances, list) { + struct trx_l1h *l1h_it = pinst_it->u.osmotrx.hdl; + if (l1h_it->phy_inst->num != 0) + continue; + osmo_fsm_inst_dispatch(l1h_it->provision_fi, TRX_PROV_EV_OTHER_TRX_READY, NULL); + return; + } +} + +/* Called from TRX0 to check if other TRX are already configured so POWERON can be sent */ +static bool trx_other_trx0_ready(struct trx_l1h *l1h) +{ + struct phy_instance *pinst = l1h->phy_inst; + struct phy_instance *pinst_it; + + /* Don't POWERON until all trx are ready */ + llist_for_each_entry(pinst_it, &pinst->phy_link->instances, list) { + struct trx_l1h *l1h_it = pinst_it->u.osmotrx.hdl; + if (l1h_it->phy_inst->num == 0) + continue; + if (!trx_is_provisioned(l1h_it)) + return false; + } + return true; +} + +/* Closes a phy_link and all its associated TRX */ +static void trx_prov_fsm_apply_close(struct phy_link *plink, int rc) +{ + struct trx_l1h *l1h; + struct phy_instance *pinst; + + if (plink->state == PHY_LINK_SHUTDOWN) + return; + + bts_model_phy_link_close(plink); + /* Notify TRX close on all TRX associated with this phy */ + llist_for_each_entry(pinst, &plink->instances, list) { + l1h = pinst->u.osmotrx.hdl; + trx_prov_fsm_state_chg(l1h->provision_fi, TRX_PROV_ST_CLOSED); + bts_model_trx_close_cb(pinst->trx, rc); + } +} + +static int trx_prov_fsm_signal_cb(unsigned int subsys, unsigned int signal, + void *hdlr_data, void *signal_data) +{ + struct nm_statechg_signal_data *nsd; + struct gsm_bts_trx *trx; + + if (subsys != SS_GLOBAL) + return -EINVAL; + + if (signal != S_NEW_OP_STATE) + return 0; + + nsd = (struct nm_statechg_signal_data *)signal_data; + + if (nsd->mo->obj_class != NM_OC_RADIO_CARRIER) + return 0; + + if (nsd->old_state != NM_OPSTATE_ENABLED && nsd->new_state == NM_OPSTATE_ENABLED) { + trx = gsm_objclass2obj(nsd->mo->bts, nsd->mo->obj_class, &nsd->mo->obj_inst, NULL); + l1if_trx_start_power_ramp(trx, NULL); + } + return 0; +} + +////////////////////////// +// FSM STATE ACTIONS +////////////////////////// + +static void st_closed(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct trx_l1h *l1h = (struct trx_l1h *)fi->priv; + + switch (event) { + case TRX_PROV_EV_OPEN: + /* enable all slots */ + l1h->config.slotmask = 0xff; + if (l1h->phy_inst->num == 0) + trx_if_cmd_poweroff(l1h, NULL); /* TODO: jump to poweroff upon cb received */ + trx_prov_fsm_state_chg(fi, TRX_PROV_ST_OPEN_POWEROFF); + break; + default: + OSMO_ASSERT(0); + } +} + +static void st_open_poweroff_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct trx_l1h *l1h = (struct trx_l1h *)fi->priv; + struct phy_instance *pinst = l1h->phy_inst; + + trx_provision_reset(l1h); + + if (pinst->trx == NULL) { + trx_if_cmd_rfmute(l1h, true); + return; + } + + /* Apply initial RFMUTE state */ + trx_if_cmd_rfmute(l1h, pinst->trx->mo.nm_state.administrative != NM_STATE_UNLOCKED); + + osmo_fsm_inst_dispatch(pinst->trx->mo.fi, NM_EV_SW_ACT, NULL); + osmo_fsm_inst_dispatch(pinst->trx->bb_transc.mo.fi, NM_EV_SW_ACT, NULL); +} + +static void st_open_poweroff(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct trx_l1h *l1h = (struct trx_l1h *)fi->priv; + struct phy_instance *pinst = l1h->phy_inst; + struct gsm_bts_trx *trx = pinst->trx; + uint16_t arfcn; + int nominal_power; + int status; + bool waiting_other_trx; + bool was_ready = trx_is_provisioned(l1h); + + switch (event) { + case TRX_PROV_EV_CLOSE: + /* In this state, we didn't for sure send a POWERON yet, hence we + are save directly applying the close as if we received a + POWEROFF RSP: */ + if (pinst->num == 0) + trx_prov_fsm_apply_close(pinst->phy_link, 0); + return; + case TRX_PROV_EV_CFG_ENABLE: + l1h->config.enabled =(bool)data; + break; + case TRX_PROV_EV_CFG_BSIC: + /* We always get BSIC from the BSC, TSC can be derived from the BCC */ + if (!pinst->phy_link->u.osmotrx.use_legacy_setbsic) { + const uint8_t tsc = BSIC2BCC((uint8_t)(intptr_t)data); + if (l1h->config.tsc != tsc || !l1h->config.tsc_valid) { + l1h->config.tsc = tsc; + l1h->config.tsc_valid = true; + l1h->config.tsc_sent = false; + } + } else { + const uint8_t bsic = (uint8_t)(intptr_t)data; + if (l1h->config.bsic != bsic || !l1h->config.bsic_valid) { + l1h->config.bsic = bsic; + l1h->config.bsic_valid = true; + l1h->config.bsic_sent = false; + } + } + break; + case TRX_PROV_EV_CFG_ARFCN: + arfcn = (uint16_t)(intptr_t)data; + if (l1h->config.arfcn != arfcn || !l1h->config.arfcn_valid) { + l1h->config.arfcn = arfcn; + l1h->config.arfcn_valid = true; + l1h->config.txtune_sent = false; + l1h->config.rxtune_sent = false; + l1h->config.nomtxpower_sent = false; + } + break; + case TRX_PROV_EV_CFG_TS: + update_ts_data(l1h, (struct trx_prov_ev_cfg_ts_data*)data); + break; + + /* CONFIRMATIONS FROM TRXC */ + case TRX_PROV_EV_RXTUNE_CNF: + if (l1h->config.rxtune_sent) + l1h->config.rxtune_acked = true; + break; + case TRX_PROV_EV_TXTUNE_CNF: + if (l1h->config.txtune_sent) + l1h->config.txtune_acked = true; + break; + case TRX_PROV_EV_NOMTXPOWER_CNF: + nominal_power = (int)(intptr_t)data; + if (l1h->config.nomtxpower_sent) + l1h->config.nomtxpower_acked = true; + l1if_trx_set_nominal_power(trx, nominal_power); + break; + case TRX_PROV_EV_SETBSIC_CNF: + if (l1h->config.bsic_sent) + l1h->config.bsic_acked = true; + break; + case TRX_PROV_EV_SETTSC_CNF: + if (l1h->config.tsc_sent) + l1h->config.tsc_acked = true; + break; + case TRX_PROV_EV_SETFORMAT_CNF: + status = (int)(intptr_t)data; + /* Transceiver may suggest a lower version (than requested) */ + if (status == l1h->config.trxd_pdu_ver_req) { + l1h->config.trxd_pdu_ver_use = status; + l1h->config.setformat_acked = true; + LOGPPHI(l1h->phy_inst, DTRX, LOGL_INFO, + "Using TRXD PDU version %u\n", + l1h->config.trxd_pdu_ver_use); + } else { + LOGPPHI(l1h->phy_inst, DTRX, LOGL_DEBUG, + "Transceiver suggests TRXD PDU version %u (requested %u)\n", + status, l1h->config.trxd_pdu_ver_req); + /* Send another SETFORMAT with suggested version */ + l1h->config.trxd_pdu_ver_req = status; + l1h->config.setformat_sent = false; + } + break; + case TRX_PROV_EV_OTHER_TRX_READY: + OSMO_ASSERT(pinst->num == 0); + /* Do nothing here, we were triggered to see if we can finally poweron TRX0 below */ + break; + default: + OSMO_ASSERT(0); + } + + l1if_provision_transceiver_trx(l1h); + + if (l1h->phy_inst->num == 0) { + waiting_other_trx = !trx_other_trx0_ready(l1h); + } else { + waiting_other_trx = false; /* we don't care about others in TRX!=0 */ + /* If we just became ready for TRX0 POWERON (aka this TRX becomes provisioned), signal it to TRX0: */ + if (l1h->phy_inst->num != 0 && (!was_ready && trx_is_provisioned(l1h))) + trx_signal_ready_trx0(l1h); + } + + /* if we gathered all data and could go forward. For TRX0, only after + * all other TRX are prepared, since it will send POWERON commad */ + if (trx_is_provisioned_and_enabled(l1h) && !waiting_other_trx) { + if (l1h->phy_inst->num != 0) + trx_prov_fsm_state_chg(fi, TRX_PROV_ST_OPEN_POWERON); + else + trx_prov_fsm_state_chg(fi, TRX_PROV_ST_OPEN_WAIT_POWERON_CNF); + } else { + LOGPFSML(fi, LOGL_INFO, "Delay poweron, wait for:%s%s%s%s%s%s%s%s\n", + l1h->config.enabled ? "" :" enable", + pinst->phy_link->u.osmotrx.use_legacy_setbsic ? + (l1h->config.bsic_valid ? (l1h->config.bsic_acked ? "" : " bsic-ack") : " bsic") : + (l1h->config.tsc_valid ? (l1h->config.tsc_acked ? "" : " tsc-ack") : " tsc"), + l1h->config.arfcn_valid ? "" : " arfcn", + l1h->config.rxtune_acked ? "" : " rxtune-ack", + l1h->config.txtune_acked ? "" : " txtune-ack", + l1h->config.nominal_power_set_by_vty ? "" : (l1h->config.nomtxpower_acked ? "" : " nomtxpower-ack"), + l1h->config.setformat_acked ? "" : " setformat-ack", + waiting_other_trx ? "" : " other-trx" + ); + } +} + + +static void st_open_wait_power_cnf_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct trx_l1h *l1h = (struct trx_l1h *)fi->priv; + struct phy_instance *pinst = l1h->phy_inst; + + trx_if_cmd_poweron(l1h, l1if_poweronoff_cb); + pinst->phy_link->u.osmotrx.poweron_sent = true; +} + +static void st_open_wait_power_cnf(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct trx_l1h *l1h = (struct trx_l1h *)fi->priv; + struct phy_instance *pinst = l1h->phy_inst; + struct phy_link *plink = pinst->phy_link; + int rc; + + switch (event) { + case TRX_PROV_EV_POWERON_CNF: + rc = (uint16_t)(intptr_t)data; + if (rc == 0 && plink->state != PHY_LINK_CONNECTED) { + trx_sched_clock_started(pinst->trx->bts); + phy_link_state_set(plink, PHY_LINK_CONNECTED); + trx_prov_fsm_state_chg(fi, TRX_PROV_ST_OPEN_POWERON); + } else if (rc != 0 && plink->state != PHY_LINK_SHUTDOWN) { + trx_sched_clock_stopped(pinst->trx->bts); + phy_link_state_set(plink, PHY_LINK_SHUTDOWN); + } + break; + case TRX_PROV_EV_CFG_TS: + update_ts_data(l1h, (struct trx_prov_ev_cfg_ts_data*)data); + break; + case TRX_PROV_EV_CLOSE: + /* power off transceiver, if not already */ + if (pinst->num == 0 && !plink->u.osmotrx.poweroff_sent) { + trx_if_cmd_poweroff(l1h, l1if_poweronoff_cb); + plink->u.osmotrx.poweroff_sent = true; + } + trx_prov_fsm_state_chg(fi, TRX_PROV_ST_OPEN_WAIT_POWEROFF_CNF); + break; + default: + OSMO_ASSERT(0); + } +} + +static void st_open_poweron_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct trx_l1h *l1h = (struct trx_l1h *)fi->priv; + uint8_t tn; + + /* after power on */ + if (l1h->config.rxgain_valid && !l1h->config.rxgain_sent) { + trx_if_cmd_setrxgain(l1h, l1h->config.rxgain); + l1h->config.rxgain_sent = true; + } + if (l1h->config.maxdly_valid && !l1h->config.maxdly_sent) { + trx_if_cmd_setmaxdly(l1h, l1h->config.maxdly); + l1h->config.maxdly_sent = true; + } + if (l1h->config.maxdlynb_valid && !l1h->config.maxdlynb_sent) { + trx_if_cmd_setmaxdlynb(l1h, l1h->config.maxdlynb); + l1h->config.maxdlynb_sent = true; + } + + for (tn = 0; tn < TRX_NR_TS; tn++) { + if (l1h->config.setslot_valid[tn] + && !l1h->config.setslot_sent[tn]) { + trx_if_cmd_setslot(l1h, tn, l1if_setslot_cb); + l1h->config.setslot_sent[tn] = true; + } + } +} + +static void st_open_poweron(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct trx_l1h *l1h = (struct trx_l1h *)fi->priv; + struct phy_instance *pinst = l1h->phy_inst; + struct phy_link *plink = pinst->phy_link; + struct trx_prov_ev_cfg_ts_data* ts_data; + + switch (event) { + case TRX_PROV_EV_CLOSE: + /* power off transceiver, if not already */ + if (pinst->num == 0 && plink->u.osmotrx.powered && !plink->u.osmotrx.poweroff_sent) { + trx_if_cmd_poweroff(l1h, l1if_poweronoff_cb); + plink->u.osmotrx.poweroff_sent = true; + } + trx_prov_fsm_state_chg(fi, TRX_PROV_ST_OPEN_WAIT_POWEROFF_CNF); + break; + case TRX_PROV_EV_CFG_TS: + ts_data = (struct trx_prov_ev_cfg_ts_data*)data; + update_ts_data(l1h, ts_data); + /* While in this state we can send SETSLOT immediately */ + trx_if_cmd_setslot(l1h, ts_data->tn, l1if_setslot_cb); + l1h->config.setslot_sent[ts_data->tn] = true; + break; + default: + OSMO_ASSERT(0); + } +} + +static void st_open_wait_poweroff_cnf(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct trx_l1h *l1h = (struct trx_l1h *)fi->priv; + struct phy_instance *pinst = l1h->phy_inst; + struct phy_link *plink = pinst->phy_link; + int rc; + + switch (event) { + case TRX_PROV_EV_POWEROFF_CNF: + rc = (uint16_t)(intptr_t)data; + trx_prov_fsm_apply_close(plink, rc); + break; + default: + OSMO_ASSERT(0); + } +} + +static struct osmo_fsm_state trx_prov_fsm_states[] = { + [TRX_PROV_ST_CLOSED] = { + .in_event_mask = + X(TRX_PROV_EV_OPEN), + .out_state_mask = + X(TRX_PROV_ST_OPEN_POWEROFF), + .name = "CLOSED", + .action = st_closed, + }, + [TRX_PROV_ST_OPEN_POWEROFF] = { + .in_event_mask = + X(TRX_PROV_EV_CLOSE) | + X(TRX_PROV_EV_OTHER_TRX_READY) | + X(TRX_PROV_EV_CFG_ENABLE) | + X(TRX_PROV_EV_CFG_BSIC) | + X(TRX_PROV_EV_CFG_ARFCN) | + X(TRX_PROV_EV_CFG_TS) | + X(TRX_PROV_EV_RXTUNE_CNF) | + X(TRX_PROV_EV_TXTUNE_CNF) | + X(TRX_PROV_EV_NOMTXPOWER_CNF) | + X(TRX_PROV_EV_SETBSIC_CNF) | + X(TRX_PROV_EV_SETTSC_CNF) | + X(TRX_PROV_EV_SETFORMAT_CNF), + .out_state_mask = + X(TRX_PROV_ST_CLOSED) | + X(TRX_PROV_ST_OPEN_WAIT_POWERON_CNF) | + X(TRX_PROV_ST_OPEN_POWERON), + .name = "OPEN_POWEROFF", + .onenter = st_open_poweroff_on_enter, + .action = st_open_poweroff, + }, + [TRX_PROV_ST_OPEN_WAIT_POWERON_CNF] = { + .in_event_mask = + X(TRX_PROV_EV_CLOSE) | + X(TRX_PROV_EV_POWERON_CNF) | + X(TRX_PROV_EV_CFG_TS), + .out_state_mask = + X(TRX_PROV_ST_OPEN_POWERON) | + X(TRX_PROV_ST_OPEN_WAIT_POWEROFF_CNF), + .name = "OPEN_WAIT_POWERON_CNF", + .onenter = st_open_wait_power_cnf_on_enter, + .action = st_open_wait_power_cnf, + }, + [TRX_PROV_ST_OPEN_POWERON] = { + .in_event_mask = + X(TRX_PROV_EV_CLOSE) | + X(TRX_PROV_EV_CFG_TS), + .out_state_mask = + X(TRX_PROV_ST_OPEN_WAIT_POWEROFF_CNF), + .name = "OPEN_POWERON", + .onenter = st_open_poweron_on_enter, + .action = st_open_poweron, + }, + [TRX_PROV_ST_OPEN_WAIT_POWEROFF_CNF] = { + .in_event_mask = + X(TRX_PROV_EV_POWEROFF_CNF), + .out_state_mask = + X(TRX_PROV_ST_CLOSED), + .name = "OPEN_WAIT_POWEROFF_CNF", + .action = st_open_wait_poweroff_cnf, + }, +}; + +const struct value_string trx_prov_fsm_event_names[] = { + OSMO_VALUE_STRING(TRX_PROV_EV_OTHER_TRX_READY), + OSMO_VALUE_STRING(TRX_PROV_EV_OPEN), + OSMO_VALUE_STRING(TRX_PROV_EV_CFG_ENABLE), + OSMO_VALUE_STRING(TRX_PROV_EV_CFG_BSIC), + OSMO_VALUE_STRING(TRX_PROV_EV_CFG_ARFCN), + OSMO_VALUE_STRING(TRX_PROV_EV_CFG_TS), + OSMO_VALUE_STRING(TRX_PROV_EV_CFG_RXGAIN), + OSMO_VALUE_STRING(TRX_PROV_EV_CFG_SETMAXDLY), + OSMO_VALUE_STRING(TRX_PROV_EV_RXTUNE_CNF), + OSMO_VALUE_STRING(TRX_PROV_EV_TXTUNE_CNF), + OSMO_VALUE_STRING(TRX_PROV_EV_NOMTXPOWER_CNF), + OSMO_VALUE_STRING(TRX_PROV_EV_SETBSIC_CNF), + OSMO_VALUE_STRING(TRX_PROV_EV_SETTSC_CNF), + OSMO_VALUE_STRING(TRX_PROV_EV_SETFORMAT_CNF), + OSMO_VALUE_STRING(TRX_PROV_EV_POWERON_CNF), + OSMO_VALUE_STRING(TRX_PROV_EV_POWEROFF_CNF), + OSMO_VALUE_STRING(TRX_PROV_EV_CLOSE), + { 0, NULL } +}; + +struct osmo_fsm trx_prov_fsm = { + .name = "TRX_PROV", + .states = trx_prov_fsm_states, + .num_states = ARRAY_SIZE(trx_prov_fsm_states), + .event_names = trx_prov_fsm_event_names, + .log_subsys = DL1C, +}; + +static __attribute__((constructor)) void trx_prov_fsm_init(void) +{ + OSMO_ASSERT(osmo_fsm_register(&trx_prov_fsm) == 0); + OSMO_ASSERT(osmo_signal_register_handler(SS_GLOBAL, trx_prov_fsm_signal_cb, NULL) == 0); +} diff --git a/src/osmo-bts-trx/trx_provision_fsm.h b/src/osmo-bts-trx/trx_provision_fsm.h new file mode 100644 index 00000000..e89cd328 --- /dev/null +++ b/src/osmo-bts-trx/trx_provision_fsm.h @@ -0,0 +1,68 @@ +/* Provision TRX over TRXC protocol FSM */ + +/* (C) 2020 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> + * Author: Pau Espin Pedrol <pespin@sysmocom.de> + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#pragma once + +#include <stdbool.h> + +#include <osmocom/core/fsm.h> + +enum trx_provision_fsm_states { + TRX_PROV_ST_CLOSED, + TRX_PROV_ST_OPEN_POWEROFF, + TRX_PROV_ST_OPEN_WAIT_POWERON_CNF, + TRX_PROV_ST_OPEN_POWERON, + TRX_PROV_ST_OPEN_WAIT_POWEROFF_CNF, +}; + +struct trx_prov_ev_cfg_ts_data { + uint8_t tn; + uint8_t slottype; + + /* Training Sequence Code and Set */ + uint8_t tsc_set; + uint8_t tsc_val; + bool tsc_valid; +}; + +enum trx_provision_fsm_events { + TRX_PROV_EV_OTHER_TRX_READY, + TRX_PROV_EV_OPEN, + TRX_PROV_EV_CFG_ENABLE, + TRX_PROV_EV_CFG_BSIC, + TRX_PROV_EV_CFG_ARFCN, + TRX_PROV_EV_CFG_TSC, + TRX_PROV_EV_CFG_TS, + TRX_PROV_EV_CFG_RXGAIN, + TRX_PROV_EV_CFG_SETMAXDLY, + TRX_PROV_EV_RXTUNE_CNF, + TRX_PROV_EV_TXTUNE_CNF, + TRX_PROV_EV_NOMTXPOWER_CNF, + TRX_PROV_EV_SETBSIC_CNF, + TRX_PROV_EV_SETTSC_CNF, + TRX_PROV_EV_SETFORMAT_CNF, + TRX_PROV_EV_POWERON_CNF, + TRX_PROV_EV_POWEROFF_CNF, + TRX_PROV_EV_CLOSE, +}; + +extern struct osmo_fsm trx_prov_fsm; diff --git a/src/osmo-bts-trx/trx_vty.c b/src/osmo-bts-trx/trx_vty.c index c52908e0..9056f027 100644 --- a/src/osmo-bts-trx/trx_vty.c +++ b/src/osmo-bts-trx/trx_vty.c @@ -1,4 +1,4 @@ -/* VTY interface for sysmoBTS */ +/* VTY interface for osmo-bts-trx */ /* (C) 2013 by Andreas Eversberg <jolly@eversberg.eu> * @@ -42,37 +42,35 @@ #include <osmo-bts/logging.h> #include <osmo-bts/vty.h> #include <osmo-bts/scheduler.h> +#include <osmo-bts/bts.h> #include "l1_if.h" #include "trx_if.h" -#include "loops.h" +#include "amr_loop.h" -#define OSMOTRX_STR "OsmoTRX Transceiver configuration\n" +#define X(x) (1 << x) -static struct gsm_bts *vty_bts; +#define OSMOTRX_STR "OsmoTRX Transceiver configuration\n" DEFUN(show_transceiver, show_transceiver_cmd, "show transceiver", SHOW_STR "Display information about transceivers\n") { - struct gsm_bts *bts = vty_bts; struct gsm_bts_trx *trx; struct trx_l1h *l1h; + unsigned int tn; - if (!transceiver_available) { - vty_out(vty, "transceiver is not connected%s", VTY_NEWLINE); - } else { - vty_out(vty, "transceiver is connected%s", VTY_NEWLINE); - } - - llist_for_each_entry(trx, &bts->trx_list, list) { + llist_for_each_entry(trx, &g_bts->trx_list, list) { struct phy_instance *pinst = trx_phy_instance(trx); - char *sname = osmo_sock_get_name(NULL, pinst->phy_link->u.osmotrx.trx_ofd_clk.fd); + struct phy_link *plink = pinst->phy_link; + char *sname = osmo_sock_get_name(NULL, plink->u.osmotrx.trx_ofd_clk.fd); l1h = pinst->u.osmotrx.hdl; vty_out(vty, "TRX %d %s%s", trx->nr, sname, VTY_NEWLINE); talloc_free(sname); vty_out(vty, " %s%s", - (l1h->config.poweron) ? "poweron":"poweroff", + trx_if_powered(l1h) ? "poweron":"poweroff", VTY_NEWLINE); + vty_out(vty, "phy link state: %s%s", + phy_link_state_name(phy_link_state_get(plink)), VTY_NEWLINE); if (l1h->config.arfcn_valid) vty_out(vty, " arfcn : %d%s%s", (l1h->config.arfcn & ~ARFCN_PCS), @@ -89,7 +87,28 @@ DEFUN(show_transceiver, show_transceiver_cmd, "show transceiver", vty_out(vty, " bsic : %d%s", l1h->config.bsic, VTY_NEWLINE); else - vty_out(vty, " bisc : undefined%s", VTY_NEWLINE); + vty_out(vty, " bsic : undefined%s", VTY_NEWLINE); + + /* trx->ts[tn].priv is NULL in absence of the A-bis connection */ + if (trx->bb_transc.rsl.link == NULL) + continue; + + for (tn = 0; tn < ARRAY_SIZE(trx->ts); tn++) { + const struct gsm_bts_trx_ts *ts = &trx->ts[tn]; + const struct l1sched_ts *l1ts = ts->priv; + const struct trx_sched_multiframe *mf; + + OSMO_ASSERT(l1ts != NULL); + mf = &trx_sched_multiframes[l1ts->mf_index]; + + vty_out(vty, " timeslot #%u (%s)%s", + tn, mf->name, VTY_NEWLINE); + vty_out(vty, " pending DL prims : %u%s", + llist_count(&l1ts->dl_prims), VTY_NEWLINE); + vty_out(vty, " interference : %ddBm%s", + l1ts->chan_state[TRXC_IDLE].meas.interf_avg, + VTY_NEWLINE); + } } return CMD_SUCCESS; @@ -100,20 +119,25 @@ static void show_phy_inst_single(struct vty *vty, struct phy_instance *pinst) { uint8_t tn; struct trx_l1h *l1h = pinst->u.osmotrx.hdl; + struct gsm_bts_trx *trx = pinst->trx; - vty_out(vty, "PHY Instance %s%s", - phy_instance_name(pinst), VTY_NEWLINE); + vty_out(vty, "PHY Instance '%s': bound to %s%s", + phy_instance_name(pinst), + gsm_trx_name(trx), + VTY_NEWLINE); + + if (trx != NULL) { + const int actual = get_p_actual_mdBm(trx, trx->power_params.p_total_tgt_mdBm); + const int max = get_p_max_out_mdBm(trx); + vty_out(vty, " tx-attenuation : %d dB%s", + (max - actual) / 1000, VTY_NEWLINE); + } if (l1h->config.rxgain_valid) vty_out(vty, " rx-gain : %d dB%s", l1h->config.rxgain, VTY_NEWLINE); else vty_out(vty, " rx-gain : undefined%s", VTY_NEWLINE); - if (l1h->config.power_valid) - vty_out(vty, " tx-attenuation : %d dB%s", - l1h->config.power, VTY_NEWLINE); - else - vty_out(vty, " tx-attenuation : undefined%s", VTY_NEWLINE); if (l1h->config.maxdly_valid) vty_out(vty, " maxdly : %d%s", l1h->config.maxdly, VTY_NEWLINE); @@ -125,16 +149,23 @@ static void show_phy_inst_single(struct vty *vty, struct phy_instance *pinst) else vty_out(vty, " maxdlynb : undefined%s", VTY_NEWLINE); for (tn = 0; tn < TRX_NR_TS; tn++) { - if (!((1 << tn) & l1h->config.slotmask)) + if (!((1 << tn) & l1h->config.slotmask)) { vty_out(vty, " slot #%d: unsupported%s", tn, VTY_NEWLINE); - else if (l1h->config.slottype_valid[tn]) - vty_out(vty, " slot #%d: type %d%s", tn, - l1h->config.slottype[tn], - VTY_NEWLINE); - else + continue; + } else if (!l1h->config.setslot_valid[tn]) { vty_out(vty, " slot #%d: undefined%s", tn, VTY_NEWLINE); + continue; + } + + vty_out(vty, " slot #%d: type %d", tn, + l1h->config.setslot[tn].slottype); + if (l1h->config.setslot[tn].tsc_valid) + vty_out(vty, " TSC-s%dc%d", + l1h->config.setslot[tn].tsc_set, + l1h->config.setslot[tn].tsc_val); + vty_out(vty, "%s", VTY_NEWLINE); } } @@ -163,96 +194,146 @@ DEFUN(show_phy, show_phy_cmd, "show phy", return CMD_SUCCESS; } -DEFUN(cfg_phy_ms_power_loop, cfg_phy_ms_power_loop_cmd, +DEFUN_HIDDEN(test_send_trxc, + test_send_trxc_cmd, + "test send-trxc-cmd <0-255> CMD [.ARGS]", + "Various testing commands\n" + "Send an arbitrary TRX command\n" + "Transceiver number\n" + "TRXC command\n" "TRXC command arguments\n") +{ + const struct gsm_bts_trx *trx; + const struct phy_instance *pinst; + struct trx_l1h *l1h; + int rc; + + trx = gsm_bts_trx_num(g_bts, atoi(argv[0])); + if (trx == NULL) { + vty_out(vty, "%% Could not find TRX%s", VTY_NEWLINE); + return CMD_WARNING; + } + + pinst = trx_phy_instance(trx); + l1h = pinst->u.osmotrx.hdl; + + if (argc > 2) { + char *cmd_args = argv_concat(argv, argc, 2); + rc = trx_ctrl_cmd(l1h, 0, argv[1], "%s", cmd_args); + talloc_free(cmd_args); + } else { + rc = trx_ctrl_cmd(l1h, 0, argv[1], ""); + } + + return (rc == 0) ? CMD_SUCCESS : CMD_WARNING; +} + +DEFUN_USRATTR(cfg_trx_nominal_power, cfg_trx_nominal_power_cmd, + X(BTS_VTY_TRX_POWERCYCLE), + "nominal-tx-power <-10-100>", + "Manually set (force) the nominal transmit output power in dBm\n" + "Nominal transmit output power level in dBm\n") +{ + struct gsm_bts_trx *trx = vty->index; + struct phy_instance *pinst = trx_phy_instance(trx); + struct trx_l1h *l1h = pinst->u.osmotrx.hdl; + int val = atoi(argv[0]); + + l1if_trx_set_nominal_power(trx, val); + l1h->config.nominal_power_set_by_vty = true; + + return CMD_SUCCESS; +} + +DEFUN_USRATTR(cfg_trx_no_nominal_power, cfg_trx_no_nominal_power_cmd, + X(BTS_VTY_TRX_POWERCYCLE), + "no nominal-tx-power", + NO_STR + "Manually set (force) the nominal transmit output power; ask the TRX instead (default)\n") +{ + struct gsm_bts_trx *trx = vty->index; + struct phy_instance *pinst = trx_phy_instance(trx); + struct trx_l1h *l1h = pinst->u.osmotrx.hdl; + + l1h->config.nominal_power_set_by_vty = false; + + return CMD_SUCCESS; +} + +DEFUN_DEPRECATED(cfg_phy_ms_power_loop, cfg_phy_ms_power_loop_cmd, "osmotrx ms-power-loop <-127-127>", OSMOTRX_STR "Enable MS power control loop\nTarget RSSI value (transceiver specific, " "should be 6dB or more above noise floor)\n") { - struct phy_link *plink = vty->index; + vty_out(vty, "'%s' is deprecated, MS Power Control is now managed by BSC%s", + self->string, VTY_NEWLINE); - plink->u.osmotrx.trx_target_rssi = atoi(argv[0]); - plink->u.osmotrx.trx_ms_power_loop = true; + uint8_t rxlev = dbm2rxlev(atoi(argv[0])); + g_bts->ms_dpc_params.rxlev_meas.lower_thresh = rxlev; + g_bts->ms_dpc_params.rxlev_meas.upper_thresh = rxlev; return CMD_SUCCESS; } -DEFUN(cfg_phy_no_ms_power_loop, cfg_phy_no_ms_power_loop_cmd, +DEFUN_DEPRECATED(cfg_phy_no_ms_power_loop, cfg_phy_no_ms_power_loop_cmd, "no osmotrx ms-power-loop", NO_STR OSMOTRX_STR "Disable MS power control loop\n") { - struct phy_link *plink = vty->index; - - plink->u.osmotrx.trx_ms_power_loop = false; + vty_out(vty, "'%s' is deprecated, MS Power Control is now managed by BSC%s", + self->string, VTY_NEWLINE); return CMD_SUCCESS; } -DEFUN(cfg_phy_timing_advance_loop, cfg_phy_timing_advance_loop_cmd, +DEFUN_DEPRECATED(cfg_phy_timing_advance_loop, cfg_phy_timing_advance_loop_cmd, "osmotrx timing-advance-loop", OSMOTRX_STR "Enable timing advance control loop\n") { - struct phy_link *plink = vty->index; - - plink->u.osmotrx.trx_ta_loop = true; + vty_out(vty, "'%s' is deprecated, Timing Advance loop is now active by default%s", + self->string, VTY_NEWLINE); return CMD_SUCCESS; } -DEFUN(cfg_phy_no_timing_advance_loop, cfg_phy_no_timing_advance_loop_cmd, +DEFUN_DEPRECATED(cfg_phy_no_timing_advance_loop, cfg_phy_no_timing_advance_loop_cmd, "no osmotrx timing-advance-loop", NO_STR OSMOTRX_STR "Disable timing advance control loop\n") { - struct phy_link *plink = vty->index; - - plink->u.osmotrx.trx_ta_loop = false; + vty_out(vty, "'%s' is deprecated, Timing Advance loop is now active by default%s", + self->string, VTY_NEWLINE); return CMD_SUCCESS; } -DEFUN(cfg_phyinst_maxdly, cfg_phyinst_maxdly_cmd, - "osmotrx maxdly <0-31>", - OSMOTRX_STR - "Set the maximum acceptable delay of an Access Burst (in GSM symbols)." - " Access Burst is the first burst a mobile transmits in order to establish" - " a connection and it is used to estimate Timing Advance (TA) which is" - " then applied to Normal Bursts to compensate for signal delay due to" - " distance. So changing this setting effectively changes maximum range of" - " the cell, because if we receive an Access Burst with a delay higher than" - " this value, it will be ignored and connection is dropped.\n" - "GSM symbols (approx. 1.1km per symbol)\n") +DEFUN_USRATTR(cfg_phyinst_maxdly, cfg_phyinst_maxdly_cmd, + X(BTS_VTY_TRX_POWERCYCLE), + "osmotrx maxdly <0-63>", + OSMOTRX_STR + "Set the maximum acceptable delay of an Access Burst\n" + "Delay in GSMK symbol periods (approx. 550m per symbol)\n") { struct phy_instance *pinst = vty->index; struct trx_l1h *l1h = pinst->u.osmotrx.hdl; l1h->config.maxdly = atoi(argv[0]); l1h->config.maxdly_valid = 1; - l1h->config.maxdly_sent = 0; - l1if_provision_transceiver_trx(l1h); + l1h->config.maxdly_sent = false; return CMD_SUCCESS; } - -DEFUN(cfg_phyinst_maxdlynb, cfg_phyinst_maxdlynb_cmd, - "osmotrx maxdlynb <0-31>", - OSMOTRX_STR - "Set the maximum acceptable delay of a Normal Burst (in GSM symbols)." - " USE FOR TESTING ONLY, DON'T CHANGE IN PRODUCTION USE!" - " During normal operation, Normal Bursts delay are controlled by a Timing" - " Advance control loop and thus Normal Bursts arrive to a BTS with no more" - " than a couple GSM symbols, which is already taken into account in osmo-trx." - " So changing this setting will have no effect in production installations" - " except increasing osmo-trx CPU load. This setting is only useful when" - " testing with a transmitter which can't precisely synchronize to the BTS" - " downlink signal, like e.g. R&S CMD57.\n" - "GSM symbols (approx. 1.1km per symbol)\n") +DEFUN_ATTR_USRATTR(cfg_phyinst_maxdlynb, cfg_phyinst_maxdlynb_cmd, + CMD_ATTR_HIDDEN, /* expert mode command */ + X(BTS_VTY_TRX_POWERCYCLE), + "osmotrx maxdlynb <0-63>", + OSMOTRX_STR + "Set the maximum acceptable delay of a Normal Burst\n" + "Delay in GMSK symbol periods (approx. 550m per symbol)\n") { struct phy_instance *pinst = vty->index; struct trx_l1h *l1h = pinst->u.osmotrx.hdl; l1h->config.maxdlynb = atoi(argv[0]); l1h->config.maxdlynb_valid = 1; - l1h->config.maxdlynb_sent = 0; - l1if_provision_transceiver_trx(l1h); + l1h->config.maxdlynb_sent = false; return CMD_SUCCESS; } @@ -277,7 +358,7 @@ DEFUN(cfg_phyinst_slotmask, cfg_phyinst_slotmask_cmd, return CMD_SUCCESS; } -DEFUN(cfg_phyinst_power_on, cfg_phyinst_power_on_cmd, +DEFUN_DEPRECATED(cfg_phyinst_power_on, cfg_phyinst_power_on_cmd, "osmotrx power (on|off)", OSMOTRX_STR "Change TRX state\n" @@ -286,21 +367,26 @@ DEFUN(cfg_phyinst_power_on, cfg_phyinst_power_on_cmd, struct phy_instance *pinst = vty->index; struct trx_l1h *l1h = pinst->u.osmotrx.hdl; + vty_out (vty, "'osmotrx power' is deprecated, use OML's standard " + "Administrative State instead to control each TRX " + "('rf_locked' VTY cmd in osmo-bsc)%s", VTY_NEWLINE); + if (strcmp(argv[0], "on")) - vty_out(vty, "OFF: %d%s", trx_if_cmd_poweroff(l1h), VTY_NEWLINE); + vty_out(vty, "OFF: %d%s", trx_if_cmd_poweroff(l1h, NULL), VTY_NEWLINE); else { - vty_out(vty, "ON: %d%s", trx_if_cmd_poweron(l1h), VTY_NEWLINE); + vty_out(vty, "ON: %d%s", trx_if_cmd_poweron(l1h, NULL), VTY_NEWLINE); } return CMD_SUCCESS; } -DEFUN(cfg_phy_fn_advance, cfg_phy_fn_advance_cmd, - "osmotrx fn-advance <0-30>", - OSMOTRX_STR - "Set the number of frames to be transmitted to transceiver in advance " - "of current FN\n" - "Advance in frames\n") +DEFUN_ATTR(cfg_phy_fn_advance, cfg_phy_fn_advance_cmd, + "osmotrx fn-advance <0-30>", + OSMOTRX_STR + "Set the number of frames to be transmitted to transceiver in advance " + "of current FN\n" + "Advance in frames\n", + CMD_ATTR_IMMEDIATE) { struct phy_link *plink = vty->index; @@ -309,12 +395,13 @@ DEFUN(cfg_phy_fn_advance, cfg_phy_fn_advance_cmd, return CMD_SUCCESS; } -DEFUN(cfg_phy_rts_advance, cfg_phy_rts_advance_cmd, - "osmotrx rts-advance <0-30>", - OSMOTRX_STR - "Set the number of frames to be requested (PCU) in advance of current " - "FN. Do not change this, unless you have a good reason!\n" - "Advance in frames\n") +DEFUN_ATTR(cfg_phy_rts_advance, cfg_phy_rts_advance_cmd, + "osmotrx rts-advance <0-30>", + OSMOTRX_STR + "Set the number of frames to be requested (PCU) in advance of current " + "FN. Do not change this, unless you have a good reason!\n" + "Advance in frames\n", + CMD_ATTR_IMMEDIATE) { struct phy_link *plink = vty->index; @@ -323,61 +410,49 @@ DEFUN(cfg_phy_rts_advance, cfg_phy_rts_advance_cmd, return CMD_SUCCESS; } -DEFUN(cfg_phyinst_rxgain, cfg_phyinst_rxgain_cmd, - "osmotrx rx-gain <0-50>", - OSMOTRX_STR - "Set the receiver gain in dB\n" - "Gain in dB\n") +DEFUN_USRATTR(cfg_phyinst_rxgain, cfg_phyinst_rxgain_cmd, + X(BTS_VTY_TRX_POWERCYCLE), + "osmotrx rx-gain <0-50>", + OSMOTRX_STR + "Set the receiver gain in dB\n" + "Gain in dB\n") { struct phy_instance *pinst = vty->index; struct trx_l1h *l1h = pinst->u.osmotrx.hdl; l1h->config.rxgain = atoi(argv[0]); l1h->config.rxgain_valid = 1; - l1h->config.rxgain_sent = 0; - l1if_provision_transceiver_trx(l1h); + l1h->config.rxgain_sent = false; return CMD_SUCCESS; } -DEFUN(cfg_phyinst_tx_atten, cfg_phyinst_tx_atten_cmd, - "osmotrx tx-attenuation <0-50>", - OSMOTRX_STR - "Set the transmitter attenuation\n" - "Fixed attenuation in dB, overriding OML\n") +DEFUN_ATTR(cfg_phyinst_tx_atten, cfg_phyinst_tx_atten_cmd, + "osmotrx tx-attenuation (oml|<0-50>)", + OSMOTRX_STR + "Set the transmitter attenuation\n" + "Use NM_ATT_RF_MAXPOWR_R (max power reduction) from BSC via OML (default)\n" + "Fixed attenuation in dB, overriding OML (default)\n", + CMD_ATTR_IMMEDIATE) { struct phy_instance *pinst = vty->index; struct trx_l1h *l1h = pinst->u.osmotrx.hdl; - l1h->config.power = atoi(argv[0]); - l1h->config.power_oml = 0; - l1h->config.power_valid = 1; - l1h->config.power_sent = 0; - l1if_provision_transceiver_trx(l1h); - - return CMD_SUCCESS; -} - -DEFUN(cfg_phyinst_tx_atten_oml, cfg_phyinst_tx_atten_oml_cmd, - "osmotrx tx-attenuation oml", - OSMOTRX_STR - "Set the transmitter attenuation\n" - "Use NM_ATT_RF_MAXPOWR_R (max power reduction) from BSC via OML\n") -{ - struct phy_instance *pinst = vty->index; - struct trx_l1h *l1h = pinst->u.osmotrx.hdl; + if (strcmp(argv[0], "oml") == 0) + l1h->config.forced_max_power_red = -1; + else + l1h->config.forced_max_power_red = atoi(argv[0]); - l1h->config.power_oml = 1; - l1h->config.power_valid = 1; - l1h->config.power_sent = 0; - l1if_provision_transceiver_trx(l1h); + if (pinst->trx && pinst->trx->mo.nm_state.operational == NM_OPSTATE_ENABLED) + l1if_trx_start_power_ramp(pinst->trx, NULL); return CMD_SUCCESS; } -DEFUN(cfg_phyinst_no_rxgain, cfg_phyinst_no_rxgain_cmd, - "no osmotrx rx-gain", - NO_STR OSMOTRX_STR "Unset the receiver gain in dB\n") +DEFUN_USRATTR(cfg_phyinst_no_rxgain, cfg_phyinst_no_rxgain_cmd, + X(BTS_VTY_TRX_POWERCYCLE), + "no osmotrx rx-gain", + NO_STR OSMOTRX_STR "Unset the receiver gain in dB\n") { struct phy_instance *pinst = vty->index; struct trx_l1h *l1h = pinst->u.osmotrx.hdl; @@ -387,22 +462,11 @@ DEFUN(cfg_phyinst_no_rxgain, cfg_phyinst_no_rxgain_cmd, return CMD_SUCCESS; } -DEFUN(cfg_phyinst_no_tx_atten, cfg_phyinst_no_tx_atten_cmd, - "no osmotrx tx-attenuation", - NO_STR OSMOTRX_STR "Unset the transmitter attenuation\n") -{ - struct phy_instance *pinst = vty->index; - struct trx_l1h *l1h = pinst->u.osmotrx.hdl; - - l1h->config.power_valid = 0; - - return CMD_SUCCESS; -} - -DEFUN(cfg_phyinst_no_maxdly, cfg_phyinst_no_maxdly_cmd, - "no osmotrx maxdly", - NO_STR OSMOTRX_STR - "Unset the maximum delay of GSM symbols\n") +DEFUN_USRATTR(cfg_phyinst_no_maxdly, cfg_phyinst_no_maxdly_cmd, + X(BTS_VTY_TRX_POWERCYCLE), + "no osmotrx maxdly", + NO_STR OSMOTRX_STR + "Unset the maximum delay of GSM symbols\n") { struct phy_instance *pinst = vty->index; struct trx_l1h *l1h = pinst->u.osmotrx.hdl; @@ -412,10 +476,11 @@ DEFUN(cfg_phyinst_no_maxdly, cfg_phyinst_no_maxdly_cmd, return CMD_SUCCESS; } -DEFUN(cfg_phyinst_no_maxdlynb, cfg_phyinst_no_maxdlynb_cmd, - "no osmotrx maxdlynb", - NO_STR OSMOTRX_STR - "Unset the maximum delay of GSM symbols\n") +DEFUN_USRATTR(cfg_phyinst_no_maxdlynb, cfg_phyinst_no_maxdlynb_cmd, + X(BTS_VTY_TRX_POWERCYCLE), + "no osmotrx maxdlynb", + NO_STR OSMOTRX_STR + "Unset the maximum delay of GSM symbols\n") { struct phy_instance *pinst = vty->index; struct trx_l1h *l1h = pinst->u.osmotrx.hdl; @@ -472,9 +537,10 @@ DEFUN(cfg_phy_base_port, cfg_phy_base_port_cmd, return CMD_SUCCESS; } -DEFUN(cfg_phy_setbsic, cfg_phy_setbsic_cmd, - "osmotrx legacy-setbsic", OSMOTRX_STR - "Use SETBSIC to configure transceiver (use ONLY with OpenBTS Transceiver!)\n") +DEFUN_USRATTR(cfg_phy_setbsic, cfg_phy_setbsic_cmd, + X(BTS_VTY_TRX_POWERCYCLE), + "osmotrx legacy-setbsic", OSMOTRX_STR + "Use SETBSIC to configure transceiver (use ONLY with OpenBTS Transceiver!)\n") { struct phy_link *plink = vty->index; plink->u.osmotrx.use_legacy_setbsic = true; @@ -486,9 +552,10 @@ DEFUN(cfg_phy_setbsic, cfg_phy_setbsic_cmd, return CMD_SUCCESS; } -DEFUN(cfg_phy_no_setbsic, cfg_phy_no_setbsic_cmd, - "no osmotrx legacy-setbsic", - NO_STR OSMOTRX_STR "Disable Legacy SETBSIC to configure transceiver\n") +DEFUN_USRATTR(cfg_phy_no_setbsic, cfg_phy_no_setbsic_cmd, + X(BTS_VTY_TRX_POWERCYCLE), + "no osmotrx legacy-setbsic", + NO_STR OSMOTRX_STR "Disable Legacy SETBSIC to configure transceiver\n") { struct phy_link *plink = vty->index; plink->u.osmotrx.use_legacy_setbsic = false; @@ -496,30 +563,31 @@ DEFUN(cfg_phy_no_setbsic, cfg_phy_no_setbsic_cmd, return CMD_SUCCESS; } -DEFUN(cfg_phy_trxd_max_version, cfg_phy_trxd_max_version_cmd, - "osmotrx trxd-max-version (latest|<0-15>)", OSMOTRX_STR - "Set maximum TRXD format version to negotiate with TRX\n" - "Use latest supported TRXD format version (default)\n" - "Maximum TRXD format version number\n") +DEFUN_USRATTR(cfg_phy_trxd_max_version, cfg_phy_trxd_max_version_cmd, + X(BTS_VTY_TRX_POWERCYCLE), + "osmotrx trxd-max-version (latest|<0-15>)", OSMOTRX_STR + "Set maximum TRXD format version to negotiate with TRX\n" + "Use latest supported TRXD format version (default)\n" + "Maximum TRXD format version number\n") { struct phy_link *plink = vty->index; int max_ver; if (strcmp(argv[0], "latest") == 0) - max_ver = TRX_DATA_FORMAT_VER; + max_ver = TRX_DATA_PDU_VER; else max_ver = atoi(argv[0]); - if (max_ver > TRX_DATA_FORMAT_VER) { + if (max_ver > TRX_DATA_PDU_VER) { vty_out(vty, "%% Format version %d is not supported, maximum supported is %d%s", - max_ver, TRX_DATA_FORMAT_VER, VTY_NEWLINE); + max_ver, TRX_DATA_PDU_VER, VTY_NEWLINE); return CMD_WARNING; } - plink->u.osmotrx.trxd_hdr_ver_max = max_ver; + plink->u.osmotrx.trxd_pdu_ver_max = max_ver; return CMD_SUCCESS; } -void bts_model_config_write_phy(struct vty *vty, struct phy_link *plink) +void bts_model_config_write_phy(struct vty *vty, const struct phy_link *plink) { if (plink->u.osmotrx.local_ip) vty_out(vty, " osmotrx ip local %s%s", @@ -528,12 +596,6 @@ void bts_model_config_write_phy(struct vty *vty, struct phy_link *plink) vty_out(vty, " osmotrx ip remote %s%s", plink->u.osmotrx.remote_ip, VTY_NEWLINE); - if (plink->u.osmotrx.trx_ms_power_loop) - vty_out(vty, " osmotrx ms-power-loop %d%s", plink->u.osmotrx.trx_target_rssi, VTY_NEWLINE); - else - vty_out(vty, " no osmotrx ms-power-loop%s", VTY_NEWLINE); - vty_out(vty, " %sosmotrx timing-advance-loop%s", (plink->u.osmotrx.trx_ta_loop) ? "" : "no ", VTY_NEWLINE); - if (plink->u.osmotrx.base_port_local) vty_out(vty, " osmotrx base-port local %"PRIu16"%s", plink->u.osmotrx.base_port_local, VTY_NEWLINE); @@ -549,24 +611,22 @@ void bts_model_config_write_phy(struct vty *vty, struct phy_link *plink) if (plink->u.osmotrx.use_legacy_setbsic) vty_out(vty, " osmotrx legacy-setbsic%s", VTY_NEWLINE); - if (plink->u.osmotrx.trxd_hdr_ver_max != TRX_DATA_FORMAT_VER) - vty_out(vty, " osmotrx trxd-max-version %d%s", plink->u.osmotrx.trxd_hdr_ver_max, VTY_NEWLINE); + if (plink->u.osmotrx.trxd_pdu_ver_max != TRX_DATA_PDU_VER) + vty_out(vty, " osmotrx trxd-max-version %d%s", plink->u.osmotrx.trxd_pdu_ver_max, VTY_NEWLINE); } -void bts_model_config_write_phy_inst(struct vty *vty, struct phy_instance *pinst) +void bts_model_config_write_phy_inst(struct vty *vty, const struct phy_instance *pinst) { struct trx_l1h *l1h = pinst->u.osmotrx.hdl; if (l1h->config.rxgain_valid) vty_out(vty, " osmotrx rx-gain %d%s", l1h->config.rxgain, VTY_NEWLINE); - if (l1h->config.power_valid) { - if (l1h->config.power_oml) - vty_out(vty, " osmotrx tx-attenuation oml%s", VTY_NEWLINE); - else - vty_out(vty, " osmotrx tx-attenuation %d%s", - l1h->config.power, VTY_NEWLINE); - } + if (l1h->config.forced_max_power_red == -1) + vty_out(vty, " osmotrx tx-attenuation oml%s", VTY_NEWLINE); + else + vty_out(vty, " osmotrx tx-attenuation %d%s", + l1h->config.forced_max_power_red, VTY_NEWLINE); if (l1h->config.maxdly_valid) vty_out(vty, " osmotrx maxdly %d%s", l1h->config.maxdly, VTY_NEWLINE); if (l1h->config.maxdlynb_valid) @@ -584,21 +644,30 @@ void bts_model_config_write_phy_inst(struct vty *vty, struct phy_instance *pinst VTY_NEWLINE); } -void bts_model_config_write_bts(struct vty *vty, struct gsm_bts *bts) +void bts_model_config_write_bts(struct vty *vty, const struct gsm_bts *bts) { } -void bts_model_config_write_trx(struct vty *vty, struct gsm_bts_trx *trx) +void bts_model_config_write_trx(struct vty *vty, const struct gsm_bts_trx *trx) { + struct phy_instance *pinst = trx_phy_instance(trx); + struct trx_l1h *l1h = pinst->u.osmotrx.hdl; + + if (l1h->config.nominal_power_set_by_vty) + vty_out(vty, " nominal-tx-power %d%s", trx->nominal_power, + VTY_NEWLINE); } -int bts_model_vty_init(struct gsm_bts *bts) +int bts_model_vty_init(void *ctx) { - vty_bts = bts; - install_element_ve(&show_transceiver_cmd); install_element_ve(&show_phy_cmd); + install_element(ENABLE_NODE, &test_send_trxc_cmd); + + install_element(TRX_NODE, &cfg_trx_nominal_power_cmd); + install_element(TRX_NODE, &cfg_trx_no_nominal_power_cmd); + install_element(PHY_NODE, &cfg_phy_ms_power_loop_cmd); install_element(PHY_NODE, &cfg_phy_no_ms_power_loop_cmd); install_element(PHY_NODE, &cfg_phy_timing_advance_loop_cmd); @@ -614,9 +683,7 @@ int bts_model_vty_init(struct gsm_bts *bts) install_element(PHY_INST_NODE, &cfg_phyinst_rxgain_cmd); install_element(PHY_INST_NODE, &cfg_phyinst_tx_atten_cmd); - install_element(PHY_INST_NODE, &cfg_phyinst_tx_atten_oml_cmd); install_element(PHY_INST_NODE, &cfg_phyinst_no_rxgain_cmd); - install_element(PHY_INST_NODE, &cfg_phyinst_no_tx_atten_cmd); install_element(PHY_INST_NODE, &cfg_phyinst_slotmask_cmd); install_element(PHY_INST_NODE, &cfg_phyinst_power_on_cmd); install_element(PHY_INST_NODE, &cfg_phyinst_maxdly_cmd); diff --git a/src/osmo-bts-virtual/Makefile.am b/src/osmo-bts-virtual/Makefile.am index 070efed6..bbb79eca 100644 --- a/src/osmo-bts-virtual/Makefile.am +++ b/src/osmo-bts-virtual/Makefile.am @@ -1,10 +1,50 @@ -AM_CFLAGS = -Wall -fno-strict-aliasing $(LIBOSMOCORE_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(LIBOSMOTRAU_CFLAGS) $(LIBOSMOABIS_CFLAGS) $(LIBOSMOCTRL_CFLAGS) $(LIBOSMOABIS_CFLAGS) $(LIBGPS_CFLAGS) +AM_CFLAGS = \ + -Wall -fno-strict-aliasing \ + $(LIBOSMOCORE_CFLAGS) \ + $(LIBOSMOGSM_CFLAGS) \ + $(LIBOSMOVTY_CFLAGS) \ + $(LIBOSMOCTRL_CFLAGS) \ + $(LIBOSMOABIS_CFLAGS) \ + $(LIBOSMOTRAU_CFLAGS) \ + $(LIBOSMONETIF_CFLAGS) \ + $(LIBOSMOCODEC_CFLAGS) \ + $(LIBGPS_CFLAGS) \ + $(NULL) + AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include -Iinclude -COMMON_LDADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOGSM_LIBS) $(LIBOSMOVTY_LIBS) $(LIBOSMOTRAU_LIBS) $(LIBOSMOABIS_LIBS) $(LIBOSMOCTRL_LIBS) -ldl -noinst_HEADERS = l1_if.h osmo_mcast_sock.h virtual_um.h +COMMON_LDADD = \ + $(LIBOSMOCORE_LIBS) \ + $(LIBOSMOGSM_LIBS) \ + $(LIBOSMOVTY_LIBS) \ + $(LIBOSMOCTRL_LIBS) \ + $(LIBOSMOABIS_LIBS) \ + $(LIBOSMOTRAU_LIBS) \ + $(LIBOSMONETIF_LIBS) \ + $(LIBOSMOCODEC_LIBS) \ + -ldl \ + $(NULL) + +noinst_HEADERS = \ + l1_if.h \ + osmo_mcast_sock.h \ + virtual_um.h \ + $(NULL) bin_PROGRAMS = osmo-bts-virtual -osmo_bts_virtual_SOURCES = main.c bts_model.c virtualbts_vty.c scheduler_virtbts.c l1_if.c virtual_um.c osmo_mcast_sock.c -osmo_bts_virtual_LDADD = $(top_builddir)/src/common/libl1sched.a $(top_builddir)/src/common/libbts.a $(COMMON_LDADD) +osmo_bts_virtual_SOURCES = \ + main.c \ + bts_model.c \ + virtualbts_vty.c \ + scheduler_virtbts.c \ + l1_if.c \ + virtual_um.c \ + osmo_mcast_sock.c \ + $(NULL) + +osmo_bts_virtual_LDADD = \ + $(top_builddir)/src/common/libl1sched.a \ + $(top_builddir)/src/common/libbts.a \ + $(COMMON_LDADD) \ + $(NULL) diff --git a/src/osmo-bts-virtual/bts_model.c b/src/osmo-bts-virtual/bts_model.c index b971af5c..a70abfb3 100644 --- a/src/osmo-bts-virtual/bts_model.c +++ b/src/osmo-bts-virtual/bts_model.c @@ -10,7 +10,7 @@ * 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. + * 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/>. @@ -23,6 +23,7 @@ #include <osmocom/core/talloc.h> #include <osmocom/core/utils.h> #include <osmocom/codec/codec.h> +#include <osmocom/core/fsm.h> #include <osmo-bts/gsm_data.h> #include <osmo-bts/phy_link.h> @@ -34,6 +35,9 @@ #include <osmo-bts/bts_model.h> #include <osmo-bts/handover.h> #include <osmo-bts/l1sap.h> +#include <osmo-bts/nm_common_fsm.h> + +#include "virtual_um.h" /* TODO: check if dummy method is sufficient, else implement */ int bts_model_lchan_deactivate(struct gsm_lchan *lchan) @@ -50,10 +54,17 @@ int osmo_amr_rtp_dec(const uint8_t *rtppayload, int payload_len, uint8_t *cmr, return -1; } -int bts_model_trx_close(struct gsm_bts_trx *trx) +void bts_model_trx_close(struct gsm_bts_trx *trx) { - LOGP(DL1C, LOGL_NOTICE, "Unimplemented %s\n", __func__); - return 0; + struct phy_instance *pinst = trx_phy_instance(trx); + struct phy_link *plink = pinst->phy_link; + + if (phy_link_state_get(plink) != PHY_LINK_SHUTDOWN) { + virt_um_destroy(plink->u.virt.virt_um); + plink->u.virt.virt_um = NULL; + phy_link_state_set(plink, PHY_LINK_SHUTDOWN); + } + bts_model_trx_close_cb(trx, 0); } int bts_model_adjst_ms_pwr(struct gsm_lchan *lchan) @@ -72,18 +83,11 @@ int bts_model_check_oml(struct gsm_bts *bts, uint8_t msg_type, static uint8_t vbts_set_bts(struct gsm_bts *bts) { struct gsm_bts_trx *trx; - uint8_t tn; llist_for_each_entry(trx, &bts->trx_list, list) { - oml_mo_state_chg(&trx->mo, NM_OPSTATE_DISABLED, NM_AVSTATE_OK); - oml_mo_state_chg(&trx->bb_transc.mo, -1, NM_AVSTATE_OK); - - for (tn = 0; tn < TRX_NR_TS; tn++) - oml_mo_state_chg(&trx->ts[tn].mo, NM_OPSTATE_DISABLED, NM_AVSTATE_DEPENDENCY); - /* report availability of trx to the bts. this will trigger the rsl connection */ - oml_mo_tx_sw_act_rep(&trx->mo); - oml_mo_tx_sw_act_rep(&trx->bb_transc.mo); + osmo_fsm_inst_dispatch(trx->mo.fi, NM_EV_SW_ACT, NULL); + osmo_fsm_inst_dispatch(trx->bb_transc.mo.fi, NM_EV_SW_ACT, NULL); } return 0; } @@ -96,34 +100,78 @@ static uint8_t vbts_set_trx(struct gsm_bts_trx *trx) static uint8_t vbts_set_ts(struct gsm_bts_trx_ts *ts) { - struct phy_instance *pinst = trx_phy_instance(ts->trx); - int rc; + enum gsm_phys_chan_config pchan; + + /* For dynamic timeslots, pick the pchan type that should currently be + * active. This should only be called during init, PDCH transitions + * will call trx_set_ts_as_pchan() directly. */ + switch (ts->pchan) { + case GSM_PCHAN_TCH_F_PDCH: + OSMO_ASSERT((ts->flags & TS_F_PDCH_PENDING_MASK) == 0); + if (ts->flags & TS_F_PDCH_ACTIVE) + pchan = GSM_PCHAN_PDCH; + else + pchan = GSM_PCHAN_TCH_F; + break; + case GSM_PCHAN_OSMO_DYN: + OSMO_ASSERT(ts->dyn.pchan_is == ts->dyn.pchan_want); + pchan = ts->dyn.pchan_is; + break; + default: + pchan = ts->pchan; + break; + } - rc = trx_sched_set_pchan(&pinst->u.virt.sched, ts->nr, ts->pchan); - if (rc) + if (trx_sched_set_pchan(ts, pchan) != 0) return NM_NACK_RES_NOTAVAIL; + /* activate lchans for [CBCH/]BCCH/CCCH */ + switch (pchan) { + case GSM_PCHAN_SDCCH8_SACCH8C_CBCH: + /* using RSL_CHAN_OSMO_CBCH4 is correct here, because the scheduler + * does not distinguish between SDCCH/4+CBCH abd SDCCH/8+CBCH. */ + trx_sched_set_lchan(&ts->lchan[CBCH_LCHAN], + RSL_CHAN_OSMO_CBCH4, LID_DEDIC, true); + break; + case GSM_PCHAN_CCCH_SDCCH4_CBCH: + trx_sched_set_lchan(&ts->lchan[CBCH_LCHAN], + RSL_CHAN_OSMO_CBCH4, LID_DEDIC, true); + /* fall-through */ + case GSM_PCHAN_CCCH_SDCCH4: + case GSM_PCHAN_CCCH: + trx_sched_set_bcch_ccch(&ts->lchan[CCCH_LCHAN], true); + ts->lchan[CCCH_LCHAN].rel_act_kind = LCHAN_REL_ACT_OML; + lchan_set_state(&ts->lchan[CCCH_LCHAN], LCHAN_S_ACTIVE); + break; + default: + break; + } + return 0; } -int bts_model_apply_oml(struct gsm_bts *bts, struct msgb *msg, - struct tlv_parsed *new_attr, int kind, void *obj) +int bts_model_apply_oml(struct gsm_bts *bts, const struct msgb *msg, + struct gsm_abis_mo *mo, void *obj) { struct abis_om_fom_hdr *foh = msgb_l3(msg); - int cause = 0; + int rc; switch (foh->msg_type) { case NM_MT_SET_BTS_ATTR: - cause = vbts_set_bts(obj); + rc = vbts_set_bts(obj); break; case NM_MT_SET_RADIO_ATTR: - cause = vbts_set_trx(obj); + rc = vbts_set_trx(obj); break; case NM_MT_SET_CHAN_ATTR: - cause = vbts_set_ts(obj); + rc = vbts_set_ts(obj); + break; + default: + rc = 0; break; } - return oml_fom_ack_nack(msg, cause); + + return rc; } /* MO: TS 12.21 Managed Object */ @@ -132,16 +180,15 @@ int bts_model_opstart(struct gsm_bts *bts, struct gsm_abis_mo *mo, void *obj) int rc; switch (mo->obj_class) { - case NM_OC_RADIO_CARRIER: - case NM_OC_CHANNEL: case NM_OC_SITE_MANAGER: - case NM_OC_BASEB_TRANSC: case NM_OC_BTS: + case NM_OC_RADIO_CARRIER: + case NM_OC_BASEB_TRANSC: + case NM_OC_CHANNEL: case NM_OC_GPRS_NSE: case NM_OC_GPRS_CELL: case NM_OC_GPRS_NSVC: - oml_mo_state_chg(mo, NM_OPSTATE_ENABLED, NM_AVSTATE_OK); - rc = oml_mo_opstart_ack(mo); + rc = osmo_fsm_inst_dispatch(mo->fi, NM_EV_OPSTART_ACK, NULL); break; default: rc = oml_mo_opstart_nack(mo, NM_NACK_OBJCLASS_NOTSUPP); @@ -166,6 +213,7 @@ int bts_model_trx_deact_rf(struct gsm_bts_trx *trx) int bts_model_change_power(struct gsm_bts_trx *trx, int p_trxout_mdBm) { LOGP(DL1C, LOGL_NOTICE, "Unimplemented %s\n", __func__); + power_trx_change_compl(trx, p_trxout_mdBm); return 0; } diff --git a/src/osmo-bts-virtual/l1_if.c b/src/osmo-bts-virtual/l1_if.c index acd8ea2b..2408557d 100644 --- a/src/osmo-bts-virtual/l1_if.c +++ b/src/osmo-bts-virtual/l1_if.c @@ -42,6 +42,7 @@ #include <osmo-bts/amr.h> #include <osmo-bts/abis.h> #include <osmo-bts/scheduler.h> +#include <osmo-bts/handover.h> #include "virtual_um.h" extern int vbts_sched_start(struct gsm_bts *bts); @@ -128,12 +129,7 @@ static void virt_um_rcv_cb(struct virt_um_inst *vui, struct msgb *msg) break; case GSMTAP_CHANNEL_TCH_F: case GSMTAP_CHANNEL_TCH_H: -#if 0 - /* TODO: handle voice messages */ - if (!facch && ! tch_acch) { - osmo_prim_init(&l1sap.oph, SAP_GSM_PH, PRIM_TCH, PRIM_OP_INDICATION, msg); - } -#endif + /* This is TCH signalling, for voice frames see GSMTAP_CHANNEL_VOICE */ case GSMTAP_CHANNEL_SDCCH4: case GSMTAP_CHANNEL_SDCCH8: case GSMTAP_CHANNEL_PACCH: @@ -151,6 +147,19 @@ static void virt_um_rcv_cb(struct virt_um_inst *vui, struct msgb *msg) l1sap.u.data.pdch_presence_info = PRES_INFO_BOTH; l1if_process_meas_res(pinst->trx, timeslot, fn, chan_nr, 0, 0, 0, 0); break; + case GSMTAP_CHANNEL_VOICE_F: + case GSMTAP_CHANNEL_VOICE_H: + /* the first byte indicates the type of voice codec (gsmtap_um_voice_type) */ + msg->l2h = msgb_pull(msg, 1); + osmo_prim_init(&l1sap.oph, SAP_GSM_PH, PRIM_TCH, PRIM_OP_INDICATION, msg); + l1sap.u.tch.chan_nr = chan_nr; + l1sap.u.tch.fn = fn; + l1sap.u.tch.rssi = 0; /* Radio Signal Strength Indicator. Best -> 0 */ + l1sap.u.tch.ber10k = 0; /* Bit Error Rate in 0.01%. Best -> 0 */ + l1sap.u.tch.ta_offs_256bits = 0; /* Burst time of arrival in quarter bits. Probably used for Timing Advance calc. Best -> 0 */ + l1sap.u.tch.lqual_cb = 10 * signal_dbm; /* Link quality in centiBel = 10 * dB. */ + l1if_process_meas_res(pinst->trx, timeslot, fn, chan_nr, 0, 0, 0, 0); + break; case GSMTAP_CHANNEL_AGCH: case GSMTAP_CHANNEL_PCH: case GSMTAP_CHANNEL_BCCH: @@ -179,6 +188,11 @@ nomessage: /* called by common part once OML link is established */ int bts_model_oml_estab(struct gsm_bts *bts) { + struct phy_instance *pinst = trx_phy_instance(bts->c0); + + if (vbts_sched_start(pinst->trx->bts) < 0) + return -ENOLINK; + return 0; } @@ -196,7 +210,7 @@ int bts_model_phy_link_open(struct phy_link *plink) plink->u.virt.virt_um = virt_um_init(plink, plink->u.virt.ms_mcast_group, plink->u.virt.ms_mcast_port, plink->u.virt.bts_mcast_group, plink->u.virt.bts_mcast_port, - virt_um_rcv_cb); + plink->u.virt.ttl, plink->u.virt.mcast_dev, virt_um_rcv_cb); if (!plink->u.virt.virt_um) { phy_link_state_set(plink, PHY_LINK_SHUTDOWN); return -1; @@ -206,20 +220,9 @@ int bts_model_phy_link_open(struct phy_link *plink) /* iterate over list of PHY instances and initialize the scheduler */ llist_for_each_entry(pinst, &plink->instances, list) { - trx_sched_init(&pinst->u.virt.sched, pinst->trx); - /* Only start the scheduler for the transceiver on C0. - * If we have multiple tranceivers, CCCH is always on C0 - * and has to be auto active */ - /* Other TRX are activated via OML by a PRIM_INFO_MODIFY - * / PRIM_INFO_ACTIVATE */ - if (pinst->trx && pinst->trx == pinst->trx->bts->c0) { - vbts_sched_start(pinst->trx->bts); - /* init lapdm layer 3 callback for the trx on timeslot 0 == BCCH */ - lchan_init_lapdm(&pinst->trx->ts[0].lchan[CCCH_LCHAN]); - /* FIXME: This is probably the wrong location to set the CCCH to active... the OML link def. needs to be reworked and fixed. */ - pinst->trx->ts[0].lchan[CCCH_LCHAN].rel_act_kind = LCHAN_REL_ACT_OML; - lchan_set_state(&pinst->trx->ts[0].lchan[CCCH_LCHAN], LCHAN_S_ACTIVE); - } + if (pinst->trx == NULL) + continue; + trx_sched_init(pinst->trx); } /* this will automatically update the MO state of all associated TRX objects */ @@ -236,28 +239,19 @@ int bts_model_phy_link_open(struct phy_link *plink) /* enable ciphering */ static int l1if_set_ciphering(struct gsm_lchan *lchan, uint8_t chan_nr, int downlink) { - struct gsm_bts_trx *trx = lchan->ts->trx; - struct phy_instance *pinst = trx_phy_instance(trx); - struct l1sched_trx *sched = &pinst->u.virt.sched; - /* ciphering already enabled in both directions */ if (lchan->ciph_state == LCHAN_CIPH_RXTX_CONF) return -EINVAL; if (!downlink) { /* set uplink */ - trx_sched_set_cipher(sched, chan_nr, 0, lchan->encr.alg_id - 1, - lchan->encr.key, lchan->encr.key_len); + trx_sched_set_cipher(lchan, chan_nr, false); lchan->ciph_state = LCHAN_CIPH_RX_CONF; } else { /* set downlink and also set uplink, if not already */ - if (lchan->ciph_state != LCHAN_CIPH_RX_CONF) { - trx_sched_set_cipher(sched, chan_nr, 0, - lchan->encr.alg_id - 1, lchan->encr.key, - lchan->encr.key_len); - } - trx_sched_set_cipher(sched, chan_nr, 1, lchan->encr.alg_id - 1, - lchan->encr.key, lchan->encr.key_len); + if (lchan->ciph_state != LCHAN_CIPH_RX_CONF) + trx_sched_set_cipher(lchan, chan_nr, false); + trx_sched_set_cipher(lchan, chan_nr, true); lchan->ciph_state = LCHAN_CIPH_RXTX_CONF; } @@ -318,12 +312,12 @@ static int l1if_process_meas_res(struct gsm_bts_trx *trx, uint8_t tn, uint32_t f /* 100% BER is n_bits_total is 0 */ float ber = n_bits_total==0 ? 1.0 : (float)n_errors / (float)n_bits_total; - DEBUGPFN(DMEAS, fn, "RX L1 frame %s chan_nr=0x%02x MS pwr=%ddBm rssi=%.1f dBFS " - "ber=%.2f%% (%d/%d bits) L1_ta=%d rqd_ta=%d toa=%.2f\n", - gsm_lchan_name(lchan), chan_nr, ms_pwr_dbm(lchan->ts->trx->bts->band, lchan->ms_power), - rssi, ber*100, n_errors, n_bits_total, lchan->meas.l1_info[1], lchan->rqd_ta, toa); + LOGPLCFN(lchan, fn, DMEAS, LOGL_DEBUG, "RX L1 frame chan_nr=0x%02x MS pwr=%ddBm rssi=%.1f dBFS " + "ber=%.2f%% (%d/%d bits) L1_ta=%d ta_ctrl.current=%d toa=%.2f\n", chan_nr, + ms_pwr_dbm(lchan->ts->trx->bts->band, lchan->ms_power_ctrl.max), rssi, ber * 100, n_errors, + n_bits_total, lchan->meas.l1_info.ta, lchan->ta_ctrl.current, toa); - l1if_fill_meas_res(&l1sap, chan_nr, lchan->rqd_ta + toa, ber, rssi, fn); + l1if_fill_meas_res(&l1sap, chan_nr, lchan->ta_ctrl.current + toa, ber, rssi, fn); return l1sap_up(trx, &l1sap); } @@ -333,11 +327,8 @@ static int l1if_process_meas_res(struct gsm_bts_trx *trx, uint8_t tn, uint32_t f /* primitive from common part */ int bts_model_l1sap_down(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap) { - struct phy_instance *pinst = trx_phy_instance(trx); - struct l1sched_trx *sched = &pinst->u.virt.sched; struct msgb *msg = l1sap->oph.msg; uint8_t chan_nr; - uint8_t tn, ss; int rc = 0; struct gsm_lchan *lchan; @@ -346,95 +337,94 @@ int bts_model_l1sap_down(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap) if (!msg) break; /* put data into scheduler's queue */ - return trx_sched_ph_data_req(sched, l1sap); + return trx_sched_ph_data_req(trx, l1sap); case OSMO_PRIM(PRIM_TCH, PRIM_OP_REQUEST): if (!msg) break; /* put data into scheduler's queue */ - return trx_sched_tch_req(sched, l1sap); + return trx_sched_tch_req(trx, l1sap); case OSMO_PRIM(PRIM_MPH_INFO, PRIM_OP_REQUEST): + if (l1sap->u.info.type == PRIM_INFO_ACT_CIPH) + chan_nr = l1sap->u.info.u.ciph_req.chan_nr; + else /* u.act_req used by PRIM_INFO_{ACTIVATE,DEACTIVATE,MODIFY} */ + chan_nr = l1sap->u.info.u.act_req.chan_nr; + lchan = get_lchan_by_chan_nr(trx, chan_nr); + if (OSMO_UNLIKELY(lchan == NULL)) { + LOGP(DL1C, LOGL_ERROR, + "Rx MPH-INFO.req (type=0x%02x) for non-existent lchan (%s)\n", + l1sap->u.info.type, rsl_chan_nr_str(chan_nr)); + rc = -ENODEV; + break; + } + switch (l1sap->u.info.type) { case PRIM_INFO_ACT_CIPH: - chan_nr = l1sap->u.info.u.ciph_req.chan_nr; - tn = L1SAP_CHAN2TS(chan_nr); - ss = l1sap_chan2ss(chan_nr); - lchan = &trx->ts[tn].lchan[ss]; if (l1sap->u.info.u.ciph_req.uplink) l1if_set_ciphering(lchan, chan_nr, 0); if (l1sap->u.info.u.ciph_req.downlink) l1if_set_ciphering(lchan, chan_nr, 1); break; case PRIM_INFO_ACTIVATE: - case PRIM_INFO_DEACTIVATE: - case PRIM_INFO_MODIFY: - chan_nr = l1sap->u.info.u.act_req.chan_nr; - tn = L1SAP_CHAN2TS(chan_nr); - ss = l1sap_chan2ss(chan_nr); - lchan = &trx->ts[tn].lchan[ss]; - /* we receive a channel activation request from the BSC, - * e.g. as a response to a channel req on RACH */ - if (l1sap->u.info.type == PRIM_INFO_ACTIVATE) { - if ((chan_nr & 0xE0) == 0x80) { - LOGP(DL1C, LOGL_ERROR, "Cannot activate" - " chan_nr 0x%02x\n", chan_nr); - break; - } - /* activate dedicated channel */ - trx_sched_set_lchan(sched, chan_nr, LID_DEDIC, 1); - /* activate associated channel */ - trx_sched_set_lchan(sched, chan_nr, LID_SACCH, 1); - /* set mode */ - trx_sched_set_mode(sched, chan_nr, - lchan->rsl_cmode, lchan->tch_mode, - lchan->tch.amr_mr.num_modes, - lchan->tch.amr_mr.bts_mode[0].mode, - lchan->tch.amr_mr.bts_mode[1].mode, - lchan->tch.amr_mr.bts_mode[2].mode, - lchan->tch.amr_mr.bts_mode[3].mode, - amr_get_initial_mode(lchan), - (lchan->ho.active == 1)); - /* init lapdm */ - lchan_init_lapdm(lchan); - /* set lchan active */ - lchan_set_state(lchan, LCHAN_S_ACTIVE); - /* set initial ciphering */ - l1if_set_ciphering(lchan, chan_nr, 0); - l1if_set_ciphering(lchan, chan_nr, 1); - if (lchan->encr.alg_id) - lchan->ciph_state = LCHAN_CIPH_RXTX_CONF; - else - lchan->ciph_state = LCHAN_CIPH_NONE; - - /* confirm */ - mph_info_chan_confirm(trx, chan_nr, - PRIM_INFO_ACTIVATE, 0); - break; - } - if (l1sap->u.info.type == PRIM_INFO_MODIFY) { - /* change mode */ - trx_sched_set_mode(sched, chan_nr, - lchan->rsl_cmode, lchan->tch_mode, - lchan->tch.amr_mr.num_modes, - lchan->tch.amr_mr.bts_mode[0].mode, - lchan->tch.amr_mr.bts_mode[1].mode, - lchan->tch.amr_mr.bts_mode[2].mode, - lchan->tch.amr_mr.bts_mode[3].mode, - amr_get_initial_mode(lchan), - 0); + if ((chan_nr & 0xE0) == 0x80) { + LOGPLCHAN(lchan, DL1C, LOGL_ERROR, "Cannot activate" + " channel %s\n", rsl_chan_nr_str(chan_nr)); + rc = -EPERM; break; } + /* activate dedicated channel */ + trx_sched_set_lchan(lchan, chan_nr, LID_DEDIC, true); + /* activate associated channel */ + trx_sched_set_lchan(lchan, chan_nr, LID_SACCH, true); + /* set mode */ + trx_sched_set_mode(lchan->ts, chan_nr, + lchan->rsl_cmode, lchan->tch_mode, + lchan->tch.amr_mr.num_modes, + lchan->tch.amr_mr.mode[0].mode, + lchan->tch.amr_mr.mode[1].mode, + lchan->tch.amr_mr.mode[2].mode, + lchan->tch.amr_mr.mode[3].mode, + amr_get_initial_mode(lchan), + (lchan->ho.active == HANDOVER_ENABLED || + rsl_chan_rt_is_asci(lchan->rsl_chan_rt))); + /* set lchan active */ + lchan_set_state(lchan, LCHAN_S_ACTIVE); + /* set initial ciphering */ + l1if_set_ciphering(lchan, chan_nr, 0); + l1if_set_ciphering(lchan, chan_nr, 1); + if (lchan->encr.alg_id) + lchan->ciph_state = LCHAN_CIPH_RXTX_CONF; + else + lchan->ciph_state = LCHAN_CIPH_NONE; + + /* confirm */ + mph_info_chan_confirm(trx, chan_nr, PRIM_INFO_ACTIVATE, 0); + break; + case PRIM_INFO_MODIFY: + /* change mode */ + trx_sched_set_mode(lchan->ts, chan_nr, + lchan->rsl_cmode, lchan->tch_mode, + lchan->tch.amr_mr.num_modes, + lchan->tch.amr_mr.mode[0].mode, + lchan->tch.amr_mr.mode[1].mode, + lchan->tch.amr_mr.mode[2].mode, + lchan->tch.amr_mr.mode[3].mode, + amr_get_initial_mode(lchan), + 0); + break; + case PRIM_INFO_DEACTIVATE: if ((chan_nr & 0xE0) == 0x80) { - LOGP(DL1C, LOGL_ERROR, "Cannot deactivate " - "chan_nr 0x%02x\n", chan_nr); + LOGPLCHAN(lchan, DL1C, LOGL_ERROR, "Cannot deactivate" + " channel %s\n", rsl_chan_nr_str(chan_nr)); + rc = -EPERM; break; } /* deactivate associated channel */ - trx_sched_set_lchan(sched, chan_nr, 0x40, 0); + trx_sched_set_lchan(lchan, chan_nr, LID_SACCH, false); if (!l1sap->u.info.u.act_req.sacch_only) { /* set lchan inactive */ lchan_set_state(lchan, LCHAN_S_NONE); /* deactivate dedicated channel */ - trx_sched_set_lchan(sched, chan_nr, 0x00, 0); + trx_sched_set_lchan(lchan, chan_nr, LID_DEDIC, false); /* confirm only on dedicated channel */ mph_info_chan_confirm(trx, chan_nr, PRIM_INFO_DEACTIVATE, 0); diff --git a/src/osmo-bts-virtual/l1_if.h b/src/osmo-bts-virtual/l1_if.h index 6a843b37..c55e2d38 100644 --- a/src/osmo-bts-virtual/l1_if.h +++ b/src/osmo-bts-virtual/l1_if.h @@ -5,9 +5,15 @@ #include "virtual_um.h" +/* gsm_bts->model_priv, specific to osmo-bts-virtual */ +struct bts_virt_priv { + uint32_t last_fn; + struct timeval tv_clock; + struct osmo_timer_list fn_timer; +}; + struct vbts_l1h { struct gsm_bts_trx *trx; - struct l1sched_trx l1s; struct virt_um_inst *virt_um; }; diff --git a/src/osmo-bts-virtual/main.c b/src/osmo-bts-virtual/main.c index aa1c608e..82becc37 100644 --- a/src/osmo-bts-virtual/main.c +++ b/src/osmo-bts-virtual/main.c @@ -12,7 +12,7 @@ * 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. + * 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/>. @@ -47,39 +47,68 @@ #include <osmo-bts/l1sap.h> #include <osmo-bts/phy_link.h> #include "virtual_um.h" +#include "l1_if.h" /* dummy, since no direct dsp support */ -uint32_t trx_get_hlayer1(struct gsm_bts_trx *trx) +uint32_t trx_get_hlayer1(const struct gsm_bts_trx *trx) { return 0; } int bts_model_init(struct gsm_bts *bts) { + struct bts_virt_priv *bts_virt = talloc_zero(bts, struct bts_virt_priv); + bts->model_priv = bts_virt; bts->variant = BTS_OSMO_VIRTUAL; bts->support.ciphers = CIPHER_A5(1) | CIPHER_A5(2) | CIPHER_A5(3); - - gsm_bts_set_feature(bts, BTS_FEAT_OML_ALERTS); - gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_F_V1); - gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_H_V1); - gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_F_EFR); - gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_F_AMR); - gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_H_AMR); - gsm_bts_set_feature(bts, BTS_FEAT_CBCH); - - bts_model_vty_init(bts); + bts->gprs.cell.support.gprs_codings = NM_IPAC_MASK_GPRS_CODING_CS + | NM_IPAC_MASK_GPRS_CODING_MCS; + + /* order alphabetically */ + osmo_bts_set_feature(bts->features, BTS_FEAT_CBCH); + osmo_bts_set_feature(bts->features, BTS_FEAT_EGPRS); + osmo_bts_set_feature(bts->features, BTS_FEAT_GPRS); + osmo_bts_set_feature(bts->features, BTS_FEAT_OML_ALERTS); + osmo_bts_set_feature(bts->features, BTS_FEAT_SPEECH_F_AMR); + osmo_bts_set_feature(bts->features, BTS_FEAT_SPEECH_F_EFR); + osmo_bts_set_feature(bts->features, BTS_FEAT_SPEECH_F_V1); + osmo_bts_set_feature(bts->features, BTS_FEAT_SPEECH_H_AMR); + osmo_bts_set_feature(bts->features, BTS_FEAT_SPEECH_H_V1); return 0; } int bts_model_trx_init(struct gsm_bts_trx *trx) { + /* Frequency bands indicated to the BSC */ + trx->support.freq_bands = NM_IPAC_F_FREQ_BAND_PGSM + | NM_IPAC_F_FREQ_BAND_EGSM + | NM_IPAC_F_FREQ_BAND_RGSM + | NM_IPAC_F_FREQ_BAND_DCS + | NM_IPAC_F_FREQ_BAND_PCS + | NM_IPAC_F_FREQ_BAND_850 + | NM_IPAC_F_FREQ_BAND_480 + | NM_IPAC_F_FREQ_BAND_450; + + /* Channel types and modes indicated to the BSC */ + trx->support.chan_types = NM_IPAC_MASK_CHANT_COMMON + | NM_IPAC_F_CHANT_BCCH_SDCCH4_CBCH + | NM_IPAC_F_CHANT_SDCCH8_CBCH + | NM_IPAC_F_CHANT_PDCHF + | NM_IPAC_F_CHANT_TCHF_PDCHF; + trx->support.chan_modes = NM_IPAC_MASK_CHANM_SPEECH + | NM_IPAC_MASK_CHANM_CSD_NT + | NM_IPAC_MASK_CHANM_CSD_T; + /* TODO: missing rate adaptation for TCH/F14.4 (see OS#6167) */ + trx->support.chan_modes &= ~NM_IPAC_F_CHANM_CSD_T_14k4; + trx->support.chan_modes &= ~NM_IPAC_F_CHANM_CSD_NT_14k4; + return 0; } void bts_model_print_help() { - LOGP(DSUM, LOGL_NOTICE, "Unimplemented %s\n", __func__); + LOGP(DLGLOBAL, LOGL_NOTICE, "Unimplemented %s\n", __func__); } int bts_model_handle_options(int argc, char **argv) @@ -116,26 +145,27 @@ void bts_model_abis_close(struct gsm_bts *bts) void bts_model_phy_link_set_defaults(struct phy_link *plink) { - plink->u.virt.bts_mcast_group = DEFAULT_BTS_MCAST_GROUP; + plink->u.virt.bts_mcast_group = talloc_strdup(plink, DEFAULT_BTS_MCAST_GROUP); plink->u.virt.bts_mcast_port = DEFAULT_BTS_MCAST_PORT; - plink->u.virt.ms_mcast_group = DEFAULT_MS_MCAST_GROUP; + plink->u.virt.ms_mcast_group = talloc_strdup(plink, DEFAULT_MS_MCAST_GROUP); plink->u.virt.ms_mcast_port = DEFAULT_MS_MCAST_PORT; + plink->u.virt.ttl = -1; /* initialize to -1 to prevent us setting the TTL */ } void bts_model_phy_instance_set_defaults(struct phy_instance *pinst) { - LOGP(DSUM, LOGL_NOTICE, "Unimplemented %s\n", __func__); + LOGP(DLGLOBAL, LOGL_NOTICE, "Unimplemented %s\n", __func__); } int bts_model_ts_disconnect(struct gsm_bts_trx_ts *ts) { - LOGP(DSUM, LOGL_NOTICE, "Unimplemented %s\n", __func__); + LOGP(DLGLOBAL, LOGL_NOTICE, "Unimplemented %s\n", __func__); return -ENOTSUP; } void bts_model_ts_connect(struct gsm_bts_trx_ts *ts, enum gsm_phys_chan_config as_pchan) { - LOGP(DSUM, LOGL_NOTICE, "Unimplemented %s\n", __func__); + LOGP(DLGLOBAL, LOGL_NOTICE, "Unimplemented %s\n", __func__); } int main(int argc, char **argv) diff --git a/src/osmo-bts-virtual/osmo_mcast_sock.c b/src/osmo-bts-virtual/osmo_mcast_sock.c index c0f0af58..c802e02d 100644 --- a/src/osmo-bts-virtual/osmo_mcast_sock.c +++ b/src/osmo-bts-virtual/osmo_mcast_sock.c @@ -3,6 +3,7 @@ #include <netdb.h> #include <osmocom/core/socket.h> #include <osmocom/core/select.h> +#include <osmocom/core/osmo_io.h> #include <stdio.h> #include <stdlib.h> #include <string.h> @@ -10,104 +11,133 @@ #include <unistd.h> #include "osmo_mcast_sock.h" +static void noop_write_cb(struct osmo_io_fd *iofd, int res, struct msgb *msg) +{ + /* nothing */ +} + +static const struct osmo_io_ops srv_ioops = { + /* no read call-back as we don't read from the socket */ + /* libosmcoore before change-id I0c071a29e508884bac331ada5e510bbfcf440bbf requires write call-back + * even if we don't care about it */ + .write_cb = noop_write_cb, +}; + /* server socket is what we use for transmission. It is not subscribed * to a multicast group or locally bound, but it is just a normal UDP * socket that's connected to the remote mcast group + port */ -int mcast_server_sock_setup(struct osmo_fd *ofd, const char* tx_mcast_group, - uint16_t tx_mcast_port, bool loopback) +static struct osmo_io_fd * +mcast_server_sock_setup(void *ctx, const char *tx_mcast_group, uint16_t tx_mcast_port, bool loopback) { - int rc; + int rc, fd; unsigned int flags = OSMO_SOCK_F_CONNECT | OSMO_SOCK_F_UDP_REUSEADDR; + struct osmo_io_fd *iofd; if (!loopback) flags |= OSMO_SOCK_F_NO_MCAST_LOOP; /* setup mcast server socket */ - rc = osmo_sock_init_ofd(ofd, AF_INET, SOCK_DGRAM, IPPROTO_UDP, - tx_mcast_group, tx_mcast_port, flags); + rc = osmo_sock_init(AF_INET, SOCK_DGRAM, IPPROTO_UDP, tx_mcast_group, tx_mcast_port, flags); if (rc < 0) { perror("Failed to create Multicast Server Socket"); - return rc; + return NULL; } + fd = rc; - return 0; + iofd = osmo_iofd_setup(ctx, rc, "mcast_server_sock", OSMO_IO_FD_MODE_READ_WRITE, &srv_ioops, NULL); + if (!iofd) { + close(fd); + return NULL; + } + + osmo_iofd_register(iofd, -1); + return iofd; +} + +static void mcast_sock_read_cb(struct osmo_io_fd *iofd, int res, struct msgb *msg) +{ + struct mcast_bidir_sock *bidir_sock = osmo_iofd_get_data(iofd); + bidir_sock->read_cb(res, msg, bidir_sock->data); } +const struct osmo_io_ops clnt_ioops = { + .read_cb = mcast_sock_read_cb, + /* no write call-back as we don't write to the socket */ +}; + /* the client socket is what we use for reception. It is a UDP socket * that's bound to the GSMTAP UDP port and subscribed to the respective * multicast group */ -int mcast_client_sock_setup(struct osmo_fd *ofd, const char *mcast_group, uint16_t mcast_port, - int (*fd_rx_cb)(struct osmo_fd *ofd, unsigned int what), - void *osmo_fd_data) +static struct osmo_io_fd * +mcast_client_sock_setup(void *ctx, const char *mcast_group, uint16_t mcast_port, + void (*read_cb)(int rc, struct msgb *msg, void *data)) { - int rc; + int rc, fd; unsigned int flags = OSMO_SOCK_F_BIND | OSMO_SOCK_F_NO_MCAST_ALL | OSMO_SOCK_F_UDP_REUSEADDR; - - ofd->cb = fd_rx_cb; - ofd->when = BSC_FD_READ; - ofd->data = osmo_fd_data; + struct osmo_io_fd *iofd; /* Create mcast client socket */ - rc = osmo_sock_init_ofd(ofd, AF_INET, SOCK_DGRAM, IPPROTO_UDP, - NULL, mcast_port, flags); + rc = osmo_sock_init(AF_INET, SOCK_DGRAM, IPPROTO_UDP, NULL, mcast_port, flags); if (rc < 0) { perror("Could not create mcast client socket"); - return rc; + return NULL; } + fd = rc; /* Configure and join the multicast group */ - rc = osmo_sock_mcast_subscribe(ofd->fd, mcast_group); + rc = osmo_sock_mcast_subscribe(fd, mcast_group); if (rc < 0) { perror("Failed to join to mcast goup"); - osmo_fd_close(ofd); - return rc; + close(fd); + return NULL; + } + + iofd = osmo_iofd_setup(ctx, fd, "mcast_client_sock", OSMO_IO_FD_MODE_READ_WRITE, &clnt_ioops, ctx); + if (!iofd) { + close(fd); + return NULL; } - return 0; + osmo_iofd_register(iofd, -1); + return iofd; } struct mcast_bidir_sock * mcast_bidir_sock_setup(void *ctx, const char *tx_mcast_group, uint16_t tx_mcast_port, const char *rx_mcast_group, uint16_t rx_mcast_port, bool loopback, - int (*fd_rx_cb)(struct osmo_fd *ofd, unsigned int what), - void *osmo_fd_data) + void (*read_cb)(int rc, struct msgb *msg, void *data), + void *data) { struct mcast_bidir_sock *bidir_sock = talloc(ctx, struct mcast_bidir_sock); - int rc; if (!bidir_sock) return NULL; - rc = mcast_client_sock_setup(&bidir_sock->rx_ofd, rx_mcast_group, rx_mcast_port, - fd_rx_cb, osmo_fd_data); - if (rc < 0) { + bidir_sock->read_cb = read_cb; + bidir_sock->data = data; + + bidir_sock->rx_iofd = mcast_client_sock_setup(bidir_sock, rx_mcast_group, rx_mcast_port, read_cb); + if (!bidir_sock->rx_iofd) { talloc_free(bidir_sock); return NULL; } - rc = mcast_server_sock_setup(&bidir_sock->tx_ofd, tx_mcast_group, tx_mcast_port, loopback); - if (rc < 0) { - osmo_fd_close(&bidir_sock->rx_ofd); + bidir_sock->tx_iofd = mcast_server_sock_setup(bidir_sock, tx_mcast_group, tx_mcast_port, loopback); + if (!bidir_sock->tx_iofd) { + osmo_iofd_free(bidir_sock->rx_iofd); talloc_free(bidir_sock); return NULL; } return bidir_sock; - -} - -int mcast_bidir_sock_tx(struct mcast_bidir_sock *bidir_sock, const uint8_t *data, - unsigned int data_len) -{ - return send(bidir_sock->tx_ofd.fd, data, data_len, 0); } -int mcast_bidir_sock_rx(struct mcast_bidir_sock *bidir_sock, uint8_t *buf, unsigned int buf_len) +int mcast_bidir_sock_tx_msg(struct mcast_bidir_sock *bidir_sock, struct msgb *msg) { - return recv(bidir_sock->rx_ofd.fd, buf, buf_len, 0); + return osmo_iofd_write_msgb(bidir_sock->tx_iofd, msg); } void mcast_bidir_sock_close(struct mcast_bidir_sock *bidir_sock) { - osmo_fd_close(&bidir_sock->tx_ofd); - osmo_fd_close(&bidir_sock->rx_ofd); + osmo_iofd_free(bidir_sock->tx_iofd); + osmo_iofd_free(bidir_sock->rx_iofd); talloc_free(bidir_sock); } diff --git a/src/osmo-bts-virtual/osmo_mcast_sock.h b/src/osmo-bts-virtual/osmo_mcast_sock.h index aa2013c6..5f68415b 100644 --- a/src/osmo-bts-virtual/osmo_mcast_sock.h +++ b/src/osmo-bts-virtual/osmo_mcast_sock.h @@ -4,26 +4,20 @@ #include <stdint.h> #include <netinet/in.h> #include <osmocom/core/select.h> +#include <osmocom/core/osmo_io.h> struct mcast_bidir_sock { - struct osmo_fd tx_ofd; - struct osmo_fd rx_ofd; + struct osmo_io_fd *tx_iofd; + struct osmo_io_fd *rx_iofd; + void (*read_cb)(int rc, struct msgb *msg, void *data); + void *data; }; -struct mcast_bidir_sock *mcast_bidir_sock_setup(void *ctx, - const char *tx_mcast_group, uint16_t tx_mcast_port, - const char *rx_mcast_group, uint16_t rx_mcast_port, bool loopback, - int (*fd_rx_cb)(struct osmo_fd *ofd, unsigned int what), - void *osmo_fd_data); +struct mcast_bidir_sock * +mcast_bidir_sock_setup(void *ctx, const char *tx_mcast_group, uint16_t tx_mcast_port, + const char *rx_mcast_group, uint16_t rx_mcast_port, bool loopback, + void (*read_cb)(int rc, struct msgb *msg, void *data), + void *data); -int mcast_server_sock_setup(struct osmo_fd *ofd, const char *tx_mcast_group, - uint16_t tx_mcast_port, bool loopback); - -int mcast_client_sock_setup(struct osmo_fd *ofd, const char *mcast_group, uint16_t mcast_port, - int (*fd_rx_cb)(struct osmo_fd *ofd, unsigned int what), - void *osmo_fd_data); - -int mcast_bidir_sock_tx(struct mcast_bidir_sock *bidir_sock, const uint8_t *data, unsigned int data_len); -int mcast_bidir_sock_rx(struct mcast_bidir_sock *bidir_sock, uint8_t *buf, unsigned int buf_len); +int mcast_bidir_sock_tx_msg(struct mcast_bidir_sock *bidir_sock, struct msgb *msg); void mcast_bidir_sock_close(struct mcast_bidir_sock* bidir_sock); - diff --git a/src/osmo-bts-virtual/scheduler_virtbts.c b/src/osmo-bts-virtual/scheduler_virtbts.c index 259a573a..87596a79 100644 --- a/src/osmo-bts-virtual/scheduler_virtbts.c +++ b/src/osmo-bts-virtual/scheduler_virtbts.c @@ -1,7 +1,8 @@ -/* Scheduler worker functiosn for Virtua OsmoBTS */ +/* Scheduler worker functions for Virtua OsmoBTS */ /* (C) 2015-2017 by Harald Welte <laforge@gnumonks.org> * (C) 2017 Sebastian Stumpf <sebastian.stumpf87@googlemail.com> + * Contributions by sysmocom - s.f.m.c. GmbH * * All Rights Reserved * @@ -13,7 +14,7 @@ * 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. + * 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/>. @@ -21,6 +22,7 @@ */ #include <stdlib.h> #include <unistd.h> +#include <string.h> #include <errno.h> #include <stdint.h> #include <ctype.h> @@ -46,25 +48,18 @@ #define MODULO_HYPERFRAME 0 -static const char *gsmtap_hdr_stringify(const struct gsmtap_hdr *gh) -{ - static char buf[256]; - snprintf(buf, sizeof(buf), "(ARFCN=%u, ts=%u, ss=%u, type=%u/%u)", - gh->arfcn & GSMTAP_ARFCN_MASK, gh->timeslot, gh->sub_slot, gh->type, gh->sub_type); - return buf; -} - /** * Send a message over the virtual um interface. * This will at first wrap the msg with a GSMTAP header and then write it to the declared multicast socket. - * TODO: we might want to remove unused argument uint8_t tn */ -static void tx_to_virt_um(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, - enum trx_chan_type chan, struct msgb *msg) +static void _tx_to_virt_um(struct l1sched_ts *l1ts, + struct trx_dl_burst_req *br, + struct msgb *msg, bool is_voice_frame) { - const struct trx_chan_desc *chdesc = &trx_chan_desc[chan]; + const struct trx_chan_desc *chdesc = &trx_chan_desc[br->chan]; + const struct gsm_bts_trx *trx = l1ts->ts->trx; struct msgb *outmsg; /* msg to send with gsmtap header prepended */ - uint16_t arfcn = l1t->trx->arfcn; /* ARFCN of the tranceiver the message is send with */ + uint16_t arfcn = trx->arfcn; /* ARFCN of the transceiver the message is send with */ uint8_t signal_dbm = 63; /* signal strength, 63 is best */ uint8_t snr = 63; /* signal noise ratio, 63 is best */ uint8_t *data = msgb_l2(msg); /* data to transmit (whole message without l1 header) */ @@ -76,177 +71,181 @@ static void tx_to_virt_um(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, rsl_dec_chan_nr(chdesc->chan_nr, &rsl_chantype, &subslot, ×lot); /* the timeslot is not encoded in the chan_nr of the chdesc, and so has to be overwritten */ - timeslot = tn; + timeslot = br->tn; /* in Osmocom, AGCH is only sent on ccch block 0. no idea why. this seems to cause false GSMTAP channel * types for agch and pch. */ if (rsl_chantype == RSL_CHAN_PCH_AGCH && - l1sap_fn2ccch_block(fn) >= num_agch(l1t->trx, "PH-DATA-REQ")) + l1sap_fn2ccch_block(br->fn) >= num_agch(trx, "PH-DATA-REQ")) gsmtap_chantype = GSMTAP_CHANNEL_PCH; else - gsmtap_chantype = chantype_rsl2gsmtap(rsl_chantype, chdesc->link_id); /* the logical channel type */ + gsmtap_chantype = chantype_rsl2gsmtap2(rsl_chantype, chdesc->link_id, is_voice_frame); /* the logical channel type */ + + if (gsmtap_chantype == GSMTAP_CHANNEL_UNKNOWN) { + LOGL1SB(DL1P, LOGL_ERROR, l1ts, br, "Tx GSMTAP for RSL channel type 0x%02x: cannot send, this" + " channel type is unknown in GSMTAP\n", rsl_chantype); + msgb_free(msg); + return; + } #if MODULO_HYPERFRAME /* Restart fn after every superframe (26 * 51 frames) to simulate hyperframe overflow each 6 seconds. */ - fn %= 26 * 51; + br->fn %= 26 * 51; #endif - outmsg = gsmtap_makemsg(arfcn, timeslot, gsmtap_chantype, subslot, fn, signal_dbm, snr, data, data_len); + outmsg = gsmtap_makemsg(arfcn, timeslot, gsmtap_chantype, subslot, br->fn, signal_dbm, snr, data, data_len); if (outmsg) { - struct phy_instance *pinst = trx_phy_instance(l1t->trx); - struct gsmtap_hdr *gh = (struct gsmtap_hdr *)msgb_data(outmsg); + struct phy_instance *pinst = trx_phy_instance(trx); int rc; rc = virt_um_write_msg(pinst->phy_link->u.virt.virt_um, outmsg); if (rc < 0) - LOGL1S(DL1P, LOGL_ERROR, l1t, tn, chan, fn, - "%s GSMTAP msg could not send to virtual Um\n", gsmtap_hdr_stringify(gh)); - else if (rc == 0) - bts_shutdown(l1t->trx->bts, "VirtPHY write socket died\n"); + LOGL1SB(DL1P, LOGL_ERROR, l1ts, br, + "GSMTAP msg could not send to virtual Um: %s\n", strerror(-rc)); else - LOGL1S(DL1P, LOGL_DEBUG, l1t, tn, chan, fn, - "%s Sending GSMTAP message to virtual Um\n", gsmtap_hdr_stringify(gh)); + LOGL1SB(DL1P, LOGL_DEBUG, l1ts, br, + "Sending GSMTAP message to virtual Um\n"); } else - LOGL1S(DL1P, LOGL_ERROR, l1t, tn, chan, fn, "GSMTAP msg could not be created!\n"); + LOGL1SB(DL1P, LOGL_ERROR, l1ts, br, "GSMTAP msg could not be created!\n"); /* free incoming message */ msgb_free(msg); } -/* - * TX on downlink - */ +static void tx_to_virt_um(struct l1sched_ts *l1ts, + struct trx_dl_burst_req *br, + struct msgb *msg) +{ + _tx_to_virt_um(l1ts, br, msg, false); +} + -/* an IDLE burst returns nothing. on C0 it is replaced by dummy burst */ -ubit_t *tx_idle_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, - enum trx_chan_type chan, uint8_t bid, uint16_t *nbits) +static struct gsm_lchan *lchan_from_l1t(const struct l1sched_ts *l1ts, + const enum trx_chan_type chan) { - return NULL; + struct gsm_bts_trx_ts *ts = l1ts->ts; + uint8_t subslot = 0; + + if (chan == TRXC_TCHH_1) + subslot = 1; + + return &ts->lchan[subslot]; } -ubit_t *tx_fcch_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, - enum trx_chan_type chan, uint8_t bid, uint16_t *nbits) +/* Determine the gsmtap_um_voice_type of a gsm_lchan */ +static int get_um_voice_type(const struct gsm_lchan *lchan) { - return NULL; + switch (lchan->tch_mode) { + case GSM48_CMODE_SPEECH_V1: + if (lchan->type == GSM_LCHAN_TCH_H) + return GSMTAP_UM_VOICE_HR; + else + return GSMTAP_UM_VOICE_FR; + case GSM48_CMODE_SPEECH_EFR: + return GSMTAP_UM_VOICE_EFR; + case GSM48_CMODE_SPEECH_AMR: + return GSMTAP_UM_VOICE_AMR; + default: + return -1; + } } -ubit_t *tx_sch_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, - enum trx_chan_type chan, uint8_t bid, uint16_t *nbits) +static void tx_to_virt_um_voice_frame(struct l1sched_ts *l1ts, + struct trx_dl_burst_req *br, + struct msgb *msg) { - return NULL; + struct gsm_lchan *lchan = lchan_from_l1t(l1ts, br->chan); + int um_voice_type; + + OSMO_ASSERT(lchan); + um_voice_type = get_um_voice_type(lchan); + if (um_voice_type < 0) { + LOGPLCHAN(lchan, DL1P, LOGL_ERROR, "Cannot determine Um voice type from lchan\n"); + um_voice_type = 0xff; + } + + /* the first byte indicates the type of voice codec (gsmtap_um_voice_type) */ + msgb_pull_to_l2(msg); + msgb_push_u8(msg, um_voice_type); + msg->l2h = msg->data; + _tx_to_virt_um(l1ts, br, msg, true); } -ubit_t *tx_data_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, - enum trx_chan_type chan, uint8_t bid, uint16_t *nbits) +/* + * TX on downlink + */ + +int tx_fcch_fn(struct l1sched_ts *l1ts, struct trx_dl_burst_req *br) +{ + return 0; +} + +int tx_sch_fn(struct l1sched_ts *l1ts, struct trx_dl_burst_req *br) +{ + return 0; +} + +int tx_data_fn(struct l1sched_ts *l1ts, struct trx_dl_burst_req *br) { struct msgb *msg; - if (bid > 0) - return NULL; + if (br->bid > 0) + return 0; /* get mac block from queue */ - msg = _sched_dequeue_prim(l1t, tn, fn, chan); + msg = _sched_dequeue_prim(l1ts, br); if (!msg) { - LOGL1S(DL1P, LOGL_INFO, l1t, tn, chan, fn, "has not been served !! No prim\n"); - return NULL; + LOGL1SB(DL1P, LOGL_INFO, l1ts, br, "has not been served !! No prim\n"); + return -ENODEV; } /* check validity of message */ if (msgb_l2len(msg) != GSM_MACBLOCK_LEN) { - LOGL1S(DL1P, LOGL_FATAL, l1t, tn, chan, fn, "Prim not 23 bytes, please FIX! (len=%d)\n", + LOGL1SB(DL1P, LOGL_FATAL, l1ts, br, "Prim not 23 bytes, please FIX! (len=%d)\n", msgb_l2len(msg)); /* free message */ msgb_free(msg); - return NULL; + return -EINVAL; } /* transmit the msg received on dl from bsc to layer1 (virt Um) */ - tx_to_virt_um(l1t, tn, fn, chan, msg); + tx_to_virt_um(l1ts, br, msg); - return NULL; + return 0; } -ubit_t *tx_pdtch_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, - enum trx_chan_type chan, uint8_t bid, uint16_t *nbits) +int tx_pdtch_fn(struct l1sched_ts *l1ts, struct trx_dl_burst_req *br) { struct msgb *msg = NULL; /* make GCC happy */ - if (bid > 0) - return NULL; + if (br->bid > 0) + return 0; /* get mac block from queue */ - msg = _sched_dequeue_prim(l1t, tn, fn, chan); + msg = _sched_dequeue_prim(l1ts, br); if (!msg) { - LOGL1S(DL1P, LOGL_INFO, l1t, tn, chan, fn, "has not been served !! No prim\n"); - return NULL; + LOGL1SB(DL1P, LOGL_INFO, l1ts, br, "has not been served !! No prim\n"); + return -ENODEV; } - tx_to_virt_um(l1t, tn, fn, chan, msg); + tx_to_virt_um(l1ts, br, msg); - return NULL; + return 0; } -static void tx_tch_common(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, - enum trx_chan_type chan, uint8_t bid, struct msgb **_msg_tch, - struct msgb **_msg_facch, int codec_mode_request) +static void tx_tch_common(struct l1sched_ts *l1ts, + const struct trx_dl_burst_req *br, + struct msgb **_msg_tch, struct msgb **_msg_facch) { - struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn); struct msgb *msg1, *msg2, *msg_tch = NULL, *msg_facch = NULL; - struct l1sched_chan_state *chan_state = &l1ts->chan_state[chan]; + struct l1sched_chan_state *chan_state = &l1ts->chan_state[br->chan]; uint8_t rsl_cmode = chan_state->rsl_cmode; uint8_t tch_mode = chan_state->tch_mode; struct osmo_phsap_prim *l1sap; -#if 0 - /* handle loss detection of received TCH frames */ - if (rsl_cmode == RSL_CMOD_SPD_SPEECH - && ++(chan_state->lost_frames) > 5) { - uint8_t tch_data[GSM_FR_BYTES]; - int len; - - LOGL1S(DL1P, LOGL_NOTICE, l1t, tn, chan, fn, "Missing TCH bursts detected, sending " - "BFI for %s\n", trx_chan_desc[chan].name); - - /* indicate bad frame */ - switch (tch_mode) { - case GSM48_CMODE_SPEECH_V1: /* FR / HR */ - if (chan != TRXC_TCHF) { /* HR */ - tch_data[0] = 0x70; /* F = 0, FT = 111 */ - memset(tch_data + 1, 0, 14); - len = 15; - break; - } - memset(tch_data, 0, GSM_FR_BYTES); - len = GSM_FR_BYTES; - break; - case GSM48_CMODE_SPEECH_EFR: /* EFR */ - if (chan != TRXC_TCHF) - goto inval_mode1; - memset(tch_data, 0, GSM_EFR_BYTES); - len = GSM_EFR_BYTES; - break; - case GSM48_CMODE_SPEECH_AMR: /* AMR */ - len = amr_compose_payload(tch_data, - chan_state->codec[chan_state->dl_cmr], - chan_state->codec[chan_state->dl_ft], 1); - if (len < 2) - break; - memset(tch_data + 2, 0, len - 2); - _sched_compose_tch_ind(l1t, tn, 0, chan, tch_data, len); - break; - default: -inval_mode1: - LOGP(DL1P, LOGL_ERROR, "TCH mode invalid, please " - "fix!\n"); - len = 0; - } - if (len) - _sched_compose_tch_ind(l1t, tn, 0, chan, tch_data, len); - } -#endif /* get frame and unlink from queue */ - msg1 = _sched_dequeue_prim(l1t, tn, fn, chan); - msg2 = _sched_dequeue_prim(l1t, tn, fn, chan); + msg1 = _sched_dequeue_prim(l1ts, br); + msg2 = _sched_dequeue_prim(l1ts, br); if (msg1) { l1sap = msgb_l1sap_prim(msg1); if (l1sap->oph.primitive == PRIM_TCH) { @@ -254,8 +253,8 @@ inval_mode1: if (msg2) { l1sap = msgb_l1sap_prim(msg2); if (l1sap->oph.primitive == PRIM_TCH) { - LOGL1S(DL1P, LOGL_FATAL, l1t, tn, chan, fn, - "TCH twice, please FIX! "); + LOGL1SB(DL1P, LOGL_FATAL, l1ts, br, + "TCH twice, please FIX!\n"); msgb_free(msg2); } else msg_facch = msg2; @@ -265,8 +264,8 @@ inval_mode1: if (msg2) { l1sap = msgb_l1sap_prim(msg2); if (l1sap->oph.primitive != PRIM_TCH) { - LOGL1S(DL1P, LOGL_FATAL, l1t, tn, chan, fn, - "FACCH twice, please FIX! "); + LOGL1SB(DL1P, LOGL_FATAL, l1ts, br, + "FACCH twice, please FIX!\n"); msgb_free(msg2); } else msg_tch = msg2; @@ -282,8 +281,8 @@ inval_mode1: /* check validity of message */ if (msg_facch && msgb_l2len(msg_facch) != GSM_MACBLOCK_LEN) { - LOGL1S(DL1P, LOGL_FATAL, l1t, tn, chan, fn, "Prim not 23 bytes, please FIX! (len=%d)\n", - msgb_l2len(msg_facch)); + LOGL1SB(DL1P, LOGL_FATAL, l1ts, br, "Prim has odd len=%u != %u\n", + msgb_l2len(msg_facch), GSM_MACBLOCK_LEN); /* free message */ msgb_free(msg_facch); msg_facch = NULL; @@ -298,18 +297,18 @@ inval_mode1: #endif if (rsl_cmode != RSL_CMOD_SPD_SPEECH) { - LOGL1S(DL1P, LOGL_NOTICE, l1t, tn, chan, fn, "Dropping speech frame, " + LOGL1SB(DL1P, LOGL_NOTICE, l1ts, br, "Dropping speech frame, " "because we are not in speech mode\n"); goto free_bad_msg; } switch (tch_mode) { case GSM48_CMODE_SPEECH_V1: /* FR / HR */ - if (chan != TRXC_TCHF) { /* HR */ + if (br->chan != TRXC_TCHF) { /* HR */ len = 15; if (msgb_l2len(msg_tch) >= 1 && (msg_tch->l2h[0] & 0xf0) != 0x00) { - LOGL1S(DL1P, LOGL_NOTICE, l1t, tn, chan, fn, + LOGL1SB(DL1P, LOGL_NOTICE, l1ts, br, "Transmitting 'bad HR frame'\n"); goto free_bad_msg; } @@ -318,76 +317,37 @@ inval_mode1: len = GSM_FR_BYTES; if (msgb_l2len(msg_tch) >= 1 && (msg_tch->l2h[0] >> 4) != 0xd) { - LOGL1S(DL1P, LOGL_NOTICE, l1t, tn, chan, fn, + LOGL1SB(DL1P, LOGL_NOTICE, l1ts, br, "Transmitting 'bad FR frame'\n"); goto free_bad_msg; } break; case GSM48_CMODE_SPEECH_EFR: /* EFR */ - if (chan != TRXC_TCHF) + if (br->chan != TRXC_TCHF) goto inval_mode2; len = GSM_EFR_BYTES; if (msgb_l2len(msg_tch) >= 1 && (msg_tch->l2h[0] >> 4) != 0xc) { - LOGL1S(DL1P, LOGL_NOTICE, l1t, tn, chan, fn, + LOGL1SB(DL1P, LOGL_NOTICE, l1ts, br, "Transmitting 'bad EFR frame'\n"); goto free_bad_msg; } break; case GSM48_CMODE_SPEECH_AMR: /* AMR */ -#if 0 - len = amr_decompose_payload(msg_tch->l2h, - msgb_l2len(msg_tch), &cmr_codec, &ft_codec, - &bfi); - cmr = -1; - ft = -1; - for (i = 0; i < chan_state->codecs; i++) { - if (chan_state->codec[i] == cmr_codec) - cmr = i; - if (chan_state->codec[i] == ft_codec) - ft = i; - } - if (cmr >= 0) { /* new request */ - chan_state->dl_cmr = cmr; - /* disable AMR loop */ - trx_loop_amr_set(chan_state, 0); - } else { - /* enable AMR loop */ - trx_loop_amr_set(chan_state, 1); - } - if (ft < 0) { - LOGL1S(DL1P, LOGL_ERROR, l1t, tn, chan, fn, - "Codec (FT = %d) of RTP frame not in list. ", ft_codec); - goto free_bad_msg; - } - if (codec_mode_request && chan_state->dl_ft != ft) { - LOGL1S(DL1P, LOGL_NOTICE, l1t, tn, chan, fn, - "Codec (FT = %d) of RTP cannot be changed now, but in " - "next frame\n", ft_codec); - goto free_bad_msg; - } - chan_state->dl_ft = ft; - if (bfi) { - LOGL1S(DL1P, LOGL_NOTICE, l1t, tn, chan, fn, - "Transmitting 'bad AMR frame'\n"); - goto free_bad_msg; - } -#else - LOGL1S(DL1P, LOGL_ERROR, l1t, tn, chan, fn, "AMR not supported!\n"); - goto free_bad_msg; -#endif + /* TODO: check length for consistency */ + goto send_frame; break; default: inval_mode2: - LOGL1S(DL1P, LOGL_ERROR, l1t, tn, chan, fn, "TCH mode invalid, please fix!\n"); + LOGL1SB(DL1P, LOGL_ERROR, l1ts, br, "TCH mode invalid, please fix!\n"); goto free_bad_msg; } if (len < 0) { - LOGL1S(DL1P, LOGL_ERROR, l1t, tn, chan, fn, "Cannot send invalid AMR payload\n"); + LOGL1SB(DL1P, LOGL_ERROR, l1ts, br, "Cannot send invalid AMR payload\n"); goto free_bad_msg; } if (msgb_l2len(msg_tch) != len) { - LOGL1S(DL1P, LOGL_ERROR, l1t, tn, chan, fn, "Cannot send payload with " + LOGL1SB(DL1P, LOGL_ERROR, l1ts, br, "Cannot send payload with " "invalid length! (expecing %d, received %d)\n", len, msgb_l2len(msg_tch)); free_bad_msg: /* free message */ @@ -402,72 +362,56 @@ send_frame: *_msg_facch = msg_facch; } -ubit_t *tx_tchf_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, - enum trx_chan_type chan, uint8_t bid, uint16_t *nbits) +int tx_tchf_fn(struct l1sched_ts *l1ts, struct trx_dl_burst_req *br) { struct msgb *msg_tch = NULL, *msg_facch = NULL; - if (bid > 0) - return NULL; + if (br->bid > 0) + return 0; - tx_tch_common(l1t, tn, fn, chan, bid, &msg_tch, &msg_facch, - (((fn + 4) % 26) >> 2) & 1); + tx_tch_common(l1ts, br, &msg_tch, &msg_facch); /* no message at all */ if (!msg_tch && !msg_facch) { - LOGL1S(DL1P, LOGL_INFO, l1t, tn, chan, fn, "has not been served !! No prim\n"); - goto send_burst; + LOGL1SB(DL1P, LOGL_INFO, l1ts, br, "has not been served !! No prim\n"); + return -ENODEV; } if (msg_facch) { - tx_to_virt_um(l1t, tn, fn, chan, msg_facch); + tx_to_virt_um(l1ts, br, msg_facch); msgb_free(msg_tch); - } else - tx_to_virt_um(l1t, tn, fn, chan, msg_tch); - -send_burst: + } else if (msg_tch) + tx_to_virt_um_voice_frame(l1ts, br, msg_tch); - return NULL; + return 0; } -ubit_t *tx_tchh_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, - enum trx_chan_type chan, uint8_t bid, uint16_t *nbits) +int tx_tchh_fn(struct l1sched_ts *l1ts, struct trx_dl_burst_req *br) { struct msgb *msg_tch = NULL, *msg_facch = NULL; - struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn); - struct l1sched_chan_state *chan_state = &l1ts->chan_state[chan]; + struct l1sched_chan_state *chan_state = &l1ts->chan_state[br->chan]; //uint8_t tch_mode = chan_state->tch_mode; /* send burst, if we already got a frame */ - if (bid > 0) - return NULL; + if (br->bid > 0) + return 0; /* get TCH and/or FACCH */ - tx_tch_common(l1t, tn, fn, chan, bid, &msg_tch, &msg_facch, - (((fn + 4) % 26) >> 2) & 1); - - /* check for FACCH alignment */ - if (msg_facch && ((((fn + 4) % 26) >> 2) & 1)) { - LOGL1S(DL1P, LOGL_ERROR, l1t, tn, chan, fn, "Cannot transmit FACCH starting on " - "even frames, please fix RTS!\n"); - msgb_free(msg_facch); - msg_facch = NULL; - } + tx_tch_common(l1ts, br, &msg_tch, &msg_facch); /* no message at all */ if (!msg_tch && !msg_facch && !chan_state->dl_ongoing_facch) { - LOGL1S(DL1P, LOGL_INFO, l1t, tn, chan, fn, "has not been served !! No prim\n"); - goto send_burst; + LOGL1SB(DL1P, LOGL_INFO, l1ts, br, "has not been served !! No prim\n"); + return -ENODEV; } if (msg_facch) { - tx_to_virt_um(l1t, tn, fn, chan, msg_facch); + tx_to_virt_um(l1ts, br, msg_facch); msgb_free(msg_tch); } else if (msg_tch) - tx_to_virt_um(l1t, tn, fn, chan, msg_tch); + tx_to_virt_um_voice_frame(l1ts, br, msg_tch); -send_burst: - return NULL; + return 0; } @@ -479,38 +423,33 @@ send_burst: * directly into the L1SAP, bypassing the TDMA multiplex logic oriented * towards receiving bursts */ -int rx_rach_fn(struct l1sched_trx *l1t, enum trx_chan_type chan, - uint8_t bid, const struct trx_ul_burst_ind *bi) +int rx_rach_fn(struct l1sched_ts *l1ts, const struct trx_ul_burst_ind *bi) { return 0; } /*! \brief a single burst was received by the PHY, process it */ -int rx_data_fn(struct l1sched_trx *l1t, enum trx_chan_type chan, - uint8_t bid, const struct trx_ul_burst_ind *bi) +int rx_data_fn(struct l1sched_ts *l1ts, const struct trx_ul_burst_ind *bi) { return 0; } -int rx_pdtch_fn(struct l1sched_trx *l1t, enum trx_chan_type chan, - uint8_t bid, const struct trx_ul_burst_ind *bi) +int rx_pdtch_fn(struct l1sched_ts *l1ts, const struct trx_ul_burst_ind *bi) { return 0; } -int rx_tchf_fn(struct l1sched_trx *l1t, enum trx_chan_type chan, - uint8_t bid, const struct trx_ul_burst_ind *bi) +int rx_tchf_fn(struct l1sched_ts *l1ts, const struct trx_ul_burst_ind *bi) { return 0; } -int rx_tchh_fn(struct l1sched_trx *l1t, enum trx_chan_type chan, - uint8_t bid, const struct trx_ul_burst_ind *bi) +int rx_tchh_fn(struct l1sched_ts *l1ts, const struct trx_ul_burst_ind *bi) { return 0; } -void _sched_act_rach_det(struct l1sched_trx *l1t, uint8_t tn, uint8_t ss, int activate) +void _sched_act_rach_det(struct gsm_bts_trx *trx, uint8_t tn, uint8_t ss, int activate) { } @@ -519,7 +458,6 @@ void _sched_act_rach_det(struct l1sched_trx *l1t, uint8_t tn, uint8_t ss, int ac ***********************************************************************/ #define RTS_ADVANCE 5 /* about 20ms */ -#define FRAME_DURATION_uS 4615 static int vbts_sched_fn(struct gsm_bts *bts, uint32_t fn) { @@ -532,13 +470,11 @@ static int vbts_sched_fn(struct gsm_bts *bts, uint32_t fn) /* advance the frame number? */ llist_for_each_entry(trx, &bts->trx_list, list) { - struct phy_instance *pinst = trx_phy_instance(trx); - struct l1sched_trx *l1t = &pinst->u.virt.sched; - int tn; - uint16_t nbits; + struct trx_dl_burst_req br = { .fn = fn }; /* do for each of the 8 timeslots */ - for (tn = 0; tn < ARRAY_SIZE(l1t->ts); tn++) { + for (br.tn = 0; br.tn < ARRAY_SIZE(trx->ts); br.tn++) { + struct l1sched_ts *l1ts = trx->ts[br.tn].priv; /* Generate RTS indication to higher layers */ /* This will basically do 2 things (check l1_if:bts_model_l1sap_down): * 1) Get pending messages from layer 2 (from the lapdm queue) @@ -546,13 +482,13 @@ static int vbts_sched_fn(struct gsm_bts *bts, uint32_t fn) * --> Handle and process non-transparent RSL-Messages (activate channel, ) * --> Forward transparent RSL-DATA-Messages to the ms by appending them to * the l1-dl-queue */ - _sched_rts(l1t, tn, (fn + RTS_ADVANCE) % GSM_HYPERFRAME); + _sched_rts(l1ts, GSM_TDMA_FN_SUM(fn, RTS_ADVANCE)); /* schedule transmit backend functions */ /* Process data in the l1-dlqueue and forward it * to MS */ /* the returned bits are not used here, the routines called will directly forward their * bits to the virt Um */ - _sched_dl_burst(l1t, tn, fn, &nbits); + _sched_dl_burst(l1ts, &br); } } @@ -562,8 +498,9 @@ static int vbts_sched_fn(struct gsm_bts *bts, uint32_t fn) static void vbts_fn_timer_cb(void *data) { struct gsm_bts *bts = data; + struct bts_virt_priv *bts_virt = (struct bts_virt_priv *)bts->model_priv; struct timeval tv_now; - struct timeval *tv_clock = &bts->vbts.tv_clock; + struct timeval *tv_clock = &bts_virt->tv_clock; int32_t elapsed_us; gettimeofday(&tv_now, NULL); @@ -574,41 +511,44 @@ static void vbts_fn_timer_cb(void *data) + (tv_now.tv_usec - tv_clock->tv_usec); /* not so good somehow a lot of time passed between two timer callbacks */ - if (elapsed_us > 2 *FRAME_DURATION_uS) + if (elapsed_us > 2 *GSM_TDMA_FN_DURATION_uS) LOGP(DL1P, LOGL_NOTICE, "vbts_fn_timer_cb after %d us\n", elapsed_us); /* schedule the current frame/s (fn = frame number) * this loop will be called at least once, but can also be executed * multiple times if more than one frame duration (4615us) passed till the last callback */ - while (elapsed_us > FRAME_DURATION_uS / 2) { + while (elapsed_us > GSM_TDMA_FN_DURATION_uS / 2) { const struct timeval tv_frame = { .tv_sec = 0, - .tv_usec = FRAME_DURATION_uS, + .tv_usec = GSM_TDMA_FN_DURATION_uS, }; timeradd(tv_clock, &tv_frame, tv_clock); /* increment the frame number in the BTS model instance */ - bts->vbts.last_fn = (bts->vbts.last_fn + 1) % GSM_HYPERFRAME; - vbts_sched_fn(bts, bts->vbts.last_fn); - elapsed_us -= FRAME_DURATION_uS; + vbts_sched_fn(bts, GSM_TDMA_FN_INC(bts_virt->last_fn)); + elapsed_us -= GSM_TDMA_FN_DURATION_uS; } /* re-schedule the timer */ /* timer is set to frame duration - elapsed time to guarantee that this cb method will be * periodically executed every 4.615ms */ - osmo_timer_schedule(&bts->vbts.fn_timer, 0, FRAME_DURATION_uS - elapsed_us); + osmo_timer_schedule(&bts_virt->fn_timer, 0, GSM_TDMA_FN_DURATION_uS - elapsed_us); } int vbts_sched_start(struct gsm_bts *bts) { + struct bts_virt_priv *bts_virt = (struct bts_virt_priv *)bts->model_priv; LOGP(DL1P, LOGL_NOTICE, "starting VBTS scheduler\n"); - memset(&bts->vbts.fn_timer, 0, sizeof(bts->vbts.fn_timer)); - bts->vbts.fn_timer.cb = vbts_fn_timer_cb; - bts->vbts.fn_timer.data = bts; + if (!bts_virt) + return -EINVAL; + + memset(&bts_virt->fn_timer, 0, sizeof(bts_virt->fn_timer)); + bts_virt->fn_timer.cb = vbts_fn_timer_cb; + bts_virt->fn_timer.data = bts; - gettimeofday(&bts->vbts.tv_clock, NULL); + gettimeofday(&bts_virt->tv_clock, NULL); /* trigger the first timer after 4615us (a frame duration) */ - osmo_timer_schedule(&bts->vbts.fn_timer, 0, FRAME_DURATION_uS); + osmo_timer_schedule(&bts_virt->fn_timer, 0, GSM_TDMA_FN_DURATION_uS); return 0; } diff --git a/src/osmo-bts-virtual/virtual_um.c b/src/osmo-bts-virtual/virtual_um.c index fd0940f0..711e75d3 100644 --- a/src/osmo-bts-virtual/virtual_um.c +++ b/src/osmo-bts-virtual/virtual_um.c @@ -12,7 +12,7 @@ * 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. + * 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/>. @@ -27,45 +27,31 @@ #include <osmocom/core/talloc.h> #include "osmo_mcast_sock.h" #include "virtual_um.h" + #include <unistd.h> +#include <errno.h> /** - * Virtual UM interface file descriptor callback. - * Should be called by select.c when the fd is ready for reading. + * Virtual UM interface file descriptor read callback. */ -static int virt_um_fd_cb(struct osmo_fd *ofd, unsigned int what) +static void virt_um_read_cb(int rc, struct msgb *msg, void *data) { - struct virt_um_inst *vui = ofd->data; - - if (what & BSC_FD_READ) { - struct msgb *msg = msgb_alloc(VIRT_UM_MSGB_SIZE, "Virtual UM Rx"); - int rc; + struct virt_um_inst *vui = data; + msg->l1h = msg->data; - /* read message from fd into message buffer */ - rc = mcast_bidir_sock_rx(vui->mcast_sock, msgb_data(msg), msgb_tailroom(msg)); - if (rc > 0) { - msgb_put(msg, rc); - msg->l1h = msgb_data(msg); - /* call the l1 callback function for a received msg */ - vui->recv_cb(vui, msg); - } else if (rc == 0) { - vui->recv_cb(vui, NULL); - osmo_fd_close(ofd); - } else - perror("Read from multicast socket"); - - } - - return 0; + /* call the l1 callback function for a received msg */ + vui->recv_cb(vui, msg); } struct virt_um_inst *virt_um_init(void *ctx, char *tx_mcast_group, uint16_t tx_mcast_port, - char *rx_mcast_group, uint16_t rx_mcast_port, + char *rx_mcast_group, uint16_t rx_mcast_port, int ttl, const char *dev_name, void (*recv_cb)(struct virt_um_inst *vui, struct msgb *msg)) { struct virt_um_inst *vui = talloc_zero(ctx, struct virt_um_inst); + int rc; + vui->mcast_sock = mcast_bidir_sock_setup(ctx, tx_mcast_group, tx_mcast_port, - rx_mcast_group, rx_mcast_port, 1, virt_um_fd_cb, vui); + rx_mcast_group, rx_mcast_port, 1, virt_um_read_cb, vui); if (!vui->mcast_sock) { perror("Unable to create VirtualUm multicast socket"); talloc_free(vui); @@ -73,8 +59,37 @@ struct virt_um_inst *virt_um_init(void *ctx, char *tx_mcast_group, uint16_t tx_m } vui->recv_cb = recv_cb; + /* -1 means default, i.e. no TTL explicitly configured in VTY */ + if (ttl >= 0) { + int txfd = osmo_iofd_get_fd(vui->mcast_sock->tx_iofd); + rc = osmo_sock_mcast_ttl_set(txfd, ttl); + if (rc < 0) { + perror("Cannot set TTL of Virtual Um transmit socket"); + goto out_close; + } + } + + if (dev_name) { + int txfd = osmo_iofd_get_fd(vui->mcast_sock->tx_iofd); + rc = osmo_sock_mcast_iface_set(txfd, dev_name); + if (rc < 0) { + perror("Cannot bind multicast tx to given device"); + goto out_close; + } + int rxfd = osmo_iofd_get_fd(vui->mcast_sock->rx_iofd); + rc = osmo_sock_mcast_iface_set(rxfd, dev_name); + if (rc < 0) { + perror("Cannot bind multicast rx to given device"); + goto out_close; + } + } + return vui; +out_close: + mcast_bidir_sock_close(vui->mcast_sock); + talloc_free(vui); + return NULL; } void virt_um_destroy(struct virt_um_inst *vui) @@ -90,11 +105,11 @@ int virt_um_write_msg(struct virt_um_inst *vui, struct msgb *msg) { int rc; - rc = mcast_bidir_sock_tx(vui->mcast_sock, msgb_data(msg), - msgb_length(msg)); - if (rc < 0) - perror("Writing to multicast socket"); - msgb_free(msg); + rc = mcast_bidir_sock_tx_msg(vui->mcast_sock, msg); + if (rc < 0) { + msgb_free(msg); + rc = -errno; + } return rc; } diff --git a/src/osmo-bts-virtual/virtual_um.h b/src/osmo-bts-virtual/virtual_um.h index ac098dd4..87cf03a5 100644 --- a/src/osmo-bts-virtual/virtual_um.h +++ b/src/osmo-bts-virtual/virtual_um.h @@ -23,7 +23,7 @@ struct virt_um_inst { struct virt_um_inst *virt_um_init( void *ctx, char *tx_mcast_group, uint16_t tx_mcast_port, - char *rx_mcast_group, uint16_t rx_mcast_port, + char *rx_mcast_group, uint16_t rx_mcast_port, int ttl, const char *dev_name, void (*recv_cb)(struct virt_um_inst *vui, struct msgb *msg)); void virt_um_destroy(struct virt_um_inst *vui); diff --git a/src/osmo-bts-virtual/virtualbts_vty.c b/src/osmo-bts-virtual/virtualbts_vty.c index 323222b4..3933bd27 100644 --- a/src/osmo-bts-virtual/virtualbts_vty.c +++ b/src/osmo-bts-virtual/virtualbts_vty.c @@ -50,25 +50,26 @@ SHOW_STR \ TRX_STR -static struct gsm_bts *vty_bts; - -void bts_model_config_write_bts(struct vty *vty, struct gsm_bts *bts) +void bts_model_config_write_bts(struct vty *vty, const struct gsm_bts *bts) { } -void bts_model_config_write_trx(struct vty *vty, struct gsm_bts_trx *trx) +void bts_model_config_write_trx(struct vty *vty, const struct gsm_bts_trx *trx) { } -void bts_model_config_write_phy_inst(struct vty *vty, struct phy_instance *pinst) +void bts_model_config_write_phy_inst(struct vty *vty, const struct phy_instance *pinst) { } -void bts_model_config_write_phy(struct vty *vty, struct phy_link *plink) +void bts_model_config_write_phy(struct vty *vty, const struct phy_link *plink) { if (plink->u.virt.mcast_dev) vty_out(vty, " virtual-um net-device %s%s", plink->u.virt.mcast_dev, VTY_NEWLINE); + if (plink->u.virt.ttl != -1) + vty_out(vty, " virtual-um ttl %d%s", + plink->u.virt.ttl, VTY_NEWLINE); if (strcmp(plink->u.virt.ms_mcast_group, DEFAULT_BTS_MCAST_GROUP)) vty_out(vty, " virtual-um ms-multicast-group %s%s", plink->u.virt.ms_mcast_group, VTY_NEWLINE); @@ -171,15 +172,31 @@ DEFUN(cfg_phy_mcast_dev, cfg_phy_mcast_dev_cmd, return CMD_SUCCESS; } -int bts_model_vty_init(struct gsm_bts *bts) +DEFUN(cfg_phy_mcast_ttl, cfg_phy_mcast_ttl_cmd, + "virtual-um ttl <0-255>", + VUM_STR "Configure the TTL for transmitted multicast GSMTAP packets\n") { - vty_bts = bts; + struct phy_link *plink = vty->index; + if (plink->state != PHY_LINK_SHUTDOWN) { + vty_out(vty, "Can only reconfigure a PHY link that is down%s", + VTY_NEWLINE); + return CMD_WARNING; + } + + plink->u.virt.ttl = atoi(argv[0]); + + return CMD_SUCCESS; +} + +int bts_model_vty_init(void *ctx) +{ install_element(PHY_NODE, &cfg_phy_ms_mcast_group_cmd); install_element(PHY_NODE, &cfg_phy_ms_mcast_port_cmd); install_element(PHY_NODE, &cfg_phy_bts_mcast_group_cmd); install_element(PHY_NODE, &cfg_phy_bts_mcast_port_cmd); install_element(PHY_NODE, &cfg_phy_mcast_dev_cmd); + install_element(PHY_NODE, &cfg_phy_mcast_ttl_cmd); return 0; } |