aboutsummaryrefslogtreecommitdiffstats
path: root/src/common
diff options
context:
space:
mode:
Diffstat (limited to 'src/common')
-rw-r--r--src/common/Makefile.am17
-rw-r--r--src/common/abis.c295
-rw-r--r--src/common/amr.c170
-rw-r--r--src/common/bts.c759
-rw-r--r--src/common/bts_ctrl_commands.c93
-rw-r--r--src/common/bts_ctrl_lookup.c115
-rw-r--r--src/common/cbch.c198
-rw-r--r--src/common/dtx_dl_amr_fsm.c477
-rw-r--r--src/common/gsm_data_shared.c807
-rw-r--r--src/common/handover.c164
-rw-r--r--src/common/l1sap.c1549
-rw-r--r--src/common/lchan.c49
-rw-r--r--src/common/load_indication.c94
-rw-r--r--src/common/logging.c150
-rw-r--r--src/common/main.c358
-rw-r--r--src/common/measurement.c723
-rw-r--r--src/common/msg_utils.c603
-rw-r--r--src/common/oml.c1495
-rw-r--r--src/common/paging.c630
-rw-r--r--src/common/pcu_sock.c982
-rw-r--r--src/common/phy_link.c163
-rw-r--r--src/common/power_control.c89
-rw-r--r--src/common/rsl.c2996
-rw-r--r--src/common/scheduler.c1025
-rw-r--r--src/common/scheduler_mframe.c1040
-rw-r--r--src/common/sysinfo.c177
-rw-r--r--src/common/tx_power.c306
-rw-r--r--src/common/vty.c1651
28 files changed, 17175 insertions, 0 deletions
diff --git a/src/common/Makefile.am b/src/common/Makefile.am
new file mode 100644
index 00000000..113ff2f4
--- /dev/null
+++ b/src/common/Makefile.am
@@ -0,0 +1,17 @@
+AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include
+AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOTRAU_CFLAGS) $(LIBOSMOCODEC_CFLAGS)
+LDADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOTRAU_LIBS) $(LIBOSMOCODEC_LIBS)
+
+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
+
+libl1sched_a_SOURCES = scheduler.c
diff --git a/src/common/abis.c b/src/common/abis.c
new file mode 100644
index 00000000..84a3a047
--- /dev/null
+++ b/src/common/abis.c
@@ -0,0 +1,295 @@
+/* Abis/IP interface routines utilizing libosmo-abis (Pablo) */
+
+/* (C) 2011 by Andreas Eversberg <jolly@eversberg.eu>
+ * (C) 2011-2013 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 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"
+
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+#include <netinet/in.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+#include <stdlib.h>
+#include <inttypes.h>
+
+#include <osmocom/core/select.h>
+#include <osmocom/core/timer.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/signal.h>
+#include <osmocom/core/macaddr.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/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/bts_model.h>
+
+static struct gsm_bts *g_bts;
+
+int abis_oml_sendmsg(struct msgb *msg)
+{
+ struct gsm_bts *bts = msg->trx->bts;
+
+ if (!bts->oml_link) {
+ llist_add_tail(&msg->list, &bts->oml_queue);
+ 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);
+ }
+}
+
+static void drain_oml_queue(struct gsm_bts *bts)
+{
+ 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);
+ }
+}
+
+int abis_bts_rsl_sendmsg(struct msgb *msg)
+{
+ OSMO_ASSERT(msg->trx);
+
+ if (msg->trx->bts->variant == BTS_OSMO_OMLDUMMY) {
+ msgb_free(msg);
+ return 0;
+ }
+
+ /* osmo-bts uses msg->trx internally, but libosmo-abis uses
+ * the signalling link at msg->dst */
+ msg->dst = msg->trx->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 gsm_bts_trx *trx;
+ int trx_nr;
+
+ switch (type) {
+ case E1INP_SIGN_OML:
+ 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);
+ 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;
+ default:
+ trx_nr = type - E1INP_SIGN_RSL;
+ LOGP(DABIS, LOGL_INFO, "RSL Signalling link for TRX%d up\n",
+ trx_nr);
+ trx = gsm_bts_trx_num(g_bts, trx_nr);
+ if (!trx) {
+ LOGP(DABIS, LOGL_ERROR, "TRX%d does not exist!\n",
+ 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;
+ trx_link_estab(trx);
+ break;
+ }
+
+ return sign_link;
+}
+
+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);
+}
+
+
+/* callback for incoming mesages from A-bis/IP */
+static int sign_link_cb(struct msgb *msg)
+{
+ struct e1inp_sign_link *link = msg->dst;
+
+ /* osmo-bts code assumes msg->trx is set, but libosmo-abis works
+ * with the sign_link stored in msg->dst, so we have to convert
+ * here */
+ msg->trx = link->trx;
+
+ switch (link->type) {
+ case E1INP_SIGN_OML:
+ down_oml(link->trx->bts, msg);
+ break;
+ case E1INP_SIGN_RSL:
+ down_rsl(link->trx, msg);
+ break;
+ default:
+ msgb_free(msg);
+ break;
+ }
+
+ return 0;
+}
+
+uint32_t get_signlink_remote_ip(struct e1inp_sign_link *link)
+{
+ int fd = link->ts->driver.ipaccess.fd.fd;
+ struct sockaddr_in sin;
+ socklen_t slen = sizeof(sin);
+ int rc;
+
+ rc = getpeername(fd, (struct sockaddr *)&sin, &slen);
+ if (rc < 0) {
+ LOGP(DOML, LOGL_ERROR, "Cannot determine remote IP Addr: %s\n",
+ strerror(errno));
+ return 0;
+ }
+
+ /* we assume that the soket is AF_INET. As Abis/IP contains
+ * lots of hard-coded IPv4 addresses, this safe */
+ OSMO_ASSERT(sin.sin_family == AF_INET);
+
+ return ntohl(sin.sin_addr.s_addr);
+}
+
+
+static int inp_s_cbfn(unsigned int subsys, unsigned int signal,
+ void *hdlr_data, void *signal_data)
+{
+ if (subsys != SS_L_INPUT)
+ return 0;
+
+ struct input_signal_data *isd = signal_data;
+ DEBUGP(DABIS, "Input Signal %s received for link_type=%s\n",
+ get_value_string(e1inp_signal_names, signal), e1inp_signtype_name(isd->link_type));
+
+ return 0;
+}
+
+
+static struct ipaccess_unit bts_dev_info = {
+ .unit_name = "sysmoBTS",
+ .equipvers = "", /* FIXME: read this from hw */
+ .swversion = PACKAGE_VERSION,
+ .location1 = "",
+ .location2 = "",
+ .serno = "",
+};
+
+static struct e1inp_line_ops line_ops = {
+ .cfg = {
+ .ipa = {
+ .role = E1INP_LINE_R_BTS,
+ .dev = &bts_dev_info,
+ },
+ },
+ .sign_link_up = sign_link_up,
+ .sign_link_down = sign_link_down,
+ .sign_link = sign_link_cb,
+};
+
+void abis_init(struct gsm_bts *bts)
+{
+ g_bts = bts;
+
+ oml_init(&bts->mo);
+ libosmo_abis_init(NULL);
+
+ 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)
+{
+ struct e1inp_line *line;
+
+ /* 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;
+
+ line = e1inp_line_find(0);
+ if (!line)
+ line = e1inp_line_create(0, "ipa");
+ if (!line)
+ return NULL;
+ e1inp_line_bind_ops(line, &line_ops);
+
+ /* This will open the OML connection now */
+ if (e1inp_line_update(line) < 0)
+ return NULL;
+
+ return line;
+}
diff --git a/src/common/amr.c b/src/common/amr.c
new file mode 100644
index 00000000..05d1aaac
--- /dev/null
+++ b/src/common/amr.c
@@ -0,0 +1,170 @@
+#include <stdint.h>
+#include <errno.h>
+
+#include <osmocom/core/logging.h>
+
+#include <osmo-bts/logging.h>
+#include <osmo-bts/amr.h>
+
+void amr_log_mr_conf(int ss, int logl, const char *pfx,
+ struct amr_multirate_conf *amr_mrc)
+{
+ int i;
+
+ LOGP(ss, logl, "%s AMR MR Conf: num_modes=%u",
+ pfx, amr_mrc->num_modes);
+
+ 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);
+ LOGPC(ss, logl, "\n");
+}
+
+static inline int get_amr_mode_idx(const struct amr_multirate_conf *amr_mrc,
+ uint8_t cmi)
+{
+ unsigned int i;
+ for (i = 0; i < amr_mrc->num_modes; i++) {
+ if (amr_mrc->bts_mode[i].mode == cmi)
+ return i;
+ }
+ return -EINVAL;
+}
+
+static inline uint8_t set_cmr_mode_idx(const struct amr_multirate_conf *amr_mrc,
+ uint8_t cmr)
+{
+ int rc;
+
+ /* Codec Mode Request is in upper 4 bits of RTP payload header,
+ * and we simply copy the CMR into the CMC */
+ if (cmr == 0xF) {
+ /* FIXME: we need some state about the last codec mode */
+ return 0;
+ }
+
+ rc = get_amr_mode_idx(amr_mrc, cmr);
+ if (rc < 0) {
+ /* FIXME: we need some state about the last codec mode */
+ LOGP(DRTP, LOGL_INFO, "RTP->L1: overriding CMR %u\n", cmr);
+ return 0;
+ }
+ return rc;
+}
+
+static inline uint8_t set_cmi_mode_idx(const struct amr_multirate_conf *amr_mrc,
+ uint8_t cmi)
+{
+ int rc = get_amr_mode_idx(amr_mrc, cmi);
+ if (rc < 0) {
+ LOGP(DRTP, LOGL_ERROR, "AMR CMI %u not part of AMR MR set\n",
+ cmi);
+ return 0;
+ }
+ return rc;
+}
+
+void amr_set_mode_pref(uint8_t *data, const struct amr_multirate_conf *amr_mrc,
+ uint8_t cmi, uint8_t cmr)
+{
+ data[0] = set_cmi_mode_idx(amr_mrc, cmi);
+ data[1] = set_cmr_mode_idx(amr_mrc, cmr);
+}
+
+/* parse a GSM 04.08 MultiRate Config IE (10.5.2.21aa) in a more
+ * comfortable internal data structure */
+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);
+ goto ret_einval;
+ }
+
+ /* check number of active codecs */
+ for (i = 0; i < 8; i++) {
+ if (mr_conf[1] & (1 << i))
+ num_codecs++;
+ }
+
+ /* check for minimum length */
+ if (num_codecs == 0 ||
+ (num_codecs == 1 && len < 2) ||
+ (num_codecs == 2 && len < 4) ||
+ (num_codecs == 3 && len < 5) ||
+ (num_codecs == 4 && len < 6) ||
+ (num_codecs > 4)) {
+ LOGP(DRSL, LOGL_ERROR, "AMR Multirate with %u modes len=%u "
+ "not possible\n", num_codecs, len);
+ goto ret_einval;
+ }
+
+ /* copy the first two octets of the IE */
+ amr_mrc->gsm48_ie[0] = mr_conf[0];
+ amr_mrc->gsm48_ie[1] = mr_conf[1];
+
+ amr_mrc->num_modes = num_codecs;
+
+ for (i = 0; i < 8; i++) {
+ if (mr_conf[1] & (1 << i)) {
+ amr_mrc->bts_mode[j++].mode = i;
+ }
+ }
+
+ if (num_codecs >= 2) {
+ amr_mrc->bts_mode[0].threshold = mr_conf[1] & 0x3F;
+ amr_mrc->bts_mode[0].hysteresis = mr_conf[2] >> 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;
+ }
+ 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;
+ }
+
+ return num_codecs;
+
+ret_einval:
+ return -EINVAL;
+}
+
+
+/*! \brief determine AMR initial codec mode for given logical channel
+ * \returns integer between 0..3 for AMR codce mode 1..4 */
+unsigned int amr_get_initial_mode(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;
+
+ if (mr_conf->icmi) {
+ /* initial mode given, coding in TS 05.09 3.4.1 */
+ return mr_conf->smod;
+ } else {
+ /* implicit rule according to TS 05.09 Chapter 3.4.3 */
+ switch (amr_mrc->num_modes) {
+ case 2:
+ case 3:
+ /* return the most robust */
+ return 0;
+ case 4:
+ /* return the second-most robust */
+ return 1;
+ case 1:
+ default:
+ /* return the only mode we have */
+ return 0;
+ }
+ }
+}
diff --git a/src/common/bts.c b/src/common/bts.c
new file mode 100644
index 00000000..abbaeb46
--- /dev/null
+++ b/src/common/bts.c
@@ -0,0 +1,759 @@
+/* BTS support code common to all supported BTS models */
+
+/* (C) 2011 by Andreas Eversberg <jolly@eversberg.eu>
+ * (C) 2011 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <errno.h>
+#include <unistd.h>
+#include <stdio.h>
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+
+#include <osmocom/core/select.h>
+#include <osmocom/core/timer.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/stats.h>
+#include <osmocom/core/rate_ctr.h>
+#include <osmocom/gsm/protocol/gsm_12_21.h>
+#include <osmocom/gsm/gsm48.h>
+#include <osmocom/gsm/lapdm.h>
+#include <osmocom/trau/osmo_ortp.h>
+
+#include <osmo-bts/logging.h>
+#include <osmo-bts/abis.h>
+#include <osmo-bts/bts.h>
+#include <osmo-bts/bts_model.h>
+#include <osmo-bts/dtx_dl_amr_fsm.h>
+#include <osmo-bts/pcuif_proto.h>
+#include <osmo-bts/rsl.h>
+#include <osmo-bts/oml.h>
+#include <osmo-bts/signal.h>
+#include <osmo-bts/dtx_dl_amr_fsm.h>
+
+#define MIN_QUAL_RACH 5.0f /* at least 5 dB C/I */
+#define MIN_QUAL_NORM -0.5f /* at least -1 dB C/I */
+
+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 */
+static const uint8_t tx_integer[] = {
+ 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 14, 16, 20, 25, 32, 50,
+};
+
+static const uint8_t s_values[][2] = {
+ { 55, 41 }, { 76, 52 }, { 109, 58 }, { 163, 86 }, { 217, 115 },
+};
+
+static int bts_signal_cbfn(unsigned int subsys, unsigned int signal,
+ void *hdlr_data, void *signal_data)
+{
+ if (subsys == SS_GLOBAL && signal == S_NEW_SYSINFO) {
+ struct gsm_bts *bts = signal_data;
+
+ bts_update_agch_max_queue_length(bts);
+ }
+ return 0;
+}
+
+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_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_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)"},
+};
+static const struct rate_ctr_group_desc bts_ctrg_desc = {
+ "bts",
+ "base transceiver station",
+ OSMO_STATS_CLASS_GLOBAL,
+ ARRAY_SIZE(bts_ctr_desc),
+ bts_ctr_desc
+};
+
+/* Initialize the BTS data structures, called before config
+ * file reading */
+int bts_init(struct gsm_bts *bts)
+{
+ int rc, i;
+ 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);
+
+ /* enable management with default levels,
+ * raise threshold to GSM_BTS_AGCH_QUEUE_THRESH_LEVEL_DISABLE to
+ * disable this feature.
+ */
+ bts->agch_queue.low_level = GSM_BTS_AGCH_QUEUE_LOW_LEVEL_DEFAULT;
+ bts->agch_queue.high_level = GSM_BTS_AGCH_QUEUE_HIGH_LEVEL_DEFAULT;
+ bts->agch_queue.thresh_level = GSM_BTS_AGCH_QUEUE_THRESH_LEVEL_DEFAULT;
+
+ /* 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;
+
+ /* configurable via OML */
+ bts->load.ccch.load_ind_period = 112;
+ load_timer_start(bts);
+ bts->rtp_jitter_buf_ms = 100;
+ bts->max_ta = 63;
+ bts->ny1 = 4;
+ bts->t3105_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];
+
+ /* 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);
+
+ /* 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);
+
+ /* 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);
+
+ rc = bts_model_init(bts);
+ if (rc < 0) {
+ llist_del(&bts->list);
+ return rc;
+ }
+
+ /* TRX0 was allocated early during gsm_bts_alloc, not later through VTY */
+ bts_trx_init(bts->c0);
+ bts_gsmnet.num_bts++;
+
+ if (!initialized) {
+ osmo_signal_register_handler(SS_GLOBAL, bts_signal_cbfn, NULL);
+ initialized = 1;
+ }
+
+ INIT_LLIST_HEAD(&bts->smscb_state.queue);
+ INIT_LLIST_HEAD(&bts->oml_queue);
+
+ /* register DTX DL FSM */
+ rc = osmo_fsm_register(&dtx_dl_amr_fsm);
+ OSMO_ASSERT(rc == 0);
+
+ 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);
+
+ /* 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);
+ }
+ }
+
+ 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_fail_rep(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;
+}
+
+int lchan_init_lapdm(struct gsm_lchan *lchan)
+{
+ struct lapdm_channel *lc = &lchan->lapdm_ch;
+
+ lapdm_channel_init(lc, LAPDM_MODE_BTS);
+ lapdm_channel_set_flags(lc, LAPDM_ENT_F_POLLING_ONLY);
+ lapdm_channel_set_l1(lc, NULL, lchan);
+ lapdm_channel_set_l3(lc, lapdm_rll_tx_cb, lchan);
+ oml_set_lchan_t200(lchan);
+
+ return 0;
+}
+
+#define CCCH_RACH_RATIO_COMBINED256 (256*1/9)
+#define CCCH_RACH_RATIO_SEPARATE256 (256*10/55)
+
+int bts_agch_max_queue_length(int T, int bcch_conf)
+{
+ int S, ccch_rach_ratio256, i;
+ int T_group = 0;
+ int is_ccch_comb = 0;
+
+ if (bcch_conf == RSL_BCCH_CCCH_CONF_1_C)
+ is_ccch_comb = 1;
+
+ /*
+ * The calculation is based on the ratio of the number RACH slots and
+ * CCCH blocks per time:
+ * Lmax = (T + 2*S) / R_RACH * R_CCCH
+ * where
+ * T3126_min = (T + 2*S) / R_RACH, as defined in GSM 04.08, 11.1.1
+ * R_RACH is the RACH slot rate (e.g. RACHs per multiframe)
+ * R_CCCH is the CCCH block rate (same time base like R_RACH)
+ * S and T are defined in GSM 04.08, 3.3.1.1.2
+ * The ratio is mainly influenced by the downlink only channels
+ * (BCCH, FCCH, SCH, CBCH) that can not be used for CCCH.
+ * An estimation with an error of < 10% is used:
+ * ~ 1/9 if CCCH is combined with SDCCH, and
+ * ~ 1/5.5 otherwise.
+ */
+ ccch_rach_ratio256 = is_ccch_comb ?
+ CCCH_RACH_RATIO_COMBINED256 :
+ CCCH_RACH_RATIO_SEPARATE256;
+
+ for (i = 0; i < ARRAY_SIZE(tx_integer); i++) {
+ if (tx_integer[i] == T) {
+ T_group = i % 5;
+ break;
+ }
+ }
+ S = s_values[T_group][is_ccch_comb];
+
+ return (T + 2 * S) * ccch_rach_ratio256 / 256;
+}
+
+static void bts_update_agch_max_queue_length(struct gsm_bts *bts)
+{
+ struct gsm48_system_information_type_3 *si3;
+ int old_max_length = bts->agch_queue.max_length;
+
+ if (!(bts->si_valid & (1<<SYSINFO_TYPE_3)))
+ return;
+
+ si3 = GSM_BTS_SI(bts, SYSINFO_TYPE_3);
+
+ bts->agch_queue.max_length =
+ bts_agch_max_queue_length(si3->rach_control.tx_integer,
+ si3->control_channel_desc.ccch_conf);
+
+ if (bts->agch_queue.max_length != old_max_length)
+ LOGP(DRSL, LOGL_INFO, "Updated AGCH max queue length to %d\n",
+ bts->agch_queue.max_length);
+}
+
+#define REQ_REFS_PER_IMM_ASS_REJ 4
+static int store_imm_ass_rej_refs(struct gsm48_imm_ass_rej *rej,
+ struct gsm48_req_ref *req_refs,
+ uint8_t *wait_inds,
+ int count)
+{
+ switch (count) {
+ case 0:
+ /* TODO: Warning ? */
+ return 0;
+ default:
+ count = 4;
+ rej->req_ref4 = req_refs[3];
+ rej->wait_ind4 = wait_inds[3];
+ /* fall through */
+ case 3:
+ rej->req_ref3 = req_refs[2];
+ rej->wait_ind3 = wait_inds[2];
+ /* fall through */
+ case 2:
+ rej->req_ref2 = req_refs[1];
+ rej->wait_ind2 = wait_inds[1];
+ /* fall through */
+ case 1:
+ rej->req_ref1 = req_refs[0];
+ rej->wait_ind1 = wait_inds[0];
+ break;
+ }
+
+ switch (count) {
+ case 1:
+ rej->req_ref2 = req_refs[0];
+ rej->wait_ind2 = wait_inds[0];
+ /* fall through */
+ case 2:
+ rej->req_ref3 = req_refs[0];
+ rej->wait_ind3 = wait_inds[0];
+ /* fall through */
+ case 3:
+ rej->req_ref4 = req_refs[0];
+ rej->wait_ind4 = wait_inds[0];
+ /* fall through */
+ default:
+ break;
+ }
+
+ return count;
+}
+
+static int extract_imm_ass_rej_refs(struct gsm48_imm_ass_rej *rej,
+ struct gsm48_req_ref *req_refs,
+ uint8_t *wait_inds)
+{
+ int count = 0;
+ req_refs[count] = rej->req_ref1;
+ wait_inds[count] = rej->wait_ind1;
+ count++;
+
+ if (memcmp(&rej->req_ref1, &rej->req_ref2, sizeof(rej->req_ref2))) {
+ req_refs[count] = rej->req_ref2;
+ wait_inds[count] = rej->wait_ind2;
+ count++;
+ }
+
+ if (memcmp(&rej->req_ref1, &rej->req_ref3, sizeof(rej->req_ref3)) &&
+ memcmp(&rej->req_ref2, &rej->req_ref3, sizeof(rej->req_ref3))) {
+ req_refs[count] = rej->req_ref3;
+ wait_inds[count] = rej->wait_ind3;
+ count++;
+ }
+
+ if (memcmp(&rej->req_ref1, &rej->req_ref4, sizeof(rej->req_ref4)) &&
+ memcmp(&rej->req_ref2, &rej->req_ref4, sizeof(rej->req_ref4)) &&
+ memcmp(&rej->req_ref3, &rej->req_ref4, sizeof(rej->req_ref4))) {
+ req_refs[count] = rej->req_ref4;
+ wait_inds[count] = rej->wait_ind4;
+ count++;
+ }
+
+ return count;
+}
+
+static int try_merge_imm_ass_rej(struct gsm48_imm_ass_rej *old_rej,
+ struct gsm48_imm_ass_rej *new_rej)
+{
+ struct gsm48_req_ref req_refs[2 * REQ_REFS_PER_IMM_ASS_REJ];
+ uint8_t wait_inds[2 * REQ_REFS_PER_IMM_ASS_REJ];
+ int count = 0;
+ int stored = 0;
+
+ if (new_rej->msg_type != GSM48_MT_RR_IMM_ASS_REJ)
+ return 0;
+ if (old_rej->msg_type != GSM48_MT_RR_IMM_ASS_REJ)
+ return 0;
+
+ /* GSM 08.58, 5.7
+ * -> The BTS may combine serveral IMM.ASS.REJ messages
+ * -> Identical request refs in one message may be squeezed
+ *
+ * GSM 04.08, 9.1.20.2
+ * -> Request ref and wait ind are duplicated to fill the message
+ */
+
+ /* Extract all entries */
+ count = extract_imm_ass_rej_refs(old_rej,
+ &req_refs[count], &wait_inds[count]);
+ if (count == REQ_REFS_PER_IMM_ASS_REJ)
+ return 0;
+
+ count += extract_imm_ass_rej_refs(new_rej,
+ &req_refs[count], &wait_inds[count]);
+
+ /* Store entries into old message */
+ stored = store_imm_ass_rej_refs(old_rej,
+ &req_refs[stored], &wait_inds[stored],
+ count);
+ count -= stored;
+ if (count == 0)
+ return 1;
+
+ /* Store remaining entries into new message */
+ stored += store_imm_ass_rej_refs(new_rej,
+ &req_refs[stored], &wait_inds[stored],
+ count);
+ return 0;
+}
+
+int bts_agch_enqueue(struct gsm_bts *bts, struct msgb *msg)
+{
+ int hard_limit = 100;
+ struct gsm48_imm_ass_rej *imm_ass_cmd = msgb_l3(msg);
+
+ if (bts->agch_queue.length > hard_limit) {
+ LOGP(DSUM, 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),
+ bts->agch_queue.length, bts->agch_queue.max_length);
+
+ bts->agch_queue.rejected_msgs++;
+ return -ENOMEM;
+ }
+
+ if (bts->agch_queue.length > 0) {
+ struct msgb *last_msg =
+ llist_entry(bts->agch_queue.queue.prev, struct msgb, list);
+ struct gsm48_imm_ass_rej *last_imm_ass_rej = msgb_l3(last_msg);
+
+ if (try_merge_imm_ass_rej(last_imm_ass_rej, imm_ass_cmd)) {
+ bts->agch_queue.merged_msgs++;
+ msgb_free(msg);
+ return 0;
+ }
+ }
+
+ msgb_enqueue(&bts->agch_queue.queue, msg);
+ bts->agch_queue.length++;
+
+ return 0;
+}
+
+struct msgb *bts_agch_dequeue(struct gsm_bts *bts)
+{
+ struct msgb *msg = msgb_dequeue(&bts->agch_queue.queue);
+ if (!msg)
+ return NULL;
+
+ bts->agch_queue.length--;
+ return msg;
+}
+
+/*
+ * 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
+ * reserved part of the CCCH.
+ */
+static void compact_agch_queue(struct gsm_bts *bts)
+{
+ struct msgb *msg, *msg2;
+ int max_len, slope, offs;
+ int level_low = bts->agch_queue.low_level;
+ int level_high = bts->agch_queue.high_level;
+ int level_thres = bts->agch_queue.thresh_level;
+
+ max_len = bts->agch_queue.max_length;
+
+ if (max_len == 0)
+ max_len = 1;
+
+ if (bts->agch_queue.length < max_len * level_thres / 100)
+ return;
+
+ /* p^
+ * 1+ /'''''
+ * | /
+ * | /
+ * 0+---/--+----+--> Q length
+ * low high max_len
+ */
+
+ offs = max_len * level_low / 100;
+ if (level_high > level_low)
+ slope = 0x10000 * 100 / (level_high - level_low);
+ else
+ slope = 0x10000 * max_len; /* p_drop >= 1 if len > offs */
+
+ llist_for_each_entry_safe(msg, msg2, &bts->agch_queue.queue, list) {
+ struct gsm48_imm_ass *imm_ass_cmd = msgb_l3(msg);
+ int p_drop;
+
+ p_drop = (bts->agch_queue.length - offs) * slope / max_len;
+
+ if ((random() & 0xffff) >= p_drop)
+ return;
+
+ llist_del(&msg->list);
+ bts->agch_queue.length--;
+ rsl_tx_delete_ind(bts, (uint8_t *)imm_ass_cmd, msgb_l3len(msg));
+ rate_ctr_inc2(bts->ctrs, BTS_CTR_AGCH_DELETED);
+ msgb_free(msg);
+
+ bts->agch_queue.dropped_msgs++;
+ }
+ return;
+}
+
+int bts_ccch_copy_msg(struct gsm_bts *bts, uint8_t *out_buf, struct gsm_time *gt,
+ int is_ag_res)
+{
+ struct msgb *msg = NULL;
+ int rc = 0;
+ int is_empty = 1;
+
+ /* Do queue house keeping.
+ * This needs to be done every time a CCCH message is requested, since
+ * the queue max length is calculated based on the CCCH block rate and
+ * PCH messages also reduce the drain of the AGCH queue.
+ */
+ 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)
+ return rc;
+
+ rate_ctr_inc2(bts->ctrs, BTS_CTR_AGCH_SENT);
+
+ /* Copy AGCH message */
+ memcpy(out_buf, msgb_l3(msg), msgb_l3len(msg));
+ rc = msgb_l3len(msg);
+ msgb_free(msg);
+
+ if (is_ag_res)
+ bts->agch_queue.agch_msgs++;
+ else
+ bts->agch_queue.pch_msgs++;
+
+ return rc;
+}
+
+int bts_supports_cipher(struct gsm_bts *bts, int rsl_cipher)
+{
+ int sup;
+
+ if (rsl_cipher < 1 || rsl_cipher > 8)
+ return -ENOTSUP;
+
+ /* No encryption is always supported */
+ if (rsl_cipher == 1)
+ return 1;
+
+ sup = (1 << (rsl_cipher - 2)) & bts->support.ciphers;
+ 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)
+{
+ enum gsm_bts_features feature = _NUM_BTS_FEAT;
+
+ /* 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:
+ feature = BTS_FEAT_SPEECH_F_V1;
+ break;
+ case GSM48_CMODE_SPEECH_EFR:
+ feature = BTS_FEAT_SPEECH_F_EFR;
+ break;
+ case GSM48_CMODE_SPEECH_AMR:
+ feature = BTS_FEAT_SPEECH_F_AMR;
+ break;
+ default:
+ /* Invalid speech codec type => Not supported! */
+ return 0;
+ }
+ break;
+
+ case GSM_PCHAN_TCH_H:
+ switch(cm) {
+ case GSM48_CMODE_SPEECH_V1:
+ feature = BTS_FEAT_SPEECH_H_V1;
+ break;
+ case GSM48_CMODE_SPEECH_AMR:
+ feature = BTS_FEAT_SPEECH_H_AMR;
+ break;
+ default:
+ /* Invalid speech codec type => Not supported! */
+ return 0;
+ }
+ 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;
+ }
+
+ /* Check if the feature is supported by this BTS */
+ if (gsm_bts_has_feature(bts, feature))
+ return 1;
+
+ return 0;
+}
diff --git a/src/common/bts_ctrl_commands.c b/src/common/bts_ctrl_commands.c
new file mode 100644
index 00000000..4efb4ee3
--- /dev/null
+++ b/src/common/bts_ctrl_commands.c
@@ -0,0 +1,93 @@
+/* Control Interface for osmo-bts */
+
+/* (C) 2014 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 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 <errno.h>
+#include <fcntl.h>
+
+#include <osmocom/gsm/protocol/gsm_12_21.h>
+#include <osmocom/ctrl/control_cmd.h>
+
+#include <osmo-bts/logging.h>
+#include <osmo-bts/gsm_data.h>
+#include <osmo-bts/tx_power.h>
+#include <osmo-bts/signal.h>
+#include <osmo-bts/oml.h>
+#include <osmo-bts/bts.h>
+
+CTRL_CMD_DEFINE(therm_att, "thermal-attenuation");
+static int get_therm_att(struct ctrl_cmd *cmd, void *data)
+{
+ struct gsm_bts_trx *trx = cmd->node;
+ struct trx_power_params *tpp = &trx->power_params;
+
+ cmd->reply = talloc_asprintf(cmd, "%d", tpp->thermal_attenuation_mdB);
+
+ return CTRL_CMD_REPLY;
+}
+
+static int set_therm_att(struct ctrl_cmd *cmd, void *data)
+{
+ struct gsm_bts_trx *trx = cmd->node;
+ struct trx_power_params *tpp = &trx->power_params;
+ int val = atoi(cmd->value);
+
+ printf("set_therm_att(trx=%p, tpp=%p)\n", trx, tpp);
+
+ tpp->thermal_attenuation_mdB = val;
+
+ power_ramp_start(trx, tpp->p_total_cur_mdBm, 0);
+
+ return get_therm_att(cmd, data);
+}
+
+static int verify_therm_att(struct ctrl_cmd *cmd, const char *value, void *data)
+{
+ int val = atoi(value);
+
+ /* permit between 0 to 40 dB attenuation */
+ if (val < 0 || val > to_mdB(40))
+ return 1;
+
+ return 0;
+}
+
+CTRL_CMD_DEFINE_WO_NOVRF(oml_alert, "oml-alert");
+static int set_oml_alert(struct ctrl_cmd *cmd, void *data)
+{
+ /* Note: we expect signal dispatch to be synchronous */
+ osmo_signal_dispatch(SS_FAIL, OSMO_EVT_EXT_ALARM, cmd->value);
+
+ cmd->reply = "OK";
+
+ return CTRL_CMD_REPLY;
+}
+
+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);
+
+ return rc;
+}
diff --git a/src/common/bts_ctrl_lookup.c b/src/common/bts_ctrl_lookup.c
new file mode 100644
index 00000000..f0157e9a
--- /dev/null
+++ b/src/common/bts_ctrl_lookup.c
@@ -0,0 +1,115 @@
+/* Control Interface for osmo-bts */
+
+/* (C) 2014 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 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/vty/command.h>
+#include <osmocom/ctrl/control_if.h>
+#include <osmocom/ctrl/ports.h>
+#include <osmo-bts/bts_model.h>
+#include <osmo-bts/logging.h>
+#include <osmo-bts/gsm_data.h>
+#include <osmo-bts/control_if.h>
+
+extern vector ctrl_node_vec;
+
+/*! \brief control interface lookup function for bsc/bts gsm_data
+ * \param[in] data Private data passed to controlif_setup()
+ * \param[in] vline Vector of the line holding the command string
+ * \param[out] node_type type (CTRL_NODE_) that was determined
+ * \param[out] node_data private dta of node that was determined
+ * \param i Current index into vline, up to which it is parsed
+ */
+static int bts_ctrl_node_lookup(void *data, vector vline, int *node_type,
+ void **node_data, int *i)
+{
+ struct gsm_bts *bts = data;
+ struct gsm_bts_trx *trx = NULL;
+ struct gsm_bts_trx_ts *ts = NULL;
+ char *token = vector_slot(vline, *i);
+ long num;
+
+ /* TODO: We need to make sure that the following chars are digits
+ * and/or use strtol to check if number conversion was successful
+ * Right now something like net.bts_stats will not work */
+ if (!strcmp(token, "trx")) {
+ if (*node_type != CTRL_NODE_ROOT || !*node_data)
+ goto err_missing;
+ bts = *node_data;
+ (*i)++;
+ if (!ctrl_parse_get_num(vline, *i, &num))
+ goto err_index;
+
+ trx = gsm_bts_trx_num(bts, num);
+ if (!trx)
+ goto err_missing;
+ *node_data = trx;
+ *node_type = CTRL_NODE_TRX;
+ } else if (!strcmp(token, "ts")) {
+ if (*node_type != CTRL_NODE_TRX || !*node_data)
+ goto err_missing;
+ trx = *node_data;
+ (*i)++;
+ if (!ctrl_parse_get_num(vline, *i, &num))
+ goto err_index;
+
+ if ((num >= 0) && (num < TRX_NR_TS))
+ ts = &trx->ts[num];
+ if (!ts)
+ goto err_missing;
+ *node_data = ts;
+ *node_type = CTRL_NODE_TS;
+ } else
+ return 0;
+
+ return 1;
+err_missing:
+ return -ENODEV;
+err_index:
+ return -ERANGE;
+}
+
+struct ctrl_handle *bts_controlif_setup(struct gsm_bts *bts,
+ const char *bind_addr, uint16_t port)
+{
+ struct ctrl_handle *hdl;
+ int rc = 0;
+
+ hdl = ctrl_interface_setup_dynip(bts, bind_addr, port,
+ bts_ctrl_node_lookup);
+ if (!hdl)
+ return NULL;
+
+ rc = bts_ctrl_cmds_install(bts);
+ if (rc) {
+ /* FIXME: close control interface */
+ return NULL;
+ }
+
+ rc = bts_model_ctrl_cmds_install(bts);
+ if (rc) {
+ /* FIXME: cleanup generic control commands */
+ /* FIXME: close control interface */
+ return NULL;
+ }
+
+ return hdl;
+}
diff --git a/src/common/cbch.c b/src/common/cbch.c
new file mode 100644
index 00000000..c628cb5a
--- /dev/null
+++ b/src/common/cbch.c
@@ -0,0 +1,198 @@
+/* Cell Broadcast routines */
+
+/* (C) 2014 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 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/utils.h>
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/gsm/protocol/gsm_04_12.h>
+
+#include <osmo-bts/bts.h>
+#include <osmo-bts/cbch.h>
+#include <osmo-bts/logging.h>
+
+struct smscb_msg {
+ struct llist_head list; /* list in smscb_state.queue */
+
+ uint8_t msg[GSM412_MSG_LEN]; /* message buffer */
+ uint8_t next_seg; /* next segment number */
+ uint8_t num_segs; /* total number of segments */
+};
+
+static int get_smscb_null_block(uint8_t *out)
+{
+ struct gsm412_block_type *block_type = (struct gsm412_block_type *) out;
+
+ block_type->spare = 0;
+ block_type->lpd = 1;
+ block_type->seq_nr = GSM412_SEQ_NULL_MSG;
+ block_type->lb = 0;
+ memset(out+1, GSM_MACBLOCK_PADDING, GSM412_BLOCK_LEN);
+
+ return 0;
+}
+
+/* get the next block of the current CB message */
+static int get_smscb_block(struct gsm_bts *bts, uint8_t *out)
+{
+ int to_copy;
+ struct gsm412_block_type *block_type;
+ struct smscb_msg *msg = bts->smscb_state.cur_msg;
+
+ if (!msg) {
+ /* No message: Send NULL mesage */
+ return get_smscb_null_block(out);
+ }
+ OSMO_ASSERT(msg->next_seg < 4);
+
+ block_type = (struct gsm412_block_type *) out++;
+
+ /* LPD is always 01 */
+ block_type->spare = 0;
+ block_type->lpd = 1;
+
+ /* determine how much data to copy */
+ to_copy = GSM412_MSG_LEN - (msg->next_seg * GSM412_BLOCK_LEN);
+ if (to_copy > GSM412_BLOCK_LEN)
+ to_copy = GSM412_BLOCK_LEN;
+ OSMO_ASSERT(to_copy >= 0);
+
+ /* copy data and increment index */
+ memcpy(out, &msg->msg[msg->next_seg * GSM412_BLOCK_LEN], to_copy);
+
+ /* set + increment sequence number */
+ block_type->seq_nr = msg->next_seg++;
+
+ /* determine if this is the last block */
+ if (block_type->seq_nr + 1 == msg->num_segs)
+ block_type->lb = 1;
+ else
+ block_type->lb = 0;
+
+ if (block_type->lb == 1) {
+ /* remove/release the message memory */
+ talloc_free(bts->smscb_state.cur_msg);
+ bts->smscb_state.cur_msg = NULL;
+ }
+
+ return block_type->lb;
+}
+
+static const uint8_t last_block_rsl2um[4] = {
+ [RSL_CB_CMD_LASTBLOCK_4] = 4,
+ [RSL_CB_CMD_LASTBLOCK_1] = 1,
+ [RSL_CB_CMD_LASTBLOCK_2] = 2,
+ [RSL_CB_CMD_LASTBLOCK_3] = 3,
+};
+
+
+/* incoming SMS broadcast command from RSL */
+int bts_process_smscb_cmd(struct gsm_bts *bts,
+ struct rsl_ie_cb_cmd_type cmd_type,
+ uint8_t msg_len, const uint8_t *msg)
+{
+ struct smscb_msg *scm;
+
+ if (msg_len > sizeof(scm->msg)) {
+ LOGP(DLSMS, LOGL_ERROR,
+ "Cannot process SMSCB of %u bytes (max %zu)\n",
+ msg_len, sizeof(scm->msg));
+ return -EINVAL;
+ }
+
+ scm = talloc_zero_size(bts, sizeof(*scm));
+ if (!scm)
+ return -1;
+
+ /* initialize entire message with default padding */
+ memset(scm->msg, GSM_MACBLOCK_PADDING, sizeof(scm->msg));
+ /* next segment is first segment */
+ scm->next_seg = 0;
+
+ switch (cmd_type.command) {
+ case RSL_CB_CMD_TYPE_NORMAL:
+ case RSL_CB_CMD_TYPE_SCHEDULE:
+ case RSL_CB_CMD_TYPE_NULL:
+ scm->num_segs = last_block_rsl2um[cmd_type.last_block&3];
+ memcpy(scm->msg, msg, msg_len);
+ /* def_bcast is ignored */
+ break;
+ case RSL_CB_CMD_TYPE_DEFAULT:
+ /* use def_bcast, ignore command */
+ /* def_bcast == 0: normal mess */
+ break;
+ }
+
+ llist_add_tail(&scm->list, &bts->smscb_state.queue);
+ /* FIXME: limit queue size and optionally send CBCH LOAD Information (overflow) via RSL */
+
+ return 0;
+}
+
+static struct smscb_msg *select_next_smscb(struct gsm_bts *bts)
+{
+ struct smscb_msg *msg;
+
+ msg = llist_first_entry_or_null(&bts->smscb_state.queue, struct smscb_msg, list);
+ if (!msg) {
+ /* FIXME: send CBCH LOAD Information (underflow) via RSL */
+ return NULL;
+ }
+
+ llist_del(&msg->list);
+
+ return msg;
+}
+
+/* call-back from bts model specific code when it wants to obtain a CBCH
+ * block for a given gsm_time. outbuf must have 23 bytes of space. */
+int bts_cbch_get(struct gsm_bts *bts, uint8_t *outbuf, struct gsm_time *g_time)
+{
+ uint32_t fn = gsm_gsmtime2fn(g_time);
+ /* According to 05.02 Section 6.5.4 */
+ uint32_t tb = (fn / 51) % 8;
+ int rc = 0;
+
+ /* The multiframes used for the basic cell broadcast channel
+ * shall be those in * which TB = 0,1,2 and 3. The multiframes
+ * used for the extended cell broadcast channel shall be those
+ * in which TB = 4, 5, 6 and 7 */
+
+ /* The SMSCB header shall be sent in the multiframe in which TB
+ * = 0 for the basic, and TB = 4 for the extended cell
+ * broadcast channel. */
+
+ switch (tb) {
+ case 0:
+ /* select a new SMSCB message */
+ bts->smscb_state.cur_msg = select_next_smscb(bts);
+ rc = get_smscb_block(bts, outbuf);
+ break;
+ case 1: case 2: case 3:
+ rc = get_smscb_block(bts, outbuf);
+ break;
+ case 4: case 5: case 6: case 7:
+ /* always send NULL frame in extended CBCH for now */
+ rc = get_smscb_null_block(outbuf);
+ break;
+ }
+
+ return rc;
+}
diff --git a/src/common/dtx_dl_amr_fsm.c b/src/common/dtx_dl_amr_fsm.c
new file mode 100644
index 00000000..38e22c95
--- /dev/null
+++ b/src/common/dtx_dl_amr_fsm.c
@@ -0,0 +1,477 @@
+/* DTX DL AMR FSM */
+
+/* (C) 2016 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 <osmo-bts/dtx_dl_amr_fsm.h>
+#include <osmo-bts/logging.h>
+
+void dtx_fsm_voice(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ switch (event) {
+ case E_VOICE:
+ case E_FACCH:
+ break;
+ case E_SID_F:
+ osmo_fsm_inst_state_chg(fi, ST_SID_F1, 0, 0);
+ break;
+ case E_SID_U:
+ osmo_fsm_inst_state_chg(fi, ST_U_NOINH, 0, 0);
+ break;
+ case E_INHIB:
+ osmo_fsm_inst_state_chg(fi, ST_F1_INH_V, 0, 0);
+ break;
+ default:
+ LOGP(DL1P, LOGL_ERROR, "Inexpected event %d\n", event);
+ OSMO_ASSERT(0);
+ break;
+ }
+}
+
+void dtx_fsm_sid_f1(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ switch (event) {
+ case E_SID_F:
+/* FIXME: what shall we do if we get SID-FIRST _again_ (twice in a row)?
+ Was observed during testing, let's just ignore it for now */
+ break;
+ case E_SID_U:
+ osmo_fsm_inst_state_chg(fi, ST_U_NOINH, 0, 0);
+ break;
+ case E_FACCH:
+ osmo_fsm_inst_state_chg(fi, ST_F1_INH_F, 0, 0);
+ break;
+ case E_FIRST:
+ osmo_fsm_inst_state_chg(fi, ST_SID_F2, 0, 0);
+ break;
+ case E_ONSET:
+ osmo_fsm_inst_state_chg(fi, ST_ONSET_V, 0, 0);
+ break;
+ default:
+ LOGP(DL1P, LOGL_ERROR, "Unexpected event %d\n", event);
+ OSMO_ASSERT(0);
+ break;
+ }
+}
+
+void dtx_fsm_sid_f2(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ switch (event) {
+ case E_COMPL:
+ osmo_fsm_inst_state_chg(fi, ST_U_NOINH, 0, 0);
+ break;
+ case E_FACCH:
+ osmo_fsm_inst_state_chg(fi, ST_ONSET_F, 0, 0);
+ break;
+ case E_ONSET:
+ osmo_fsm_inst_state_chg(fi, ST_ONSET_V, 0, 0);
+ break;
+ default:
+ LOGP(DL1P, LOGL_ERROR, "Unexpected event %d\n", event);
+ OSMO_ASSERT(0);
+ break;
+ }
+}
+
+void dtx_fsm_f1_inh_v(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ switch (event) {
+ case E_COMPL:
+ osmo_fsm_inst_state_chg(fi, ST_F1_INH_V_REC, 0, 0);
+ break;
+ default:
+ LOGP(DL1P, LOGL_ERROR, "Unexpected event %d\n", event);
+ OSMO_ASSERT(0);
+ break;
+ }
+}
+
+void dtx_fsm_f1_inh_f(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ switch (event) {
+ case E_COMPL:
+ osmo_fsm_inst_state_chg(fi, ST_F1_INH_F_REC, 0, 0);
+ break;
+ default:
+ LOGP(DL1P, LOGL_ERROR, "Unexpected event %d\n", event);
+ OSMO_ASSERT(0);
+ break;
+ }
+}
+
+void dtx_fsm_u_inh_v(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ switch (event) {
+ case E_COMPL:
+ osmo_fsm_inst_state_chg(fi, ST_U_INH_V_REC, 0, 0);
+ break;
+ default:
+ LOGP(DL1P, LOGL_ERROR, "Unexpected event %d\n", event);
+ OSMO_ASSERT(0);
+ break;
+ }
+}
+
+void dtx_fsm_u_inh_f(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ switch (event) {
+ case E_COMPL:
+ osmo_fsm_inst_state_chg(fi, ST_U_INH_F_REC, 0, 0);
+ break;
+ default:
+ LOGP(DL1P, LOGL_ERROR, "Unexpected event %d\n", event);
+ OSMO_ASSERT(0);
+ break;
+ }
+}
+
+void dtx_fsm_f1_inh_v_rec(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ switch (event) {
+ case E_VOICE:
+ case E_COMPL:
+ osmo_fsm_inst_state_chg(fi, ST_VOICE, 0, 0);
+ break;
+ default:
+ LOGP(DL1P, LOGL_ERROR, "Unexpected event %d\n", event);
+ OSMO_ASSERT(0);
+ break;
+ }
+}
+
+void dtx_fsm_f1_inh_f_rec(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ switch (event) {
+ case E_FACCH:
+ case E_COMPL:
+ osmo_fsm_inst_state_chg(fi, ST_FACCH, 0, 0);
+ break;
+ default:
+ LOGP(DL1P, LOGL_ERROR, "Unexpected event %d\n", event);
+ OSMO_ASSERT(0);
+ break;
+ }
+}
+
+void dtx_fsm_u_inh_v_rec(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ switch (event) {
+ case E_VOICE:
+ case E_COMPL:
+ osmo_fsm_inst_state_chg(fi, ST_VOICE, 0, 0);
+ break;
+ default:
+ LOGP(DL1P, LOGL_ERROR, "Unexpected event %d\n", event);
+ OSMO_ASSERT(0);
+ break;
+ }
+}
+
+void dtx_fsm_u_inh_f_rec(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ switch (event) {
+ case E_FACCH:
+ case E_COMPL:
+ osmo_fsm_inst_state_chg(fi, ST_FACCH, 0, 0);
+ break;
+ default:
+ LOGP(DL1P, LOGL_ERROR, "Unexpected event %d\n", event);
+ OSMO_ASSERT(0);
+ break;
+ }
+}
+
+void dtx_fsm_u_noinh(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ switch (event) {
+ case E_FACCH:
+ osmo_fsm_inst_state_chg(fi, ST_ONSET_F, 0, 0);
+ break;
+ case E_VOICE:
+ osmo_fsm_inst_state_chg(fi, ST_VOICE, 0, 0);
+ break;
+ case E_COMPL:
+ osmo_fsm_inst_state_chg(fi, ST_SID_U, 0, 0);
+ break;
+ case E_SID_U:
+ case E_SID_F:
+/* FIXME: what shall we do if we get SID-FIRST _after_ sending SID-UPDATE?
+ Was observed during testing, let's just ignore it for now */
+ break;
+ case E_ONSET:
+ osmo_fsm_inst_state_chg(fi, ST_ONSET_V, 0, 0);
+ break;
+ default:
+ LOGP(DL1P, LOGL_ERROR, "Unexpected event %d\n", event);
+ OSMO_ASSERT(0);
+ break;
+ }
+}
+
+void dtx_fsm_sid_upd(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ switch (event) {
+ case E_FACCH:
+ osmo_fsm_inst_state_chg(fi, ST_U_INH_F, 0, 0);
+ break;
+ case E_VOICE:
+ osmo_fsm_inst_state_chg(fi, ST_VOICE, 0, 0);
+ break;
+ case E_INHIB:
+ osmo_fsm_inst_state_chg(fi, ST_U_INH_V, 0, 0);
+ break;
+ case E_SID_U:
+ case E_SID_F:
+ osmo_fsm_inst_state_chg(fi, ST_U_NOINH, 0, 0);
+ break;
+ default:
+ LOGP(DL1P, LOGL_ERROR, "Unexpected event %d\n", event);
+ OSMO_ASSERT(0);
+ break;
+ }
+}
+
+void dtx_fsm_onset_v(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ switch (event) {
+ case E_COMPL:
+ osmo_fsm_inst_state_chg(fi, ST_ONSET_V_REC, 0, 0);
+ break;
+ default:
+ LOGP(DL1P, LOGL_ERROR, "Unexpected event %d\n", event);
+ OSMO_ASSERT(0);
+ break;
+ }
+}
+
+void dtx_fsm_onset_f(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ switch (event) {
+ case E_COMPL:
+ osmo_fsm_inst_state_chg(fi, ST_ONSET_F_REC, 0, 0);
+ break;
+ default:
+ LOGP(DL1P, LOGL_ERROR, "Unexpected event %d\n", event);
+ OSMO_ASSERT(0);
+ break;
+ }
+}
+
+void dtx_fsm_onset_v_rec(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ switch (event) {
+ case E_COMPL:
+ osmo_fsm_inst_state_chg(fi, ST_VOICE, 0, 0);
+ break;
+ default:
+ LOGP(DL1P, LOGL_ERROR, "Unexpected event %d\n", event);
+ OSMO_ASSERT(0);
+ break;
+ }
+}
+
+void dtx_fsm_onset_f_rec(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ switch (event) {
+ case E_COMPL:
+ osmo_fsm_inst_state_chg(fi, ST_FACCH, 0, 0);
+ break;
+ default:
+ LOGP(DL1P, LOGL_ERROR, "Unexpected event %d\n", event);
+ OSMO_ASSERT(0);
+ break;
+ }
+}
+
+void dtx_fsm_facch(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ switch (event) {
+ case E_SID_U:
+ case E_SID_F:
+ case E_FACCH:
+ break;
+ case E_VOICE:
+ osmo_fsm_inst_state_chg(fi, ST_VOICE, 0, 0);
+ break;
+ case E_COMPL:
+ osmo_fsm_inst_state_chg(fi, ST_SID_F1, 0, 0);
+ break;
+ default:
+ LOGP(DL1P, LOGL_ERROR, "Unexpected event %d\n", event);
+ OSMO_ASSERT(0);
+ break;
+ }
+}
+
+static struct osmo_fsm_state dtx_dl_amr_fsm_states[] = {
+ /* default state for non-DTX and DTX when SPEECH is in progress */
+ [ST_VOICE] = {
+ .in_event_mask = X(E_SID_F) | X(E_SID_U) | X(E_VOICE) | X(E_FACCH) | X(E_INHIB),
+ .out_state_mask = X(ST_SID_F1) | X(ST_U_NOINH) | X(ST_F1_INH_V),
+ .name = "Voice",
+ .action = dtx_fsm_voice,
+ },
+ /* SID-FIRST or SID-FIRST-P1 in case of AMR HR:
+ start of silence period (might be interrupted in case of AMR HR) */
+ [ST_SID_F1]= {
+ .in_event_mask = X(E_SID_F) | X(E_SID_U) | X(E_FACCH) | X(E_FIRST) | X(E_ONSET),
+ .out_state_mask = X(ST_U_NOINH) | X(ST_ONSET_F) | X(ST_SID_F2) | X(ST_ONSET_V),
+ .name = "SID-FIRST (P1)",
+ .action = dtx_fsm_sid_f1,
+ },
+ /* SID-FIRST P2 (only for AMR HR):
+ actual start of silence period in case of AMR HR */
+ [ST_SID_F2]= {
+ .in_event_mask = X(E_COMPL) | X(E_FACCH) | X(E_ONSET),
+ .out_state_mask = X(ST_U_NOINH) | X(ST_ONSET_F) | X(ST_ONSET_V),
+ .name = "SID-FIRST (P2)",
+ .action = dtx_fsm_sid_f2,
+ },
+ /* SID-FIRST Inhibited: incoming SPEECH (only for AMR HR) */
+ [ST_F1_INH_V]= {
+ .in_event_mask = X(E_COMPL),
+ .out_state_mask = X(ST_F1_INH_V_REC),
+ .name = "SID-FIRST (Inh, SPEECH)",
+ .action = dtx_fsm_f1_inh_v,
+ },
+ /* SID-FIRST Inhibited: incoming FACCH frame (only for AMR HR) */
+ [ST_F1_INH_F]= {
+ .in_event_mask = X(E_COMPL),
+ .out_state_mask = X(ST_F1_INH_F_REC),
+ .name = "SID-FIRST (Inh, FACCH)",
+ .action = dtx_fsm_f1_inh_f,
+ },
+ /* SID-UPDATE Inhibited: incoming SPEECH (only for AMR HR) */
+ [ST_U_INH_V]= {
+ .in_event_mask = X(E_COMPL),
+ .out_state_mask = X(ST_U_INH_V_REC),
+ .name = "SID-UPDATE (Inh, SPEECH)",
+ .action = dtx_fsm_u_inh_v,
+ },
+ /* SID-UPDATE Inhibited: incoming FACCH frame (only for AMR HR) */
+ [ST_U_INH_F]= {
+ .in_event_mask = X(E_COMPL),
+ .out_state_mask = X(ST_U_INH_F_REC),
+ .name = "SID-UPDATE (Inh, FACCH)",
+ .action = dtx_fsm_u_inh_f,
+ },
+ /* SID-UPDATE: Inhibited not allowed (only for AMR HR) */
+ [ST_U_NOINH]= {
+ .in_event_mask = X(E_FACCH) | X(E_VOICE) | X(E_COMPL) | X(E_SID_U) | X(E_SID_F) | X(E_ONSET),
+ .out_state_mask = X(ST_ONSET_F) | X(ST_VOICE) | X(ST_SID_U) | X(ST_ONSET_V),
+ .name = "SID-UPDATE (NoInh)",
+ .action = dtx_fsm_u_noinh,
+ },
+ /* SID-FIRST Inhibition recursion in progress:
+ Inhibit itself was already sent, now have to send the voice that caused it */
+ [ST_F1_INH_V_REC]= {
+ .in_event_mask = X(E_COMPL) | X(E_VOICE),
+ .out_state_mask = X(ST_VOICE),
+ .name = "SID-FIRST (Inh, SPEECH, Rec)",
+ .action = dtx_fsm_f1_inh_v_rec,
+ },
+ /* SID-FIRST Inhibition recursion in progress:
+ Inhibit itself was already sent, now have to send the data that caused it */
+ [ST_F1_INH_F_REC]= {
+ .in_event_mask = X(E_COMPL) | X(E_FACCH),
+ .out_state_mask = X(ST_FACCH),
+ .name = "SID-FIRST (Inh, FACCH, Rec)",
+ .action = dtx_fsm_f1_inh_f_rec,
+ },
+ /* SID-UPDATE Inhibition recursion in progress:
+ Inhibit itself was already sent, now have to send the voice that caused it */
+ [ST_U_INH_V_REC]= {
+ .in_event_mask = X(E_COMPL) | X(E_VOICE),
+ .out_state_mask = X(ST_VOICE),
+ .name = "SID-UPDATE (Inh, SPEECH, Rec)",
+ .action = dtx_fsm_u_inh_v_rec,
+ },
+ /* SID-UPDATE Inhibition recursion in progress:
+ Inhibit itself was already sent, now have to send the data that caused it */
+ [ST_U_INH_F_REC]= {
+ .in_event_mask = X(E_COMPL) | X(E_FACCH),
+ .out_state_mask = X(ST_FACCH),
+ .name = "SID-UPDATE (Inh, FACCH, Rec)",
+ .action = dtx_fsm_u_inh_f_rec,
+ },
+ /* Silence period with periodic comfort noise data updates */
+ [ST_SID_U]= {
+ .in_event_mask = X(E_FACCH) | X(E_VOICE) | X(E_INHIB) | X(E_SID_U) | X(E_SID_F),
+ .out_state_mask = X(ST_ONSET_F) | X(ST_VOICE) | X(ST_U_INH_V) | X(ST_U_INH_F) | X(ST_U_NOINH),
+ .name = "SID-UPDATE (AMR/HR)",
+ .action = dtx_fsm_sid_upd,
+ },
+ /* ONSET - end of silent period due to incoming SPEECH frame */
+ [ST_ONSET_V]= {
+ .in_event_mask = X(E_COMPL),
+ .out_state_mask = X(ST_ONSET_V_REC),
+ .name = "ONSET (SPEECH)",
+ .action = dtx_fsm_onset_v,
+ },
+ /* ONSET - end of silent period due to incoming FACCH frame */
+ [ST_ONSET_F]= {
+ .in_event_mask = X(E_COMPL),
+ .out_state_mask = X(ST_ONSET_F_REC),
+ .name = "ONSET (FACCH)",
+ .action = dtx_fsm_onset_f,
+ },
+ /* ONSET recursion in progress:
+ ONSET itself was already sent, now have to send the voice that caused it */
+ [ST_ONSET_V_REC]= {
+ .in_event_mask = X(E_COMPL),
+ .out_state_mask = X(ST_VOICE),
+ .name = "ONSET (SPEECH, Rec)",
+ .action = dtx_fsm_onset_v_rec,
+ },
+ /* ONSET recursion in progress:
+ ONSET itself was already sent, now have to send the data that caused it */
+ [ST_ONSET_F_REC]= {
+ .in_event_mask = X(E_COMPL),
+ .out_state_mask = X(ST_FACCH),
+ .name = "ONSET (FACCH, Rec)",
+ .action = dtx_fsm_onset_f_rec,
+ },
+ /* FACCH sending state */
+ [ST_FACCH]= {
+ .in_event_mask = X(E_FACCH) | X(E_VOICE) | X(E_COMPL) | X(E_SID_U) | X(E_SID_F),
+ .out_state_mask = X(ST_VOICE) | X(ST_SID_F1),
+ .name = "FACCH",
+ .action = dtx_fsm_facch,
+ },
+};
+
+const struct value_string dtx_dl_amr_fsm_event_names[] = {
+ { E_VOICE, "Voice" },
+ { E_ONSET, "ONSET" },
+ { E_FACCH, "FACCH" },
+ { E_COMPL, "Complete" },
+ { E_FIRST, "FIRST P1->P2" },
+ { E_INHIB, "Inhibit" },
+ { E_SID_F, "SID-FIRST" },
+ { E_SID_U, "SID-UPDATE" },
+ { 0, NULL }
+};
+
+struct osmo_fsm dtx_dl_amr_fsm = {
+ .name = "DTX_DL_AMR_FSM",
+ .states = dtx_dl_amr_fsm_states,
+ .num_states = ARRAY_SIZE(dtx_dl_amr_fsm_states),
+ .event_names = dtx_dl_amr_fsm_event_names,
+ .log_subsys = DL1C,
+};
diff --git a/src/common/gsm_data_shared.c b/src/common/gsm_data_shared.c
new file mode 100644
index 00000000..2d9af783
--- /dev/null
+++ b/src/common/gsm_data_shared.c
@@ -0,0 +1,807 @@
+/* (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 <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;
+}
+
+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" },
+ { 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++;
+ trx->mo.nm_state.administrative = NM_STATE_UNLOCKED;
+
+ gsm_mo_init(&trx->mo, bts, NM_OC_RADIO_CARRIER,
+ bts->nr, trx->nr, 0xff);
+ gsm_mo_init(&trx->bb_transc.mo, bts, NM_OC_BASEB_TRANSC,
+ bts->nr, trx->nr, 0xff);
+
+ for (k = 0; k < TRX_NR_TS; k++) {
+ struct gsm_bts_trx_ts *ts = &trx->ts[k];
+ int l;
+
+ ts->trx = trx;
+ ts->nr = k;
+ ts->pchan = GSM_PCHAN_NONE;
+ ts->dyn.pchan_is = GSM_PCHAN_NONE;
+ ts->dyn.pchan_want = GSM_PCHAN_NONE;
+ ts->tsc = -1;
+
+ gsm_mo_init(&ts->mo, bts, NM_OC_CHANNEL,
+ bts->nr, trx->nr, ts->nr);
+
+ ts->hopping.arfcns.data_len = sizeof(ts->hopping.arfcns_data);
+ ts->hopping.arfcns.data = ts->hopping.arfcns_data;
+ ts->hopping.ma.data_len = sizeof(ts->hopping.ma_data);
+ ts->hopping.ma.data = ts->hopping.ma_data;
+
+ for (l = 0; l < TS_MAX_LCHAN; l++) {
+ struct gsm_lchan *lchan;
+ char *name;
+ lchan = &ts->lchan[l];
+
+ lchan->ts = ts;
+ lchan->nr = l;
+ lchan->type = GSM_LCHAN_NONE;
+
+ name = gsm_lchan_name_compute(lchan);
+ lchan->name = talloc_strdup(trx, name);
+ 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 */
+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 }
+};
diff --git a/src/common/handover.c b/src/common/handover.c
new file mode 100644
index 00000000..54b131ff
--- /dev/null
+++ b/src/common/handover.c
@@ -0,0 +1,164 @@
+/* Paging message encoding + queue management */
+
+/* (C) 2012-2013 by Harald Welte <laforge@gnumonks.org>
+ * Andreas Eversberg <jolly@eversberg.eu>
+ * (C) 2014 by Holger Hans Peter Freyther
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU 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/handover.h>
+#include <osmo-bts/l1sap.h>
+
+/* Transmit a handover related PHYS INFO on given lchan */
+static int ho_tx_phys_info(struct gsm_lchan *lchan)
+{
+ struct msgb *msg = msgb_alloc_headroom(1024, 128, "PHYS INFO");
+ struct gsm48_hdr *gh;
+
+ if (!msg)
+ return -ENOMEM;
+
+ LOGP(DHO, LOGL_INFO,
+ "%s Sending PHYSICAL INFORMATION to MS.\n",
+ gsm_lchan_name(lchan));
+
+ /* Build RSL UNITDATA REQUEST message with 04.08 PHYS INFO */
+ msg->l3h = msg->data;
+ 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);
+
+ rsl_rll_push_l3(msg, RSL_MT_UNIT_DATA_REQ, gsm_lchan2chan_nr(lchan),
+ 0x00, 0);
+
+ lapdm_rslms_recvmsg(msg, &lchan->lapdm_ch);
+ return 0;
+}
+
+/* timer call-back for T3105 (handover PHYS INFO re-transmit) */
+static void ho_t3105_cb(void *data)
+{
+ struct gsm_lchan *lchan = data;
+ struct gsm_bts *bts = lchan->ts->trx->bts;
+
+ LOGP(DHO, LOGL_INFO, "%s T3105 timeout (%d resends left)\n",
+ gsm_lchan_name(lchan), bts->ny1 - lchan->ho.phys_info_count);
+
+ if (lchan->state != LCHAN_S_ACTIVE) {
+ LOGP(DHO, LOGL_NOTICE,
+ "%s is in not active. It is in state %s. Ignoring\n",
+ gsm_lchan_name(lchan), gsm_lchans_name(lchan->state));
+ return;
+ }
+
+ if (lchan->ho.phys_info_count >= bts->ny1) {
+ /* HO Abort */
+ LOGP(DHO, LOGL_NOTICE, "%s NY1 reached, sending CONNection "
+ "FAILure to BSC.\n", gsm_lchan_name(lchan));
+ rsl_tx_conn_fail(lchan, RSL_ERR_HANDOVER_ACC_FAIL);
+ return;
+ }
+
+ ho_tx_phys_info(lchan);
+ lchan->ho.phys_info_count++;
+ osmo_timer_schedule(&lchan->ho.t3105, 0, bts->t3105_ms * 1000);
+}
+
+/* received random access on dedicated channel */
+void handover_rach(struct gsm_lchan *lchan, uint8_t ra, uint8_t acc_delay)
+{
+ struct gsm_bts *bts = lchan->ts->trx->bts;
+
+ /* Ignore invalid handover ref */
+ if (lchan->ho.ref != ra) {
+ LOGP(DHO, LOGL_INFO, "%s RACH on dedicated channel received, but "
+ "ra=0x%02x != expected ref=0x%02x. (This is no bug)\n",
+ gsm_lchan_name(lchan), ra, lchan->ho.ref);
+ return;
+ }
+
+ /* Ignore handover on channels other than DCCH and SACCH */
+ if (lchan->type != GSM_LCHAN_SDCCH && lchan->type != GSM_LCHAN_TCH_H &&
+ lchan->type != GSM_LCHAN_TCH_F) {
+ LOGP(DHO, LOGL_ERROR, "%s handover RACH received on %s?!\n",
+ gsm_lchan_name(lchan), gsm_lchant_name(lchan->type));
+ return;
+ }
+
+ LOGP(DHO, LOGL_NOTICE,
+ "%s RACH on dedicated channel type %s received with TA=%u, ref=%u\n",
+ gsm_lchan_name(lchan), gsm_lchant_name(lchan->type), acc_delay, ra);
+
+ /* Set timing advance */
+ lchan->rqd_ta = acc_delay;
+
+ /* Stop handover detection, wait for valid frame */
+ lchan->ho.active = HANDOVER_WAIT_FRAME;
+ if (l1sap_chan_modify(lchan->ts->trx, gsm_lchan2chan_nr(lchan)) != 0) {
+ LOGP(DHO, LOGL_ERROR,
+ "%s failed to modify channel after handover\n",
+ gsm_lchan_name(lchan));
+ rsl_tx_conn_fail(lchan, RSL_ERR_HANDOVER_ACC_FAIL);
+ return;
+ }
+
+ /* Send HANDover DETect to BSC */
+ rsl_tx_hando_det(lchan, &lchan->rqd_ta);
+
+ /* Send PHYS INFO */
+ lchan->ho.phys_info_count = 1;
+ ho_tx_phys_info(lchan);
+
+ /* Start T3105 */
+ LOGP(DHO, LOGL_DEBUG,
+ "%s Starting T3105 with %u ms\n",
+ gsm_lchan_name(lchan), bts->t3105_ms);
+ lchan->ho.t3105.cb = ho_t3105_cb;
+ lchan->ho.t3105.data = lchan;
+ osmo_timer_schedule(&lchan->ho.t3105, 0, bts->t3105_ms * 1000);
+}
+
+/* received frist valid data frame on dedicated channel */
+void handover_frame(struct gsm_lchan *lchan)
+{
+ LOGP(DHO, LOGL_INFO,
+ "%s First valid frame detected\n", gsm_lchan_name(lchan));
+ handover_reset(lchan);
+}
+
+/* release handover state */
+void handover_reset(struct gsm_lchan *lchan)
+{
+ /* Stop T3105 */
+ osmo_timer_del(&lchan->ho.t3105);
+
+ /* Handover process is done */
+ lchan->ho.active = HANDOVER_NONE;
+}
diff --git a/src/common/l1sap.c b/src/common/l1sap.c
new file mode 100644
index 00000000..dba08dfb
--- /dev/null
+++ b/src/common/l1sap.c
@@ -0,0 +1,1549 @@
+/* L1 SAP primitives */
+
+/* (C) 2011 by Harald Welte <laforge@gnumonks.org>
+ * (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 <errno.h>
+#include <fcntl.h>
+#include <stdbool.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <inttypes.h>
+
+#include <osmocom/core/msgb.h>
+#include <osmocom/gsm/l1sap.h>
+#include <osmocom/gsm/gsm_utils.h>
+#include <osmocom/gsm/rsl.h>
+#include <osmocom/core/gsmtap.h>
+#include <osmocom/core/gsmtap_util.h>
+#include <osmocom/core/utils.h>
+
+#include <osmocom/trau/osmo_ortp.h>
+
+#include <osmo-bts/logging.h>
+#include <osmo-bts/gsm_data.h>
+#include <osmo-bts/l1sap.h>
+#include <osmo-bts/dtx_dl_amr_fsm.h>
+#include <osmo-bts/pcu_if.h>
+#include <osmo-bts/measurement.h>
+#include <osmo-bts/bts.h>
+#include <osmo-bts/rsl.h>
+#include <osmo-bts/oml.h>
+#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/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 */
+};
+
+/* 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];
+ /* if FN is negative, we were called for something that's not CCCH! */
+ OSMO_ASSERT(rc >= 0);
+ return rc;
+}
+
+struct gsm_lchan *get_lchan_by_chan_nr(struct gsm_bts_trx *trx,
+ unsigned int chan_nr)
+{
+ unsigned int tn, ss;
+
+ tn = L1SAP_CHAN2TS(chan_nr);
+ OSMO_ASSERT(tn < ARRAY_SIZE(trx->ts));
+
+ 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));
+
+ return &trx->ts[tn].lchan[ss];
+}
+
+static struct gsm_lchan *
+get_active_lchan_by_chan_nr(struct gsm_bts_trx *trx, unsigned int chan_nr)
+{
+ struct gsm_lchan *lchan = get_lchan_by_chan_nr(trx, chan_nr);
+
+ if (lchan && lchan->state != LCHAN_S_ACTIVE) {
+ LOGP(DL1P, LOGL_NOTICE, "%s: assuming active lchan, but "
+ "state is %s\n", gsm_lchan_name(lchan),
+ gsm_lchans_name(lchan->state));
+ return NULL;
+ }
+ return lchan;
+}
+
+static int l1sap_down(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap);
+
+static uint32_t fn_ms_adj(uint32_t fn, const struct gsm_lchan *lchan)
+{
+ uint32_t samples_passed, r;
+
+ if (lchan->tch.last_fn != LCHAN_FN_DUMMY) {
+ /* 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);
+ /* round number of samples to the nearest multiple of
+ GSM_RTP_DURATION */
+ r = samples_passed + GSM_RTP_DURATION / 2;
+ r -= r % GSM_RTP_DURATION;
+
+ if (r != GSM_RTP_DURATION)
+ LOGP(DRTP, LOGL_ERROR, "RTP clock out of sync with lower layer:"
+ " %"PRIu32" vs %d (%"PRIu32"->%"PRIu32")\n",
+ r, GSM_RTP_DURATION, lchan->tch.last_fn, fn);
+ }
+ 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 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;
+ struct msgb *msg = msgb_alloc_headroom(size, headroom, "l1sap_prim");
+
+ if (!msg)
+ return NULL;
+
+ msg->l1h = msgb_put(msg, sizeof(struct osmo_phsap_prim));
+
+ return msg;
+}
+
+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)
+{
+ struct osmo_phsap_prim *l1sap;
+
+ LOGP(DL1P, LOGL_DEBUG, "%s Rx -> RTP: %s\n",
+ gsm_lchan_name(lchan), osmo_hexdump(rmsg->data, rmsg->len));
+
+ rmsg->l2h = rmsg->data;
+ rmsg->l1h = msgb_push(rmsg, sizeof(*l1sap));
+ l1sap = msgb_l1sap_prim(rmsg);
+ osmo_prim_init(&l1sap->oph, SAP_GSM_PH, PRIM_TCH, PRIM_OP_INDICATION,
+ rmsg);
+ l1sap->u.tch.chan_nr = chan_nr;
+ l1sap->u.tch.fn = fn;
+ l1sap->u.tch.ber10k = ber10k;
+ l1sap->u.tch.lqual_cb = lqual_cb;
+
+ return l1sap_up(trx, l1sap);
+}
+
+static int l1sap_tx_ciph_req(struct gsm_bts_trx *trx, uint8_t chan_nr,
+ uint8_t downlink, uint8_t uplink)
+{
+ struct osmo_phsap_prim l1sap_ciph;
+
+ osmo_prim_init(&l1sap_ciph.oph, SAP_GSM_PH, PRIM_MPH_INFO,
+ PRIM_OP_REQUEST, NULL);
+ l1sap_ciph.u.info.type = PRIM_INFO_ACT_CIPH;
+ l1sap_ciph.u.info.u.ciph_req.chan_nr = chan_nr;
+ l1sap_ciph.u.info.u.ciph_req.downlink = downlink;
+ l1sap_ciph.u.info.u.ciph_req.uplink = uplink;
+
+ return l1sap_down(trx, &l1sap_ciph);
+}
+
+
+/* check if the message is a GSM48_MT_RR_CIPH_M_CMD, and if yes, enable
+ * uni-directional de-cryption on the uplink. We need this ugly layering
+ * violation as we have no way of passing down L3 metadata (RSL CIPHERING CMD)
+ * to this point in L1 */
+static int check_for_ciph_cmd(struct msgb *msg, struct gsm_lchan *lchan,
+ uint8_t chan_nr)
+{
+ uint8_t n_s;
+
+ /* only do this if we are in the right state */
+ switch (lchan->ciph_state) {
+ case LCHAN_CIPH_NONE:
+ case LCHAN_CIPH_RX_REQ:
+ break;
+ default:
+ return 0;
+ }
+
+ /* First byte (Address Field) of LAPDm header) */
+ if (msg->data[0] != 0x03)
+ return 0;
+ /* First byte (protocol discriminator) of RR */
+ if ((msg->data[3] & 0xF) != GSM48_PDISC_RR)
+ return 0;
+ /* 2nd byte (msg type) of RR */
+ if ((msg->data[4] & 0x3F) != GSM48_MT_RR_CIPH_M_CMD)
+ return 0;
+
+ /* Remember N(S) + 1 to find the first ciphered frame */
+ n_s = (msg->data[1] >> 1) & 0x7;
+ lchan->ciph_ns = (n_s + 1) % 8;
+
+ l1sap_tx_ciph_req(lchan->ts->trx, chan_nr, 0, 1);
+
+ return 1;
+}
+
+/* public helpers for the test */
+int bts_check_for_ciph_cmd(struct msgb *msg, struct gsm_lchan *lchan,
+ uint8_t chan_nr)
+{
+ 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" },
+ { 0, NULL }
+};
+
+/* 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,
+ uint8_t num_agch)
+{
+ struct msgb *msg = l1sap->oph.msg;
+ uint8_t chan_nr, link_id;
+
+ *data = msgb_l2(msg);
+ *len = msgb_l2len(msg);
+ chan_nr = l1sap->u.data.chan_nr;
+ link_id = l1sap->u.data.link_id;
+
+ if (L1SAP_IS_CHAN_TCHF(chan_nr)) {
+ *chan_type = GSMTAP_CHANNEL_TCH_F;
+ } else if (L1SAP_IS_CHAN_TCHH(chan_nr)) {
+ *ss = L1SAP_CHAN2SS_TCHH(chan_nr);
+ *chan_type = GSMTAP_CHANNEL_TCH_H;
+ } else if (L1SAP_IS_CHAN_SDCCH4(chan_nr)) {
+ *ss = L1SAP_CHAN2SS_SDCCH4(chan_nr);
+ *chan_type = GSMTAP_CHANNEL_SDCCH;
+ } else if (L1SAP_IS_CHAN_SDCCH8(chan_nr)) {
+ *ss = L1SAP_CHAN2SS_SDCCH8(chan_nr);
+ *chan_type = GSMTAP_CHANNEL_SDCCH;
+ } else if (L1SAP_IS_CHAN_BCCH(chan_nr)) {
+ *chan_type = GSMTAP_CHANNEL_BCCH;
+ } else if (L1SAP_IS_CHAN_AGCH_PCH(chan_nr)) {
+ /* The sapi depends on DSP configuration, not
+ * on the actual SYSTEM INFORMATION 3. */
+ if (l1sap_fn2ccch_block(fn) >= num_agch)
+ *chan_type = GSMTAP_CHANNEL_PCH;
+ else
+ *chan_type = GSMTAP_CHANNEL_AGCH;
+ } else if (L1SAP_IS_CHAN_CBCH(chan_nr)) {
+ *chan_type = GSMTAP_CHANNEL_CBCH51;
+ } else if (L1SAP_IS_CHAN_PDCH(chan_nr)) {
+ *chan_type = GSMTAP_CHANNEL_PDTCH;
+ }
+ if (L1SAP_IS_LINK_SACCH(link_id))
+ *chan_type |= GSMTAP_CHANNEL_ACCH;
+
+ 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)
+{
+ struct msgb *msg = l1sap->oph.msg;
+
+ *data = msgb_l2(msg);
+ *len = msgb_l2len(msg);
+
+ 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;
+
+ 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)
+{
+ uint8_t chan_nr;
+
+ *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;
+ 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;
+
+ return 0;
+}
+
+/* Paging Request 1 with "no identity" content, i.e. empty/dummy paging */
+static const uint8_t paging_fill[GSM_MACBLOCK_LEN] = {
+ 0x15, 0x06, 0x21, 0x00, 0x01, 0xf0, 0x2b, 0x2b, 0x2b, 0x2b,
+ 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b,
+ 0x2b, 0x2b, 0x2b };
+
+static bool is_fill_frame(uint8_t chan_type, const uint8_t *data, unsigned int len)
+{
+ switch (chan_type) {
+ case GSMTAP_CHANNEL_AGCH:
+ if (!memcmp(data, fill_frame, GSM_MACBLOCK_LEN))
+ return true;
+ break;
+ case GSMTAP_CHANNEL_PCH:
+ if (!memcmp(data, paging_fill, GSM_MACBLOCK_LEN))
+ return true;
+ break;
+ /* don't use 'default' case here as the above only conditionally return true */
+ }
+ return false;
+}
+
+static int to_gsmtap(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap)
+{
+ uint8_t *data;
+ unsigned int len;
+ uint8_t chan_type = 0, tn = 0, ss = 0;
+ uint32_t fn;
+ uint16_t uplink = GSMTAP_ARFCN_F_UPLINK;
+ int rc;
+
+ if (!gsmtap)
+ return 0;
+
+ switch (OSMO_PRIM_HDR(&l1sap->oph)) {
+ case OSMO_PRIM(PRIM_PH_DATA, PRIM_OP_REQUEST):
+ uplink = 0;
+ /* fall through */
+ case OSMO_PRIM(PRIM_PH_DATA, PRIM_OP_INDICATION):
+ fn = l1sap->u.data.fn;
+ tn = L1SAP_CHAN2TS(l1sap->u.data.chan_nr);
+ if (ts_is_pdch(&trx->ts[tn]))
+ rc = gsmtap_pdch(l1sap, &chan_type, &ss, fn, &data,
+ &len);
+ else
+ rc = gsmtap_ph_data(l1sap, &chan_type, &ss, fn, &data,
+ &len, num_agch(trx, "GSMTAP"));
+ break;
+ case OSMO_PRIM(PRIM_PH_RACH, PRIM_OP_INDICATION):
+ rc = gsmtap_ph_rach(l1sap, &chan_type, &tn, &ss, &fn, &data,
+ &len);
+ break;
+ default:
+ rc = -ENOTSUP;
+ }
+
+ if (rc)
+ return rc;
+
+ if (len == 0)
+ return 0;
+ if ((chan_type & GSMTAP_CHANNEL_ACCH)) {
+ if (!gsmtap_sapi_acch)
+ return 0;
+ } else {
+ if (!((1 << (chan_type & 31)) & gsmtap_sapi_mask))
+ return 0;
+ }
+
+ /* don't log fill frames via GSMTAP; they serve no purpose other than
+ * to clog up your logs */
+ 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);
+
+ return 0;
+}
+
+/* Calculate the number of RACH slots that expire in a certain GSM frame
+ * See also 3GPP TS 05.02 Clause 7 Table 5 of 9 */
+static unsigned int calc_exprd_rach_frames(struct gsm_bts *bts, uint32_t fn)
+{
+ int rach_frames_expired = 0;
+ uint8_t ccch_conf;
+ struct gsm48_system_information_type_3 *si3;
+ unsigned int blockno;
+
+ si3 = GSM_BTS_SI(bts, SYSINFO_TYPE_3);
+ ccch_conf = si3->control_channel_desc.ccch_conf;
+
+ if (ccch_conf == RSL_BCCH_CCCH_CONF_1_C) {
+ /* It is possible to combine a CCCH with an SDCCH4, in this
+ * case the CCCH will have to share the available frames with
+ * the other channel, this results in a limited number of
+ * available rach slots */
+ blockno = fn % 51;
+ if (blockno == 4 || blockno == 5
+ || (blockno >= 15 && blockno <= 36) || blockno == 45
+ || blockno == 46)
+ rach_frames_expired = 1;
+ } else {
+ /* It is possible to have multiple CCCH channels on
+ * different physical channels (large cells), this
+ * also multiplies the available/expired RACH channels.
+ * See also TS 04.08, Chapter 10.5.2.11, table 10.29 */
+ if (ccch_conf == RSL_BCCH_CCCH_CONF_2_NC)
+ rach_frames_expired = 2;
+ else if (ccch_conf == RSL_BCCH_CCCH_CONF_3_NC)
+ rach_frames_expired = 3;
+ else if (ccch_conf == RSL_BCCH_CCCH_CONF_4_NC)
+ rach_frames_expired = 4;
+ else
+ rach_frames_expired = 1;
+ }
+
+ /* Each Frame has room for 4 RACH slots, since RACH
+ * slots are short enough to fit into a single radio
+ * burst, so we need to multiply the final result by 4 */
+ return rach_frames_expired * 4;
+}
+
+/* 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;
+
+ 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;
+ 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",
+ info_time_ind->fn, bts->gsm_time.fn, frames_expired);
+ }
+
+ /* Update our data structures with the current GSM time */
+ gsm_fn2gsmtime(&bts->gsm_time, info_time_ind->fn);
+
+ /* Update time on PCU interface */
+ pcu_tx_time_ind(info_time_ind->fn);
+
+ /* increment number of RACH slots that have passed by since the
+ * last time indication */
+ bts->load.rach.total +=
+ calc_exprd_rach_frames(bts, info_time_ind->fn) * frames_expired;
+
+ return 0;
+}
+
+static inline void set_ms_to_data(struct gsm_lchan *lchan, int16_t data, bool set_ms_to)
+{
+ if (!lchan)
+ return;
+
+ if (data + 63 > 255) { /* According to 3GPP TS 48.058 §9.3.37 Timing Offset field cannot exceed 255 */
+ LOGP(DL1P, LOGL_ERROR, "Attempting to set invalid Timing Offset value %d (MS TO = %u)!\n",
+ data, set_ms_to);
+ return;
+ }
+
+ if (set_ms_to) {
+ lchan->ms_t_offs = data + 63;
+ lchan->p_offs = -1;
+ } else {
+ lchan->p_offs = data + 63;
+ lchan->ms_t_offs = -1;
+ }
+}
+
+/* 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)
+{
+ 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;
+ }
+
+ 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);
+
+ /* 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;
+
+ 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;
+
+ /* we assume that symbol period is 1 bit: */
+ set_ms_to_data(lchan, info_meas_ind->ta_offs_256bits / 256, true);
+
+ lchan_meas_process_measurement(lchan, &ulm, info_meas_ind->fn);
+
+ return 0;
+}
+
+/* any L1 MPH_INFO indication prim recevied from bts model */
+static int l1sap_mph_info_ind(struct gsm_bts_trx *trx,
+ struct osmo_phsap_prim *l1sap, struct mph_info_param *info)
+{
+ int rc = 0;
+
+ switch (info->type) {
+ case PRIM_INFO_TIME:
+ if (trx != trx->bts->c0) {
+ LOGPFN(DL1P, LOGL_NOTICE, info->u.time_ind.fn,
+ "BTS model is sending us PRIM_INFO_TIME for TRX %u, please fix it\n",
+ trx->nr);
+ rc = -1;
+ } else
+ rc = l1sap_info_time_ind(trx->bts, l1sap,
+ &info->u.time_ind);
+ break;
+ case PRIM_INFO_MEAS:
+ rc = l1sap_info_meas_ind(trx, l1sap, &info->u.meas_ind);
+ break;
+ default:
+ LOGP(DL1P, LOGL_NOTICE, "unknown MPH_INFO ind type %d\n",
+ info->type);
+ break;
+ }
+
+ return rc;
+}
+
+/* activation confirm received from bts model */
+static int l1sap_info_act_cnf(struct gsm_bts_trx *trx,
+ struct osmo_phsap_prim *l1sap,
+ struct info_act_cnf_param *info_act_cnf)
+{
+ struct gsm_lchan *lchan;
+
+ LOGP(DL1C, LOGL_INFO, "activate confirm chan_nr=%s trx=%d\n",
+ rsl_chan_nr_str(info_act_cnf->chan_nr), trx->nr);
+
+ lchan = get_lchan_by_chan_nr(trx, info_act_cnf->chan_nr);
+
+ rsl_tx_chan_act_acknack(lchan, info_act_cnf->cause);
+
+ /* During PDCH ACT, this is where we know that the PCU is done
+ * activating a PDCH, and PDCH switchover is complete. See
+ * rsl_rx_dyn_pdch() */
+ if (lchan->ts->pchan == GSM_PCHAN_TCH_F_PDCH
+ && (lchan->ts->flags & TS_F_PDCH_ACT_PENDING))
+ ipacc_dyn_pdch_complete(lchan->ts,
+ info_act_cnf->cause? -EIO : 0);
+
+ return 0;
+}
+
+/* activation confirm received from bts model */
+static int l1sap_info_rel_cnf(struct gsm_bts_trx *trx,
+ struct osmo_phsap_prim *l1sap,
+ struct info_act_cnf_param *info_act_cnf)
+{
+ struct gsm_lchan *lchan;
+
+ LOGP(DL1C, LOGL_INFO, "deactivate confirm chan_nr=%s trx=%d\n",
+ rsl_chan_nr_str(info_act_cnf->chan_nr), trx->nr);
+
+ lchan = get_lchan_by_chan_nr(trx, info_act_cnf->chan_nr);
+
+ rsl_tx_rf_rel_ack(lchan);
+
+ /* During PDCH DEACT, this marks the deactivation of the PDTCH as
+ * requested by the PCU. Next up, we disconnect the TS completely and
+ * call back to cb_ts_disconnected(). See rsl_rx_dyn_pdch(). */
+ if (lchan->ts->pchan == GSM_PCHAN_TCH_F_PDCH
+ && (lchan->ts->flags & TS_F_PDCH_DEACT_PENDING))
+ bts_model_ts_disconnect(lchan->ts);
+
+ return 0;
+}
+
+/* any L1 MPH_INFO confirm prim recevied from bts model */
+static int l1sap_mph_info_cnf(struct gsm_bts_trx *trx,
+ struct osmo_phsap_prim *l1sap, struct mph_info_param *info)
+{
+ int rc = 0;
+
+ switch (info->type) {
+ case PRIM_INFO_ACTIVATE:
+ rc = l1sap_info_act_cnf(trx, l1sap, &info->u.act_cnf);
+ break;
+ case PRIM_INFO_DEACTIVATE:
+ rc = l1sap_info_rel_cnf(trx, l1sap, &info->u.act_cnf);
+ break;
+ default:
+ LOGP(DL1C, LOGL_NOTICE, "unknown MPH_INFO cnf type %d\n",
+ info->type);
+ break;
+ }
+
+ return rc;
+}
+
+/*! handling for PDTCH loopback mode, used for BER testing
+ * \param[in] lchan logical channel on which we operate
+ * \param[in] rts_ind PH-RTS.ind from PHY which we process
+ * \param[out] msg Message buffer to which we write data
+ *
+ * The function will fill \a msg, from which the caller can then
+ * subsequently build a PH-DATA.req */
+static int lchan_pdtch_ph_rts_ind_loop(struct gsm_lchan *lchan,
+ const struct ph_data_param *rts_ind,
+ struct msgb *msg, const struct gsm_time *tm)
+{
+ struct msgb *loop_msg;
+ uint8_t *p;
+
+ /* de-queue response message (loopback) */
+ loop_msg = msgb_dequeue(&lchan->dl_tch_queue);
+ if (!loop_msg) {
+ LOGPGT(DL1P, LOGL_NOTICE, tm, "%s: no looped PDTCH message, sending empty\n",
+ gsm_lchan_name(lchan));
+ /* 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));
+ /* 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));
+ msgb_free(loop_msg);
+ }
+ return 0;
+}
+
+/* Check if given CCCH frame number is for a 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) {
+ /* 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");
+}
+
+/* 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)
+{
+ struct msgb *msg = l1sap->oph.msg;
+ struct gsm_time g_time;
+ struct gsm_lchan *lchan;
+ uint8_t chan_nr, link_id;
+ uint8_t tn;
+ uint32_t fn;
+ uint8_t *p, *si;
+ struct lapdm_entity *le;
+ struct osmo_phsap_prim pp;
+ bool dtxd_facch = false;
+ int rc;
+ int is_ag_res;
+
+ chan_nr = rts_ind->chan_nr;
+ link_id = rts_ind->link_id;
+ fn = rts_ind->fn;
+ tn = L1SAP_CHAN2TS(chan_nr);
+
+ gsm_fn2gsmtime(&g_time, fn);
+
+ DEBUGPGT(DL1P, &g_time, "Rx PH-RTS.ind chan_nr=%s link_id=0x%02xd\n", rsl_chan_nr_str(chan_nr), link_id);
+
+ /* reuse PH-RTS.ind for PH-DATA.req */
+ if (!msg) {
+ LOGPGT(DL1P, LOGL_FATAL, &g_time, "RTS without msg to be reused. Please fix!\n");
+ abort();
+ }
+ msgb_trim(msg, sizeof(*l1sap));
+ osmo_prim_init(&l1sap->oph, SAP_GSM_PH, PRIM_PH_DATA, PRIM_OP_REQUEST,
+ msg);
+ msg->l2h = msg->l1h + sizeof(*l1sap);
+
+ if (ts_is_pdch(&trx->ts[tn])) {
+ lchan = get_active_lchan_by_chan_nr(trx, chan_nr);
+ if (lchan && lchan->loopback) {
+ if (!L1SAP_IS_PTCCH(rts_ind->fn))
+ lchan_pdtch_ph_rts_ind_loop(lchan, rts_ind, msg, &g_time);
+ /* continue below like for SACCH/FACCH/... */
+ } else {
+ /* forward RTS.ind to PCU */
+ if (L1SAP_IS_PTCCH(rts_ind->fn)) {
+ pcu_tx_rts_req(&trx->ts[tn], 1, fn, 1 /* ARFCN */,
+ L1SAP_FN2PTCCHBLOCK(fn));
+ } else {
+ pcu_tx_rts_req(&trx->ts[tn], 0, fn, 0 /* ARFCN */,
+ L1SAP_FN2MACBLOCK(fn));
+ }
+ /* return early, PCU takes care of rest */
+ return 0;
+ }
+ } else if (L1SAP_IS_CHAN_BCCH(chan_nr)) {
+ p = msgb_put(msg, GSM_MACBLOCK_LEN);
+ /* get them from bts->si_buf[] */
+ si = bts_sysinfo_get(trx->bts, &g_time);
+ if (si)
+ memcpy(p, si, GSM_MACBLOCK_LEN);
+ else
+ memcpy(p, fill_frame, GSM_MACBLOCK_LEN);
+ } else if (L1SAP_IS_CHAN_CBCH(chan_nr)) {
+ p = msgb_put(msg, GSM_MACBLOCK_LEN);
+ bts_cbch_get(trx->bts, p, &g_time);
+ } else if (!(chan_nr & 0x80)) { /* only TCH/F, TCH/H, SDCCH/4 and SDCCH/8 have C5 bit cleared */
+ 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;
+ }
+ 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;
+ le = &lchan->lapdm_ch.lapdm_acch;
+ } else {
+ if (lchan->ts->trx->bts->dtxd)
+ dtxd_facch = true;
+ le = &lchan->lapdm_ch.lapdm_dcch;
+ }
+ rc = lapdm_phsap_dequeue_prim(le, &pp);
+ if (rc < 0) {
+ if (L1SAP_IS_LINK_SACCH(link_id)) {
+ /* No SACCH data from LAPDM pending, send SACCH filling */
+ uint8_t *si = lchan_sacch_get(lchan);
+ if (si) {
+ /* The +2 is empty space where the DSP inserts the L1 hdr */
+ memcpy(p + 2, si, GSM_MACBLOCK_LEN - 2);
+ } else
+ memcpy(p + 2, fill_frame, GSM_MACBLOCK_LEN - 2);
+ } 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)) {
+ /*
+ * SDCCH or TCH in signalling mode without DTX.
+ *
+ * Send fill frame according to GSM 05.08, section 8.3: "On the SDCCH and on the
+ * half rate speech traffic channel in signalling only mode DTX is not allowed.
+ * In these cases and during signalling on the TCH when DTX is not used, the same
+ * L2 fill frame shall be transmitted in case there is nothing else to transmit."
+ */
+ p = msgb_put(msg, GSM_MACBLOCK_LEN);
+ memcpy(p, fill_frame, GSM_MACBLOCK_LEN);
+ } /* else the message remains empty, so TCH frames are sent */
+ } 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);
+ else {
+ p = msgb_put(msg, GSM_MACBLOCK_LEN);
+ memcpy(p, pp.oph.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);
+ if (dtxd_facch)
+ dtx_dispatch(lchan, E_FACCH);
+ }
+ msgb_free(pp.oph.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);
+ if (rc <= 0)
+ memcpy(p, fill_frame, GSM_MACBLOCK_LEN);
+ }
+
+ DEBUGPGT(DL1P, &g_time, "Tx PH-DATA.req chan_nr=%s link_id=0x%02x\n", rsl_chan_nr_str(chan_nr), link_id);
+
+ l1sap_down(trx, l1sap);
+
+ /* don't free, because we forwarded data */
+ 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)
+
+ if(payload_len < 2 || AMR_PADDING1(rtp_pl) || AMR_PADDING2(rtp_pl))
+ return false;
+
+ return true;
+}
+
+static bool rtppayload_is_valid(struct gsm_lchan *lchan, struct msgb *resp_msg)
+{
+ /* 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)) {
+ LOGP(DL1P, LOGL_NOTICE,
+ "%s RTP->L1: Dropping unexpected AMR encoding (bw-efficient?) %s\n",
+ gsm_lchan_name(lchan), osmo_hexdump(resp_msg->data, resp_msg->len));
+ return false;
+ }
+ return true;
+}
+
+/* TCH-RTS-IND prim recevied 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)
+{
+ struct msgb *resp_msg;
+ struct osmo_phsap_prim *resp_l1sap, empty_l1sap;
+ struct gsm_time g_time;
+ struct gsm_lchan *lchan;
+ uint8_t chan_nr, marker = 0;
+ uint32_t fn;
+ int rc;
+
+ 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;
+ }
+
+ if (!lchan->loopback && lchan->abis_ip.rtp_socket) {
+ osmo_rtp_socket_poll(lchan->abis_ip.rtp_socket);
+ /* FIXME: we _assume_ that we never miss TDMA
+ * frames and that we always get to this point
+ * for every to-be-transmitted voice frame. A
+ * better solution would be to compute
+ * rx_user_ts based on how many TDMA frames have
+ * elapsed since the last call */
+ 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);
+ 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;
+ resp_l1sap = &empty_l1sap;
+ } else {
+ /* Obtain RTP header Marker bit from control buffer */
+ marker = rtpmsg_marker_bit(resp_msg);
+
+ resp_msg->l2h = resp_msg->data;
+ msgb_push(resp_msg, sizeof(*resp_l1sap));
+ resp_msg->l1h = resp_msg->data;
+ resp_l1sap = msgb_l1sap_prim(resp_msg);
+ }
+
+ /* 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;
+ }
+
+ memset(resp_l1sap, 0, sizeof(*resp_l1sap));
+ osmo_prim_init(&resp_l1sap->oph, SAP_GSM_PH, PRIM_TCH, PRIM_OP_REQUEST,
+ resp_msg);
+ resp_l1sap->u.tch.chan_nr = chan_nr;
+ 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));
+
+ l1sap_down(trx, resp_l1sap);
+
+ return 0;
+}
+
+/* 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)
+{
+ struct gsm_bts *bts = lchan->ts->trx->bts;
+
+ /* Bypass radio link timeout if set to -1 */
+ if (bts->radio_link_timeout < 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));
+ 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)
+ rsl_tx_conn_fail(lchan, RSL_ERR_RADIO_LINK_FAIL);
+ return;
+ }
+
+ if (lchan->s < bts->radio_link_timeout) {
+ /* 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);
+ }
+}
+
+static inline int check_for_first_ciphrd(struct gsm_lchan *lchan,
+ uint8_t *data, int len)
+{
+ uint8_t n_r;
+
+ /* if this is the first valid message after enabling Rx
+ * decryption, we have to enable Tx encryption */
+ if (lchan->ciph_state != LCHAN_CIPH_RX_CONF)
+ return 0;
+
+ /* HACK: check if it's an I frame, in order to
+ * ignore some still buffered/queued UI frames received
+ * before decryption was enabled */
+ if (data[0] != 0x01)
+ return 0;
+
+ if ((data[1] & 0x01) != 0)
+ return 0;
+
+ n_r = data[1] >> 5;
+ if (lchan->ciph_ns != n_r)
+ return 0;
+
+ return 1;
+}
+
+/* public helper for the test */
+int bts_check_for_first_ciphrd(struct gsm_lchan *lchan,
+ uint8_t *data, int len)
+{
+ return check_for_first_ciphrd(lchan, data, len);
+}
+
+/* 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)
+{
+ struct msgb *msg = l1sap->oph.msg;
+ struct gsm_time g_time;
+ struct gsm_lchan *lchan;
+ struct lapdm_entity *le;
+ uint8_t *data = msg->l2h;
+ int len = msgb_l2len(msg);
+ 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;
+ tn = L1SAP_CHAN2TS(chan_nr);
+
+ gsm_fn2gsmtime(&g_time, fn);
+
+ DEBUGPGT(DL1P, &g_time, "Rx PH-DATA.ind chan_nr=%s link_id=0x%02x len=%d\n",
+ rsl_chan_nr_str(chan_nr), link_id, len);
+
+ if (ts_is_pdch(&trx->ts[tn])) {
+ 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)) {
+ /* 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);
+
+ /* 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;
+ 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);
+ }
+ return 0;
+ }
+
+ lchan = get_active_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));
+ return 0;
+ }
+
+ /* bad frame */
+ if (len == 0) {
+ if (L1SAP_IS_LINK_SACCH(link_id))
+ radio_link_timeout(lchan, 1);
+ return -EINVAL;
+ }
+
+ /* report first valid received frame to handover process */
+ 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 (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;
+ lapdm_phsap_up(&l1sap->oph, le);
+
+ /* don't free, because we forwarded data */
+ return 1;
+}
+
+/* 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)
+{
+ struct gsm_bts *bts = trx->bts;
+ struct msgb *msg = l1sap->oph.msg;
+ struct gsm_time g_time;
+ struct gsm_lchan *lchan;
+ uint8_t chan_nr;
+ uint32_t fn;
+
+ chan_nr = tch_ind->chan_nr;
+ fn = tch_ind->fn;
+
+ 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;
+ }
+
+ msgb_pull(msg, sizeof(*l1sap));
+
+ /* 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 / 10 >= bts->min_qual_norm) {
+ /* 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);
+ /* 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);
+ /* 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;
+ }
+
+ lchan->tch.last_fn = fn;
+ return 0;
+}
+
+#define RACH_MIN_TOA256 -2 * 256
+
+static bool rach_pass_filter(struct ph_rach_ind_param *rach_ind, struct gsm_bts *bts)
+{
+ 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",
+ rach_ind->ber10k, bts->max_ber10k_rach);
+ return false;
+ }
+
+ /**
+ * Make sure that ToA (Timing of Arrival) is acceptable.
+ * We allow early arrival up to 2 symbols, and delay
+ * 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",
+ toa256, RACH_MIN_TOA256, bts->max_ta * 256);
+ return false;
+ }
+
+ 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)
+{
+ /* Filter out noise / interference / ghosts */
+ if (!rach_pass_filter(rach_ind, trx->bts)) {
+ 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);
+
+ /* must return 0, so in case of msg at l1sap, it will be freed */
+ return 0;
+}
+
+/* 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)
+{
+ struct gsm_bts *bts = trx->bts;
+ struct lapdm_channel *lc;
+
+ DEBUGPFN(DL1P, rach_ind->fn, "Rx PH-RA.ind");
+
+ /* 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);
+ }
+
+ rate_ctr_inc2(trx->bts->ctrs, BTS_CTR_RACH_RCVD);
+
+ /* increment number of busy RACH slots, if required */
+ if (rach_ind->rssi >= bts->load.rach.busy_thresh)
+ bts->load.rach.busy++;
+
+ /* Filter out noise / interference / ghosts */
+ if (!rach_pass_filter(rach_ind, bts)) {
+ rate_ctr_inc2(trx->bts->ctrs, BTS_CTR_RACH_DROP);
+ return 0;
+ }
+
+ /* increment number of RACH slots with valid non-handover RACH burst */
+ bts->load.rach.access++;
+
+ lc = &trx->ts[0].lchan[CCCH_LCHAN].lapdm_ch;
+
+ /* According to 3GPP TS 48.058 § 9.3.17 Access Delay is expressed same way as TA (number of symbols) */
+ set_ms_to_data(get_lchan_by_chan_nr(trx, rach_ind->chan_nr),
+ rach_ind->acc_delay, false);
+
+ /* check for packet access */
+ if ((trx == bts->c0 && L1SAP_IS_PACKET_RACH(rach_ind->ra)) ||
+ (trx == bts->c0 && rach_ind->is_11bit)) {
+ rate_ctr_inc2(trx->bts->ctrs, BTS_CTR_RACH_PS);
+
+ 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);
+ return 0;
+ }
+
+ LOGPFN(DL1P, LOGL_INFO, rach_ind->fn, "RACH for RR access (toa=%d, ra=%d)\n",
+ rach_ind->acc_delay, rach_ind->ra);
+ rate_ctr_inc2(trx->bts->ctrs, BTS_CTR_RACH_CS);
+ lapdm_phsap_up(&l1sap->oph, &lc->lapdm_dcch);
+
+ return 0;
+}
+
+/* Process any L1 prim received from bts model.
+ *
+ * This function takes ownership of the msgb.
+ * If l1sap contains a msgb, it assumes that msgb->l2h was set by lower layer.
+ */
+int l1sap_up(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap)
+{
+ struct msgb *msg = l1sap->oph.msg;
+ int rc = 0;
+
+ switch (OSMO_PRIM_HDR(&l1sap->oph)) {
+ case OSMO_PRIM(PRIM_MPH_INFO, PRIM_OP_INDICATION):
+ rc = l1sap_mph_info_ind(trx, l1sap, &l1sap->u.info);
+ break;
+ case OSMO_PRIM(PRIM_MPH_INFO, PRIM_OP_CONFIRM):
+ rc = l1sap_mph_info_cnf(trx, l1sap, &l1sap->u.info);
+ break;
+ case OSMO_PRIM(PRIM_PH_RTS, PRIM_OP_INDICATION):
+ rc = l1sap_ph_rts_ind(trx, l1sap, &l1sap->u.data);
+ break;
+ case OSMO_PRIM(PRIM_TCH_RTS, PRIM_OP_INDICATION):
+ rc = l1sap_tch_rts_ind(trx, l1sap, &l1sap->u.tch);
+ break;
+ case OSMO_PRIM(PRIM_PH_DATA, PRIM_OP_INDICATION):
+ to_gsmtap(trx, l1sap);
+ rc = l1sap_ph_data_ind(trx, l1sap, &l1sap->u.data);
+ break;
+ case OSMO_PRIM(PRIM_TCH, PRIM_OP_INDICATION):
+ rc = l1sap_tch_ind(trx, l1sap, &l1sap->u.tch);
+ break;
+ case OSMO_PRIM(PRIM_PH_RACH, PRIM_OP_INDICATION):
+ to_gsmtap(trx, l1sap);
+ rc = l1sap_ph_rach_ind(trx, l1sap, &l1sap->u.rach_ind);
+ break;
+ default:
+ LOGP(DL1P, LOGL_NOTICE, "unknown prim %d op %d\n",
+ l1sap->oph.primitive, l1sap->oph.operation);
+ oml_fail_rep(OSMO_EVT_MAJ_UKWN_MSG, "unknown prim %d op %d",
+ l1sap->oph.primitive, l1sap->oph.operation);
+ break;
+ }
+
+ /* Special return value '1' means: do not free */
+ if (rc != 1)
+ msgb_free(msg);
+
+ return rc;
+}
+
+/* any L1 prim sent to bts model */
+static int l1sap_down(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap)
+{
+ if (OSMO_PRIM_HDR(&l1sap->oph) ==
+ OSMO_PRIM(PRIM_PH_DATA, PRIM_OP_REQUEST))
+ to_gsmtap(trx, l1sap);
+
+ return bts_model_l1sap_down(trx, l1sap);
+}
+
+/* pcu (socket interface) sends us a data request primitive */
+int l1sap_pdch_req(struct gsm_bts_trx_ts *ts, int is_ptcch, uint32_t fn,
+ uint16_t arfcn, uint8_t block_nr, uint8_t *data, uint8_t len)
+{
+ struct msgb *msg;
+ struct osmo_phsap_prim *l1sap;
+ struct gsm_time g_time;
+
+ gsm_fn2gsmtime(&g_time, fn);
+
+ DEBUGP(DL1P, "TX packet data %s is_ptcch=%d trx=%d ts=%d "
+ "block_nr=%d, arfcn=%d, len=%d\n", osmo_dump_gsmtime(&g_time),
+ is_ptcch, ts->trx->nr, ts->nr, block_nr, arfcn, len);
+
+ msg = l1sap_msgb_alloc(len);
+ l1sap = msgb_l1sap_prim(msg);
+ osmo_prim_init(&l1sap->oph, SAP_GSM_PH, PRIM_PH_DATA, PRIM_OP_REQUEST,
+ msg);
+ 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);
+
+ return l1sap_down(ts->trx, l1sap);
+}
+
+/*! \brief call-back function for incoming RTP */
+void l1sap_rtp_rx_cb(struct osmo_rtp_socket *rs, const uint8_t *rtp_pl,
+ unsigned int rtp_pl_len, uint16_t seq_number,
+ uint32_t timestamp, bool marker)
+{
+ struct gsm_lchan *lchan = rs->priv;
+ struct msgb *msg;
+ struct osmo_phsap_prim *l1sap;
+
+ /* if we're in loopback mode, we don't accept frames from the
+ * RTP socket anymore */
+ if (lchan->loopback)
+ return;
+
+ msg = l1sap_msgb_alloc(rtp_pl_len);
+ if (!msg)
+ return;
+ memcpy(msgb_put(msg, rtp_pl_len), rtp_pl, rtp_pl_len);
+ msgb_pull(msg, sizeof(*l1sap));
+
+ /* Store RTP header Marker bit in control buffer */
+ rtpmsg_marker_bit(msg) = marker;
+ /* Store RTP header Sequence Number in control buffer */
+ rtpmsg_seq(msg) = seq_number;
+ /* Store RTP header Timestamp in control buffer */
+ rtpmsg_ts(msg) = timestamp;
+
+ /* 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);
+}
+
+static int l1sap_chan_act_dact_modify(struct gsm_bts_trx *trx, uint8_t chan_nr,
+ enum osmo_mph_info_type type, uint8_t sacch_only)
+{
+ struct osmo_phsap_prim l1sap;
+
+ memset(&l1sap, 0, sizeof(l1sap));
+ osmo_prim_init(&l1sap.oph, SAP_GSM_PH, PRIM_MPH_INFO, PRIM_OP_REQUEST,
+ NULL);
+ l1sap.u.info.type = type;
+ l1sap.u.info.u.act_req.chan_nr = chan_nr;
+ l1sap.u.info.u.act_req.sacch_only = sacch_only;
+
+ return l1sap_down(trx, &l1sap);
+}
+
+int l1sap_chan_act(struct gsm_bts_trx *trx, uint8_t chan_nr, struct tlv_parsed *tp)
+{
+ struct gsm_lchan *lchan = get_lchan_by_chan_nr(trx, chan_nr);
+ struct gsm48_chan_desc *cd;
+ int rc;
+
+ LOGP(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)) {
+ LOGP(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;
+ }
+ }
+
+ lchan->sacch_deact = 0;
+ lchan->s = lchan->ts->trx->bts->radio_link_timeout;
+
+ rc = l1sap_chan_act_dact_modify(trx, chan_nr, PRIM_INFO_ACTIVATE, 0);
+ if (rc)
+ return -RSL_ERR_EQUIPMENT_FAIL;
+
+ /* 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);
+ lchan->tch.dtx.dl_amr_fsm = osmo_fsm_inst_alloc(&dtx_dl_amr_fsm,
+ tall_bts_ctx,
+ lchan,
+ LOGL_DEBUG,
+ name);
+ if (!lchan->tch.dtx.dl_amr_fsm) {
+ l1sap_chan_act_dact_modify(trx, chan_nr, PRIM_INFO_DEACTIVATE, 0);
+ return -RSL_ERR_EQUIPMENT_FAIL;
+ }
+ }
+ return 0;
+}
+
+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);
+ LOGP(DL1C, LOGL_INFO, "deactivating channel chan_nr=%s trx=%d\n",
+ rsl_chan_nr_str(chan_nr), trx->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;
+ }
+
+ return l1sap_chan_act_dact_modify(trx, chan_nr, PRIM_INFO_DEACTIVATE,
+ 0);
+}
+
+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);
+
+ LOGP(DL1C, LOGL_INFO, "deactivating sacch chan_nr=%s trx=%d\n",
+ rsl_chan_nr_str(chan_nr), trx->nr);
+
+ lchan->sacch_deact = 1;
+
+ return l1sap_chan_act_dact_modify(trx, chan_nr, PRIM_INFO_DEACTIVATE,
+ 1);
+}
+
+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);
+
+ return l1sap_chan_act_dact_modify(trx, chan_nr, PRIM_INFO_MODIFY, 0);
+}
diff --git a/src/common/lchan.c b/src/common/lchan.c
new file mode 100644
index 00000000..9e98166d
--- /dev/null
+++ b/src/common/lchan.c
@@ -0,0 +1,49 @@
+/* OsmoBTS lchan interface */
+
+/* (C) 2012 by Holger Hans Peter Freyther
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <osmocom/core/logging.h>
+#include <osmo-bts/logging.h>
+#include <osmo-bts/gsm_data.h>
+
+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));
+ lchan->state = state;
+}
+
+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_TCH_F_TCH_H_PDCH:
+ return ts->dyn.pchan_is == GSM_PCHAN_PDCH
+ && ts->dyn.pchan_want == ts->dyn.pchan_is;
+ default:
+ return false;
+ }
+}
diff --git a/src/common/load_indication.c b/src/common/load_indication.c
new file mode 100644
index 00000000..e91f6d49
--- /dev/null
+++ b/src/common/load_indication.c
@@ -0,0 +1,94 @@
+/* Support for generating RSL Load Indication */
+
+/* (C) 2011 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU 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 <osmocom/core/timer.h>
+#include <osmocom/core/msgb.h>
+
+#include <osmo-bts/gsm_data.h>
+#include <osmo-bts/rsl.h>
+#include <osmo-bts/paging.h>
+
+static void reset_load_counters(struct gsm_bts *bts)
+{
+ /* re-set the counters */
+ bts->load.ccch.pch_used = bts->load.ccch.pch_total = 0;
+}
+
+static void load_timer_cb(void *data)
+{
+ struct gsm_bts *bts = data;
+ unsigned int pch_percent, rach_percent;
+
+ /* compute percentages */
+ if (bts->load.ccch.pch_total == 0)
+ pch_percent = 0;
+ else
+ pch_percent = (bts->load.ccch.pch_used * 100) /
+ bts->load.ccch.pch_total;
+
+ if (pch_percent >= bts->load.ccch.load_ind_thresh) {
+ /* send RSL load indication message to BSC */
+ 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
+ * 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 */
+ rsl_tx_ccch_load_ind_pch(bts, 0xffff);
+ }
+
+ if (bts->load.rach.total == 0)
+ rach_percent = 0;
+ else
+ rach_percent = (bts->load.rach.busy * 100) /
+ bts->load.rach.total;
+
+ if (rach_percent >= bts->load.ccch.load_ind_thresh) {
+ /* send RSL load indication message to BSC */
+ rsl_tx_ccch_load_ind_rach(bts, bts->load.rach.total,
+ bts->load.rach.busy,
+ bts->load.rach.access);
+ }
+
+ reset_load_counters(bts);
+
+ /* re-schedule the timer */
+ osmo_timer_schedule(&bts->load.ccch.timer,
+ bts->load.ccch.load_ind_period, 0);
+}
+
+void load_timer_start(struct gsm_bts *bts)
+{
+ if (!bts->load.ccch.timer.data) {
+ bts->load.ccch.timer.data = bts;
+ bts->load.ccch.timer.cb = load_timer_cb;
+ reset_load_counters(bts);
+ }
+ osmo_timer_schedule(&bts->load.ccch.timer, bts->load.ccch.load_ind_period, 0);
+}
+
+void load_timer_stop(struct gsm_bts *bts)
+{
+ osmo_timer_del(&bts->load.ccch.timer);
+}
diff --git a/src/common/logging.c b/src/common/logging.c
new file mode 100644
index 00000000..3315a019
--- /dev/null
+++ b/src/common/logging.c
@@ -0,0 +1,150 @@
+/* libosmocore logging support */
+
+/* (C) 2011 by Andreas Eversberg <jolly@eversberg.eu>
+ * (C) 2011 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU 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/logging.h>
+#include <osmocom/core/application.h>
+#include <osmocom/core/utils.h>
+
+#include <osmo-bts/bts.h>
+#include <osmo-bts/logging.h>
+
+static struct log_info_cat bts_log_info_cat[] = {
+ [DRSL] = {
+ .name = "DRSL",
+ .description = "A-bis Radio Siganlling Link (RSL)",
+ .color = "\033[1;35m",
+ .enabled = 1, .loglevel = LOGL_INFO,
+ },
+ [DOML] = {
+ .name = "DOML",
+ .description = "A-bis Network Management / O&M (NM/OML)",
+ .color = "\033[1;36m",
+ .enabled = 1, .loglevel = LOGL_INFO,
+ },
+ [DRLL] = {
+ .name = "DRLL",
+ .description = "A-bis Radio Link Layer (RLL)",
+ .color = "\033[1;31m",
+ .enabled = 1, .loglevel = LOGL_NOTICE,
+ },
+ [DRR] = {
+ .name = "DRR",
+ .description = "Layer3 Radio Resource (RR)",
+ .color = "\033[1;34m",
+ .enabled = 1, .loglevel = LOGL_NOTICE,
+ },
+ [DMEAS] = {
+ .name = "DMEAS",
+ .description = "Radio Measurement Processing",
+ .enabled = 1, .loglevel = LOGL_NOTICE,
+ },
+ [DPAG] = {
+ .name = "DPAG",
+ .description = "Paging Subsystem",
+ .color = "\033[1;38m",
+ .enabled = 1, .loglevel = LOGL_INFO,
+ },
+ [DL1C] = {
+ .name = "DL1C",
+ .description = "Layer 1 Control (MPH)",
+ .loglevel = LOGL_INFO,
+ .enabled = 1,
+ },
+ [DL1P] = {
+ .name = "DL1P",
+ .description = "Layer 1 Primitives (PH)",
+ .loglevel = LOGL_INFO,
+ .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,
+ },
+ [DPCU] = {
+ .name = "DPCU",
+ .description = "PCU interface",
+ .loglevel = LOGL_NOTICE,
+ .enabled = 1,
+ },
+ [DHO] = {
+ .name = "DHO",
+ .description = "Handover",
+ .color = "\033[0;37m",
+ .enabled = 1, .loglevel = LOGL_NOTICE,
+ },
+ [DTRX] = {
+ .name = "DTRX",
+ .description = "TRX interface",
+ .color = "\033[1;33m",
+ .enabled = 1, .loglevel = LOGL_NOTICE,
+ },
+ [DLOOP] = {
+ .name = "DLOOP",
+ .description = "Control loops",
+ .color = "\033[0;34m",
+ .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",
+ .loglevel = LOGL_NOTICE,
+ .enabled = 1,
+ },
+};
+
+const struct log_info bts_log_info = {
+ .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
new file mode 100644
index 00000000..f90a4d4d
--- /dev/null
+++ b/src/common/main.c
@@ -0,0 +1,358 @@
+/* Main program for Osmocom BTS */
+
+/* (C) 2011-2016 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU 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 <getopt.h>
+#include <limits.h>
+#include <sys/signal.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sched.h>
+
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/application.h>
+#include <osmocom/vty/telnet_interface.h>
+#include <osmocom/vty/logging.h>
+#include <osmocom/core/gsmtap_util.h>
+#include <osmocom/core/gsmtap.h>
+
+#include <osmo-bts/gsm_data.h>
+#include <osmo-bts/phy_link.h>
+#include <osmo-bts/logging.h>
+#include <osmo-bts/abis.h>
+#include <osmo-bts/bts.h>
+#include <osmo-bts/vty.h>
+#include <osmo-bts/l1sap.h>
+#include <osmo-bts/bts_model.h>
+#include <osmo-bts/pcu_if.h>
+#include <osmo-bts/control_if.h>
+#include <osmocom/ctrl/control_if.h>
+#include <osmocom/ctrl/ports.h>
+#include <osmocom/ctrl/control_vty.h>
+#include <osmo-bts/oml.h>
+
+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 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"
+ );
+ bts_model_print_help();
+}
+
+/* FIXME: finally get some option parsing code into libosmocore */
+static void handle_options(int argc, char **argv)
+{
+ char *argv_out[argc];
+ int argc_out = 0;
+
+ argv_out[argc_out++] = argv[0];
+
+ /* disable generation of error messages on encountering unknown
+ * options */
+ opterr = 0;
+
+ while (1) {
+ int option_idx = 0, c;
+ static const struct option long_options[] = {
+ /* FIXME: all those are generic Osmocom app options */
+ { "help", 0, 0, 'h' },
+ { "debug", 1, 0, 'd' },
+ { "daemonize", 0, 0, 'D' },
+ { "config-file", 1, 0, 'c' },
+ { "disable-color", 0, 0, 's' },
+ { "timestamp", 0, 0, 'T' },
+ { "version", 0, 0, 'V' },
+ { "log-level", 1, 0, 'e' },
+ /* FIXME: generic BTS app options */
+ { "gsmtap-ip", 1, 0, 'i' },
+ { "trx-num", 1, 0, 't' },
+ { "realtime", 1, 0, 'r' },
+ { 0, 0, 0, 0 }
+ };
+
+ c = getopt_long(argc, argv, "-hc:d:Dc:sTVe:i:t:r:",
+ long_options, &option_idx);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'h':
+ print_help();
+ exit(0);
+ break;
+ case 's':
+ log_set_use_color(osmo_stderr_target, 0);
+ break;
+ case 'd':
+ log_parse_category_mask(osmo_stderr_target, optarg);
+ break;
+ case 'D':
+ daemonize = 1;
+ break;
+ case 'c':
+ config_file = optarg;
+ break;
+ case 'T':
+ log_set_print_timestamp(osmo_stderr_target, 1);
+ break;
+ case 'V':
+ print_version(1);
+ exit(0);
+ break;
+ case 'e':
+ log_set_log_level(osmo_stderr_target, atoi(optarg));
+ break;
+ case 'r':
+ rt_prio = atoi(optarg);
+ break;
+ case 'i':
+ gsmtap_ip = optarg;
+ break;
+ case 't':
+ fprintf(stderr, "Parameter -t is deprecated and does nothing, "
+ "TRX num is calculated from VTY\n");
+ break;
+ case '?':
+ case 1:
+ /* prepare argv[] for bts_model */
+ argv_out[argc_out++] = argv[optind-1];
+ break;
+ default:
+ break;
+ }
+ }
+
+ /* re-set opt-ind for new parsig round */
+ optind = 1;
+ /* enable error-checking for the following getopt call */
+ opterr = 1;
+ if (bts_model_handle_options(argc_out, argv_out)) {
+ print_help();
+ exit(1);
+ }
+}
+
+static struct gsm_bts *bts;
+
+static void signal_handler(int signal)
+{
+ fprintf(stderr, "signal %u received\n", signal);
+
+ switch (signal) {
+ case SIGINT:
+ case SIGTERM:
+ if (!quit) {
+ oml_fail_rep(OSMO_EVT_CRIT_PROC_STOP,
+ "BTS: SIGINT received -> shutdown");
+ bts_shutdown(bts, "SIGINT");
+ }
+ quit++;
+ break;
+ case SIGABRT:
+ case SIGUSR1:
+ case SIGUSR2:
+ oml_fail_rep(OSMO_EVT_CRIT_PROC_STOP,
+ "BTS: signal %d (%s) received", signal,
+ strsignal(signal));
+ talloc_report_full(tall_bts_ctx, stderr);
+ break;
+ default:
+ break;
+ }
+}
+
+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();
+
+ tall_bts_ctx = talloc_named_const(NULL, 1, "OsmoBTS context");
+ msgb_talloc_ctx_init(tall_bts_ctx, 100*1024);
+ bts_vty_info.tall_ctx = tall_bts_ctx;
+
+ osmo_init_logging2(tall_bts_ctx, &bts_log_info);
+ vty_init(&bts_vty_info);
+ ctrl_vty_init(tall_bts_ctx);
+ rate_ctr_init(tall_bts_ctx);
+
+ handle_options(argc, argv);
+
+ bts = gsm_bts_alloc(tall_bts_ctx, 0);
+ if (!bts) {
+ fprintf(stderr, "Failed to create BTS structure\n");
+ exit(1);
+ }
+
+ e1inp_vty_init();
+ bts_vty_init(bts, &bts_log_info);
+
+ /* enable realtime priority for us */
+ if (rt_prio != -1) {
+ struct sched_param param;
+
+ memset(&param, 0, sizeof(param));
+ param.sched_priority = rt_prio;
+ rc = sched_setscheduler(getpid(), SCHED_RR, &param);
+ if (rc != 0) {
+ fprintf(stderr, "Setting SCHED_RR priority(%d) failed: %s\n",
+ param.sched_priority, strerror(errno));
+ exit(1);
+ }
+ }
+
+ 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) {
+ fprintf(stderr, "unable to open bts\n");
+ exit(1);
+ }
+
+ abis_init(bts);
+
+ rc = vty_read_config_file(config_file, NULL);
+ if (rc < 0) {
+ fprintf(stderr, "Failed to parse the config file: '%s'\n",
+ config_file);
+ exit(1);
+ }
+
+ if (!phy_link_by_num(0)) {
+ fprintf(stderr, "You need to configure at least phy0\n");
+ exit(1);
+ }
+
+ llist_for_each_entry(trx, &bts->trx_list, list) {
+ if (!trx->role_bts.l1h) {
+ fprintf(stderr, "TRX %u has no associated PHY instance\n",
+ trx->nr);
+ exit(1);
+ }
+ }
+
+ write_pid_file("osmo-bts");
+
+ bts_controlif_setup(bts, ctrl_vty_get_bind_addr(), OSMO_CTRL_PORT_BTS);
+
+ rc = telnet_init_dynif(tall_bts_ctx, NULL, vty_get_bind_addr(),
+ g_vty_port_num);
+ if (rc < 0) {
+ fprintf(stderr, "Error initializing telnet\n");
+ exit(1);
+ }
+
+ if (pcu_sock_init(bts->pcu.sock_path)) {
+ fprintf(stderr, "PCU L1 socket failed\n");
+ exit(1);
+ }
+
+ signal(SIGINT, &signal_handler);
+ signal(SIGTERM, &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");
+ exit(1);
+ }
+
+ line = abis_open(bts, bts->bsc_oml_host, "sysmoBTS");
+ if (!line) {
+ fprintf(stderr, "unable to connect to BSC\n");
+ exit(2);
+ }
+
+ rc = phy_links_open();
+ if (rc < 0) {
+ fprintf(stderr, "unable to open PHY link(s)\n");
+ exit(2);
+ }
+
+ if (daemonize) {
+ rc = osmo_daemonize();
+ if (rc < 0) {
+ perror("Error during daemonize");
+ exit(1);
+ }
+ }
+
+ while (quit < 2) {
+ log_reset_context();
+ osmo_select_main(0);
+ }
+
+ return EXIT_SUCCESS;
+}
diff --git a/src/common/measurement.c b/src/common/measurement.c
new file mode 100644
index 00000000..c2001dae
--- /dev/null
+++ b/src/common/measurement.c
@@ -0,0 +1,723 @@
+
+#include <stdint.h>
+#include <errno.h>
+
+#include <osmocom/gsm/gsm_utils.h>
+#include <osmocom/core/utils.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>
+
+/* 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 };
+
+/* 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
+ * conditions. We set up a dummy measurement result here that reflects the
+ * worst possible result. In our* calculation we will use this dummy to replace
+ * 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) {
+ .ber10k = MEASUREMENT_DUMMY_BER,
+ .ta_offs_256bits = 0,
+ .c_i = 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)
+{
+ 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 */
+
+ switch (lchan->type) {
+ case GSM_LCHAN_TCH_F:
+ switch (lchan->tch_mode) {
+ case GSM48_CMODE_SIGN:
+ case GSM48_CMODE_SPEECH_V1:
+ 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)
+ return true;
+ break;
+ default:
+ LOGPFN(DMEAS, LOGL_ERROR, fn, "%s: Unsupported lchan->tch_mode %u\n",
+ gsm_lchan_name(lchan), 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)
+ return true;
+ break;
+ case GSM48_CMODE_SIGN:
+ /* No DTX allowed; SUB=FULL, therefore measurements at all frame numbers are
+ * SUB */
+ return true;
+ default:
+ LOGPFN(DMEAS, LOGL_ERROR, fn, "%s: Unsupported lchan->tch_mode %u\n",
+ gsm_lchan_name(lchan), lchan->tch_mode);
+ break;
+ }
+ break;
+ case GSM_LCHAN_SDCCH:
+ /* No DTX allowed; SUB=FULL, therefore measurements at all frame numbers are SUB */
+ return true;
+ default:
+ break;
+ }
+ return false;
+}
+
+/* Measurement reporting period and mapping of SACCH message block for TCHF
+ * and TCHH chan As per in 3GPP TS 45.008, section 8.4.1.
+ *
+ * Timeslot number (TN) TDMA frame number (FN) modulo 104
+ * Half rate, Half rate, Reporting SACCH
+ * Full Rate subch.0 subch.1 period Message block
+ * 0 0 and 1 0 to 103 12, 38, 64, 90
+ * 1 0 and 1 13 to 12 25, 51, 77, 103
+ * 2 2 and 3 26 to 25 38, 64, 90, 12
+ * 3 2 and 3 39 to 38 51, 77, 103, 25
+ * 4 4 and 5 52 to 51 64, 90, 12, 38
+ * 5 4 and 5 65 to 64 77, 103, 25, 51
+ * 6 6 and 7 78 to 77 90, 12, 38, 64
+ * 7 6 and 7 91 to 90 103, 25, 51, 77
+ *
+ * Note: The array index of the following three lookup tables refes to a
+ * timeslot number. */
+
+static const uint8_t tchf_meas_rep_fn104_by_ts[] = {
+ [0] = 90,
+ [1] = 103,
+ [2] = 12,
+ [3] = 25,
+ [4] = 38,
+ [5] = 51,
+ [6] = 64,
+ [7] = 77,
+};
+static const uint8_t tchh0_meas_rep_fn104_by_ts[] = {
+ [0] = 90,
+ [1] = 90,
+ [2] = 12,
+ [3] = 12,
+ [4] = 38,
+ [5] = 38,
+ [6] = 64,
+ [7] = 64,
+};
+static const uint8_t tchh1_meas_rep_fn104_by_ts[] = {
+ [0] = 103,
+ [1] = 103,
+ [2] = 25,
+ [3] = 25,
+ [4] = 51,
+ [5] = 51,
+ [6] = 77,
+ [7] = 77,
+};
+
+/* Measurement reporting period for SDCCH8 and SDCCH4 chan
+ * As per in 3GPP TS 45.008, section 8.4.2.
+ *
+ * Logical Chan TDMA frame number
+ * (FN) modulo 102
+ *
+ * SDCCH/8 12 to 11
+ * SDCCH/4 37 to 36
+ *
+ *
+ * Note: The array index of the following three lookup tables refes to a
+ * subslot number. */
+
+/* FN of the first burst whose block completes before reaching fn%102=11 */
+static const uint8_t sdcch8_meas_rep_fn102_by_ss[] = {
+ [0] = 66, /* 15(SDCCH), 47(SACCH), 66(SDCCH) */
+ [1] = 70, /* 19(SDCCH), 51(SACCH), 70(SDCCH) */
+ [2] = 74, /* 23(SDCCH), 55(SACCH), 74(SDCCH) */
+ [3] = 78, /* 27(SDCCH), 59(SACCH), 78(SDCCH) */
+ [4] = 98, /* 31(SDCCH), 98(SACCH), 82(SDCCH) */
+ [5] = 0, /* 35(SDCCH), 0(SACCH), 86(SDCCH) */
+ [6] = 4, /* 39(SDCCH), 4(SACCH), 90(SDCCH) */
+ [7] = 8, /* 43(SDCCH), 8(SACCH), 94(SDCCH) */
+};
+
+/* FN of the first burst whose block completes before reaching fn%102=37 */
+static const uint8_t sdcch4_meas_rep_fn102_by_ss[] = {
+ [0] = 88, /* 37(SDCCH), 57(SACCH), 88(SDCCH) */
+ [1] = 92, /* 41(SDCCH), 61(SACCH), 92(SDCCH) */
+ [2] = 6, /* 6(SACCH), 47(SDCCH), 98(SDCCH) */
+ [3] = 10 /* 10(SACCH), 0(SDCCH), 51(SDCCH) */
+};
+
+/* Note: The reporting of the measurement results is done via the SACCH channel.
+ * The measurement interval is not aligned with the interval in which the
+ * SACCH is transmitted. When we receive the measurement indication with the
+ * SACCH block, the corresponding measurement interval will already have ended
+ * and we will get the results late, but on spot with the beginning of the
+ * next measurement interval.
+ *
+ * For example: We get a measurement indication on FN%104=38 in TS=2. Then we
+ * will have to look at 3GPP TS 45.008, section 8.4.1 (or 3GPP TS 05.02 Clause 7
+ * Table 1 of 9) what value we need to feed into the lookup tables in order to
+ * detect the measurement period ending. In this example the "real" ending
+ * was on FN%104=12. This is the value we have to look for in
+ * tchf_meas_rep_fn104_by_ts to know that a measurement period has just ended. */
+
+/* See also 3GPP TS 05.02 Clause 7 Table 1 of 9:
+ * Mapping of logical channels onto physical channels (see subclauses 6.3, 6.4, 6.5) */
+static uint8_t translate_tch_meas_rep_fn104(uint8_t fn_mod)
+{
+ switch (fn_mod) {
+ case 25:
+ return 103;
+ case 38:
+ return 12;
+ case 51:
+ return 25;
+ case 64:
+ return 38;
+ case 77:
+ return 51;
+ case 90:
+ return 64;
+ case 103:
+ return 77;
+ case 12:
+ return 90;
+ }
+
+ /* Invalid / not of interest */
+ return 0;
+}
+
+/* determine if a measurement period ends at the given frame number
+ * (this function is only used internally, it is public to call it from
+ * unit-tests) */
+int is_meas_complete(struct gsm_lchan *lchan, uint32_t fn)
+{
+ unsigned int fn_mod = -1;
+ const uint8_t *tbl;
+ 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);
+ if (tchf_meas_rep_fn104_by_ts[lchan->ts->nr] == fn_mod)
+ rc = 1;
+ break;
+ case GSM_PCHAN_TCH_H:
+ fn_mod = translate_tch_meas_rep_fn104(fn % 104);
+ if (lchan->nr == 0)
+ tbl = tchh0_meas_rep_fn104_by_ts;
+ else
+ tbl = tchh1_meas_rep_fn104_by_ts;
+ if (tbl[lchan->ts->nr] == fn_mod)
+ rc = 1;
+ break;
+ case GSM_PCHAN_SDCCH8_SACCH8C:
+ case GSM_PCHAN_SDCCH8_SACCH8C_CBCH:
+ fn_mod = fn % 102;
+ if (sdcch8_meas_rep_fn102_by_ss[lchan->nr] == fn_mod)
+ rc = 1;
+ break;
+ case GSM_PCHAN_CCCH_SDCCH4:
+ case GSM_PCHAN_CCCH_SDCCH4_CBCH:
+ fn_mod = fn % 102;
+ if (sdcch4_meas_rep_fn102_by_ss[lchan->nr] == fn_mod)
+ rc = 1;
+ break;
+ default:
+ rc = 0;
+ break;
+ }
+
+ 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));
+ }
+
+ return rc;
+}
+
+/* determine the measurement interval modulus by a given lchan */
+static uint8_t modulus_by_lchan(struct gsm_lchan *lchan)
+{
+ enum gsm_phys_chan_config pchan = ts_pchan(lchan->ts);
+
+ switch (pchan) {
+ 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)
+{
+ uint32_t fn_mod = fn % modulus_by_lchan(lchan);
+
+ 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);
+ }
+
+ 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);
+ return -ENOSPC;
+ }
+
+ /* 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 */
+ if (!ulm->is_sub)
+ ulm->is_sub = ts45008_83_is_sub(lchan, fn, false);
+
+ 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));
+
+ lchan->meas.last_fn = fn;
+
+ return 0;
+}
+
+/* input: BER in steps of .01%, i.e. percent/100 */
+static uint8_t ber10k_to_rxqual(uint32_t ber10k)
+{
+ /* Eight levels of Rx quality are defined and are mapped to the
+ * equivalent BER before channel decoding, as per in 3GPP TS 45.008,
+ * secton 8.2.4.
+ *
+ * RxQual: BER Range:
+ * RXQUAL_0 BER < 0,2 % Assumed value = 0,14 %
+ * RXQUAL_1 0,2 % < BER < 0,4 % Assumed value = 0,28 %
+ * RXQUAL_2 0,4 % < BER < 0,8 % Assumed value = 0,57 %
+ * RXQUAL_3 0,8 % < BER < 1,6 % Assumed value = 1,13 %
+ * RXQUAL_4 1,6 % < BER < 3,2 % Assumed value = 2,26 %
+ * RXQUAL_5 3,2 % < BER < 6,4 % Assumed value = 4,53 %
+ * RXQUAL_6 6,4 % < BER < 12,8 % Assumed value = 9,05 %
+ * RXQUAL_7 12,8 % < BER Assumed value = 18,10 % */
+
+ if (ber10k < 20)
+ return 0;
+ if (ber10k < 40)
+ return 1;
+ if (ber10k < 80)
+ return 2;
+ if (ber10k < 160)
+ return 3;
+ if (ber10k < 320)
+ return 4;
+ if (ber10k < 640)
+ return 5;
+ if (ber10k < 1280)
+ return 6;
+ return 7;
+}
+
+/* Get the number of measurements that we expect for a specific lchan.
+ * (This is a static number that is defined by the specific slot layout of
+ * the channel) */
+static unsigned int lchan_meas_num_expected(const struct gsm_lchan *lchan)
+{
+ enum gsm_phys_chan_config pchan = ts_pchan(lchan->ts);
+
+ switch (pchan) {
+ case GSM_PCHAN_TCH_F:
+ /* 24 for TCH + 1 for SACCH */
+ return 25;
+ case GSM_PCHAN_TCH_H:
+ /* 24 half-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 */
+ return 3;
+ case GSM_PCHAN_CCCH_SDCCH4:
+ case GSM_PCHAN_CCCH_SDCCH4_CBCH:
+ /* 2 for SDCCH + 1 for SACCH */
+ return 3;
+ default:
+ return lchan->meas.num_ul_meas;
+ }
+}
+
+/* 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)
+{
+ 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;
+ case GSM_PCHAN_TCH_H:
+ /* 1 block SDCCH, 4 half-blocks TCH */
+ return 5;
+ case GSM_PCHAN_SDCCH8_SACCH8C:
+ case GSM_PCHAN_SDCCH8_SACCH8C_CBCH:
+ /* no DTX here, all blocks must be present! */
+ return 3;
+ case GSM_PCHAN_CCCH_SDCCH4:
+ case GSM_PCHAN_CCCH_SDCCH4_CBCH:
+ /* no DTX here, all blocks must be present! */
+ return 3;
+ default:
+ return 0;
+ }
+}
+
+/* if we clip the TOA value to 12 bits, i.e. toa256=3200,
+ * -> the maximum deviation can be 2*3200 = 6400
+ * -> the maximum squared deviation can be 6400^2 = 40960000
+ * -> the maximum sum of squared deviations can be 104*40960000 = 4259840000
+ * and hence fit into uint32_t
+ * -> once the value is divided by 104, it's again below 40960000
+ * leaving 6 MSBs of freedom, i.e. we could extend by 64, resulting in 2621440000
+ * -> as a result, the standard deviation could be communicated with up to six bits
+ * of fractional fixed-point number.
+ */
+
+/* compute Osmocom extended measurements for the given lchan */
+static void lchan_meas_compute_extended(struct gsm_lchan *lchan)
+{
+ unsigned int num_ul_meas;
+ unsigned int num_ul_meas_excess = 0;
+ unsigned int num_ul_meas_expect;
+
+ /* we assume that lchan_meas_check_compute() has already computed the mean value
+ * and we can compute the min/max/variance/stddev from this */
+ int i;
+
+ /* 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;
+
+ /* In case we do not have any measurement values collected there is no
+ * computation possible. We just skip the whole computation here, the
+ * lchan->meas.flags will not get the LC_UL_M_F_OSMO_EXT_VALID flag set
+ * so no extended measurement results will be reported back via RSL.
+ * this is ok, since we have nothing to report anyway and apart of that
+ * we also just lost the signal (otherwise we would have at least some
+ * measurements). */
+ if (!lchan->meas.num_ul_meas)
+ return;
+
+ /* initialize min/max values with their counterpart */
+ lchan->meas.ext.toa256_min = INT16_MAX;
+ lchan->meas.ext.toa256_max = INT16_MIN;
+
+ /* Determine the number of measurement values we need to take into the
+ * computation. In this case we only compute over the measurements we
+ * have indeed received. Since this computation is about timing
+ * information it does not make sense to approach missing measurement
+ * samples the TOA with 0. This would bend the average towards 0. What
+ * counts is the average TOA of the properly received blocks so that
+ * the TA logic can make a proper decision. */
+ num_ul_meas_expect = lchan_meas_num_expected(lchan);
+ if (lchan->meas.num_ul_meas > num_ul_meas_expect) {
+ num_ul_meas = num_ul_meas_expect;
+ num_ul_meas_excess = lchan->meas.num_ul_meas - num_ul_meas_expect;
+ }
+ else
+ num_ul_meas = lchan->meas.num_ul_meas;
+
+ /* all computations are done on the relative arrival time of the burst, relative to the
+ * beginning of its slot. This is of course excluding the TA value that the MS has already
+ * compensated/pre-empted its transmission */
+
+ /* step 1: compute the sum of the squared difference of each value to mean */
+ for (i = 0; i < num_ul_meas; i++) {
+ const struct bts_ul_meas *m;
+
+ OSMO_ASSERT(i < lchan->meas.num_ul_meas);
+ m = &lchan->meas.uplink[i+num_ul_meas_excess];
+
+ int32_t diff = (int32_t)m->ta_offs_256bits - (int32_t)lchan->meas.ms_toa256;
+ /* diff can now be any value of +65535 to -65535, so we can safely square it,
+ * but only in unsigned math. As squaring looses the sign, we can simply drop
+ * it before squaring, too. */
+ uint32_t diff_abs = labs(diff);
+ uint32_t diff_squared = diff_abs * diff_abs;
+ sq_diff_sum += diff_squared;
+
+ /* also use this loop iteration to compute min/max values */
+ if (m->ta_offs_256bits > lchan->meas.ext.toa256_max)
+ lchan->meas.ext.toa256_max = m->ta_offs_256bits;
+ if (m->ta_offs_256bits < lchan->meas.ext.toa256_min)
+ lchan->meas.ext.toa256_min = m->ta_offs_256bits;
+ }
+ /* step 2: compute the variance (mean of sum of squared differences) */
+ sq_diff_sum = sq_diff_sum / num_ul_meas;
+ /* as the individual summed values can each not exceed 2^32, and we're
+ * dividing by the number of summands, the resulting value can also not exceed 2^32 */
+ OSMO_ASSERT(sq_diff_sum <= UINT32_MAX);
+ /* step 3: compute the standard deviation from the variance */
+ lchan->meas.ext.toa256_std_dev = osmo_isqrt32(sq_diff_sum);
+ lchan->meas.flags |= LC_UL_M_F_OSMO_EXT_VALID;
+}
+
+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;
+ uint32_t ber_sub_sum = 0;
+ uint32_t irssi_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;
+ unsigned int num_ul_meas;
+ unsigned int num_ul_meas_actual = 0;
+ unsigned int num_ul_meas_subst = 0;
+ unsigned int num_ul_meas_expect;
+ unsigned int num_ul_meas_excess = 0;
+ int i;
+
+ /* if measurement period is not complete, abort */
+ 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)));
+
+ /* 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;
+ }
+
+ 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);
+ if (num_ul_meas_excess)
+ LOGP(DMEAS, LOGL_DEBUG, "%s received %u excess UL measurements\n", gsm_lchan_name(lchan),
+ num_ul_meas_excess);
+
+ /* Measurement computation step 1: add up */
+ for (i = 0; i < num_ul_meas; i++) {
+ const struct bts_ul_meas *m;
+ bool is_sub = false;
+
+ /* Note: We will always compute over a full measurement,
+ * interval even when not enough measurement samples are in
+ * the buffer. As soon as we run out of measurement values
+ * we continue the calculation using dummy values. This works
+ * well for the BER, since there we can safely assume 100%
+ * since a missing measurement means that the data (block)
+ * is lost as well (some phys do not give us measurement
+ * reports for lost blocks or blocks that are spaced out for
+ * DTX). However, for RSSI and TA this does not work since
+ * there we would distort the calculation if we would replace
+ * them with a made up number. This means for those values we
+ * only compute over the data we have actually received. */
+
+ if (i < lchan->meas.num_ul_meas) {
+ m = &lchan->meas.uplink[i + num_ul_meas_excess];
+ if (m->is_sub) {
+ irssi_sub_sum += m->inv_rssi;
+ num_meas_sub_actual++;
+ is_sub = true;
+ }
+ irssi_full_sum += m->inv_rssi;
+ ta256b_sum += m->ta_offs_256bits;
+
+ 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;
+ }
+
+ num_ul_meas_subst++;
+ }
+
+ ber_full_sum += m->ber10k;
+ if (is_sub) {
+ num_meas_sub++;
+ ber_sub_sum += m->ber10k;
+ }
+ }
+
+ 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. */
+ }
+
+ /* Measurement computation step 2: divide */
+ ber_full_sum = ber_full_sum / num_ul_meas;
+
+ if (!irssi_full_sum)
+ ber_full_sum = MEASUREMENT_DUMMY_IRSSI;
+ else
+ irssi_full_sum = irssi_full_sum / num_ul_meas_actual;
+
+ if (!num_ul_meas_actual)
+ ta256b_sum = lchan->meas.ms_toa256;
+ else
+ ta256b_sum = ta256b_sum / 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)
+ irssi_sub_sum = MEASUREMENT_DUMMY_IRSSI;
+ else
+ irssi_sub_sum = irssi_sub_sum / 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);
+
+ /* store results */
+ mru = &lchan->meas.ul_res;
+ mru->full.rx_lev = dbm2rxlev((int)irssi_full_sum * -1);
+ mru->sub.rx_lev = dbm2rxlev((int)irssi_sub_sum * -1);
+ 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;
+
+ 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);
+
+ lchan->meas.flags |= LC_UL_M_F_RES_VALID;
+
+ lchan_meas_compute_extended(lchan);
+
+ lchan->meas.num_ul_meas = 0;
+
+ /* return 1 to indicte that the computation has been done and the next
+ * interval begins. */
+ return 1;
+}
+
+/* Process a single uplink measurement sample. This function is called from
+ * 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)
+{
+ lchan_new_ul_meas(lchan, ulm, fn);
+ return lchan_meas_check_compute(lchan, fn);
+}
+
+/* Reset all measurement related struct members to their initial values. This
+ * function will be called every time an lchan is activated to ensure the
+ * measurement process starts with a defined state. */
+void lchan_meas_reset(struct gsm_lchan *lchan)
+{
+ memset(&lchan->meas, 0, sizeof(lchan->meas));
+ lchan->meas.last_fn = LCHAN_FN_DUMMY;
+}
diff --git a/src/common/msg_utils.c b/src/common/msg_utils.c
new file mode 100644
index 00000000..f936c983
--- /dev/null
+++ b/src/common/msg_utils.c
@@ -0,0 +1,603 @@
+/* (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 <osmo-bts/dtx_dl_amr_fsm.h>
+#include <osmo-bts/msg_utils.h>
+#include <osmo-bts/logging.h>
+#include <osmo-bts/oml.h>
+#include <osmo-bts/amr.h>
+#include <osmo-bts/rsl.h>
+
+#include <osmocom/gsm/protocol/ipaccess.h>
+#include <osmocom/gsm/protocol/gsm_12_21.h>
+#include <osmocom/gsm/abis_nm.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/fsm.h>
+#include <osmocom/trau/osmo_ortp.h>
+
+#include <arpa/inet.h>
+#include <errno.h>
+
+#define STI_BIT_MASK 16
+
+static int check_fom(struct abis_om_hdr *omh, size_t len)
+{
+ if (omh->length != len) {
+ LOGP(DL1C, LOGL_ERROR, "Incorrect OM hdr length value %d %zu\n",
+ omh->length, len);
+ return -1;
+ }
+
+ if (len < sizeof(struct abis_om_fom_hdr)) {
+ LOGP(DL1C, LOGL_ERROR, "FOM header insufficient space %zu %zu\n",
+ len, sizeof(struct abis_om_fom_hdr));
+ return -1;
+ }
+
+ return 0;
+}
+
+static int check_manuf(struct msgb *msg, struct abis_om_hdr *omh, size_t msg_size)
+{
+ int type;
+ size_t size;
+
+ if (msg_size < 1) {
+ LOGP(DL1C, LOGL_ERROR, "No ManId Length Indicator %zu\n",
+ msg_size);
+ return -1;
+ }
+
+ if (omh->data[0] >= msg_size - 1) {
+ LOGP(DL1C, LOGL_ERROR,
+ "Insufficient message space for this ManId Length %d %zu\n",
+ omh->data[0], msg_size - 1);
+ return -1;
+ }
+
+ if (omh->data[0] == sizeof(abis_nm_ipa_magic) &&
+ strncmp(abis_nm_ipa_magic, (const char *)omh->data + 1,
+ sizeof(abis_nm_ipa_magic)) == 0) {
+ type = OML_MSG_TYPE_IPA;
+ size = sizeof(abis_nm_ipa_magic) + 1;
+ } else if (omh->data[0] == sizeof(abis_nm_osmo_magic) &&
+ strncmp(abis_nm_osmo_magic, (const char *) omh->data + 1,
+ sizeof(abis_nm_osmo_magic)) == 0) {
+ type = OML_MSG_TYPE_OSMO;
+ size = sizeof(abis_nm_osmo_magic) + 1;
+ } else {
+ LOGP(DL1C, LOGL_ERROR, "Manuf Label Unknown\n");
+ return -1;
+ }
+
+ /* we have verified that the vendor string fits */
+ msg->l3h = omh->data + size;
+ if (check_fom(omh, msgb_l3len(msg)) != 0)
+ return -1;
+ return type;
+}
+
+/* check that DTX is in the middle of silence */
+static inline bool dtx_is_update(const struct gsm_lchan *lchan)
+{
+ if (!dtx_dl_amr_enabled(lchan))
+ return false;
+ if (lchan->tch.dtx.dl_amr_fsm->state == ST_SID_U ||
+ lchan->tch.dtx.dl_amr_fsm->state == ST_U_NOINH)
+ return true;
+ return false;
+}
+
+/* check that DTX is in the beginning of silence for AMR HR */
+bool dtx_is_first_p1(const struct gsm_lchan *lchan)
+{
+ if (!dtx_dl_amr_enabled(lchan))
+ return false;
+ if ((lchan->type == GSM_LCHAN_TCH_H &&
+ lchan->tch.dtx.dl_amr_fsm->state == ST_SID_F1))
+ return true;
+ return false;
+}
+
+/* update lchan SID status */
+void lchan_set_marker(bool t, struct gsm_lchan *lchan)
+{
+ if (t)
+ lchan->tch.dtx.ul_sid = true;
+ else if (lchan->tch.dtx.ul_sid) {
+ lchan->tch.dtx.ul_sid = false;
+ lchan->rtp_tx_marker = true;
+ }
+}
+
+/*! \brief Store the last SID frame in lchan context
+ * \param[in] lchan Logical channel on which we check scheduling
+ * \param[in] l1_payload buffer with SID data
+ * \param[in] length length of l1_payload
+ * \param[in] fn Frame Number for which we check scheduling
+ * \param[in] update 0 if SID_FIRST, 1 if SID_UPDATE, -1 if not AMR SID
+ */
+void dtx_cache_payload(struct gsm_lchan *lchan, const uint8_t *l1_payload,
+ size_t length, uint32_t fn, int update)
+{
+ size_t amr = (update < 0) ? 0 : 2,
+ copy_len = OSMO_MIN(length,
+ ARRAY_SIZE(lchan->tch.dtx.cache) - amr);
+
+ lchan->tch.dtx.len = copy_len + amr;
+ /* SID FIRST is special because it's both sent and cached: */
+ if (update == 0) {
+ lchan->tch.dtx.is_update = false; /* Mark SID FIRST explicitly */
+ /* for non-AMR case - always update FN for incoming SID FIRST */
+ if (!amr || !dtx_is_update(lchan))
+ lchan->tch.dtx.fn = fn;
+ /* for AMR case - do not update FN if SID FIRST arrives in a
+ middle of silence: this should not be happening according to
+ the spec */
+ }
+
+ memcpy(lchan->tch.dtx.cache + amr, l1_payload, copy_len);
+}
+
+/*! \brief Check current state of DTX DL AMR FSM and dispatch necessary events
+ * \param[in] lchan Logical channel on which we check scheduling
+ * \param[in] rtp_pl buffer with RTP data
+ * \param[in] rtp_pl_len length of rtp_pl
+ * \param[in] fn Frame Number for which we check scheduling
+ * \param[in] l1_payload buffer where CMR and CMI prefix should be added
+ * \param[in] marker RTP Marker bit
+ * \param[out] len Length of expected L1 payload
+ * \param[out] ft_out Frame Type to be populated after decoding
+ * \returns 0 in case of success; negative on error
+ */
+int dtx_dl_amr_fsm_step(struct gsm_lchan *lchan, const uint8_t *rtp_pl,
+ size_t rtp_pl_len, uint32_t fn, uint8_t *l1_payload,
+ bool marker, uint8_t *len, uint8_t *ft_out)
+{
+ uint8_t cmr;
+ enum osmo_amr_type ft;
+ enum osmo_amr_quality bfi;
+ int8_t sti, cmi;
+ int rc;
+
+ if (dtx_dl_amr_enabled(lchan)) {
+ if (lchan->type == GSM_LCHAN_TCH_H && !rtp_pl) {
+ /* we're called by gen_empty_tch_msg() to handle states
+ specific to AMR HR DTX */
+ switch (lchan->tch.dtx.dl_amr_fsm->state) {
+ case ST_SID_F2:
+ *len = 3; /* SID-FIRST P1 -> P2 completion */
+ memcpy(l1_payload, lchan->tch.dtx.cache, 2);
+ rc = 0;
+ dtx_dispatch(lchan, E_COMPL);
+ break;
+ case ST_SID_U:
+ rc = -EBADMSG;
+ dtx_dispatch(lchan, E_SID_U);
+ break;
+ default:
+ rc = -EBADMSG;
+ }
+ return rc;
+ }
+ }
+
+ if (!rtp_pl_len)
+ return -EBADMSG;
+
+ rc = osmo_amr_rtp_dec(rtp_pl, rtp_pl_len, &cmr, &cmi, &ft, &bfi, &sti);
+ if (rc < 0) {
+ LOGP(DRTP, LOGL_ERROR, "failed to decode AMR RTP (length %zu, "
+ "%p)\n", rtp_pl_len, rtp_pl);
+ return rc;
+ }
+
+ /* only needed for old sysmo firmware: */
+ *ft_out = ft;
+
+ /* CMI in downlink tells the L1 encoder which encoding function
+ * it will use, so we have to use the frame type */
+ if (osmo_amr_is_speech(ft))
+ cmi = ft;
+
+ /* populate L1 payload with CMR/CMI - might be ignored by caller: */
+ amr_set_mode_pref(l1_payload, &lchan->tch.amr_mr, cmi, cmr);
+
+ /* populate DTX cache with CMR/CMI - overwrite cache which will be
+ either updated or invalidated by caller anyway: */
+ amr_set_mode_pref(lchan->tch.dtx.cache, &lchan->tch.amr_mr, cmi, cmr);
+ *len = 3 + rtp_pl_len;
+
+ /* DTX DL is not enabled, move along */
+ if (!lchan->ts->trx->bts->dtxd)
+ return 0;
+
+ if (osmo_amr_is_speech(ft)) {
+ /* AMR HR - SID-FIRST_P1 Inhibition */
+ if (marker && lchan->tch.dtx.dl_amr_fsm->state == ST_VOICE)
+ return osmo_fsm_inst_dispatch(lchan->tch.dtx.dl_amr_fsm,
+ E_INHIB, (void *)lchan);
+
+ /* AMR HR - SID-UPDATE Inhibition */
+ if (marker && lchan->type == GSM_LCHAN_TCH_H &&
+ lchan->tch.dtx.dl_amr_fsm->state == ST_SID_U)
+ return osmo_fsm_inst_dispatch(lchan->tch.dtx.dl_amr_fsm,
+ E_INHIB, (void *)lchan);
+
+ /* AMR FR & HR - generic */
+ if (marker && (lchan->tch.dtx.dl_amr_fsm->state == ST_SID_F1 ||
+ lchan->tch.dtx.dl_amr_fsm->state == ST_SID_F2 ||
+ lchan->tch.dtx.dl_amr_fsm->state == ST_U_NOINH))
+ return osmo_fsm_inst_dispatch(lchan->tch.dtx.dl_amr_fsm,
+ E_ONSET, (void *)lchan);
+
+ if (lchan->tch.dtx.dl_amr_fsm->state != ST_VOICE)
+ return osmo_fsm_inst_dispatch(lchan->tch.dtx.dl_amr_fsm,
+ E_VOICE, (void *)lchan);
+
+ return 0;
+ }
+
+ if (ft == AMR_SID) {
+ if (lchan->tch.dtx.dl_amr_fsm->state == ST_VOICE) {
+ /* SID FIRST/UPDATE scheduling logic relies on SID FIRST
+ being sent first hence we have to force caching of SID
+ as FIRST regardless of actually decoded type */
+ dtx_cache_payload(lchan, rtp_pl, rtp_pl_len, fn, false);
+ return osmo_fsm_inst_dispatch(lchan->tch.dtx.dl_amr_fsm,
+ sti ? E_SID_U : E_SID_F,
+ (void *)lchan);
+ } else if (lchan->tch.dtx.dl_amr_fsm->state != ST_FACCH)
+ dtx_cache_payload(lchan, rtp_pl, rtp_pl_len, fn, sti);
+ if (lchan->tch.dtx.dl_amr_fsm->state == ST_SID_F2)
+ return osmo_fsm_inst_dispatch(lchan->tch.dtx.dl_amr_fsm,
+ E_COMPL, (void *)lchan);
+ return osmo_fsm_inst_dispatch(lchan->tch.dtx.dl_amr_fsm,
+ sti ? E_SID_U : E_SID_F,
+ (void *)lchan);
+ }
+
+ if (ft != AMR_NO_DATA) {
+ LOGP(DRTP, LOGL_ERROR, "unsupported AMR FT 0x%02x\n", ft);
+ return -ENOTSUP;
+ }
+
+ if (marker)
+ osmo_fsm_inst_dispatch(lchan->tch.dtx.dl_amr_fsm, E_VOICE,
+ (void *)lchan);
+ *len = 0;
+ return 0;
+}
+
+/* STI is located in payload byte 6, cache contains 2 byte prefix (CMR/CMI)
+ * STI set = SID UPDATE, STI unset = SID FIRST
+ */
+static inline void dtx_sti_set(struct gsm_lchan *lchan)
+{
+ lchan->tch.dtx.cache[6 + 2] |= STI_BIT_MASK;
+}
+
+static inline void dtx_sti_unset(struct gsm_lchan *lchan)
+{
+ lchan->tch.dtx.cache[6 + 2] &= ~STI_BIT_MASK;
+}
+
+/*! \brief Check if enough time has passed since last SID (if any) to repeat it
+ * \param[in] lchan Logical channel on which we check scheduling
+ * \param[in] fn Frame Number for which we check scheduling
+ * \returns true if transmission can be omitted, false otherwise
+ */
+static inline bool dtx_amr_sid_optional(struct gsm_lchan *lchan, uint32_t fn)
+{
+ if (!dtx_dl_amr_enabled(lchan))
+ return true;
+
+ /* Compute approx. time delta x26 based on Fn duration */
+ uint32_t dx26 = 120 * (fn - lchan->tch.dtx.fn);
+
+ /* We're resuming after FACCH interruption */
+ if (lchan->tch.dtx.dl_amr_fsm->state == ST_FACCH) {
+ /* force STI bit to 0 so cache is treated as SID FIRST */
+ dtx_sti_unset(lchan);
+ lchan->tch.dtx.is_update = false;
+ /* check that this FN has not been used for FACCH message
+ 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->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) {
+ lchan->tch.dtx.fn = LCHAN_FN_WAIT;
+ } else
+ lchan->tch.dtx.fn = fn;
+ } 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);
+ return false;
+ } else
+ lchan->tch.dtx.fn = fn;
+ }
+ /* this FN was already used for FACCH or ONSET message so we just
+ prepare things for next one */
+ return true;
+ }
+
+ if (lchan->tch.dtx.dl_amr_fsm->state == ST_VOICE)
+ return true;
+
+ /* according to 3GPP TS 26.093 A.5.1.1:
+ (*26) to avoid float math, add 1 FN tolerance (-120) */
+ if (lchan->tch.dtx.is_update) { /* SID UPDATE: every 8th RTP frame */
+ if (dx26 < GSM_RTP_FRAME_DURATION_MS * 8 * 26 - 120)
+ return true;
+ return false;
+ }
+ /* 3rd frame after SID FIRST should be SID UPDATE */
+ if (dx26 < GSM_RTP_FRAME_DURATION_MS * 3 * 26 - 120)
+ return true;
+ return false;
+}
+
+static inline bool fn_chk(const uint8_t *t, uint32_t fn, uint8_t len)
+{
+ uint8_t i;
+ for (i = 0; i < len; i++)
+ if (fn % 104 == t[i])
+ return false;
+ return true;
+}
+
+/*! \brief Check if TX scheduling is optional for a given FN in case of DTX
+ * \param[in] lchan Logical channel on which we check scheduling
+ * \param[in] fn Frame Number for which we check scheduling
+ * \returns true if transmission can be omitted, false otherwise
+ */
+static inline bool dtx_sched_optional(struct gsm_lchan *lchan, uint32_t fn)
+{
+ /* According to 3GPP TS 45.008 § 8.3: */
+ 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) {
+ 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));
+ }
+ return false;
+}
+
+/*! \brief Check if DTX DL AMR is enabled for a given lchan (it have proper type,
+ * FSM is allocated etc.)
+ * \param[in] lchan Logical channel on which we check scheduling
+ * \returns true if DTX DL AMR is enabled, false otherwise
+ */
+bool dtx_dl_amr_enabled(const struct gsm_lchan *lchan)
+{
+ if (lchan->ts->trx->bts->dtxd &&
+ lchan->tch.dtx.dl_amr_fsm &&
+ lchan->tch_mode == GSM48_CMODE_SPEECH_AMR)
+ return true;
+ return false;
+}
+
+/*! \brief Check if DTX DL AMR FSM state is recursive: requires secondary
+ * response to a single RTS request from L1.
+ * \param[in] lchan Logical channel on which we check scheduling
+ * \returns true if DTX DL AMR FSM state is recursive, false otherwise
+ */
+bool dtx_recursion(const struct gsm_lchan *lchan)
+{
+ if (!dtx_dl_amr_enabled(lchan))
+ return false;
+
+ if (lchan->tch.dtx.dl_amr_fsm->state == ST_U_INH_V ||
+ lchan->tch.dtx.dl_amr_fsm->state == ST_U_INH_F ||
+ lchan->tch.dtx.dl_amr_fsm->state == ST_U_INH_V_REC ||
+ lchan->tch.dtx.dl_amr_fsm->state == ST_U_INH_F_REC ||
+ lchan->tch.dtx.dl_amr_fsm->state == ST_F1_INH_V ||
+ lchan->tch.dtx.dl_amr_fsm->state == ST_F1_INH_F ||
+ lchan->tch.dtx.dl_amr_fsm->state == ST_F1_INH_V_REC ||
+ lchan->tch.dtx.dl_amr_fsm->state == ST_F1_INH_F_REC ||
+ lchan->tch.dtx.dl_amr_fsm->state == ST_ONSET_F ||
+ lchan->tch.dtx.dl_amr_fsm->state == ST_ONSET_V ||
+ lchan->tch.dtx.dl_amr_fsm->state == ST_ONSET_F_REC ||
+ lchan->tch.dtx.dl_amr_fsm->state == ST_ONSET_V_REC)
+ return true;
+
+ return false;
+}
+
+/*! \brief Send signal to FSM: with proper check if DIX is enabled for this lchan
+ * \param[in] lchan Logical channel on which we check scheduling
+ * \param[in] e DTX DL AMR FSM Event
+ */
+void dtx_dispatch(struct gsm_lchan *lchan, enum dtx_dl_amr_fsm_events e)
+{
+ if (dtx_dl_amr_enabled(lchan))
+ osmo_fsm_inst_dispatch(lchan->tch.dtx.dl_amr_fsm, e,
+ (void *)lchan);
+}
+
+/*! \brief Send internal signal to FSM: check that DTX is enabled for this chan,
+ * check that current FSM and lchan states are permitting such signal.
+ * Note: this should be the only way to dispatch E_COMPL to FSM from
+ * BTS code.
+ * \param[in] lchan Logical channel on which we check scheduling
+ */
+void dtx_int_signal(struct gsm_lchan *lchan)
+{
+ if (!dtx_dl_amr_enabled(lchan))
+ return;
+
+ if (dtx_is_first_p1(lchan) || dtx_recursion(lchan))
+ dtx_dispatch(lchan, E_COMPL);
+}
+
+/*! \brief Repeat last SID if possible in case of DTX
+ * \param[in] lchan Logical channel on which we check scheduling
+ * \param[in] dst Buffer to copy last SID into
+ * \returns Number of bytes copied + 1 (to accommodate for extra byte with
+ * payload type), 0 if there's nothing to copy
+ */
+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;
+ } else
+ if (dtx_amr_sid_optional(lchan, fn))
+ return 0;
+
+ if (lchan->tch.dtx.len) {
+ if (dtx_dl_amr_enabled(lchan)) {
+ if ((lchan->type == GSM_LCHAN_TCH_H &&
+ lchan->tch.dtx.dl_amr_fsm->state == ST_SID_F2) ||
+ (lchan->type == GSM_LCHAN_TCH_F &&
+ lchan->tch.dtx.dl_amr_fsm->state == ST_SID_F1)) {
+ /* advance FSM in case we've just sent SID FIRST
+ to restore silence after FACCH interruption */
+ osmo_fsm_inst_dispatch(lchan->tch.dtx.dl_amr_fsm,
+ E_SID_U, (void *)lchan);
+ dtx_sti_unset(lchan);
+ } else if (dtx_is_update(lchan)) {
+ /* enforce SID UPDATE for next repetition: it
+ might have been altered by FACCH handling */
+ dtx_sti_set(lchan);
+ if (lchan->type == GSM_LCHAN_TCH_H &&
+ lchan->tch.dtx.dl_amr_fsm->state ==
+ ST_U_NOINH)
+ osmo_fsm_inst_dispatch(lchan->tch.dtx.dl_amr_fsm,
+ E_COMPL,
+ (void *)lchan);
+ lchan->tch.dtx.is_update = true;
+ }
+ }
+ memcpy(dst, lchan->tch.dtx.cache, lchan->tch.dtx.len);
+ lchan->tch.dtx.fn = fn;
+ return lchan->tch.dtx.len + 1;
+ }
+
+ LOGP(DL1C, LOGL_DEBUG, "Have to send %s frame on TCH but SID buffer "
+ "is empty - sent nothing\n",
+ get_value_string(gsm48_chan_mode_names, lchan->tch_mode));
+
+ return 0;
+}
+
+/**
+ * Return 0 in case the IPA structure is okay and in this
+ * case the l2h will be set to the beginning of the data.
+ */
+int msg_verify_ipa_structure(struct msgb *msg)
+{
+ struct ipaccess_head *hh;
+
+ if (msgb_l1len(msg) < sizeof(struct ipaccess_head)) {
+ LOGP(DL1C, LOGL_ERROR,
+ "Ipa header insufficient space %d %zu\n",
+ msgb_l1len(msg), sizeof(struct ipaccess_head));
+ return -1;
+ }
+
+ hh = (struct ipaccess_head *) msg->l1h;
+
+ if (ntohs(hh->len) != msgb_l1len(msg) - sizeof(struct ipaccess_head)) {
+ LOGP(DL1C, LOGL_ERROR,
+ "Incorrect ipa header msg size %d %zu\n",
+ ntohs(hh->len), msgb_l1len(msg) - sizeof(struct ipaccess_head));
+ return -1;
+ }
+
+ if (hh->proto == IPAC_PROTO_OSMO) {
+ struct ipaccess_head_ext *hh_ext = (struct ipaccess_head_ext *) hh->data;
+ if (ntohs(hh->len) < sizeof(*hh_ext)) {
+ LOGP(DL1C, LOGL_ERROR, "IPA length shorter than OSMO header\n");
+ return -1;
+ }
+ msg->l2h = hh_ext->data;
+ } else
+ msg->l2h = hh->data;
+
+ return 0;
+}
+
+/**
+ * \brief Verify the structure of the OML message and set l3h
+ *
+ * This function verifies that the data in \param in msg is a proper
+ * OML message. This code assumes that msg->l2h points to the
+ * beginning of the OML message. In the successful case the msg->l3h
+ * will be set and will point to the FOM header. The value is undefined
+ * in all other cases.
+ *
+ * \param msg The message to analyze starting from msg->l2h.
+ * \return In case the structure is correct a positive number will be
+ * returned and msg->l3h will point to the FOM. The number is a
+ * classification of the vendor type of the message.
+ */
+int msg_verify_oml_structure(struct msgb *msg)
+{
+ struct abis_om_hdr *omh;
+
+ if (msgb_l2len(msg) < sizeof(*omh)) {
+ LOGP(DL1C, LOGL_ERROR, "Om header insufficient space %d %zu\n",
+ msgb_l2len(msg), sizeof(*omh));
+ return -1;
+ }
+
+ omh = (struct abis_om_hdr *) msg->l2h;
+ if (omh->mdisc != ABIS_OM_MDISC_FOM &&
+ omh->mdisc != ABIS_OM_MDISC_MANUF) {
+ LOGP(DL1C, LOGL_ERROR, "Incorrect om mdisc value %x\n",
+ omh->mdisc);
+ return -1;
+ }
+
+ if (omh->placement != ABIS_OM_PLACEMENT_ONLY) {
+ LOGP(DL1C, LOGL_ERROR, "Incorrect om placement value %x %x\n",
+ omh->placement, ABIS_OM_PLACEMENT_ONLY);
+ return -1;
+ }
+
+ if (omh->sequence != 0) {
+ LOGP(DL1C, LOGL_ERROR, "Incorrect om sequence value %d\n",
+ omh->sequence);
+ return -1;
+ }
+
+ if (omh->mdisc == ABIS_OM_MDISC_MANUF)
+ return check_manuf(msg, omh, msgb_l2len(msg) - sizeof(*omh));
+
+ msg->l3h = omh->data;
+ if (check_fom(omh, msgb_l3len(msg)) != 0)
+ return -1;
+ return OML_MSG_TYPE_ETSI;
+}
diff --git a/src/common/oml.c b/src/common/oml.c
new file mode 100644
index 00000000..41debc1b
--- /dev/null
+++ b/src/common/oml.c
@@ -0,0 +1,1495 @@
+/* GSM TS 12.21 O&M / OML, BTS side */
+
+/* (C) 2011 by Andreas Eversberg <jolly@eversberg.eu>
+ * (C) 2011-2013 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 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/>.
+ *
+ */
+
+/*
+ * Operation and Maintenance Messages
+ */
+
+#include "btsconfig.h"
+
+#include <errno.h>
+#include <stdarg.h>
+#include <string.h>
+#include <sys/types.h>
+#include <arpa/inet.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/gsm/protocol/gsm_12_21.h>
+#include <osmocom/gsm/abis_nm.h>
+#include <osmocom/abis/e1_input.h>
+#include <osmocom/abis/ipaccess.h>
+
+#include <osmo-bts/logging.h>
+#include <osmo-bts/gsm_data.h>
+#include <osmo-bts/abis.h>
+#include <osmo-bts/oml.h>
+#include <osmo-bts/bts_model.h>
+#include <osmo-bts/bts.h>
+#include <osmo-bts/signal.h>
+#include <osmo-bts/phy_link.h>
+
+static int oml_ipa_set_attr(struct gsm_bts *bts, struct msgb *msg);
+
+static struct tlv_definition abis_nm_att_tlvdef_ipa_local = {};
+
+/*
+ * support
+ */
+
+static int oml_tlv_parse(struct tlv_parsed *tp, const uint8_t *buf, int len)
+{
+ return tlv_parse(tp, &abis_nm_att_tlvdef_ipa_local, buf, len, 0, 0);
+}
+
+struct msgb *oml_msgb_alloc(void)
+{
+ return msgb_alloc_headroom(1024, 128, "OML");
+}
+
+/* 3GPP TS 12.21 § 8.8.2 */
+static int oml_tx_failure_event_rep(struct gsm_abis_mo *mo, uint16_t cause_value,
+ const char *fmt, ...)
+{
+ struct msgb *nmsg;
+ va_list ap;
+
+ LOGP(DOML, LOGL_NOTICE, "Sending %s to BSC: ", get_value_string(abis_mm_event_cause_names, cause_value));
+ va_start(ap, fmt);
+ osmo_vlogp(DOML, LOGL_NOTICE, __FILE__, __LINE__, 1, fmt, ap);
+ nmsg = abis_nm_fail_evt_vrep(NM_EVT_PROC_FAIL, NM_SEVER_CRITICAL,
+ NM_PCAUSE_T_MANUF, cause_value, fmt, ap);
+ va_end(ap);
+ LOGPC(DOML, LOGL_NOTICE, "\n");
+
+ if (!nmsg)
+ return -ENOMEM;
+
+ return oml_mo_send_msg(mo, nmsg, NM_MT_FAILURE_EVENT_REP);
+}
+
+void oml_fail_rep(uint16_t cause_value, const char *fmt, ...)
+{
+ va_list ap;
+ char *rep;
+
+ va_start(ap, fmt);
+ rep = talloc_asprintf(tall_bts_ctx, fmt, ap);
+ va_end(ap);
+
+ osmo_signal_dispatch(SS_FAIL, cause_value, rep);
+ /* signal dispatch is synchronous so all the signal handlers are
+ finished already: we're free to free */
+ talloc_free(rep);
+}
+
+int oml_send_msg(struct msgb *msg, int is_manuf)
+{
+ struct abis_om_hdr *omh;
+
+ if (is_manuf) {
+ /* length byte, string + 0 termination */
+ uint8_t *manuf = msgb_push(msg, 1 + sizeof(abis_nm_ipa_magic));
+ manuf[0] = strlen(abis_nm_ipa_magic)+1;
+ memcpy(manuf+1, abis_nm_ipa_magic, strlen(abis_nm_ipa_magic));
+ }
+
+ /* Push the main OML header and send it off */
+ omh = (struct abis_om_hdr *) msgb_push(msg, sizeof(*omh));
+ if (is_manuf)
+ omh->mdisc = ABIS_OM_MDISC_MANUF;
+ else
+ omh->mdisc = ABIS_OM_MDISC_FOM;
+ omh->placement = ABIS_OM_PLACEMENT_ONLY;
+ omh->sequence = 0;
+ omh->length = msgb_l3len(msg);
+
+ msg->l2h = (uint8_t *)omh;
+
+ return abis_oml_sendmsg(msg);
+}
+
+int oml_mo_send_msg(struct gsm_abis_mo *mo, struct msgb *msg, uint8_t msg_type)
+{
+ struct abis_om_fom_hdr *foh;
+
+ msg->l3h = msgb_push(msg, sizeof(*foh));
+ foh = (struct abis_om_fom_hdr *) msg->l3h;
+ foh->msg_type = msg_type;
+ 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;
+
+ return oml_send_msg(msg, 0);
+}
+
+/* FIXME: move to gsm_data_shared */
+static char mo_buf[128];
+char *gsm_abis_mo_name(const struct gsm_abis_mo *mo)
+{
+ snprintf(mo_buf, sizeof(mo_buf), "OC=%s INST=(%02x,%02x,%02x)",
+ get_value_string(abis_nm_obj_class_names, mo->obj_class),
+ mo->obj_inst.bts_nr, mo->obj_inst.trx_nr, mo->obj_inst.ts_nr);
+ return mo_buf;
+}
+
+static inline void add_bts_attrs(struct msgb *msg, const struct gsm_bts *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);
+}
+
+/* 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);
+}
+
+static inline void add_trx_attr(struct msgb *msg, struct gsm_bts_trx *trx)
+{
+ const struct phy_instance *pinst = trx_phy_instance(trx);
+
+ abis_nm_put_sw_file(msg, btsatttr2str(TRX_PHY_VERSION), pinst && strlen(pinst->version) ? pinst->version : "Unknown",
+ true);
+}
+
+/* The number of attributes in §9.4.26 List of Required Attributes is 2 bytes,
+ but the Count of not-reported attributes from §9.4.64 is 1 byte */
+static inline uint8_t pack_num_unreported_attr(uint16_t attrs)
+{
+ if (attrs > 255) {
+ LOGP(DOML, LOGL_ERROR, "O&M Get Attributes, Count of not-reported attributes is too big: %u\n",
+ attrs);
+ return 255;
+ }
+ return attrs; /* Return number of unhandled attributes */
+}
+
+/* copy all the attributes accumulated in msg to out and return the total length of out buffer */
+static inline int cleanup_attr_msg(uint8_t *out, int out_offset, struct msgb *msg)
+{
+ int len = 0;
+
+ out[0] = pack_num_unreported_attr(out_offset - 1);
+
+ if (msg) {
+ memcpy(out + out_offset, msgb_data(msg), msg->len);
+ len = msg->len;
+ msgb_free(msg);
+ }
+
+ return len + out_offset + 1;
+}
+
+static inline int handle_attrs_trx(uint8_t *out, struct gsm_bts_trx *trx, const uint8_t *attr, uint16_t attr_len)
+{
+ uint16_t i, attr_out_index = 1; /* byte 0 is reserved for unsupported attributes counter */
+ struct msgb *attr_buf = oml_msgb_alloc();
+
+ if (!attr_buf)
+ return -NM_NACK_CANT_PERFORM;
+
+ for (i = 0; i < attr_len; i++) {
+ bool processed = false;
+ switch (attr[i]) {
+ case NM_ATT_SW_CONFIG:
+ if (trx) {
+ add_trx_attr(attr_buf, trx);
+ processed = true;
+ } else
+ LOGP(DOML, LOGL_ERROR, "O&M Get Attributes [%u], %s is unhandled due to missing TRX.\n",
+ i, get_value_string(abis_nm_att_names, attr[i]));
+ break;
+ default:
+ LOGP(DOML, LOGL_ERROR, "O&M Get Attributes [%u], %s is unsupported by TRX.\n", i,
+ get_value_string(abis_nm_att_names, attr[i]));
+ }
+ /* assemble values of supported attributes and list of unsupported ones */
+ if (!processed) {
+ out[attr_out_index] = attr[i];
+ attr_out_index++;
+ }
+ }
+
+ return cleanup_attr_msg(out, attr_out_index, attr_buf);
+}
+
+static inline int handle_attrs_bts(uint8_t *out, const struct gsm_bts *bts, const uint8_t *attr, uint16_t attr_len)
+{
+ uint16_t i, attr_out_index = 1; /* byte 0 is reserved for unsupported attributes counter */
+ struct msgb *attr_buf = oml_msgb_alloc();
+
+ if (!attr_buf)
+ return -NM_NACK_CANT_PERFORM;
+
+ for (i = 0; i < attr_len; i++) {
+ switch (attr[i]) {
+ case NM_ATT_SW_CONFIG:
+ add_bts_attrs(attr_buf, bts);
+ break;
+ case NM_ATT_MANUF_ID:
+ add_bts_feat(attr_buf, 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]));
+ out[attr_out_index] = attr[i]; /* assemble values of supported attributes and list of unsupported ones */
+ attr_out_index++;
+ }
+ }
+
+ return cleanup_attr_msg(out, attr_out_index, attr_buf);
+}
+
+/* send 3GPP TS 52.021 §8.11.2 Get Attribute Response */
+static int oml_tx_attr_resp(struct gsm_bts *bts, const struct abis_om_fom_hdr *foh, const uint8_t *attr,
+ uint16_t attr_len)
+{
+ struct msgb *nmsg = oml_msgb_alloc();
+ uint8_t resp[MAX_VERSION_LENGTH * attr_len * 2]; /* heuristic for Attribute Response Info space requirements */
+ int len;
+
+ LOGP(DOML, LOGL_INFO, "%s Tx Get Attribute Response\n",
+ get_value_string(abis_nm_obj_class_names, foh->obj_class));
+
+ if (!nmsg)
+ return -NM_NACK_CANT_PERFORM;
+
+ switch (foh->obj_class) {
+ case NM_OC_BTS:
+ len = handle_attrs_bts(resp, bts, attr, attr_len);
+ break;
+ case NM_OC_BASEB_TRANSC:
+ len = handle_attrs_trx(resp, gsm_bts_trx_num(bts, foh->obj_inst.trx_nr), attr, attr_len);
+ break;
+ default:
+ LOGP(DOML, LOGL_ERROR, "Unsupported MO class %s in Get Attribute Response\n",
+ get_value_string(abis_nm_obj_class_names, foh->obj_class));
+ len = -NM_NACK_RES_NOTIMPL;
+ }
+
+ if (len < 0) {
+ LOGP(DOML, LOGL_ERROR, "Tx Get Attribute Response FAILED with %d\n", len);
+ msgb_free(nmsg);
+ return len;
+ }
+
+ /* §9.4.64 Get Attribute Response Info */
+ msgb_tl16v_put(nmsg, NM_ATT_GET_ARI, len, resp);
+
+ len = oml_mo_send_msg(&bts->mo, nmsg, NM_MT_GET_ATTR_RESP);
+ return (len < 0) ? -NM_NACK_CANT_PERFORM : len;
+}
+
+/* 8.8.1 sending State Changed Event Report */
+int oml_tx_state_changed(struct gsm_abis_mo *mo)
+{
+ struct msgb *nmsg;
+
+ LOGP(DOML, LOGL_INFO, "%s Tx STATE CHG REP\n", gsm_abis_mo_name(mo));
+
+ nmsg = oml_msgb_alloc();
+ if (!nmsg)
+ return -ENOMEM;
+
+ /* 9.4.38 Operational State */
+ 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);
+
+ /* 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);
+
+ return oml_mo_send_msg(mo, nmsg, NM_MT_STATECHG_EVENT_REP);
+}
+
+/* First initialization of MO, does _not_ generate state changes */
+void oml_mo_state_init(struct gsm_abis_mo *mo, int op_state, int avail_state)
+{
+ mo->nm_state.availability = 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 rc = 0;
+
+ if ((op_state != -1 && mo->nm_state.operational != op_state) ||
+ (avail_state != -1 && mo->nm_state.availability != avail_state)) {
+ if (avail_state != -1) {
+ LOGP(DOML, LOGL_INFO, "%s AVAIL STATE %s -> %s\n",
+ gsm_abis_mo_name(mo),
+ abis_nm_avail_name(mo->nm_state.availability),
+ abis_nm_avail_name(avail_state));
+ mo->nm_state.availability = avail_state;
+ }
+ if (op_state != -1) {
+ 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));
+ mo->nm_state.operational = op_state;
+ osmo_signal_dispatch(SS_GLOBAL, S_NEW_OP_STATE, NULL);
+ }
+
+ /* send state change report */
+ rc = oml_tx_state_changed(mo);
+ }
+ return rc;
+}
+
+int oml_mo_fom_ack_nack(struct gsm_abis_mo *mo, uint8_t orig_msg_type,
+ uint8_t cause)
+{
+ struct msgb *msg;
+ uint8_t new_msg_type;
+
+ msg = oml_msgb_alloc();
+ if (!msg)
+ return -ENOMEM;
+
+ if (cause) {
+ new_msg_type = orig_msg_type + 2;
+ msgb_tv_put(msg, NM_ATT_NACK_CAUSES, cause);
+ } else {
+ new_msg_type = orig_msg_type + 1;
+ }
+
+ return oml_mo_send_msg(mo, msg, new_msg_type);
+}
+
+int oml_mo_statechg_ack(struct gsm_abis_mo *mo)
+{
+ struct msgb *msg;
+ int rc = 0;
+
+ msg = oml_msgb_alloc();
+ if (!msg)
+ return -ENOMEM;
+
+ msgb_tv_put(msg, NM_ATT_ADM_STATE, mo->nm_state.administrative);
+
+ rc = oml_mo_send_msg(mo, msg, NM_MT_CHG_ADM_STATE_ACK);
+ if (rc != 0)
+ return rc;
+
+ /* Emulate behaviour of ipaccess nanobts: Send a 'State Changed Event Report' as well. */
+ return oml_tx_state_changed(mo);
+}
+
+int oml_mo_statechg_nack(struct gsm_abis_mo *mo, uint8_t nack_cause)
+{
+ return oml_mo_fom_ack_nack(mo, NM_MT_CHG_ADM_STATE, nack_cause);
+}
+
+int oml_mo_opstart_ack(struct gsm_abis_mo *mo)
+{
+ return oml_mo_fom_ack_nack(mo, NM_MT_OPSTART, 0);
+}
+
+int oml_mo_opstart_nack(struct gsm_abis_mo *mo, uint8_t nack_cause)
+{
+ return oml_mo_fom_ack_nack(mo, NM_MT_OPSTART, nack_cause);
+}
+
+int oml_fom_ack_nack(struct msgb *old_msg, uint8_t cause)
+{
+ struct abis_om_hdr *old_oh = msgb_l2(old_msg);
+ struct abis_om_fom_hdr *old_foh = msgb_l3(old_msg);
+ struct msgb *msg;
+ struct abis_om_fom_hdr *foh;
+ int is_manuf = 0;
+
+ msg = oml_msgb_alloc();
+ if (!msg)
+ return -ENOMEM;
+
+ /* make sure to respond with MANUF if request was MANUF */
+ if (old_oh->mdisc == ABIS_OM_MDISC_MANUF)
+ is_manuf = 1;
+
+ msg->trx = old_msg->trx;
+
+ /* copy over old FOM-Header and later only change the msg_type */
+ msg->l3h = msgb_push(msg, sizeof(*foh));
+ foh = (struct abis_om_fom_hdr *) msg->l3h;
+ memcpy(foh, old_foh, sizeof(*foh));
+
+ /* alter message type */
+ if (cause) {
+ LOGP(DOML, LOGL_NOTICE, "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);
+ } else {
+ LOGP(DOML, LOGL_DEBUG, "Sending FOM ACK.\n");
+ foh->msg_type++; /* ack */
+ }
+
+ return oml_send_msg(msg, is_manuf);
+}
+
+/*
+ * Formatted O&M messages
+ */
+
+/* 8.3.7 sending SW Activated Report */
+int oml_mo_tx_sw_act_rep(struct gsm_abis_mo *mo)
+{
+ struct msgb *nmsg;
+
+ LOGP(DOML, LOGL_INFO, "%s Tx SW ACT REP\n", gsm_abis_mo_name(mo));
+
+ nmsg = oml_msgb_alloc();
+ 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 the libosmocore default of 1s for
+ * DCCH and 2s for ACCH. 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,
+};
+
+static void dl_set_t200(struct lapdm_datalink *dl, unsigned int t200_msec)
+{
+ dl->dl.t200_sec = t200_msec / 1000;
+ dl->dl.t200_usec = (t200_msec % 1000) * 1000;
+}
+
+/* Configure LAPDm T200 timers for this lchan according to OML */
+int oml_set_lchan_t200(struct gsm_lchan *lchan)
+{
+ struct gsm_bts *bts = lchan->ts->trx->bts;
+ struct lapdm_channel *lc = &lchan->lapdm_ch;
+ unsigned int t200_dcch, t200_dcch_sapi3, t200_acch, t200_acch_sapi3;
+
+ /* set T200 for main and associated channel */
+ switch (lchan->type) {
+ case GSM_LCHAN_SDCCH:
+ t200_dcch = bts->t200_ms[T200_SDCCH];
+ t200_dcch_sapi3 = bts->t200_ms[T200_SDCCH_SAPI3];
+ t200_acch = bts->t200_ms[T200_SACCH_SDCCH];
+ t200_acch_sapi3 = bts->t200_ms[T200_SACCH_SDCCH];
+ break;
+ case GSM_LCHAN_TCH_F:
+ t200_dcch = bts->t200_ms[T200_FACCH_F];
+ t200_dcch_sapi3 = bts->t200_ms[T200_FACCH_F];
+ t200_acch = bts->t200_ms[T200_SACCH_TCH_SAPI0];
+ t200_acch_sapi3 = bts->t200_ms[T200_SACCH_TCH_SAPI3];
+ break;
+ case GSM_LCHAN_TCH_H:
+ t200_dcch = bts->t200_ms[T200_FACCH_H];
+ t200_dcch_sapi3 = bts->t200_ms[T200_FACCH_H];
+ t200_acch = bts->t200_ms[T200_SACCH_TCH_SAPI0];
+ t200_acch_sapi3 = bts->t200_ms[T200_SACCH_TCH_SAPI3];
+ break;
+ default:
+ return -1;
+ }
+
+ DEBUGP(DLLAPD, "%s: Setting T200 D0=%u, D3=%u, S0=%u, S3=%u"
+ "(all in ms)\n", gsm_lchan_name(lchan), t200_dcch,
+ t200_dcch_sapi3, t200_acch, t200_acch_sapi3);
+
+ dl_set_t200(&lc->lapdm_dcch.datalink[DL_SAPI0], t200_dcch);
+ dl_set_t200(&lc->lapdm_dcch.datalink[DL_SAPI3], t200_dcch_sapi3);
+ dl_set_t200(&lc->lapdm_acch.datalink[DL_SAPI0], t200_acch);
+ dl_set_t200(&lc->lapdm_acch.datalink[DL_SAPI3], t200_acch_sapi3);
+
+ return 0;
+}
+
+/* 3GPP TS 52.021 §8.11.1 Get Attributes has been received */
+static int oml_rx_get_attr(struct gsm_bts *bts, struct msgb *msg)
+{
+ struct abis_om_fom_hdr *foh = msgb_l3(msg);
+ struct tlv_parsed tp;
+ int rc;
+
+ if (!foh || !bts)
+ return -EINVAL;
+
+ abis_nm_debugp_foh(DOML, foh);
+ DEBUGPC(DOML, "Rx GET ATTR\n");
+
+ rc = oml_tlv_parse(&tp, foh->data, msgb_l3len(msg) - sizeof(*foh));
+ if (rc < 0) {
+ oml_tx_failure_event_rep(&bts->mo, OSMO_EVT_MAJ_UNSUP_ATTR, "Get Attribute parsing failure");
+ return oml_fom_ack_nack(msg, NM_NACK_INCORR_STRUCT);
+ }
+
+ if (!TLVP_PRES_LEN(&tp, NM_ATT_LIST_REQ_ATTR, 1)) {
+ LOGP(DOML, LOGL_ERROR, "O&M Get Attributes message without Attribute List?!\n");
+ oml_tx_failure_event_rep(&bts->mo, OSMO_EVT_MAJ_UNSUP_ATTR, "Get Attribute without Attribute List");
+ return oml_fom_ack_nack(msg, NM_NACK_INCORR_STRUCT);
+ }
+
+ rc = oml_tx_attr_resp(bts, foh, TLVP_VAL(&tp, NM_ATT_LIST_REQ_ATTR), TLVP_LEN(&tp, NM_ATT_LIST_REQ_ATTR));
+ if (rc < 0) {
+ LOGP(DOML, LOGL_ERROR, "responding to O&M Get Attributes message with NACK 0%x\n", -rc);
+ return oml_fom_ack_nack(msg, -rc);
+ }
+
+ return 0;
+}
+
+/* 8.6.1 Set BTS Attributes has been received */
+static int oml_rx_set_bts_attr(struct gsm_bts *bts, struct msgb *msg)
+{
+ struct abis_om_fom_hdr *foh = msgb_l3(msg);
+ struct tlv_parsed tp, *tp_merged;
+ int rc, i;
+ const uint8_t *payload;
+
+ abis_nm_debugp_foh(DOML, foh);
+ DEBUGPC(DOML, "Rx SET BTS ATTR\n");
+
+ rc = oml_tlv_parse(&tp, foh->data, msgb_l3len(msg) - sizeof(*foh));
+ if (rc < 0) {
+ oml_tx_failure_event_rep(&bts->mo, OSMO_EVT_MAJ_UNSUP_ATTR,
+ "New value for Attribute not supported");
+ return oml_fom_ack_nack(msg, NM_NACK_INCORR_STRUCT);
+ }
+
+ /* 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) {
+ oml_tx_failure_event_rep(&bts->mo, OSMO_EVT_WARN_SW_WARN,
+ "Given ARFCN %u is not supported",
+ arfcn);
+ LOGP(DOML, LOGL_NOTICE, "Given ARFCN %d is not supported.\n", arfcn);
+ return oml_fom_ack_nack(msg, NM_NACK_FREQ_NOTAVAIL);
+ }
+ }
+ /* 9.4.52 Starting Time */
+ if (TLVP_PRESENT(&tp, NM_ATT_START_TIME)) {
+ oml_tx_failure_event_rep(&bts->mo, OSMO_EVT_MAJ_UNSUP_ATTR,
+ "NM_ATT_START_TIME Attribute not "
+ "supported");
+ return oml_fom_ack_nack(msg, NM_NACK_SPEC_IMPL_NOTSUPP);
+ }
+
+ /* merge existing BTS attributes with new attributes */
+ tp_merged = osmo_tlvp_copy(bts->mo.nm_attr, bts);
+ osmo_tlvp_merge(tp_merged, &tp);
+
+ /* Ask BTS driver to validate new merged attributes */
+ rc = bts_model_check_oml(bts, foh->msg_type, bts->mo.nm_attr, tp_merged, bts);
+ if (rc < 0) {
+ talloc_free(tp_merged);
+ return oml_fom_ack_nack(msg, -rc);
+ }
+
+ /* Success: replace old BTS attributes with new */
+ talloc_free(bts->mo.nm_attr);
+ bts->mo.nm_attr = tp_merged;
+
+ /* ... and actually still parse them */
+
+ /* 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;
+ bts->interference.boundary[i] = -1 * boundary;
+ }
+ }
+ /* 9.4.24 Intave Parameter */
+ if (TLVP_PRES_LEN(&tp, NM_ATT_INTAVE_PARAM, 1))
+ bts->interference.intave = *TLVP_VAL(&tp, NM_ATT_INTAVE_PARAM);
+
+ /* 9.4.14 Connection Failure Criterion */
+ if (TLVP_PRES_LEN(&tp, NM_ATT_CONN_FAIL_CRIT, 1)) {
+ const uint8_t *val = TLVP_VAL(&tp, NM_ATT_CONN_FAIL_CRIT);
+
+ switch (val[0]) {
+ case 0xFF: /* Osmocom specific Extension of TS 12.21 */
+ LOGP(DOML, LOGL_NOTICE, "WARNING: Radio Link Timeout "
+ "explicitly disabled, only use this for lab testing!\n");
+ bts->radio_link_timeout = -1;
+ 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];
+ break;
+ }
+ /* fall-through */
+ case 0x02: /* Based on RXLEV/RXQUAL measurements */
+ default:
+ LOGP(DOML, LOGL_NOTICE, "Given Conn. Failure Criterion "
+ "not supported. Please use criterion 0x01 with "
+ "RADIO_LINK_TIMEOUT value of 4..64\n");
+ return oml_fom_ack_nack(msg, NM_NACK_PARAM_RANGE);
+ }
+ }
+
+ /* 9.4.53 T200 */
+ if (TLVP_PRES_LEN(&tp, NM_ATT_T200, ARRAY_SIZE(bts->t200_ms))) {
+ payload = TLVP_VAL(&tp, NM_ATT_T200);
+ for (i = 0; i < ARRAY_SIZE(bts->t200_ms); i++) {
+ uint32_t t200_ms = payload[i] * abis_nm_t200_ms[i];
+#if 0
+ bts->t200_ms[i] = t200_ms;
+ DEBUGP(DOML, "T200[%u]: OML=%u, mult=%u => %u ms\n",
+ i, payload[i], abis_nm_t200_mult[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? */
+ LOGP(DOML, LOGL_NOTICE, "Ignoring T200[%u] (%u ms) "
+ "as sent by BSC due to suspected LAPDm bug!\n",
+ i, t200_ms);
+#endif
+ }
+ }
+
+ /* 9.4.31 Maximum Timing Advance */
+ if (TLVP_PRES_LEN(&tp, NM_ATT_MAX_TA, 1))
+ bts->max_ta = *TLVP_VAL(&tp, NM_ATT_MAX_TA);
+
+ /* 9.4.39 Overload Period */
+ if (TLVP_PRES_LEN(&tp, NM_ATT_OVERL_PERIOD, 1))
+ bts->load.overload_period = *TLVP_VAL(&tp, NM_ATT_OVERL_PERIOD);
+
+ /* 9.4.12 CCCH Load Threshold */
+ if (TLVP_PRES_LEN(&tp, NM_ATT_CCCH_L_T, 1))
+ bts->load.ccch.load_ind_thresh = *TLVP_VAL(&tp, NM_ATT_CCCH_L_T);
+
+ /* 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);
+ }
+
+ /* 9.4.44 RACH Busy Threshold */
+ if (TLVP_PRES_LEN(&tp, NM_ATT_RACH_B_THRESH, 1)) {
+ int16_t thresh = *TLVP_VAL(&tp, NM_ATT_RACH_B_THRESH);
+ bts->load.rach.busy_thresh = -1 * thresh;
+ }
+
+ /* 9.4.45 RACH Load Averaging Slots */
+ if (TLVP_PRES_LEN(&tp, NM_ATT_LDAVG_SLOTS, 2)) {
+ bts->load.rach.averaging_slots =
+ ntohs(tlvp_val16_unal(&tp, NM_ATT_LDAVG_SLOTS));
+ }
+
+ /* 9.4.10 BTS Air Timer */
+ 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) {
+ LOGP(DOML, LOGL_NOTICE,
+ "T3105 must have a value != 0.\n");
+ return oml_fom_ack_nack(msg, NM_NACK_PARAM_RANGE);
+ }
+ bts->t3105_ms = t3105 * 10;
+ }
+
+ /* 9.4.37 NY1 */
+ if (TLVP_PRES_LEN(&tp, NM_ATT_NY1, 1))
+ bts->ny1 = *TLVP_VAL(&tp, NM_ATT_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))
+ bts->bsic = *TLVP_VAL(&tp, NM_ATT_BSIC);
+
+ /* call into BTS driver to apply new attributes to hardware */
+ return bts_model_apply_oml(bts, msg, tp_merged, NM_OC_BTS, bts);
+}
+
+/* 8.6.2 Set Radio Attributes has been received */
+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;
+
+ abis_nm_debugp_foh(DOML, foh);
+ DEBUGPC(DOML, "Rx SET RADIO CARRIER ATTR\n");
+
+ rc = oml_tlv_parse(&tp, foh->data, msgb_l3len(msg) - sizeof(*foh));
+ if (rc < 0) {
+ oml_tx_failure_event_rep(&trx->mo, OSMO_EVT_MAJ_UNSUP_ATTR,
+ "New value for Set Radio Attribute not"
+ " supported");
+ 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);
+ osmo_tlvp_merge(tp_merged, &tp);
+
+ /* Ask BTS driver to validate new merged attributes */
+ rc = bts_model_check_oml(trx->bts, foh->msg_type, trx->mo.nm_attr, tp_merged, trx);
+ if (rc < 0) {
+ talloc_free(tp_merged);
+ return oml_fom_ack_nack(msg, -rc);
+ }
+
+ /* Success: replace old BTS attributes with new */
+ talloc_free(trx->mo.nm_attr);
+ trx->mo.nm_attr = tp_merged;
+
+ /* ... and actually still parse them */
+
+ /* 9.4.47 RF Max Power Reduction */
+ if (TLVP_PRES_LEN(&tp, NM_ATT_RF_MAXPOWR_R, 1)) {
+ trx->max_power_red = *TLVP_VAL(&tp, NM_ATT_RF_MAXPOWR_R) * 2;
+ LOGP(DOML, LOGL_INFO, "Set RF Max Power Reduction = %d dBm\n",
+ trx->max_power_red);
+ }
+ /* 9.4.5 ARFCN List */
+#if 0
+ if (TLVP_PRESENT(&tp, NM_ATT_ARFCN_LIST)) {
+ uint8_t *value = TLVP_VAL(&tp, NM_ATT_ARFCN_LIST);
+ uint16_t _value;
+ uint16_t length = TLVP_LEN(&tp, NM_ATT_ARFCN_LIST);
+ uint16_t arfcn;
+ int i;
+ for (i = 0; i < length; i++) {
+ memcpy(&_value, value, 2);
+ arfcn = ntohs(_value);
+ value += 2;
+ if (arfcn > 1024)
+ return oml_fom_ack_nack(msg, NM_NACK_FREQ_NOTAVAIL);
+ trx->arfcn_list[i] = arfcn;
+ LOGP(DOML, LOGL_INFO, " ARFCN list = %d\n", trx->arfcn_list[i]);
+ }
+ trx->arfcn_num = length;
+ } else
+ trx->arfcn_num = 0;
+#else
+ if (trx != trx->bts->c0 && TLVP_PRESENT(&tp, NM_ATT_ARFCN_LIST)) {
+ const uint8_t *value = TLVP_VAL(&tp, NM_ATT_ARFCN_LIST);
+ uint16_t _value;
+ uint16_t length = TLVP_LEN(&tp, NM_ATT_ARFCN_LIST);
+ uint16_t arfcn;
+ if (length != 2) {
+ LOGP(DOML, LOGL_ERROR, "Expecting only one ARFCN, "
+ "because hopping not supported\n");
+ return oml_fom_ack_nack(msg, NM_NACK_MSGINCONSIST_PHYSCFG);
+ }
+ memcpy(&_value, value, 2);
+ arfcn = ntohs(_value);
+ value += 2;
+ if (arfcn > 1024) {
+ oml_tx_failure_event_rep(&trx->bts->mo,
+ OSMO_EVT_WARN_SW_WARN,
+ "Given ARFCN %u is unsupported",
+ arfcn);
+ LOGP(DOML, LOGL_NOTICE,
+ "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);
+}
+
+static int conf_lchans(struct gsm_bts_trx_ts *ts)
+{
+ enum gsm_phys_chan_config pchan = ts->pchan;
+
+ /* RSL_MT_IPAC_PDCH_ACT style dyn PDCH */
+ if (pchan == GSM_PCHAN_TCH_F_PDCH)
+ pchan = ts->flags & TS_F_PDCH_ACTIVE? GSM_PCHAN_PDCH
+ : GSM_PCHAN_TCH_F;
+
+ /* Osmocom RSL CHAN ACT style dyn TS */
+ if (pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH) {
+ pchan = ts->dyn.pchan_is;
+
+ /* If the dyn TS doesn't have a pchan yet, do nothing. */
+ if (pchan == GSM_PCHAN_NONE)
+ return 0;
+ }
+
+ return conf_lchans_as_pchan(ts, pchan);
+}
+
+int conf_lchans_as_pchan(struct gsm_bts_trx_ts *ts,
+ enum gsm_phys_chan_config pchan)
+{
+ struct gsm_lchan *lchan;
+ unsigned int i;
+
+ 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;
+ }
+ }
+ /* fallthrough */
+ case GSM_PCHAN_CCCH:
+ lchan = &ts->lchan[CCCH_LCHAN];
+ lchan->type = GSM_LCHAN_CCCH;
+ break;
+ case GSM_PCHAN_TCH_F:
+ lchan = &ts->lchan[0];
+ lchan->type = GSM_LCHAN_TCH_F;
+ break;
+ case GSM_PCHAN_TCH_H:
+ for (i = 0; i < 2; i++) {
+ lchan = &ts->lchan[i];
+ lchan->type = GSM_LCHAN_TCH_H;
+ }
+ 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;
+ }
+ }
+ break;
+ case GSM_PCHAN_PDCH:
+ lchan = &ts->lchan[0];
+ lchan->type = GSM_LCHAN_PDTCH;
+ 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;
+}
+
+/* 8.6.3 Set Channel Attributes has been received */
+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;
+
+ abis_nm_debugp_foh(DOML, foh);
+ DEBUGPC(DOML, "Rx SET CHAN ATTR\n");
+
+ rc = oml_tlv_parse(&tp, foh->data, msgb_l3len(msg) - sizeof(*foh));
+ if (rc < 0) {
+ oml_tx_failure_event_rep(&ts->mo, OSMO_EVT_MAJ_UNSUP_ATTR,
+ "New value for Set Channel Attribute "
+ "not supported");
+ return oml_fom_ack_nack(msg, NM_NACK_INCORR_STRUCT);
+ }
+
+ /* 9.4.21 HSN... */
+ /* 9.4.27 MAIO */
+ if (TLVP_PRESENT(&tp, NM_ATT_HSN) || TLVP_PRESENT(&tp, NM_ATT_MAIO)) {
+ LOGP(DOML, LOGL_NOTICE, "SET CHAN ATTR: Frequency hopping not supported.\n");
+ return oml_fom_ack_nack(msg, NM_NACK_SPEC_IMPL_NOTSUPP);
+ }
+
+ /* 9.4.52 Starting Time */
+ if (TLVP_PRESENT(&tp, NM_ATT_START_TIME)) {
+ LOGP(DOML, LOGL_NOTICE, "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);
+ osmo_tlvp_merge(tp_merged, &tp);
+
+ /* Call into BTS driver to check attribute values */
+ rc = bts_model_check_oml(bts, foh->msg_type, ts->mo.nm_attr, tp_merged, ts);
+ if (rc < 0) {
+ LOGP(DOML, LOGL_ERROR, "SET CHAN ATTR: invalid attribute value, rc=%d\n", rc);
+ talloc_free(tp_merged);
+ /* Send NACK */
+ return oml_fom_ack_nack(msg, -rc);
+ }
+
+ /* Success: replace old BTS 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) {
+ LOGP(DOML, LOGL_ERROR, "SET CHAN ATTR: invalid Chan Comb 0x%x"
+ " (pchan=%s, conf_lchans()->%d)\n",
+ comb, gsm_pchan_name(ts->pchan), rc);
+ talloc_free(tp_merged);
+ /* Send NACK */
+ return oml_fom_ack_nack(msg, -rc);
+ }
+ }
+
+ /* 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);
+ }
+ LOGP(DOML, LOGL_INFO, "%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);
+}
+
+/* 8.9.2 Opstart has been received */
+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;
+
+ abis_nm_debugp_foh(DOML, foh);
+ DEBUGPC(DOML, "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);
+
+ /* Step 2: Do some global dependency/consistency checking */
+ if (mo->nm_state.operational == NM_OPSTATE_ENABLED) {
+ DEBUGP(DOML, "... automatic ACK, OP state already was Enabled\n");
+ return oml_mo_opstart_ack(mo);
+ }
+
+ /* Step 3: Ask BTS driver to apply the opstart */
+ return bts_model_opstart(bts, mo, obj);
+}
+
+static int oml_rx_chg_adm_state(struct gsm_bts *bts, struct msgb *msg)
+{
+ struct abis_om_fom_hdr *foh = msgb_l3(msg);
+ struct tlv_parsed tp;
+ struct gsm_abis_mo *mo;
+ uint8_t adm_state;
+ void *obj;
+ int rc;
+
+ abis_nm_debugp_foh(DOML, foh);
+ DEBUGPC(DOML, "Rx CHG ADM STATE\n");
+
+ rc = oml_tlv_parse(&tp, foh->data, msgb_l3len(msg) - sizeof(*foh));
+ if (rc < 0) {
+ LOGP(DOML, LOGL_ERROR, "Rx CHG ADM STATE: error during TLV parse\n");
+ return oml_fom_ack_nack(msg, NM_NACK_INCORR_STRUCT);
+ }
+
+ if (!TLVP_PRESENT(&tp, NM_ATT_ADM_STATE)) {
+ LOGP(DOML, LOGL_ERROR, "Rx CHG ADM STATE: no ADM state attribute\n");
+ return oml_fom_ack_nack(msg, NM_NACK_INCORR_STRUCT);
+ }
+
+ 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);
+
+ /* Step 2: Do some global dependency/consistency checking */
+ if (mo->nm_state.administrative == adm_state)
+ LOGP(DOML, LOGL_NOTICE,
+ "ADM state already was %s\n",
+ 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);
+}
+
+/* Check and report if the BTS number received via OML is incorrect:
+ according to 3GPP TS 52.021 §9.3 BTS number is used to distinguish between different BTS of the same Site Manager.
+ As we always have only single BTS per Site Manager (in case of Abis/IP with each BTS having dedicated OML connection
+ to BSC), the only valid values are 0 and 0xFF (means all BTS' of a given Site Manager). */
+static inline bool report_bts_number_incorrect(struct gsm_bts *bts, const struct abis_om_fom_hdr *foh, bool is_formatted)
+{
+ struct gsm_bts_trx *trx;
+ struct gsm_abis_mo *mo = &bts->mo;
+ const char *form = is_formatted ?
+ "Unexpected BTS %d in formatted O&M %s (exp. 0 or 0xFF)" :
+ "Unexpected BTS %d in manufacturer O&M %s (exp. 0 or 0xFF)";
+
+ if (foh->obj_inst.bts_nr != 0 && foh->obj_inst.bts_nr != 0xff) {
+ LOGP(DOML, LOGL_ERROR, form, foh->obj_inst.bts_nr, get_value_string(abis_nm_msgtype_names,
+ foh->msg_type));
+ LOGPC(DOML, LOGL_ERROR, "\n");
+ trx = gsm_bts_trx_num(bts, foh->obj_inst.trx_nr);
+ if (trx) {
+ trx->mo.obj_inst.bts_nr = 0;
+ trx->mo.obj_inst.trx_nr = foh->obj_inst.trx_nr;
+ trx->mo.obj_inst.ts_nr = 0xff;
+ mo = &trx->mo;
+ }
+ oml_tx_failure_event_rep(mo, OSMO_EVT_MAJ_UKWN_MSG, form, foh->obj_inst.bts_nr,
+ get_value_string(abis_nm_msgtype_names, foh->msg_type));
+
+ return true;
+ }
+
+ return false;
+}
+
+static int down_fom(struct gsm_bts *bts, struct msgb *msg)
+{
+ struct abis_om_fom_hdr *foh = msgb_l3(msg);
+ struct gsm_bts_trx *trx;
+ int ret;
+
+ if (msgb_l2len(msg) < sizeof(*foh)) {
+ LOGP(DOML, LOGL_NOTICE, "Formatted O&M message too short\n");
+ trx = gsm_bts_trx_num(bts, foh->obj_inst.trx_nr);
+ if (trx) {
+ trx->mo.obj_inst.bts_nr = 0;
+ trx->mo.obj_inst.trx_nr = foh->obj_inst.trx_nr;
+ trx->mo.obj_inst.ts_nr = 0xff;
+ oml_tx_failure_event_rep(&trx->mo, OSMO_EVT_MAJ_UKWN_MSG,
+ "Formatted O&M message too short");
+ }
+ return -EIO;
+ }
+
+ if (report_bts_number_incorrect(bts, foh, true))
+ return oml_fom_ack_nack(msg, NM_NACK_BTSNR_UNKN);
+
+ switch (foh->msg_type) {
+ case NM_MT_SET_BTS_ATTR:
+ ret = oml_rx_set_bts_attr(bts, msg);
+ break;
+ case NM_MT_SET_RADIO_ATTR:
+ trx = gsm_bts_trx_num(bts, foh->obj_inst.trx_nr);
+ if (!trx)
+ return oml_fom_ack_nack(msg, NM_NACK_TRXNR_UNKN);
+ ret = oml_rx_set_radio_attr(trx, msg);
+ break;
+ case NM_MT_SET_CHAN_ATTR:
+ trx = gsm_bts_trx_num(bts, foh->obj_inst.trx_nr);
+ if (!trx)
+ return oml_fom_ack_nack(msg, NM_NACK_TRXNR_UNKN);
+ if (foh->obj_inst.ts_nr >= ARRAY_SIZE(trx->ts))
+ return oml_fom_ack_nack(msg, NM_NACK_OBJINST_UNKN);
+ ret = oml_rx_set_chan_attr(&trx->ts[foh->obj_inst.ts_nr], msg);
+ break;
+ case NM_MT_OPSTART:
+ ret = oml_rx_opstart(bts, msg);
+ break;
+ case NM_MT_CHG_ADM_STATE:
+ ret = oml_rx_chg_adm_state(bts, msg);
+ break;
+ case NM_MT_IPACC_SET_ATTR:
+ ret = oml_ipa_set_attr(bts, msg);
+ break;
+ case NM_MT_GET_ATTR:
+ ret = oml_rx_get_attr(bts, msg);
+ break;
+ default:
+ LOGP(DOML, LOGL_INFO, "unknown Formatted O&M msg_type 0x%02x\n",
+ foh->msg_type);
+ trx = gsm_bts_trx_num(bts, foh->obj_inst.trx_nr);
+ if (trx) {
+ trx->mo.obj_inst.bts_nr = 0;
+ trx->mo.obj_inst.trx_nr = foh->obj_inst.trx_nr;
+ trx->mo.obj_inst.ts_nr = 0xff;
+ oml_tx_failure_event_rep(&trx->mo, OSMO_EVT_MAJ_UKWN_MSG,
+ "unknown Formatted O&M "
+ "msg_type 0x%02x",
+ foh->msg_type);
+ } else
+ oml_tx_failure_event_rep(&bts->mo, OSMO_EVT_MAJ_UKWN_MSG,
+ "unknown Formatted O&M "
+ "msg_type 0x%02x",
+ foh->msg_type);
+ ret = oml_fom_ack_nack(msg, NM_NACK_MSGTYPE_INVAL);
+ }
+
+ return ret;
+}
+
+/*
+ * manufacturer related messages
+ */
+
+static int oml_ipa_mo_set_attr_nse(void *obj, struct tlv_parsed *tp)
+{
+ struct gsm_bts *bts = container_of(obj, struct gsm_bts, gprs.nse);
+
+ if (TLVP_PRES_LEN(tp, NM_ATT_IPACC_NSEI, 2))
+ bts->gprs.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,
+ TLVP_VAL(tp, NM_ATT_IPACC_NS_CFG), 7);
+ }
+
+ if (TLVP_PRES_LEN(tp, NM_ATT_IPACC_BSSGP_CFG, 11)) {
+ memcpy(&bts->gprs.cell.timer,
+ TLVP_VAL(tp, NM_ATT_IPACC_BSSGP_CFG), 11);
+ }
+
+ 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)
+{
+ struct gsm_bts *bts = container_of(obj, struct gsm_bts, gprs.cell);
+ struct gprs_rlc_cfg *rlcc = &bts->gprs.cell.rlc_cfg;
+ const uint8_t *cur;
+ uint16_t _cur_s;
+
+ if (TLVP_PRES_LEN(tp, NM_ATT_IPACC_RAC, 1))
+ bts->gprs.rac = *TLVP_VAL(tp, NM_ATT_IPACC_RAC);
+
+ if (TLVP_PRES_LEN(tp, NM_ATT_IPACC_GPRS_PAGING_CFG, 2)) {
+ cur = TLVP_VAL(tp, NM_ATT_IPACC_GPRS_PAGING_CFG);
+ rlcc->paging.repeat_time = cur[0] * 50;
+ rlcc->paging.repeat_count = cur[1];
+ }
+
+ if (TLVP_PRES_LEN(tp, NM_ATT_IPACC_BVCI, 2))
+ bts->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);
+ rlcc->parameter[RLC_T3142] = cur[0];
+ rlcc->parameter[RLC_T3169] = cur[1];
+ rlcc->parameter[RLC_T3191] = cur[2];
+ rlcc->parameter[RLC_T3193] = cur[3];
+ rlcc->parameter[RLC_T3195] = cur[4];
+ rlcc->parameter[RLC_N3101] = cur[5];
+ rlcc->parameter[RLC_N3103] = cur[6];
+ rlcc->parameter[RLC_N3105] = cur[7];
+ rlcc->parameter[CV_COUNTDOWN] = cur[8];
+ }
+
+ if (TLVP_PRES_LEN(tp, NM_ATT_IPACC_CODING_SCHEMES, 2)) {
+ int i;
+ rlcc->cs_mask = 0;
+ cur = TLVP_VAL(tp, NM_ATT_IPACC_CODING_SCHEMES);
+
+ for (i = 0; i < 4; i++) {
+ if (cur[0] & (1 << i))
+ rlcc->cs_mask |= (1 << (GPRS_CS1+i));
+ }
+ if (cur[0] & 0x80)
+ rlcc->cs_mask |= (1 << GPRS_MCS9);
+ for (i = 0; i < 8; i++) {
+ if (cur[1] & (1 << i))
+ rlcc->cs_mask |= (1 << (GPRS_MCS1+i));
+ }
+ }
+
+ if (TLVP_PRES_LEN(tp, NM_ATT_IPACC_RLC_CFG_2, 5)) {
+ cur = TLVP_VAL(tp, NM_ATT_IPACC_RLC_CFG_2);
+ memcpy(&_cur_s, cur, 2);
+ rlcc->parameter[T_DL_TBF_EXT] = ntohs(_cur_s) * 10;
+ cur += 2;
+ memcpy(&_cur_s, cur, 2);
+ rlcc->parameter[T_UL_TBF_EXT] = ntohs(_cur_s) * 10;
+ cur += 2;
+ rlcc->initial_cs = *cur;
+ }
+
+ if (TLVP_PRES_LEN(tp, NM_ATT_IPACC_RLC_CFG_3, 1)) {
+ rlcc->initial_mcs = *TLVP_VAL(tp, NM_ATT_IPACC_RLC_CFG_3);
+ }
+
+ 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)
+{
+ if (TLVP_PRES_LEN(tp, NM_ATT_IPACC_NSVCI, 2))
+ nsvc->nsvci = ntohs(tlvp_val16_unal(tp, NM_ATT_IPACC_NSVCI));
+
+ if (TLVP_PRES_LEN(tp, NM_ATT_IPACC_NS_LINK_CFG, 8)) {
+ const uint8_t *cur = TLVP_VAL(tp, NM_ATT_IPACC_NS_LINK_CFG);
+ uint16_t _cur_s;
+ uint32_t _cur_l;
+
+ memcpy(&_cur_s, cur, 2);
+ nsvc->remote_port = ntohs(_cur_s);
+ cur += 2;
+ memcpy(&_cur_l, cur, 4);
+ nsvc->remote_ip = ntohl(_cur_l);
+ cur += 4;
+ memcpy(&_cur_s, cur, 2);
+ nsvc->local_port = ntohs(_cur_s);
+ }
+
+ osmo_signal_dispatch(SS_GLOBAL, S_NEW_NSVC_ATTR, nsvc);
+
+ return 0;
+}
+
+static int oml_ipa_mo_set_attr(struct gsm_bts *bts, struct gsm_abis_mo *mo,
+ void *obj, struct tlv_parsed *tp)
+{
+ int rc;
+
+ 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;
+ }
+
+ return rc;
+}
+
+static int oml_ipa_set_attr(struct gsm_bts *bts, struct msgb *msg)
+{
+ struct abis_om_fom_hdr *foh = msgb_l3(msg);
+ struct gsm_abis_mo *mo;
+ struct tlv_parsed tp;
+ void *obj;
+ int rc;
+
+ abis_nm_debugp_foh(DOML, foh);
+ DEBUGPC(DOML, "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);
+ oml_tx_failure_event_rep(mo, 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);
+
+ rc = oml_ipa_mo_set_attr(bts, mo, obj, &tp);
+
+ return oml_fom_ack_nack(msg, rc);
+}
+
+static int rx_oml_ipa_rsl_connect(struct gsm_bts_trx *trx, struct msgb *msg,
+ 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);
+ struct in_addr in;
+ int rc;
+
+ uint8_t stream_id = 0;
+
+ 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)) {
+ port = ntohs(tlvp_val16_unal(tp, NM_ATT_IPACC_DST_IP_PORT));
+ }
+ if (TLVP_PRES_LEN(tp, NM_ATT_IPACC_STREAM_ID, 1)) {
+ stream_id = *TLVP_VAL(tp, NM_ATT_IPACC_STREAM_ID);
+ }
+
+ in.s_addr = htonl(ip);
+ LOGP(DOML, LOGL_INFO, "Rx IPA RSL CONNECT IP=%s PORT=%u STREAM=0x%02x\n",
+ inet_ntoa(in), port, stream_id);
+
+ if (trx->bts->variant == BTS_OSMO_OMLDUMMY) {
+ rc = 0;
+ LOGP(DOML, LOGL_NOTICE, "Not connecting RSL in OML-DUMMY!\n");
+ } 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, "Error in abis_open(RSL): %d\n", rc);
+ return oml_fom_ack_nack(msg, NM_NACK_CANT_PERFORM);
+ }
+
+ return oml_fom_ack_nack(msg, 0);
+}
+
+static int down_mom(struct gsm_bts *bts, struct msgb *msg)
+{
+ struct abis_om_hdr *oh = msgb_l2(msg);
+ struct abis_om_fom_hdr *foh;
+ struct gsm_bts_trx *trx;
+ uint8_t idstrlen = oh->data[0];
+ struct tlv_parsed tp;
+ int ret;
+
+ if (msgb_l2len(msg) < sizeof(*foh)) {
+ LOGP(DOML, LOGL_NOTICE, "Manufacturer O&M message too short\n");
+ return -EIO;
+ }
+
+ if (strncmp((char *)&oh->data[1], abis_nm_ipa_magic, idstrlen)) {
+ LOGP(DOML, LOGL_ERROR, "Manufacturer OML message != ipaccess not supported\n");
+ return -EINVAL;
+ }
+
+ msg->l3h = oh->data + 1 + idstrlen;
+ foh = (struct abis_om_fom_hdr *) msg->l3h;
+
+ if (report_bts_number_incorrect(bts, foh, false))
+ return oml_fom_ack_nack(msg, NM_NACK_BTSNR_UNKN);
+
+ ret = oml_tlv_parse(&tp, foh->data, oh->length - sizeof(*foh));
+ if (ret < 0) {
+ LOGP(DOML, LOGL_ERROR, "TLV parse error %d\n", ret);
+ return oml_fom_ack_nack(msg, NM_NACK_BTSNR_UNKN);
+ }
+
+ abis_nm_debugp_foh(DOML, foh);
+ DEBUGPC(DOML, "Rx IPACCESS(0x%02x): ", foh->msg_type);
+
+ 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);
+ break;
+ case NM_MT_IPACC_SET_ATTR:
+ ret = oml_ipa_set_attr(bts, msg);
+ break;
+ default:
+ LOGP(DOML, LOGL_INFO, "Manufacturer Formatted O&M msg_type 0x%02x\n",
+ foh->msg_type);
+ ret = oml_fom_ack_nack(msg, NM_NACK_MSGTYPE_INVAL);
+ }
+
+ return ret;
+}
+
+/* incoming OML message from BSC */
+int down_oml(struct gsm_bts *bts, struct msgb *msg)
+{
+ struct abis_om_hdr *oh = msgb_l2(msg);
+ int ret = 0;
+
+ if (msgb_l2len(msg) < 1) {
+ LOGP(DOML, LOGL_NOTICE, "OML message too short\n");
+ msgb_free(msg);
+ return -EIO;
+ }
+ msg->l3h = (unsigned char *)oh + sizeof(*oh);
+
+ switch (oh->mdisc) {
+ case ABIS_OM_MDISC_FOM:
+ if (msgb_l2len(msg) < sizeof(*oh)) {
+ LOGP(DOML, LOGL_NOTICE, "Formatted O&M message too short\n");
+ ret = -EIO;
+ break;
+ }
+ ret = down_fom(bts, msg);
+ break;
+ case ABIS_OM_MDISC_MANUF:
+ if (msgb_l2len(msg) < sizeof(*oh)) {
+ LOGP(DOML, LOGL_NOTICE, "Manufacturer O&M message too short\n");
+ ret = -EIO;
+ break;
+ }
+ ret = down_mom(bts, msg);
+ break;
+ default:
+ LOGP(DOML, LOGL_NOTICE, "unknown OML msg_discr 0x%02x\n",
+ oh->mdisc);
+ ret = -EINVAL;
+ }
+
+ msgb_free(msg);
+
+ return ret;
+}
+
+static int handle_fail_sig(unsigned int subsys, unsigned int signal, void *handle,
+ void *signal_data)
+{
+ if (signal_data)
+ oml_tx_failure_event_rep(handle, signal, "%s", signal_data);
+ else
+ oml_tx_failure_event_rep(handle, signal, "");
+
+ return 0;
+}
+
+int oml_init(struct gsm_abis_mo *mo)
+{
+ 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);
+ osmo_signal_register_handler(SS_FAIL, handle_fail_sig, mo);
+
+ return 0;
+}
diff --git a/src/common/paging.c b/src/common/paging.c
new file mode 100644
index 00000000..aa604e72
--- /dev/null
+++ b/src/common/paging.c
@@ -0,0 +1,630 @@
+/* Paging message encoding + queue management */
+
+/* (C) 2011-2012 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+/* TODO:
+ * eMLPP priprity
+ * add P1/P2/P3 rest octets
+ */
+
+#include <stdlib.h>
+#include <stdint.h>
+#include <errno.h>
+#include <string.h>
+#include <time.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/linuxlist.h>
+
+#include <osmocom/gsm/protocol/gsm_04_08.h>
+#include <osmocom/gsm/gsm0502.h>
+#include <osmocom/gsm/gsm48.h>
+
+#include <osmo-bts/bts.h>
+#include <osmo-bts/rsl.h>
+#include <osmo-bts/logging.h>
+#include <osmo-bts/paging.h>
+#include <osmo-bts/signal.h>
+#include <osmo-bts/pcu_if.h>
+
+#define MAX_PAGING_BLOCKS_CCCH 9
+#define MAX_BS_PA_MFRMS 9
+
+enum paging_record_type {
+ PAGING_RECORD_PAGING,
+ PAGING_RECORD_IMM_ASS
+};
+
+struct paging_record {
+ struct llist_head list;
+ enum paging_record_type type;
+ union {
+ struct {
+ time_t expiration_time;
+ uint8_t chan_needed;
+ uint8_t identity_lv[9];
+ } paging;
+ struct {
+ uint8_t msg[GSM_MACBLOCK_LEN];
+ } imm_ass;
+ } u;
+};
+
+struct paging_state {
+ struct gsm_bts *bts;
+
+ /* parameters taken / interpreted from BCCH/CCCH configuration */
+ struct gsm48_control_channel_descr chan_desc;
+
+ /* configured otherwise */
+ unsigned int paging_lifetime; /* in seconds */
+ unsigned int num_paging_max;
+
+ /* 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];
+};
+
+unsigned int paging_get_lifetime(struct paging_state *ps)
+{
+ return ps->paging_lifetime;
+}
+
+unsigned int paging_get_queue_max(struct paging_state *ps)
+{
+ return ps->num_paging_max;
+}
+
+void paging_set_lifetime(struct paging_state *ps, unsigned int lifetime)
+{
+ ps->paging_lifetime = lifetime;
+}
+
+void paging_set_queue_max(struct paging_state *ps, unsigned int queue_max)
+{
+ ps->num_paging_max = queue_max;
+}
+
+static int tmsi_mi_to_uint(uint32_t *out, const uint8_t *tmsi_lv)
+{
+ if (tmsi_lv[0] < 5)
+ return -EINVAL;
+ if ((tmsi_lv[1] & 7) != GSM_MI_TYPE_TMSI)
+ return -EINVAL;
+
+ *out = *((uint32_t *)(tmsi_lv+2));
+
+ return 0;
+}
+
+/* paging block numbers in a simple non-combined CCCH */
+static const uint8_t block_by_tdma51[51] = {
+ 255, 255, /* FCCH, SCH */
+ 255, 255, 255, 255, /* BCCH */
+ 0, 0, 0, 0, /* B0(6..9) */
+ 255, 255, /* FCCH, SCH */
+ 1, 1, 1, 1, /* B1(12..15) */
+ 2, 2, 2, 2, /* B2(16..19) */
+ 255, 255, /* FCCH, SCH */
+ 3, 3, 3, 3, /* B3(22..25) */
+ 4, 4, 4, 4, /* B3(26..29) */
+ 255, 255, /* FCCH, SCH */
+ 5, 5, 5, 5, /* B3(32..35) */
+ 6, 6, 6, 6, /* B3(36..39) */
+ 255, 255, /* FCCH, SCH */
+ 7, 7, 7, 7, /* B3(42..45) */
+ 8, 8, 8, 8, /* B3(46..49) */
+ 255, /* empty */
+};
+
+/* get the paging block number _within_ current 51 multiframe */
+static int get_pag_idx_n(struct paging_state *ps, struct gsm_time *gt)
+{
+ int blk_n = block_by_tdma51[gt->t3];
+ int blk_idx;
+
+ if (blk_n == 255)
+ return -EINVAL;
+
+ blk_idx = blk_n - ps->chan_desc.bs_ag_blks_res;
+ if (blk_idx < 0)
+ return -EINVAL;
+
+ return blk_idx;
+}
+
+/* get paging block index over multiple 51 multiframes */
+static int get_pag_subch_nr(struct paging_state *ps, struct gsm_time *gt)
+{
+ int pag_idx = get_pag_idx_n(ps, gt);
+ unsigned int n_pag_blks_51 = gsm0502_get_n_pag_blocks(&ps->chan_desc);
+ unsigned int mfrm_part;
+
+ if (pag_idx < 0)
+ return pag_idx;
+
+ mfrm_part = ((gt->fn / 51) % (ps->chan_desc.bs_pa_mfrms+2)) * n_pag_blks_51;
+
+ return pag_idx + mfrm_part;
+}
+
+int paging_buffer_space(struct paging_state *ps)
+{
+ if (ps->num_paging >= ps->num_paging_max)
+ return 0;
+ else
+ return ps->num_paging_max - ps->num_paging;
+}
+
+/* Add an identity to the paging queue */
+int paging_add_identity(struct paging_state *ps, uint8_t paging_group,
+ const uint8_t *identity_lv, uint8_t chan_needed)
+{
+ struct llist_head *group_q = &ps->paging_queue[paging_group];
+ int blocks = gsm48_number_of_paging_subchannels(&ps->chan_desc);
+ struct paging_record *pr;
+
+ rate_ctr_inc2(ps->bts->ctrs, BTS_CTR_PAGING_RCVD);
+
+ if (paging_group >= blocks) {
+ LOGP(DPAG, LOGL_ERROR, "BSC Send PAGING for group %u, but number of paging "
+ "sub-channels is only %u\n", paging_group, blocks);
+ rate_ctr_inc2(ps->bts->ctrs, BTS_CTR_PAGING_DROP);
+ return -EINVAL;
+ }
+
+ if (ps->num_paging >= ps->num_paging_max) {
+ LOGP(DPAG, LOGL_NOTICE, "Dropping paging, queue full (%u)\n",
+ ps->num_paging);
+ rate_ctr_inc2(ps->bts->ctrs, BTS_CTR_PAGING_DROP);
+ return -ENOSPC;
+ }
+
+ /* Check if we already have this identity */
+ llist_for_each_entry(pr, group_q, list) {
+ if (pr->type != PAGING_RECORD_PAGING)
+ continue;
+ if (identity_lv[0] == pr->u.paging.identity_lv[0] &&
+ !memcmp(identity_lv+1, pr->u.paging.identity_lv+1,
+ identity_lv[0])) {
+ LOGP(DPAG, LOGL_INFO, "Ignoring duplicate paging\n");
+ pr->u.paging.expiration_time =
+ time(NULL) + ps->paging_lifetime;
+ return -EEXIST;
+ }
+ }
+
+ pr = talloc_zero(ps, struct paging_record);
+ if (!pr)
+ return -ENOMEM;
+ pr->type = PAGING_RECORD_PAGING;
+
+ if (*identity_lv + 1 > sizeof(pr->u.paging.identity_lv)) {
+ talloc_free(pr);
+ return -E2BIG;
+ }
+
+ 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);
+
+ /* enqueue the new identity to the HEAD of the queue,
+ * to ensure it will be paged quickly at least once. */
+ llist_add(&pr->list, group_q);
+ ps->num_paging++;
+
+ 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)
+{
+ struct llist_head *group_q;
+ struct paging_record *pr;
+ uint16_t imsi, paging_group;
+
+ if (len != GSM_MACBLOCK_LEN + 3) {
+ LOGP(DPAG, LOGL_ERROR, "IMM.ASS invalid length %d\n", len);
+ return -EINVAL;
+ }
+ len -= 3;
+
+ imsi = 100 * ((*(data++)) - '0');
+ imsi += 10 * ((*(data++)) - '0');
+ imsi += (*(data++)) - '0';
+ 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;
+
+ LOGP(DPAG, LOGL_INFO, "Add IMM.ASS to queue (group=%u)\n",
+ paging_group);
+ memcpy(pr->u.imm_ass.msg, data, GSM_MACBLOCK_LEN);
+
+ /* enqueue the new message to the HEAD of the queue */
+ llist_add(&pr->list, group_q);
+
+ return 0;
+}
+
+#define L2_PLEN(len) (((len - 1) << 2) | 0x01)
+
+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)
+{
+ struct gsm48_paging1 *pt1 = (struct gsm48_paging1 *) out_buf;
+ uint8_t *cur;
+
+ memset(out_buf, 0, sizeof(*pt1));
+
+ 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);
+
+ return cur - out_buf;
+}
+
+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)
+{
+ struct gsm48_paging2 *pt2 = (struct gsm48_paging2 *) out_buf;
+ uint8_t *cur;
+
+ memset(out_buf, 0, sizeof(*pt2));
+
+ pt2->proto_discr = GSM48_PDISC_RR;
+ pt2->msg_type = GSM48_MT_RR_PAG_REQ_2;
+ pt2->pag_mode = GSM48_PM_NORMAL;
+ pt2->cneed1 = cneed1;
+ pt2->cneed2 = cneed2;
+ tmsi_mi_to_uint(&pt2->tmsi1, tmsi1_lv);
+ tmsi_mi_to_uint(&pt2->tmsi2, tmsi2_lv);
+ cur = out_buf + sizeof(*pt2);
+
+ if (identity3_lv)
+ cur = tlv_put(pt2->data, GSM48_IE_MOBILE_ID, identity3_lv[0], identity3_lv+1);
+
+ pt2->l2_plen = L2_PLEN(cur - out_buf);
+
+ return cur - out_buf;
+}
+
+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)
+{
+ struct gsm48_paging3 *pt3 = (struct gsm48_paging3 *) out_buf;
+
+ memset(out_buf, 0, sizeof(*pt3));
+
+ pt3->proto_discr = GSM48_PDISC_RR;
+ pt3->msg_type = GSM48_MT_RR_PAG_REQ_3;
+ pt3->pag_mode = GSM48_PM_NORMAL;
+ pt3->cneed1 = cneed1;
+ pt3->cneed2 = cneed2;
+ tmsi_mi_to_uint(&pt3->tmsi1, tmsi1_lv);
+ tmsi_mi_to_uint(&pt3->tmsi2, tmsi2_lv);
+ tmsi_mi_to_uint(&pt3->tmsi3, tmsi3_lv);
+ tmsi_mi_to_uint(&pt3->tmsi4, tmsi4_lv);
+
+ /* 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;
+
+ return 21;
+}
+
+static const uint8_t empty_id_lv[] = { 0x01, 0xF0 };
+
+static struct paging_record *dequeue_pr(struct llist_head *group_q)
+{
+ struct paging_record *pr;
+
+ pr = llist_entry(group_q->next, struct paging_record, list);
+ llist_del(&pr->list);
+
+ return pr;
+}
+
+static int pr_is_imsi(struct paging_record *pr)
+{
+ if ((pr->u.paging.identity_lv[1] & 7) == GSM_MI_TYPE_IMSI)
+ return 1;
+ else
+ return 0;
+}
+
+static void sort_pr_tmsi_imsi(struct paging_record *pr[], unsigned int n)
+{
+ int i, j;
+ struct paging_record *t;
+
+ if (n < 2)
+ return;
+
+ /* simple bubble sort */
+ for (i = n-2; i >= 0; i--) {
+ for (j=0; j<=i ; j++) {
+ if (pr_is_imsi(pr[j]) > pr_is_imsi(pr[j+1])) {
+ t = pr[j];
+ pr[j] = pr[j+1];
+ pr[j+1] = t;
+ }
+ }
+ }
+}
+
+/* generate paging message for given gsm time */
+int paging_gen_msg(struct paging_state *ps, uint8_t *out_buf, struct gsm_time *gt,
+ int *is_empty)
+{
+ struct llist_head *group_q;
+ int group;
+ int len;
+
+ *is_empty = 0;
+ ps->bts->load.ccch.pch_total += 1;
+
+ group = get_pag_subch_nr(ps, gt);
+ if (group < 0) {
+ LOGP(DPAG, LOGL_ERROR,
+ "Paging called for GSM wrong time: FN %d/%d/%d/%d.\n",
+ gt->fn, gt->t1, gt->t2, gt->t3);
+ return -1;
+ }
+
+ group_q = &ps->paging_queue[group];
+
+ /* There is nobody to be paged, send Type1 with two empty ID */
+ if (llist_empty(group_q)) {
+ //DEBUGP(DPAG, "Tx PAGING TYPE 1 (empty)\n");
+ len = fill_paging_type_1(out_buf, empty_id_lv, 0,
+ NULL, 0);
+ *is_empty = 1;
+ } else {
+ struct paging_record *pr[4];
+ unsigned int num_pr = 0, imm_ass = 0;
+ time_t now = time(NULL);
+ unsigned int i, num_imsi = 0;
+
+ ps->bts->load.ccch.pch_used += 1;
+
+ /* get (if we have) up to four paging records */
+ for (i = 0; i < ARRAY_SIZE(pr); i++) {
+ if (llist_empty(group_q))
+ break;
+ pr[i] = dequeue_pr(group_q);
+
+ /* check for IMM.ASS */
+ if (pr[i]->type == PAGING_RECORD_IMM_ASS) {
+ imm_ass = 1;
+ break;
+ }
+
+ num_pr++;
+
+ /* count how many IMSIs are among them */
+ if (pr_is_imsi(pr[i]))
+ num_imsi++;
+ }
+
+ /* if we have an IMMEDIATE ASSIGNMENT */
+ if (imm_ass) {
+ /* re-add 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,
+ GSM_MACBLOCK_LEN);
+ talloc_free(pr[num_pr]);
+ return GSM_MACBLOCK_LEN;
+ }
+
+ /* make sure the TMSIs are ahead of the IMSIs in the array */
+ sort_pr_tmsi_imsi(pr, num_pr);
+
+ if (num_pr == 4 && num_imsi == 0) {
+ /* No IMSI: easy case, can use TYPE 3 */
+ DEBUGP(DPAG, "Tx PAGING TYPE 3 (4 TMSI)\n");
+ 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);
+ } 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");
+ 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);
+ if (num_pr == 4) {
+ /* re-add #4 for next time */
+ llist_add(&pr[3]->list, group_q);
+ pr[3] = NULL;
+ }
+ } else if (num_pr == 1) {
+ DEBUGP(DPAG, "Tx PAGING TYPE 1 (1 xMSI,1 empty)\n");
+ len = fill_paging_type_1(out_buf,
+ pr[0]->u.paging.identity_lv,
+ pr[0]->u.paging.chan_needed,
+ NULL, 0);
+ } else {
+ /* 2 (any type) or
+ * 3 or 4, of which only 2 will be sent */
+ DEBUGP(DPAG, "Tx PAGING TYPE 1 (2 xMSI)\n");
+ 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);
+ if (num_pr >= 3) {
+ /* re-add #4 for next time */
+ llist_add(&pr[2]->list, group_q);
+ pr[2] = NULL;
+ }
+ if (num_pr == 4) {
+ /* re-add #4 for next time */
+ llist_add(&pr[3]->list, group_q);
+ pr[3] = NULL;
+ }
+ }
+
+ for (i = 0; i < num_pr; i++) {
+ /* skip those that we might have re-added above */
+ if (pr[i] == NULL)
+ continue;
+ rate_ctr_inc2(ps->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) {
+ talloc_free(pr[i]);
+ ps->num_paging--;
+ LOGP(DPAG, LOGL_INFO, "Removed paging record, queue_len=%u\n",
+ ps->num_paging);
+ } else
+ llist_add_tail(&pr[i]->list, group_q);
+ }
+ }
+ memset(out_buf+len, 0x2B, GSM_MACBLOCK_LEN-len);
+ return len;
+}
+
+int paging_si_update(struct paging_state *ps, struct gsm48_control_channel_descr *chan_desc)
+{
+ LOGP(DPAG, LOGL_INFO, "Paging SI update\n");
+
+ ps->chan_desc = *chan_desc;
+
+ /* FIXME: do we need to re-sort the old paging_records? */
+
+ return 0;
+}
+
+static int paging_signal_cbfn(unsigned int subsys, unsigned int signal, void *hdlr_data,
+ void *signal_data)
+{
+ if (subsys == SS_GLOBAL && signal == S_NEW_SYSINFO) {
+ struct gsm_bts *bts = signal_data;
+ struct paging_state *ps = bts->paging_state;
+ struct gsm48_system_information_type_3 *si3 = (void *) bts->si_buf[SYSINFO_TYPE_3];
+
+ paging_si_update(ps, &si3->control_channel_desc);
+ }
+ return 0;
+}
+
+static int initialized = 0;
+
+struct paging_state *paging_init(struct gsm_bts *bts,
+ unsigned int num_paging_max,
+ unsigned int paging_lifetime)
+{
+ struct paging_state *ps;
+ unsigned int i;
+
+ ps = talloc_zero(bts, struct paging_state);
+ if (!ps)
+ return NULL;
+
+ ps->bts = bts;
+ ps->paging_lifetime = paging_lifetime;
+ ps->num_paging_max = num_paging_max;
+
+ for (i = 0; i < ARRAY_SIZE(ps->paging_queue); i++)
+ INIT_LLIST_HEAD(&ps->paging_queue[i]);
+
+ if (!initialized) {
+ osmo_signal_register_handler(SS_GLOBAL, paging_signal_cbfn, NULL);
+ initialized = 1;
+ }
+ return ps;
+}
+
+void paging_config(struct paging_state *ps,
+ unsigned int num_paging_max,
+ unsigned int paging_lifetime)
+{
+ ps->num_paging_max = num_paging_max;
+ ps->paging_lifetime = paging_lifetime;
+}
+
+void paging_reset(struct paging_state *ps)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(ps->paging_queue); i++) {
+ struct llist_head *queue = &ps->paging_queue[i];
+ struct paging_record *pr, *pr2;
+ llist_for_each_entry_safe(pr, pr2, queue, list) {
+ llist_del(&pr->list);
+ talloc_free(pr);
+ ps->num_paging--;
+ }
+ }
+
+ if (ps->num_paging != 0)
+ LOGP(DPAG, LOGL_NOTICE, "num_paging != 0 after flushing all records?!?\n");
+
+ ps->num_paging = 0;
+}
+
+/**
+ * \brief Helper for the unit tests
+ */
+int paging_group_queue_empty(struct paging_state *ps, uint8_t grp)
+{
+ if (grp >= ARRAY_SIZE(ps->paging_queue))
+ return 1;
+ return llist_empty(&ps->paging_queue[grp]);
+}
+
+int paging_queue_length(struct paging_state *ps)
+{
+ return ps->num_paging;
+}
diff --git a/src/common/pcu_sock.c b/src/common/pcu_sock.c
new file mode 100644
index 00000000..60e0f7a7
--- /dev/null
+++ b/src/common/pcu_sock.c
@@ -0,0 +1,982 @@
+/* pcu_sock.c: Connect from PCU via unix domain socket */
+
+/* (C) 2008-2010 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2009-2012 by Andreas Eversberg <jolly@eversberg.eu>
+ * (C) 2012 by Holger Hans Peter Freyther
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <assert.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <inttypes.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/socket.h>
+#include <osmocom/gsm/gsm23003.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/rsl.h>
+#include <osmo-bts/signal.h>
+#include <osmo-bts/l1sap.h>
+
+uint32_t trx_get_hlayer1(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",
+};
+
+static int pcu_sock_send(struct gsm_network *net, struct msgb *msg);
+
+/*
+ * PCU messages
+ */
+
+struct msgb *pcu_msgb_alloc(uint8_t msg_type, uint8_t bts_nr)
+{
+ struct msgb *msg;
+ struct gsm_pcu_if *pcu_prim;
+
+ msg = msgb_alloc(sizeof(struct gsm_pcu_if), "pcu_sock_tx");
+ if (!msg)
+ return NULL;
+ msgb_put(msg, sizeof(struct gsm_pcu_if));
+ pcu_prim = (struct gsm_pcu_if *) msg->data;
+ pcu_prim->msg_type = msg_type;
+ pcu_prim->bts_nr = bts_nr;
+
+ return msg;
+}
+
+static bool ts_should_be_pdch(struct gsm_bts_trx_ts *ts) {
+ if (ts->pchan == GSM_PCHAN_PDCH)
+ return true;
+ if (ts->pchan == GSM_PCHAN_TCH_F_PDCH) {
+ /* When we're busy deactivating the PDCH, we first set
+ * DEACT_PENDING, tell the PCU about it and wait for a
+ * response. So DEACT_PENDING means "no PDCH" to the PCU.
+ * Similarly, when we're activating PDCH, we set the
+ * ACT_PENDING and wait for an activation response from the
+ * PCU, so ACT_PENDING means "is PDCH". */
+ if (ts->flags & TS_F_PDCH_ACTIVE)
+ return !(ts->flags & TS_F_PDCH_DEACT_PENDING);
+ else
+ return (ts->flags & TS_F_PDCH_ACT_PENDING);
+ }
+ if (ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH) {
+ /*
+ * When we're busy de-/activating the PDCH, we first set
+ * ts->dyn.pchan_want, tell the PCU about it and wait for a
+ * response. So only care about dyn.pchan_want here.
+ */
+ return ts->dyn.pchan_want == GSM_PCHAN_PDCH;
+ }
+ return false;
+}
+
+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;
+
+ LOGP(DPCU, LOGL_INFO, "Sending info\n");
+
+ /* FIXME: allow multiple BTS */
+ bts = llist_entry(net->bts_list.next, struct gsm_bts, list);
+ rlcc = &bts->gprs.cell.rlc_cfg;
+
+ msg = pcu_msgb_alloc(PCU_IF_MSG_INFO_IND, bts->nr);
+ if (!msg)
+ return -ENOMEM;
+ pcu_prim = (struct gsm_pcu_if *) msg->data;
+ info_ind = &pcu_prim->u.info_ind;
+ info_ind->version = PCU_IF_VERSION;
+
+ if (avail_lai && avail_nse && avail_cell && avail_nsvc[0]) {
+ info_ind->flags |= PCU_IF_FLAG_ACTIVE;
+ LOGP(DPCU, LOGL_INFO, "BTS is up\n");
+ } else
+ LOGP(DPCU, LOGL_INFO, "BTS is down\n");
+
+ if (pcu_direct)
+ info_ind->flags |= PCU_IF_FLAG_SYSMO;
+
+ /* 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->lac = bts->location_area_code;
+ info_ind->rac = bts->gprs.rac;
+
+ /* NSE */
+ info_ind->nsei = bts->gprs.nse.nsei;
+ memcpy(info_ind->nse_timer, bts->gprs.nse.timer, 7);
+ memcpy(info_ind->cell_timer, bts->gprs.cell.timer, 11);
+
+ /* cell attributes */
+ info_ind->cell_id = bts->cell_identity;
+ info_ind->repeat_time = rlcc->paging.repeat_time;
+ info_ind->repeat_count = rlcc->paging.repeat_count;
+ info_ind->bvci = bts->gprs.cell.bvci;
+ info_ind->t3142 = rlcc->parameter[RLC_T3142];
+ info_ind->t3169 = rlcc->parameter[RLC_T3169];
+ info_ind->t3191 = rlcc->parameter[RLC_T3191];
+ info_ind->t3193_10ms = rlcc->parameter[RLC_T3193];
+ info_ind->t3195 = rlcc->parameter[RLC_T3195];
+ info_ind->n3101 = rlcc->parameter[RLC_N3101];
+ info_ind->n3103 = rlcc->parameter[RLC_N3103];
+ info_ind->n3105 = rlcc->parameter[RLC_N3105];
+ info_ind->cv_countdown = rlcc->parameter[CV_COUNTDOWN];
+ if (rlcc->cs_mask & (1 << GPRS_CS1))
+ info_ind->flags |= PCU_IF_FLAG_CS1;
+ if (rlcc->cs_mask & (1 << GPRS_CS2))
+ info_ind->flags |= PCU_IF_FLAG_CS2;
+ if (rlcc->cs_mask & (1 << GPRS_CS3))
+ info_ind->flags |= PCU_IF_FLAG_CS3;
+ if (rlcc->cs_mask & (1 << GPRS_CS4))
+ info_ind->flags |= PCU_IF_FLAG_CS4;
+ if (rlcc->cs_mask & (1 << GPRS_MCS1))
+ info_ind->flags |= PCU_IF_FLAG_MCS1;
+ if (rlcc->cs_mask & (1 << GPRS_MCS2))
+ info_ind->flags |= PCU_IF_FLAG_MCS2;
+ if (rlcc->cs_mask & (1 << GPRS_MCS3))
+ info_ind->flags |= PCU_IF_FLAG_MCS3;
+ if (rlcc->cs_mask & (1 << GPRS_MCS4))
+ info_ind->flags |= PCU_IF_FLAG_MCS4;
+ if (rlcc->cs_mask & (1 << GPRS_MCS5))
+ info_ind->flags |= PCU_IF_FLAG_MCS5;
+ if (rlcc->cs_mask & (1 << GPRS_MCS6))
+ info_ind->flags |= PCU_IF_FLAG_MCS6;
+ if (rlcc->cs_mask & (1 << GPRS_MCS7))
+ info_ind->flags |= PCU_IF_FLAG_MCS7;
+ if (rlcc->cs_mask & (1 << GPRS_MCS8))
+ info_ind->flags |= PCU_IF_FLAG_MCS8;
+ if (rlcc->cs_mask & (1 << GPRS_MCS9))
+ info_ind->flags |= PCU_IF_FLAG_MCS9;
+#warning "isn't dl_tbf_ext wrong?: * 10 and no ntohs"
+ info_ind->dl_tbf_ext = rlcc->parameter[T_DL_TBF_EXT];
+#warning "isn't ul_tbf_ext wrong?: * 10 and no ntohs"
+ info_ind->ul_tbf_ext = rlcc->parameter[T_UL_TBF_EXT];
+ info_ind->initial_cs = rlcc->initial_cs;
+ info_ind->initial_mcs = rlcc->initial_mcs;
+
+ /* NSVC */
+ for (i = 0; i < 2; i++) {
+ nsvc = &bts->gprs.nsvc[i];
+ info_ind->nsvci[i] = nsvc->nsvci;
+ info_ind->local_port[i] = nsvc->local_port;
+ info_ind->remote_port[i] = nsvc->remote_port;
+ info_ind->remote_ip[i] = nsvc->remote_ip;
+ }
+
+ for (i = 0; i < 8; i++) {
+ trx = gsm_bts_trx_num(bts, i);
+ if (!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);
+ }
+ }
+ }
+
+ return pcu_sock_send(net, 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_bts *bts;
+ struct gsm48_system_information_type_3 *si3;
+ int id;
+
+ if (subsys != SS_GLOBAL)
+ return -EINVAL;
+
+ switch(signal) {
+ case S_NEW_SYSINFO:
+ bts = signal_data;
+ if (!(bts->si_valid & (1 << SYSINFO_TYPE_3)))
+ break;
+ si3 = (struct gsm48_system_information_type_3 *)
+ bts->si_buf[SYSINFO_TYPE_3];
+ osmo_plmn_from_bcd(si3->lai.digits, &net->plmn);
+ bts->location_area_code = ntohs(si3->lai.lac);
+ bts->cell_identity = si3->cell_identity;
+ avail_lai = 1;
+ break;
+ case S_NEW_NSE_ATTR:
+ bts = signal_data;
+ avail_nse = 1;
+ break;
+ case S_NEW_CELL_ATTR:
+ bts = signal_data;
+ avail_cell = 1;
+ break;
+ case S_NEW_NSVC_ATTR:
+ nsvc = signal_data;
+ id = nsvc->id;
+ if (id < 0 || id > 1)
+ return -EINVAL;
+ avail_nsvc[id] = 1;
+ break;
+ case S_NEW_OP_STATE:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* 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])
+ pcu_tx_info_ind();
+ return 0;
+}
+
+
+int pcu_tx_rts_req(struct gsm_bts_trx_ts *ts, uint8_t is_ptcch, uint32_t fn,
+ uint16_t arfcn, uint8_t block_nr)
+{
+ struct msgb *msg;
+ struct gsm_pcu_if *pcu_prim;
+ struct gsm_pcu_if_rts_req *rts_req;
+ struct gsm_bts *bts = ts->trx->bts;
+
+ LOGP(DPCU, LOGL_DEBUG, "Sending rts request: is_ptcch=%d arfcn=%d "
+ "block=%d\n", is_ptcch, arfcn, block_nr);
+
+ msg = pcu_msgb_alloc(PCU_IF_MSG_RTS_REQ, bts->nr);
+ if (!msg)
+ return -ENOMEM;
+ pcu_prim = (struct gsm_pcu_if *) msg->data;
+ rts_req = &pcu_prim->u.rts_req;
+
+ rts_req->sapi = (is_ptcch) ? PCU_IF_SAPI_PTCCH : PCU_IF_SAPI_PDTCH;
+ rts_req->fn = fn;
+ rts_req->arfcn = arfcn;
+ rts_req->trx_nr = ts->trx->nr;
+ rts_req->ts_nr = ts->nr;
+ rts_req->block_nr = block_nr;
+
+ return pcu_sock_send(&bts_gsmnet, msg);
+}
+
+int pcu_tx_data_ind(struct gsm_bts_trx_ts *ts, uint8_t sapi, uint32_t fn,
+ uint16_t arfcn, uint8_t block_nr, uint8_t *data, uint8_t len,
+ int8_t rssi, uint16_t ber10k, int16_t bto, int16_t lqual)
+{
+ struct msgb *msg;
+ struct gsm_pcu_if *pcu_prim;
+ struct gsm_pcu_if_data *data_ind;
+ struct gsm_bts *bts = ts->trx->bts;
+
+ 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 / 10 < bts->min_qual_norm) {
+ LOGP(DPCU, LOGL_DEBUG, "Link quality %"PRId16" is below threshold %f, 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;
+ pcu_prim = (struct gsm_pcu_if *) msg->data;
+ data_ind = &pcu_prim->u.data_ind;
+
+ data_ind->sapi = sapi;
+ data_ind->rssi = rssi;
+ data_ind->fn = fn;
+ data_ind->arfcn = arfcn;
+ data_ind->trx_nr = ts->trx->nr;
+ data_ind->ts_nr = ts->nr;
+ data_ind->block_nr = block_nr;
+ data_ind->ber10k = ber10k;
+ data_ind->ta_offs_qbits = bto;
+ data_ind->lqual_cb = lqual;
+ memcpy(data_ind->data, data, len);
+ data_ind->len = len;
+
+ return pcu_sock_send(&bts_gsmnet, 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)
+{
+ struct msgb *msg;
+ struct gsm_pcu_if *pcu_prim;
+ struct gsm_pcu_if_rach_ind *rach_ind;
+
+ LOGP(DPCU, LOGL_INFO, "Sending RACH indication: qta=%d, ra=%d, "
+ "fn=%d\n", qta, ra, fn);
+
+ msg = pcu_msgb_alloc(PCU_IF_MSG_RACH_IND, bts->nr);
+ if (!msg)
+ return -ENOMEM;
+ pcu_prim = (struct gsm_pcu_if *) msg->data;
+ rach_ind = &pcu_prim->u.rach_ind;
+
+ rach_ind->sapi = PCU_IF_SAPI_RACH;
+ rach_ind->ra = ra;
+ rach_ind->qta = qta;
+ rach_ind->fn = fn;
+ rach_ind->is_11bit = is_11bit;
+ rach_ind->burst_type = burst_type;
+
+ return pcu_sock_send(&bts_gsmnet, msg);
+}
+
+int pcu_tx_time_ind(uint32_t fn)
+{
+ struct msgb *msg;
+ struct gsm_pcu_if *pcu_prim;
+ struct gsm_pcu_if_time_ind *time_ind;
+ uint8_t fn13 = fn % 13;
+
+ /* omit frame numbers not starting at a MAC block */
+ if (fn13 != 0 && fn13 != 4 && fn13 != 8)
+ return 0;
+
+ msg = pcu_msgb_alloc(PCU_IF_MSG_TIME_IND, 0);
+ if (!msg)
+ return -ENOMEM;
+ pcu_prim = (struct gsm_pcu_if *) msg->data;
+ time_ind = &pcu_prim->u.time_ind;
+
+ time_ind->fn = fn;
+
+ return pcu_sock_send(&bts_gsmnet, 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 msgb *msg;
+ struct gsm_pcu_if *pcu_prim;
+ struct gsm_pcu_if_pag_req *pag_req;
+
+ /* check if identity does not fit: length > sizeof(lv) - 1 */
+ if (identity_lv[0] >= sizeof(pag_req->identity_lv)) {
+ LOGP(DPCU, LOGL_ERROR, "Paging identity too large (%d)\n",
+ identity_lv[0]);
+ return -EINVAL;
+ }
+
+ /* socket not created */
+ if (!state) {
+ LOGP(DPCU, LOGL_DEBUG, "PCU socket not created, ignoring "
+ "paging message\n");
+ return 0;
+ }
+
+ msg = pcu_msgb_alloc(PCU_IF_MSG_PAG_REQ, 0);
+ if (!msg)
+ return -ENOMEM;
+ pcu_prim = (struct gsm_pcu_if *) msg->data;
+ pag_req = &pcu_prim->u.pag_req;
+
+ pag_req->chan_needed = chan_needed;
+ memcpy(pag_req->identity_lv, identity_lv, identity_lv[0] + 1);
+
+ return pcu_sock_send(&bts_gsmnet, msg);
+}
+
+int pcu_tx_pch_data_cnf(uint32_t fn, uint8_t *data, uint8_t len)
+{
+ 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);
+
+ LOGP(DPCU, LOGL_INFO, "Sending PCH confirm\n");
+
+ msg = pcu_msgb_alloc(PCU_IF_MSG_DATA_CNF, bts->nr);
+ if (!msg)
+ return -ENOMEM;
+ pcu_prim = (struct gsm_pcu_if *) msg->data;
+ data_cnf = &pcu_prim->u.data_cnf;
+
+ 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);
+}
+
+static int pcu_rx_data_req(struct gsm_bts *bts, uint8_t msg_type,
+ struct gsm_pcu_if_data *data_req)
+{
+ uint8_t is_ptcch;
+ struct gsm_bts_trx *trx;
+ struct gsm_bts_trx_ts *ts;
+ struct msgb *msg;
+ int rc = 0;
+
+ LOGP(DPCU, LOGL_DEBUG, "Data request received: sapi=%s arfcn=%d "
+ "block=%d data=%s\n", sapi_string[data_req->sapi],
+ data_req->arfcn, data_req->block_nr,
+ osmo_hexdump(data_req->data, data_req->len));
+
+ switch (data_req->sapi) {
+ case PCU_IF_SAPI_PCH:
+ paging_add_imm_ass(bts->paging_state, data_req->data, data_req->len);
+ break;
+ case PCU_IF_SAPI_AGCH:
+ msg = msgb_alloc(data_req->len, "pcu_agch");
+ if (!msg) {
+ rc = -ENOMEM;
+ break;
+ }
+ msg->l3h = msgb_put(msg, data_req->len);
+ memcpy(msg->l3h, data_req->data, data_req->len);
+ if (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);
+ if (!trx) {
+ LOGP(DPCU, LOGL_ERROR, "Received PCU data request with "
+ "not existing TRX %d\n", data_req->trx_nr);
+ rc = -EINVAL;
+ break;
+ }
+ if (data_req->ts_nr >= ARRAY_SIZE(trx->ts)) {
+ LOGP(DPCU, LOGL_ERROR, "Received PCU data request with "
+ "not existing TS %u\n", data_req->ts_nr);
+ rc = -EINVAL;
+ break;
+ }
+ ts = &trx->ts[data_req->ts_nr];
+ if (!ts_should_be_pdch(ts)) {
+ LOGP(DPCU, LOGL_ERROR, "%s: Received PCU DATA request for non-PDCH TS\n",
+ gsm_ts_name(ts));
+ rc = -EINVAL;
+ break;
+ }
+ if (ts->lchan[0].state != LCHAN_S_ACTIVE) {
+ LOGP(DPCU, LOGL_ERROR, "%s: Received PCU DATA request for inactive lchan\n",
+ gsm_ts_name(ts));
+ rc = -EINVAL;
+ break;
+ }
+ is_ptcch = (data_req->sapi == PCU_IF_SAPI_PTCCH);
+ rc = l1sap_pdch_req(ts, is_ptcch, data_req->fn, data_req->arfcn,
+ data_req->block_nr, data_req->data, data_req->len);
+ break;
+ default:
+ LOGP(DPCU, LOGL_ERROR, "Received PCU data request with "
+ "unsupported sapi %d\n", data_req->sapi);
+ rc = -EINVAL;
+ }
+
+ return rc;
+}
+
+static int pcu_rx_pag_req(struct gsm_bts *bts, uint8_t msg_type,
+ struct gsm_pcu_if_pag_req *pag_req)
+{
+ int rc = 0;
+
+ OSMO_ASSERT(msg_type == PCU_IF_MSG_PAG_REQ);
+
+ /* FIXME: Add function to schedule paging request.
+ * At present, osmo-pcu sends paging requests in PCU_IF_MSG_DATA_REQ
+ * messages which are processed by pcu_rx_data_req().
+ * This code path is not triggered in practice. */
+ LOGP(DPCU, LOGL_NOTICE, "Paging request received: chan_needed=%d length=%d "
+ "(dropping message because support for PCU_IF_MSG_PAG_REQ is not yet implemented)\n",
+ pag_req->chan_needed, pag_req->identity_lv[0]);
+
+ return rc;
+}
+
+int pcu_tx_si13(const struct gsm_bts *bts, 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);
+ if (rc < 0)
+ LOGP(DPCU, LOGL_NOTICE, "Failed to send SI13 to PCU: %d\n", rc);
+
+ return rc;
+}
+
+static int pcu_rx_txt_ind(struct gsm_bts *bts,
+ struct gsm_pcu_if_txt_ind *txt)
+{
+ switch (txt->type) {
+ case PCU_VERSION:
+ LOGP(DPCU, LOGL_INFO, "OsmoPCU version %s connected\n",
+ txt->text);
+ osmo_signal_dispatch(SS_FAIL, OSMO_EVT_PCU_VERS, txt->text);
+ osmo_strlcpy(bts->pcu_version, txt->text, MAX_VERSION_LENGTH);
+
+ if (GSM_BTS_HAS_SI(bts, SYSINFO_TYPE_13))
+ return pcu_tx_si13(bts, true);
+
+ LOGP(DPCU, LOGL_INFO, "SI13 is not available on PCU connection\n");
+ break;
+ case PCU_OML_ALERT:
+ osmo_signal_dispatch(SS_FAIL, OSMO_EVT_EXT_ALARM, txt->text);
+ break;
+ default:
+ LOGP(DPCU, LOGL_ERROR, "Unknown TXT_IND type %u received\n",
+ txt->type);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int pcu_rx_act_req(struct gsm_bts *bts,
+ struct gsm_pcu_if_act_req *act_req)
+{
+ struct gsm_bts_trx *trx;
+ struct gsm_lchan *lchan;
+
+ LOGP(DPCU, LOGL_INFO, "%s request received: TRX=%d TX=%d\n",
+ (act_req->activate) ? "Activate" : "Deactivate",
+ act_req->trx_nr, act_req->ts_nr);
+
+ trx = gsm_bts_trx_num(bts, act_req->trx_nr);
+ if (!trx || act_req->ts_nr >= 8)
+ return -EINVAL;
+
+ lchan = trx->ts[act_req->ts_nr].lchan;
+ lchan->rel_act_kind = LCHAN_REL_ACT_PCU;
+ if (lchan->type != GSM_LCHAN_PDTCH) {
+ LOGP(DPCU, LOGL_ERROR,
+ "%s request, but lchan is not of type PDTCH (is %s)\n",
+ (act_req->activate) ? "Activate" : "Deactivate",
+ gsm_lchant_name(lchan->type));
+ return -EINVAL;
+ }
+ if (act_req->activate)
+ l1sap_chan_act(trx, gsm_lchan2chan_nr(lchan), NULL);
+ 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)
+{
+ int rc = 0;
+ struct gsm_bts *bts;
+
+ /* FIXME: allow multiple BTS */
+ if (pcu_prim->bts_nr != 0) {
+ 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:
+ rc = pcu_rx_data_req(bts, msg_type, &pcu_prim->u.data_req);
+ break;
+ case PCU_IF_MSG_PAG_REQ:
+ rc = pcu_rx_pag_req(bts, msg_type, &pcu_prim->u.pag_req);
+ break;
+ case PCU_IF_MSG_ACT_REQ:
+ rc = pcu_rx_act_req(bts, &pcu_prim->u.act_req);
+ break;
+ case PCU_IF_MSG_TXT_IND:
+ rc = pcu_rx_txt_ind(bts, &pcu_prim->u.txt_ind);
+ break;
+ default:
+ LOGP(DPCU, LOGL_ERROR, "Received unknwon PCU msg type %d\n",
+ msg_type);
+ rc = -EINVAL;
+ }
+
+ return rc;
+}
+
+/*
+ * PCU socket interface
+ */
+
+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 */
+};
+
+static int pcu_sock_send(struct gsm_network *net, struct msgb *msg)
+{
+ struct pcu_sock_state *state = net->pcu_state;
+ struct osmo_fd *conn_bfd;
+ struct gsm_pcu_if *pcu_prim = (struct gsm_pcu_if *) msg->data;
+
+ if (!state) {
+ if (pcu_prim->msg_type != PCU_IF_MSG_TIME_IND)
+ LOGP(DPCU, LOGL_INFO, "PCU socket not created, "
+ "dropping message\n");
+ msgb_free(msg);
+ return -EINVAL;
+ }
+ conn_bfd = &state->conn_bfd;
+ if (conn_bfd->fd <= 0) {
+ if (pcu_prim->msg_type != PCU_IF_MSG_TIME_IND)
+ LOGP(DPCU, LOGL_NOTICE, "PCU socket not connected, "
+ "dropping message\n");
+ msgb_free(msg);
+ return -EIO;
+ }
+ msgb_enqueue(&state->upqueue, msg);
+ conn_bfd->when |= BSC_FD_WRITE;
+
+ return 0;
+}
+
+static void pcu_sock_close(struct pcu_sock_state *state)
+{
+ struct osmo_fd *bfd = &state->conn_bfd;
+ struct gsm_bts *bts;
+ struct gsm_bts_trx *trx;
+ struct gsm_bts_trx_ts *ts;
+ int i, j;
+
+ /* FIXME: allow multiple BTS */
+ bts = llist_entry(state->net->bts_list.next, struct gsm_bts, list);
+
+ LOGP(DPCU, LOGL_NOTICE, "PCU socket has LOST connection\n");
+ osmo_signal_dispatch(SS_FAIL, OSMO_EVT_PCU_VERS, NULL);
+ bts->pcu_version[0] = '\0';
+
+ close(bfd->fd);
+ bfd->fd = -1;
+ osmo_fd_unregister(bfd);
+
+ /* re-enable the generation of ACCEPT for new connections */
+ state->listen_bfd.when |= BSC_FD_READ;
+
+#if 0
+ /* remove si13, ... */
+ bts->si_valid &= ~(1 << SYSINFO_TYPE_13);
+ osmo_signal_dispatch(SS_GLOBAL, S_NEW_SYSINFO, bts);
+#endif
+
+ /* release PDCH */
+ for (i = 0; i < 8; i++) {
+ trx = gsm_bts_trx_num(bts, i);
+ if (!trx)
+ break;
+ for (j = 0; j < 8; j++) {
+ ts = &trx->ts[j];
+ if (ts->mo.nm_state.operational == NM_OPSTATE_ENABLED
+ && ts->pchan == GSM_PCHAN_PDCH) {
+ 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);
+ }
+}
+
+static int pcu_sock_read(struct osmo_fd *bfd)
+{
+ struct pcu_sock_state *state = (struct pcu_sock_state *)bfd->data;
+ struct gsm_pcu_if *pcu_prim;
+ struct msgb *msg;
+ int rc;
+
+ msg = msgb_alloc(sizeof(*pcu_prim), "pcu_sock_rx");
+ if (!msg)
+ return -ENOMEM;
+
+ pcu_prim = (struct gsm_pcu_if *) msg->tail;
+
+ rc = recv(bfd->fd, msg->tail, msgb_tailroom(msg), 0);
+ if (rc == 0)
+ goto close;
+
+ if (rc < 0) {
+ if (errno == EAGAIN)
+ return 0;
+ goto close;
+ }
+
+ if (rc < sizeof(*pcu_prim)) {
+ LOGP(DPCU, LOGL_ERROR, "Received %d bytes on PCU Socket, but primitive size "
+ "is %lu, discarding\n", rc, sizeof(*pcu_prim));
+ return 0;
+ }
+
+ rc = pcu_rx(state->net, pcu_prim->msg_type, pcu_prim);
+
+ /* as we always synchronously process the message in pcu_rx() and
+ * its callbacks, we can free the message here. */
+ msgb_free(msg);
+
+ return rc;
+
+close:
+ msgb_free(msg);
+ pcu_sock_close(state);
+ return -1;
+}
+
+static int pcu_sock_write(struct osmo_fd *bfd)
+{
+ struct pcu_sock_state *state = bfd->data;
+ int rc;
+
+ while (!llist_empty(&state->upqueue)) {
+ struct msgb *msg, *msg2;
+ struct gsm_pcu_if *pcu_prim;
+
+ /* peek at the beginning of the queue */
+ msg = llist_entry(state->upqueue.next, struct msgb, list);
+ pcu_prim = (struct gsm_pcu_if *)msg->data;
+
+ bfd->when &= ~BSC_FD_WRITE;
+
+ /* bug hunter 8-): maybe someone forgot msgb_put(...) ? */
+ if (!msgb_length(msg)) {
+ LOGP(DPCU, LOGL_ERROR, "message type (%d) with ZERO "
+ "bytes!\n", pcu_prim->msg_type);
+ goto dontsend;
+ }
+
+ /* try to send it over the socket */
+ rc = write(bfd->fd, msgb_data(msg), msgb_length(msg));
+ if (rc == 0)
+ goto close;
+ if (rc < 0) {
+ if (errno == EAGAIN) {
+ bfd->when |= BSC_FD_WRITE;
+ break;
+ }
+ goto close;
+ }
+
+dontsend:
+ /* _after_ we send it, we can deueue */
+ msg2 = msgb_dequeue(&state->upqueue);
+ assert(msg == msg2);
+ msgb_free(msg);
+ }
+ return 0;
+
+close:
+ pcu_sock_close(state);
+
+ return -1;
+}
+
+static int pcu_sock_cb(struct osmo_fd *bfd, unsigned int flags)
+{
+ int rc = 0;
+
+ if (flags & BSC_FD_READ)
+ rc = pcu_sock_read(bfd);
+ if (rc < 0)
+ return rc;
+
+ if (flags & BSC_FD_WRITE)
+ rc = pcu_sock_write(bfd);
+
+ return rc;
+}
+
+/* accept connection comming from PCU */
+static int pcu_sock_accept(struct osmo_fd *bfd, unsigned int flags)
+{
+ struct pcu_sock_state *state = (struct pcu_sock_state *)bfd->data;
+ struct osmo_fd *conn_bfd = &state->conn_bfd;
+ struct sockaddr_un un_addr;
+ socklen_t len;
+ int rc;
+
+ len = sizeof(un_addr);
+ rc = accept(bfd->fd, (struct sockaddr *) &un_addr, &len);
+ if (rc < 0) {
+ LOGP(DPCU, LOGL_ERROR, "Failed to accept a new connection\n");
+ return -1;
+ }
+
+ if (conn_bfd->fd >= 0) {
+ LOGP(DPCU, LOGL_NOTICE, "PCU connects but we already have "
+ "another active connection ?!?\n");
+ /* We already have one PCU connected, this is all we support */
+ state->listen_bfd.when &= ~BSC_FD_READ;
+ close(rc);
+ return 0;
+ }
+
+ conn_bfd->fd = rc;
+ conn_bfd->when = BSC_FD_READ;
+ conn_bfd->cb = pcu_sock_cb;
+ conn_bfd->data = state;
+
+ if (osmo_fd_register(conn_bfd) != 0) {
+ LOGP(DPCU, LOGL_ERROR, "Failed to register new connection "
+ "fd\n");
+ close(conn_bfd->fd);
+ conn_bfd->fd = -1;
+ return -1;
+ }
+
+ LOGP(DPCU, LOGL_NOTICE, "PCU socket connected to external PCU\n");
+
+ /* send current info */
+ pcu_tx_info_ind();
+
+ return 0;
+}
+
+int pcu_sock_init(const char *path)
+{
+ struct pcu_sock_state *state;
+ struct osmo_fd *bfd;
+ int rc;
+
+ state = talloc_zero(NULL, struct pcu_sock_state);
+ if (!state)
+ return -ENOMEM;
+
+ INIT_LLIST_HEAD(&state->upqueue);
+ state->net = &bts_gsmnet;
+ state->conn_bfd.fd = -1;
+
+ bfd = &state->listen_bfd;
+
+ bfd->fd = osmo_sock_unix_init(SOCK_SEQPACKET, 0, path,
+ OSMO_SOCK_F_BIND);
+ if (bfd->fd < 0) {
+ LOGP(DPCU, LOGL_ERROR, "Could not create %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;
+
+ rc = osmo_fd_register(bfd);
+ if (rc < 0) {
+ LOGP(DPCU, LOGL_ERROR, "Could not register listen fd: %d\n",
+ rc);
+ close(bfd->fd);
+ talloc_free(state);
+ return rc;
+ }
+
+ osmo_signal_register_handler(SS_GLOBAL, pcu_if_signal_cb, NULL);
+
+ bts_gsmnet.pcu_state = state;
+
+ LOGP(DPCU, LOGL_INFO, "Started listening on PCU socket: %s\n", path);
+
+ return 0;
+}
+
+void pcu_sock_exit(void)
+{
+ struct pcu_sock_state *state = bts_gsmnet.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;
+ 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;
+}
+
+bool pcu_connected(void) {
+ struct gsm_network *net = &bts_gsmnet;
+ struct pcu_sock_state *state = net->pcu_state;
+
+ if (!state)
+ return false;
+ if (state->conn_bfd.fd <= 0)
+ return false;
+ return true;
+}
diff --git a/src/common/phy_link.c b/src/common/phy_link.c
new file mode 100644
index 00000000..588fcc91
--- /dev/null
+++ b/src/common/phy_link.c
@@ -0,0 +1,163 @@
+#include <stdint.h>
+
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/talloc.h>
+
+#include <osmo-bts/bts.h>
+#include <osmo-bts/gsm_data.h>
+#include <osmo-bts/phy_link.h>
+#include <osmo-bts/oml.h>
+#include <osmo-bts/logging.h>
+#include <osmo-bts/bts_model.h>
+
+static LLIST_HEAD(g_phy_links);
+
+struct phy_link *phy_link_by_num(int num)
+{
+ struct phy_link *plink;
+
+ llist_for_each_entry(plink, &g_phy_links, list) {
+ if (plink->num == num)
+ return plink;
+ }
+
+ return NULL;
+}
+
+struct phy_link *phy_link_create(void *ctx, int num)
+{
+ struct phy_link *plink;
+
+ if (phy_link_by_num(num))
+ return NULL;
+
+ plink = talloc_zero(ctx, struct phy_link);
+ plink->num = num;
+ plink->state = PHY_LINK_SHUTDOWN;
+ INIT_LLIST_HEAD(&plink->instances);
+ llist_add_tail(&plink->list, &g_phy_links);
+
+ bts_model_phy_link_set_defaults(plink);
+
+ return plink;
+}
+
+const struct value_string phy_link_state_vals[] = {
+ { PHY_LINK_SHUTDOWN, "shutdown" },
+ { PHY_LINK_CONNECTING, "connecting" },
+ { PHY_LINK_CONNECTED, "connected" },
+ { 0, NULL }
+};
+
+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));
+
+ /* notify all TRX associated with this phy */
+ llist_for_each_entry(pinst, &plink->instances, list) {
+ struct gsm_bts_trx *trx = pinst->trx;
+ 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;
+ }
+ }
+
+ plink->state = state;
+}
+
+struct phy_instance *phy_instance_by_num(struct phy_link *plink, int num)
+{
+ struct phy_instance *pinst;
+
+ llist_for_each_entry(pinst, &plink->instances, list) {
+ if (pinst->num == num)
+ return pinst;
+ }
+ return NULL;
+}
+
+struct phy_instance *phy_instance_create(struct phy_link *plink, int num)
+{
+ struct phy_instance *pinst;
+
+ if (phy_instance_by_num(plink, num))
+ return NULL;
+
+ pinst = talloc_zero(plink, struct phy_instance);
+ pinst->num = num;
+ pinst->phy_link = plink;
+ llist_add_tail(&pinst->list, &plink->instances);
+
+ bts_model_phy_instance_set_defaults(pinst);
+
+ return pinst;
+}
+
+void phy_instance_link_to_trx(struct phy_instance *pinst, struct gsm_bts_trx *trx)
+{
+ trx->role_bts.l1h = pinst;
+ pinst->trx = trx;
+}
+
+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;
+
+ talloc_free(pinst);
+}
+
+void phy_link_destroy(struct phy_link *plink)
+{
+ struct phy_instance *pinst, *pinst2;
+
+ llist_for_each_entry_safe(pinst, pinst2, &plink->instances, list)
+ phy_instance_destroy(pinst);
+
+ talloc_free(plink);
+}
+
+int phy_links_open(void)
+{
+ struct phy_link *plink;
+
+ llist_for_each_entry(plink, &g_phy_links, list) {
+ int rc;
+
+ rc = bts_model_phy_link_open(plink);
+ if (rc < 0)
+ return rc;
+ }
+
+ return 0;
+}
+
+const char *phy_instance_name(struct phy_instance *pinst)
+{
+ static char buf[32];
+
+ snprintf(buf, sizeof(buf), "phy%u.%u", pinst->phy_link->num,
+ pinst->num);
+ return buf;
+}
diff --git a/src/common/power_control.c b/src/common/power_control.c
new file mode 100644
index 00000000..b1728705
--- /dev/null
+++ b/src/common/power_control.c
@@ -0,0 +1,89 @@
+/* MS Power Control Loop L1 */
+
+/* (C) 2014 by Holger Hans Peter Freyther
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU 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 <errno.h>
+
+#include <osmo-bts/logging.h>
+#include <osmo-bts/bts.h>
+#include <osmo-bts/gsm_data.h>
+#include <osmo-bts/measurement.h>
+#include <osmo-bts/bts_model.h>
+#include <osmo-bts/l1sap.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.
+ */
+int lchan_ms_pwr_ctrl(struct gsm_lchan *lchan,
+ const uint8_t ms_power, const int rxLevel)
+{
+ int rx;
+ int cur_dBm, new_dBm, new_pwr;
+ struct gsm_bts *bts = lchan->ts->trx->bts;
+ const enum gsm_band band = bts->band;
+
+ if (!trx_ms_pwr_ctrl_is_osmo(lchan->ts->trx))
+ return 0;
+ if (lchan->ms_power_ctrl.fixed)
+ return 0;
+
+ /* The phone hasn't reached the power level yet */
+ if (lchan->ms_power_ctrl.current != ms_power)
+ return 0;
+
+ /* What is the difference between what we want and received? */
+ rx = bts->ul_power_target - rxLevel;
+
+ cur_dBm = ms_pwr_dbm(band, ms_power);
+ new_dBm = cur_dBm + rx;
+
+ /* Clamp negative values and do it depending on the band */
+ if (new_dBm < 0)
+ new_dBm = 0;
+
+ 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;
+ }
+
+ 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;
+ }
+
+ return 0;
+}
diff --git a/src/common/rsl.c b/src/common/rsl.c
new file mode 100644
index 00000000..507e8aaf
--- /dev/null
+++ b/src/common/rsl.c
@@ -0,0 +1,2996 @@
+/* GSM TS 08.58 RSL, BTS Side */
+
+/* (C) 2011 by Andreas Eversberg <jolly@eversberg.eu>
+ * (C) 2011-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 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 <sys/types.h>
+#include <arpa/inet.h>
+
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/gsm/rsl.h>
+#include <osmocom/gsm/lapdm.h>
+#include <osmocom/gsm/protocol/gsm_12_21.h>
+#include <osmocom/gsm/protocol/gsm_08_58.h>
+#include <osmocom/gsm/protocol/ipaccess.h>
+#include <osmocom/trau/osmo_ortp.h>
+
+#include <osmo-bts/logging.h>
+#include <osmo-bts/gsm_data.h>
+#include <osmo-bts/abis.h>
+#include <osmo-bts/bts.h>
+#include <osmo-bts/rsl.h>
+#include <osmo-bts/oml.h>
+#include <osmo-bts/amr.h>
+#include <osmo-bts/signal.h>
+#include <osmo-bts/measurement.h>
+#include <osmo-bts/pcu_if.h>
+#include <osmo-bts/handover.h>
+#include <osmo-bts/cbch.h>
+#include <osmo-bts/l1sap.h>
+#include <osmo-bts/bts_model.h>
+#include <osmo-bts/pcuif_proto.h>
+
+//#define FAKE_CIPH_MODE_COMPL
+
+
+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);
+
+/* list of RSL SI types that can occur on the SACCH */
+static const unsigned int rsl_sacch_sitypes[] = {
+ RSL_SYSTEM_INFO_5,
+ RSL_SYSTEM_INFO_6,
+ RSL_SYSTEM_INFO_5bis,
+ RSL_SYSTEM_INFO_5ter,
+ RSL_EXT_MEAS_ORDER,
+ RSL_MEAS_INFO,
+};
+
+/* FIXME: move this to libosmocore */
+int osmo_in_array(unsigned int search, const unsigned int *arr, unsigned int size)
+{
+ unsigned int i;
+ for (i = 0; i < size; i++) {
+ if (arr[i] == search)
+ return 1;
+ }
+ return 0;
+}
+#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)
+{
+ uint8_t t1p = gtime->t1 % 32;
+ out[0] = (t1p << 3) | (gtime->t3 >> 3);
+ 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)
+{
+ lchan->rsl_cmode = cm->spd_ind;
+ lchan->ts->trx->bts->dtxd = (cm->dtx_dtu & RSL_CMOD_DTXd) ? true : false;
+
+ switch (cm->chan_rate) {
+ case RSL_CMOD_SP_GSM1:
+ lchan->tch_mode = GSM48_CMODE_SPEECH_V1;
+ break;
+ case RSL_CMOD_SP_GSM2:
+ lchan->tch_mode = GSM48_CMODE_SPEECH_EFR;
+ break;
+ case RSL_CMOD_SP_GSM3:
+ lchan->tch_mode = GSM48_CMODE_SPEECH_AMR;
+ break;
+ case RSL_CMOD_SP_NT_14k5:
+ lchan->tch_mode = GSM48_CMODE_DATA_14k5;
+ break;
+ case RSL_CMOD_SP_NT_12k0:
+ lchan->tch_mode = GSM48_CMODE_DATA_12k0;
+ break;
+ case RSL_CMOD_SP_NT_6k0:
+ lchan->tch_mode = GSM48_CMODE_DATA_6k0;
+ break;
+ }
+}
+
+
+/*
+ * support
+ */
+
+/* Is this channel number for a dedicated channel (true) or not (false) */
+static bool chan_nr_is_dchan(uint8_t chan_nr)
+{
+ /* See TS 48.058 9.3.1 + Osmocom extension for RSL_CHAN_OSMO_PDCH */
+ if ((chan_nr & 0xc0) == 0x80)
+ return false;
+ else
+ return true;
+}
+
+static struct gsm_lchan *lchan_lookup(struct gsm_bts_trx *trx, uint8_t chan_nr,
+ const char *log_name)
+{
+ int rc;
+ struct gsm_lchan *lchan = rsl_lchan_lookup(trx, chan_nr, &rc);
+
+ if (!lchan) {
+ LOGP(DRSL, LOGL_ERROR, "%sunknown chan_nr=0x%02x\n", log_name,
+ chan_nr);
+ return NULL;
+ }
+
+ if (rc < 0) {
+ LOGP(DRSL, LOGL_ERROR, "%s %smismatching chan_nr=0x%02x\n",
+ gsm_ts_and_pchan_name(lchan->ts), log_name, chan_nr);
+ return NULL;
+ }
+ return lchan;
+}
+
+static struct msgb *rsl_msgb_alloc(int hdr_size)
+{
+ struct msgb *nmsg;
+
+ hdr_size += sizeof(struct ipaccess_head);
+
+ nmsg = msgb_alloc_headroom(600+hdr_size, hdr_size, "RSL");
+ if (!nmsg)
+ return NULL;
+ nmsg->l3h = nmsg->data;
+ return nmsg;
+}
+
+static void rsl_trx_push_hdr(struct msgb *msg, uint8_t msg_type)
+{
+ struct abis_rsl_common_hdr *th;
+
+ th = (struct abis_rsl_common_hdr *) msgb_push(msg, sizeof(*th));
+ th->msg_discr = ABIS_RSL_MDISC_TRX;
+ th->msg_type = msg_type;
+}
+
+static void rsl_cch_push_hdr(struct msgb *msg, uint8_t msg_type, uint8_t chan_nr)
+{
+ struct abis_rsl_cchan_hdr *cch;
+
+ cch = (struct abis_rsl_cchan_hdr *) msgb_push(msg, sizeof(*cch));
+ cch->c.msg_discr = ABIS_RSL_MDISC_COM_CHAN;
+ cch->c.msg_type = msg_type;
+ cch->ie_chan = RSL_IE_CHAN_NR;
+ cch->chan_nr = chan_nr;
+}
+
+static void rsl_dch_push_hdr(struct msgb *msg, uint8_t msg_type, uint8_t chan_nr)
+{
+ struct abis_rsl_dchan_hdr *dch;
+
+ dch = (struct abis_rsl_dchan_hdr *) msgb_push(msg, sizeof(*dch));
+ dch->c.msg_discr = ABIS_RSL_MDISC_DED_CHAN;
+ dch->c.msg_type = msg_type;
+ dch->ie_chan = RSL_IE_CHAN_NR;
+ dch->chan_nr = chan_nr;
+}
+
+static void rsl_ipa_push_hdr(struct msgb *msg, uint8_t msg_type, uint8_t chan_nr)
+{
+ struct abis_rsl_dchan_hdr *dch;
+
+ dch = (struct abis_rsl_dchan_hdr *) msgb_push(msg, sizeof(*dch));
+ dch->c.msg_discr = ABIS_RSL_MDISC_IPACCESS;
+ dch->c.msg_type = msg_type;
+ dch->ie_chan = RSL_IE_CHAN_NR;
+ dch->chan_nr = chan_nr;
+}
+
+/*
+ * TRX related messages
+ */
+
+/* 8.6.4 sending ERROR REPORT */
+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)
+{
+ unsigned int len = sizeof(struct abis_rsl_common_hdr);
+ struct msgb *nmsg;
+
+ LOGP(DRSL, LOGL_NOTICE, "Tx RSL Error Report: cause = 0x%02x\n", cause);
+
+ if (orig_msg)
+ len += 2 + 3+msgb_l2len(orig_msg); /* chan_nr + TLV(orig_msg) */
+ if (chan_nr)
+ len += 2;
+ if (link_id)
+ len += 2;
+
+ nmsg = rsl_msgb_alloc(len);
+ if (!nmsg)
+ return -ENOMEM;
+ msgb_tlv_put(nmsg, RSL_IE_CAUSE, 1, &cause);
+ if (orig_msg && msgb_l2len(orig_msg) >= sizeof(struct abis_rsl_common_hdr)) {
+ struct abis_rsl_common_hdr *ch = (struct abis_rsl_common_hdr *) msgb_l2(orig_msg);
+ msgb_tv_put(nmsg, RSL_IE_MSG_ID, ch->msg_type);
+ }
+ if (chan_nr)
+ msgb_tv_put(nmsg, RSL_IE_CHAN_NR, *chan_nr);
+ if (link_id)
+ msgb_tv_put(nmsg, RSL_IE_LINK_IDENT, *link_id);
+ if (orig_msg)
+ msgb_tlv_put(nmsg, RSL_IE_ERR_MSG, msgb_l2len(orig_msg), msgb_l2(orig_msg));
+
+ rsl_trx_push_hdr(nmsg, RSL_MT_ERROR_REPORT);
+ nmsg->trx = trx;
+
+ return abis_bts_rsl_sendmsg(nmsg);
+}
+
+/* 8.6.1 sending RF RESOURCE INDICATION */
+int rsl_tx_rf_res(struct gsm_bts_trx *trx)
+{
+ struct msgb *nmsg;
+
+ LOGP(DRSL, LOGL_INFO, "Tx RSL RF RESource INDication\n");
+
+ 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);
+ rsl_trx_push_hdr(nmsg, RSL_MT_RF_RES_IND);
+ nmsg->trx = trx;
+
+ return abis_bts_rsl_sendmsg(nmsg);
+}
+
+/*
+ * common channel releated messages
+ */
+
+/* 8.5.1 BCCH INFOrmation is received */
+static int rsl_rx_bcch_info(struct gsm_bts_trx *trx, struct msgb *msg)
+{
+ struct gsm_bts *bts = trx->bts;
+ struct tlv_parsed tp;
+ uint8_t rsl_si, count;
+ 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));
+
+ /* 9.3.30 System Info Type */
+ if (!TLVP_PRESENT(&tp, RSL_IE_SYSINFO_TYPE))
+ return rsl_tx_error_report(trx, RSL_ERR_MAND_IE_ERROR, NULL, NULL, msg);
+
+ rsl_si = *TLVP_VAL(&tp, RSL_IE_SYSINFO_TYPE);
+ if (OSMO_IN_ARRAY(rsl_si, rsl_sacch_sitypes))
+ return rsl_tx_error_report(trx, RSL_ERR_IE_CONTENT, NULL, NULL, msg);
+
+ osmo_si = osmo_rsl2sitype(rsl_si);
+ if (osmo_si == SYSINFO_TYPE_NONE) {
+ LOGP(DRSL, LOGL_NOTICE, " Rx RSL SI 0x%02x not supported.\n", rsl_si);
+ return rsl_tx_error_report(trx, RSL_ERR_IE_CONTENT, NULL, NULL, msg);
+ }
+ /* 9.3.39 Full BCCH Information */
+ if (TLVP_PRESENT(&tp, RSL_IE_FULL_BCCH_INFO)) {
+ uint8_t len = TLVP_LEN(&tp, RSL_IE_FULL_BCCH_INFO);
+ if (len > sizeof(sysinfo_buf_t)) {
+ LOGP(DRSL, LOGL_ERROR, "Truncating received Full BCCH Info (%u -> %zu) for SI%s\n",
+ len, sizeof(sysinfo_buf_t), get_value_string(osmo_sitype_strs, osmo_si));
+ len = sizeof(sysinfo_buf_t);
+ }
+
+ 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) {
+ 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;
+ bv.cur_bit = 3;
+ bts->si2q_index = (uint8_t) bitvec_get_uint(&bv, 4);
+
+ count = (uint8_t) bitvec_get_uint(&bv, 4);
+ if (bts->si2q_count && bts->si2q_count != count) {
+ LOGP(DRSL, LOGL_NOTICE, " Rx RSL SI2quater count updated: %u -> %d\n",
+ bts->si2q_count, count);
+ }
+
+ bts->si2q_count = count;
+ if (bts->si2q_index > bts->si2q_count) {
+ LOGP(DRSL, LOGL_ERROR, " Rx RSL SI2quater with index %u > count %u\n",
+ bts->si2q_index, bts->si2q_count);
+ return rsl_tx_error_report(trx, RSL_ERR_IE_CONTENT, NULL, NULL, msg);
+ }
+
+ if (bts->si2q_index > SI2Q_MAX_NUM || bts->si2q_count > SI2Q_MAX_NUM) {
+ LOGP(DRSL, LOGL_ERROR, " Rx RSL SI2quater with impossible parameters: index %u, count %u"
+ "should be <= %u\n", bts->si2q_index, bts->si2q_count, SI2Q_MAX_NUM);
+ return rsl_tx_error_report(trx, RSL_ERR_IE_CONTENT, NULL, NULL, 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 {
+ 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 && 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() */
+ trx->bts->c0->ts[0].lchan[CCCH_LCHAN].rel_act_kind =
+ LCHAN_REL_ACT_REACT;
+ }
+
+ 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))
+ len = sizeof(sysinfo_buf_t);
+ bts->si_valid |= (1 << osmo_si);
+ memset(bts->si_buf[osmo_si], 0x2b, sizeof(sysinfo_buf_t));
+ memcpy(bts->si_buf[osmo_si],
+ TLVP_VAL(&tp, RSL_IE_L3_INFO), len);
+ LOGP(DRSL, LOGL_INFO, " Rx RSL BCCH INFO (SI%s)\n",
+ get_value_string(osmo_sitype_strs, osmo_si));
+ } else {
+ 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);
+ }
+ osmo_signal_dispatch(SS_GLOBAL, S_NEW_SYSINFO, bts);
+
+ return 0;
+}
+
+/* 8.5.2 CCCH Load Indication (PCH) */
+int rsl_tx_ccch_load_ind_pch(struct gsm_bts *bts, uint16_t paging_avail)
+{
+ struct msgb *msg;
+
+ msg = rsl_msgb_alloc(sizeof(struct abis_rsl_cchan_hdr));
+ if (!msg)
+ return -ENOMEM;
+ rsl_cch_push_hdr(msg, RSL_MT_CCCH_LOAD_IND, RSL_CHAN_PCH_AGCH);
+ msgb_tv16_put(msg, RSL_IE_PAGING_LOAD, paging_avail);
+ msg->trx = bts->c0;
+
+ return abis_bts_rsl_sendmsg(msg);
+}
+
+/* 8.5.2 CCCH Load Indication (RACH) */
+int rsl_tx_ccch_load_ind_rach(struct gsm_bts *bts, uint16_t total,
+ uint16_t busy, uint16_t access)
+{
+ struct msgb *msg;
+
+ msg = rsl_msgb_alloc(sizeof(struct abis_rsl_cchan_hdr));
+ if (!msg)
+ return -ENOMEM;
+ rsl_cch_push_hdr(msg, RSL_MT_CCCH_LOAD_IND, RSL_CHAN_RACH);
+ /* tag and length */
+ msgb_tv_put(msg, RSL_IE_RACH_LOAD, 6);
+ /* content of the IE */
+ msgb_put_u16(msg, total);
+ msgb_put_u16(msg, busy);
+ msgb_put_u16(msg, access);
+
+ msg->trx = bts->c0;
+
+ return abis_bts_rsl_sendmsg(msg);
+}
+
+/* 8.5.4 DELETE INDICATION */
+int rsl_tx_delete_ind(struct gsm_bts *bts, const uint8_t *ia, uint8_t ia_len)
+{
+ struct msgb *msg;
+
+ msg = rsl_msgb_alloc(sizeof(struct abis_rsl_cchan_hdr));
+ if (!msg)
+ return -ENOMEM;
+ rsl_cch_push_hdr(msg, RSL_MT_DELETE_IND, RSL_CHAN_PCH_AGCH);
+ msgb_tlv_put(msg, RSL_IE_FULL_IMM_ASS_INFO, ia_len, ia);
+ msg->trx = bts->c0;
+
+ return abis_bts_rsl_sendmsg(msg);
+}
+
+/* 8.5.5 PAGING COMMAND */
+static int rsl_rx_paging_cmd(struct gsm_bts_trx *trx, struct msgb *msg)
+{
+ struct tlv_parsed tp;
+ struct gsm_bts *bts = trx->bts;
+ uint8_t chan_needed = 0, paging_group;
+ const uint8_t *identity_lv;
+ int rc;
+
+ rsl_tlv_parse(&tp, msgb_l3(msg), msgb_l3len(msg));
+
+ if (!TLVP_PRESENT(&tp, RSL_IE_PAGING_GROUP) ||
+ !TLVP_PRESENT(&tp, RSL_IE_MS_IDENTITY))
+ return rsl_tx_error_report(trx, RSL_ERR_MAND_IE_ERROR, NULL, NULL, msg);
+
+ paging_group = *TLVP_VAL(&tp, RSL_IE_PAGING_GROUP);
+ identity_lv = TLVP_VAL(&tp, RSL_IE_MS_IDENTITY)-1;
+
+ if (TLVP_PRES_LEN(&tp, RSL_IE_CHAN_NEEDED, 1))
+ chan_needed = *TLVP_VAL(&tp, RSL_IE_CHAN_NEEDED);
+
+ rc = paging_add_identity(bts->paging_state, paging_group, identity_lv, chan_needed);
+ if (rc < 0) {
+ /* FIXME: notfiy the BSC on other errors? */
+ if (rc == -ENOSPC)
+ oml_fail_rep(OSMO_EVT_MIN_PAG_TAB_FULL,
+ "BTS paging table is full");
+ }
+
+ pcu_tx_pag_req(identity_lv, chan_needed);
+
+ return 0;
+}
+
+/* 8.5.8 SMS BROADCAST COMMAND */
+static int rsl_rx_sms_bcast_cmd(struct gsm_bts_trx *trx, struct msgb *msg)
+{
+ struct tlv_parsed tp;
+ struct rsl_ie_cb_cmd_type *cb_cmd_type;
+
+ rsl_tlv_parse(&tp, msgb_l3(msg), msgb_l3len(msg));
+
+ if (!TLVP_PRESENT(&tp, RSL_IE_CB_CMD_TYPE) ||
+ !TLVP_PRESENT(&tp, RSL_IE_SMSCB_MSG))
+ return rsl_tx_error_report(trx, RSL_ERR_MAND_IE_ERROR, NULL, NULL, msg);
+
+ cb_cmd_type = (struct rsl_ie_cb_cmd_type *)
+ TLVP_VAL(&tp, RSL_IE_CB_CMD_TYPE);
+
+ return bts_process_smscb_cmd(trx->bts, *cb_cmd_type,
+ TLVP_LEN(&tp, RSL_IE_SMSCB_MSG),
+ TLVP_VAL(&tp, RSL_IE_SMSCB_MSG));
+}
+
+/*! Prefix a given SACCH frame with a L2/LAPDm UI header and store it in given output buffer.
+ * \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] 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)
+{
+ /* 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",
+ get_value_string(osmo_sitype_strs, osmo_si), len, sizeof(sysinfo_buf_t) - 2);
+ len = sizeof(sysinfo_buf_t) - 2;
+ }
+
+ (*valid) |= (1 << osmo_si);
+ buf[0] = 0x03; /* C/R + EA */
+ buf[1] = 0x03; /* UI frame */
+
+ memset(buf + 2, GSM_MACBLOCK_PADDING, sizeof(sysinfo_buf_t) - 2);
+ memcpy(buf + 2, current, len);
+}
+
+/*! 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] 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)
+{
+ lapdm_ui_prefix(GSM_BTS_SI(bts, osmo_si), &bts->si_valid, current, osmo_si, len);
+}
+
+/*! 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] 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)
+{
+ lapdm_ui_prefix(GSM_LCHAN_SI(lchan, osmo_si), &lchan->si.valid, current, osmo_si, len);
+}
+
+/* 8.6.2 SACCH FILLING */
+static int rsl_rx_sacch_fill(struct gsm_bts_trx *trx, struct msgb *msg)
+{
+ struct gsm_bts *bts = trx->bts;
+ struct tlv_parsed tp;
+ uint8_t rsl_si;
+ enum osmo_sysinfo_type osmo_si;
+
+ rsl_tlv_parse(&tp, msgb_l3(msg), msgb_l3len(msg));
+
+ /* 9.3.30 System Info Type */
+ if (!TLVP_PRESENT(&tp, RSL_IE_SYSINFO_TYPE))
+ return rsl_tx_error_report(trx, RSL_ERR_MAND_IE_ERROR, NULL, NULL, msg);
+
+ rsl_si = *TLVP_VAL(&tp, RSL_IE_SYSINFO_TYPE);
+ if (!OSMO_IN_ARRAY(rsl_si, rsl_sacch_sitypes))
+ return rsl_tx_error_report(trx, RSL_ERR_IE_CONTENT, NULL, NULL, msg);
+
+ osmo_si = osmo_rsl2sitype(rsl_si);
+ if (osmo_si == SYSINFO_TYPE_NONE) {
+ LOGP(DRSL, LOGL_NOTICE, " Rx SACCH SI 0x%02x not supported.\n", rsl_si);
+ return rsl_tx_error_report(trx, RSL_ERR_IE_CONTENT, NULL, NULL, msg);
+ }
+ if (TLVP_PRESENT(&tp, RSL_IE_L3_INFO)) {
+ uint16_t len = TLVP_LEN(&tp, RSL_IE_L3_INFO);
+ struct gsm_bts_trx *t;
+
+ lapdm_ui_prefix_bts(bts, TLVP_VAL(&tp, RSL_IE_L3_INFO), osmo_si, len);
+
+ /* Propagate SI change to all lchans which adhere to BTS-global default. */
+ llist_for_each_entry(t, &bts->trx_list, list) {
+ int i, j;
+ for (i = 0; i < ARRAY_SIZE(t->ts); i++) {
+ struct gsm_bts_trx_ts *ts = &t->ts[i];
+ for (j = 0; j < ARRAY_SIZE(ts->lchan); j++) {
+ struct gsm_lchan *lchan = &ts->lchan[j];
+ if (lchan->state == LCHAN_S_NONE || (lchan->si.overridden & (1 << osmo_si)))
+ continue;
+ lapdm_ui_prefix_lchan(lchan, TLVP_VAL(&tp, RSL_IE_L3_INFO), osmo_si, len);
+ }
+ }
+ }
+
+ LOGP(DRSL, LOGL_INFO, " Rx RSL SACCH FILLING (SI%s, %u bytes)\n",
+ get_value_string(osmo_sitype_strs, osmo_si), len);
+ } else {
+ struct gsm_bts_trx *t;
+
+ bts->si_valid &= ~(1 << osmo_si);
+
+ /* Propagate SI change to all lchans which adhere to BTS-global default. */
+ llist_for_each_entry(t, &bts->trx_list, list) {
+ int i, j;
+ for (i = 0; i < ARRAY_SIZE(t->ts); i++) {
+ struct gsm_bts_trx_ts *ts = &t->ts[i];
+ for (j = 0; j < ARRAY_SIZE(ts->lchan); j++) {
+ struct gsm_lchan *lchan = &ts->lchan[j];
+ if (lchan->state == LCHAN_S_NONE || (lchan->si.overridden & (1 << osmo_si)))
+ continue;
+ lchan->si.valid &= ~(1 << osmo_si);
+ }
+ }
+ }
+ LOGP(DRSL, LOGL_INFO, " Rx RSL Disabling SACCH FILLING (SI%s)\n",
+ get_value_string(osmo_sitype_strs, osmo_si));
+ }
+ osmo_signal_dispatch(SS_GLOBAL, S_NEW_SYSINFO, bts);
+
+ 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 tlv_parsed tp;
+
+ rsl_tlv_parse(&tp, msgb_l3(msg), msgb_l3len(msg));
+
+ if (!TLVP_PRESENT(&tp, RSL_IE_FULL_IMM_ASS_INFO))
+ return rsl_tx_error_report(trx, RSL_ERR_MAND_IE_ERROR, NULL, NULL, msg);
+
+ rate_ctr_inc2(trx->bts->ctrs, BTS_CTR_AGCH_RCVD);
+
+ /* cut down msg to the 04.08 RR part */
+ msg->l3h = (uint8_t *) TLVP_VAL(&tp, RSL_IE_FULL_IMM_ASS_INFO);
+ msg->data = msg->l3h;
+ msg->l2h = NULL;
+ msg->len = TLVP_LEN(&tp, RSL_IE_FULL_IMM_ASS_INFO);
+
+ /* 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 */
+ rsl_tx_delete_ind(trx->bts, TLVP_VAL(&tp, RSL_IE_FULL_IMM_ASS_INFO),
+ TLVP_LEN(&tp, RSL_IE_FULL_IMM_ASS_INFO));
+ rate_ctr_inc2(trx->bts->ctrs, BTS_CTR_AGCH_DELETED);
+ msgb_free(msg);
+ }
+
+ /* return 1 means: don't msgb_free() the msg */
+ return 1;
+}
+
+/*
+ * dedicated channel related messages
+ */
+
+/* Send an RF CHANnel RELease ACKnowledge with the given chan_nr. This chan_nr may mismatch the current
+ * lchan state, if we received a CHANnel RELease for an already released channel, and we're just acking
+ * what we got without taking any action. */
+static int tx_rf_rel_ack(struct gsm_lchan *lchan, uint8_t chan_nr)
+{
+ struct msgb *msg;
+
+ msg = rsl_msgb_alloc(sizeof(struct abis_rsl_dchan_hdr));
+ if (!msg)
+ return -ENOMEM;
+
+ rsl_dch_push_hdr(msg, RSL_MT_RF_CHAN_REL_ACK, chan_nr);
+ msg->trx = lchan->ts->trx;
+
+ return abis_bts_rsl_sendmsg(msg);
+}
+
+/* 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);
+ bool send_rel_ack;
+
+ switch (lchan->rel_act_kind) {
+ case LCHAN_REL_ACT_RSL:
+ send_rel_ack = true;
+ break;
+
+ case LCHAN_REL_ACT_PCU:
+ switch (lchan->ts->pchan) {
+ case GSM_PCHAN_TCH_F_TCH_H_PDCH:
+ 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;
+ 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;
+ default:
+ LOGP(DRSL, LOGL_ERROR, "%s PCU rel ack for unexpected lchan kind\n",
+ gsm_lchan_name(lchan));
+ /* Release certainly was not requested by the BSC via RSL, so don't ack. */
+ send_rel_ack = false;
+ break;
+ }
+ break;
+
+ default:
+ /* A rel that was not requested by the BSC via RSL, hence not sending a rel ack to the
+ * BSC. */
+ send_rel_ack = false;
+ break;
+ }
+
+ if (!send_rel_ack) {
+ LOGP(DRSL, LOGL_NOTICE, "%s not sending REL ACK\n",
+ gsm_lchan_name(lchan));
+ return 0;
+ }
+
+ LOGP(DRSL, LOGL_NOTICE, "%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);
+}
+
+/* 8.4.2 sending CHANnel ACTIVation ACKnowledge */
+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 ie[2];
+
+ LOGP(DRSL, LOGL_NOTICE, "%s (ss=%d) %s Tx CHAN ACT ACK\n",
+ gsm_ts_and_pchan_name(lchan->ts), lchan->nr,
+ gsm_lchant_name(lchan->type));
+
+ msg = rsl_msgb_alloc(sizeof(struct abis_rsl_dchan_hdr));
+ if (!msg)
+ return -ENOMEM;
+
+ gsm48_gen_starting_time(ie, gtime);
+ msgb_tv_fixed_put(msg, RSL_IE_FRAME_NUMBER, 2, ie);
+ rsl_dch_push_hdr(msg, RSL_MT_CHAN_ACTIV_ACK, chan_nr);
+ msg->trx = lchan->ts->trx;
+
+ /* since activation was successful, do some lchan initialization */
+ lchan_meas_reset(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)
+{
+ struct msgb *msg;
+ uint8_t chan_nr = gsm_lchan2chan_nr(lchan);
+
+ LOGP(DRSL, LOGL_INFO, "Sending HANDOver DETect\n");
+
+ 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);
+
+ rsl_dch_push_hdr(msg, RSL_MT_HANDO_DET, chan_nr);
+ msg->trx = lchan->ts->trx;
+
+ return abis_bts_rsl_sendmsg(msg);
+}
+
+/* 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)
+{
+ struct msgb *msg;
+
+ if (lchan)
+ LOGP(DRSL, LOGL_NOTICE, "%s: ", gsm_lchan_name(lchan));
+ else
+ LOGP(DRSL, LOGL_NOTICE, "0x%02x: ", chan_nr);
+ LOGPC(DRSL, LOGL_NOTICE, "Sending Channel Activated NACK: cause = 0x%02x\n", cause);
+
+ msg = rsl_msgb_alloc(sizeof(struct abis_rsl_dchan_hdr));
+ if (!msg)
+ return -ENOMEM;
+
+ /* 9.3.26 Cause */
+ msgb_tlv_put(msg, RSL_IE_CAUSE, 1, &cause);
+ rsl_dch_push_hdr(msg, RSL_MT_CHAN_ACTIV_NACK, chan_nr);
+ msg->trx = trx;
+
+ 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);
+}
+
+/* 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) {
+ LOGP(DRSL, LOGL_NOTICE, "%s not sending CHAN ACT %s\n",
+ gsm_lchan_name(lchan), cause ? "NACK" : "ACK");
+ return 0;
+ }
+
+ if (cause)
+ return rsl_tx_chan_act_nack(lchan, cause);
+ return rsl_tx_chan_act_ack(lchan);
+}
+
+/* 8.4.4 sending CONNection FAILure */
+int rsl_tx_conn_fail(struct gsm_lchan *lchan, uint8_t cause)
+{
+ struct msgb *msg;
+ uint8_t chan_nr = gsm_lchan2chan_nr(lchan);
+
+ LOGP(DRSL, LOGL_NOTICE,
+ "%s Sending Connection Failure: cause = 0x%02x\n",
+ gsm_lchan_name(lchan), cause);
+
+ msg = rsl_msgb_alloc(sizeof(struct abis_rsl_dchan_hdr));
+ if (!msg)
+ return -ENOMEM;
+
+ /* 9.3.26 Cause */
+ msgb_tlv_put(msg, RSL_IE_CAUSE, 1, &cause);
+ rsl_dch_push_hdr(msg, RSL_MT_CONN_FAIL, chan_nr);
+ msg->trx = lchan->ts->trx;
+
+ return abis_bts_rsl_sendmsg(msg);
+}
+
+/* 8.5.3 sending CHANnel ReQuireD */
+int rsl_tx_chan_rqd(struct gsm_bts_trx *trx, struct gsm_time *gtime,
+ uint8_t ra, uint8_t acc_delay)
+{
+ struct msgb *nmsg;
+ uint8_t payload[3];
+
+ LOGP(DRSL, LOGL_NOTICE, "Sending Channel Required\n");
+
+ nmsg = rsl_msgb_alloc(sizeof(struct abis_rsl_cchan_hdr));
+ if (!nmsg)
+ return -ENOMEM;
+
+ /* 9.3.19 Request Reference */
+ payload[0] = ra;
+ gsm48_gen_starting_time(payload+1, gtime);
+ msgb_tv_fixed_put(nmsg, RSL_IE_REQ_REFERENCE, 3, payload);
+
+ /* 9.3.17 Access Delay */
+ msgb_tv_put(nmsg, RSL_IE_ACCESS_DELAY, acc_delay);
+
+ rsl_cch_push_hdr(nmsg, RSL_MT_CHAN_RQD, 0x88); // FIXME
+ nmsg->trx = trx;
+
+ return abis_bts_rsl_sendmsg(nmsg);
+}
+
+/* copy the SACCH related sysinfo from BTS global buffer to lchan specific buffer */
+static void copy_sacch_si_to_lchan(struct gsm_lchan *lchan)
+{
+ struct gsm_bts *bts = lchan->ts->trx->bts;
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(rsl_sacch_sitypes); i++) {
+ uint8_t rsl_si = rsl_sacch_sitypes[i];
+ int osmo_si = osmo_rsl2sitype(rsl_si);
+ uint32_t osmo_si_shifted = (1 << osmo_si);
+ osmo_static_assert(_MAX_SYSINFO_TYPE <= sizeof(osmo_si_shifted) * 8,
+ si_enum_vals_fit_in_bit_mask);
+
+ if (osmo_si == SYSINFO_TYPE_NONE)
+ continue;
+ if (!(bts->si_valid & osmo_si_shifted)) {
+ lchan->si.valid &= ~osmo_si_shifted;
+ continue;
+ }
+ lchan->si.valid |= osmo_si_shifted;
+ memcpy(GSM_LCHAN_SI(lchan, osmo_si), GSM_BTS_SI(bts, osmo_si), sizeof(sysinfo_buf_t));
+ }
+}
+
+
+static int encr_info2lchan(struct gsm_lchan *lchan,
+ const uint8_t *val, uint8_t len)
+{
+ int rc;
+ struct gsm_bts *bts = lchan->ts->trx->bts;
+ const char *ciph_name = get_value_string(gsm0808_chosen_enc_alg_names, *val);
+
+ /* check if the encryption algorithm sent by BSC is supported! */
+ rc = bts_supports_cipher(bts, *val);
+ if (rc != 1) {
+ LOGP(DRSL, LOGL_ERROR, "%s: BTS doesn't support cipher %s\n",
+ gsm_lchan_name(lchan), ciph_name);
+ return -EINVAL;
+ }
+
+ /* length can be '1' in case of no ciphering */
+ if (len < 1) {
+ LOGP(DRSL, LOGL_ERROR, "%s: Encryption Info cannot have len=%d\n",
+ gsm_lchan_name(lchan), len);
+ return -EINVAL;
+ }
+
+ lchan->encr.alg_id = *val++;
+ lchan->encr.key_len = len -1;
+ if (lchan->encr.key_len > sizeof(lchan->encr.key))
+ lchan->encr.key_len = sizeof(lchan->encr.key);
+ memcpy(lchan->encr.key, val, lchan->encr.key_len);
+ DEBUGP(DRSL, "%s: Setting lchan cipher algorithm %s\n",
+ gsm_lchan_name(lchan), ciph_name);
+
+ return 0;
+}
+
+/* Make sure no state from TCH use remains. */
+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;
+ copy_sacch_si_to_lchan(lchan);
+ memset(&lchan->tch, 0, sizeof(lchan->tch));
+}
+
+/*!
+ * 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)
+{
+ 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:
+ break;
+ case GSM_PCHAN_PDCH:
+ /* Only the first lchan matters for PDCH */
+ clear_lchan_for_pdch_activ(ts->lchan);
+ break;
+ default:
+ LOGP(DRSL, LOGL_ERROR,
+ "%s Cannot reconnect as pchan %s\n",
+ gsm_ts_and_pchan_name(ts),
+ gsm_pchan_name(ts->dyn.pchan_want));
+ 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);
+}
+
+static enum gsm_phys_chan_config dyn_pchan_from_chan_nr(uint8_t chan_nr)
+{
+ uint8_t cbits = chan_nr & RSL_CHAN_NR_MASK;
+ switch (cbits) {
+ case RSL_CHAN_Bm_ACCHs:
+ return GSM_PCHAN_TCH_F;
+ case RSL_CHAN_Lm_ACCHs:
+ case (RSL_CHAN_Lm_ACCHs + RSL_CHAN_NR_1):
+ return GSM_PCHAN_TCH_H;
+ case RSL_CHAN_OSMO_PDCH:
+ return GSM_PCHAN_PDCH;
+ default:
+ LOGP(DRSL, LOGL_ERROR,
+ "chan nr 0x%x not covered by dyn_pchan_from_chan_nr()\n",
+ chan_nr);
+ return GSM_PCHAN_UNKNOWN;
+ }
+}
+
+/* 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 tlv_parsed tp;
+ uint8_t type;
+ int rc;
+
+ if (lchan->state != LCHAN_S_NONE) {
+ LOGP(DRSL, LOGL_ERROR,
+ "%s: error: lchan is not available, but in state: %s.\n",
+ gsm_lchan_name(lchan), 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) {
+ 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));
+
+ if (ts->dyn.pchan_is != ts->dyn.pchan_want) {
+ /*
+ * The phy has the timeslot connected in a different
+ * 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);
+ if (rc)
+ return rsl_tx_chan_act_nack(lchan, RSL_ERR_NORMAL_UNSPEC);
+ /* indicate that the msgb should not be freed. */
+ return 1;
+ }
+ }
+
+ LOGP(DRSL, LOGL_DEBUG, "%s: rx Channel Activation in state: %s.\n",
+ gsm_lchan_name(lchan), 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;
+
+ rsl_tlv_parse(&tp, msgb_l3(msg), msgb_l3len(msg));
+
+ /* 9.3.3 Activation Type */
+ if (!TLVP_PRESENT(&tp, RSL_IE_ACT_TYPE)) {
+ LOGP(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);
+
+ /* 9.3.6 Channel Mode */
+ if (type != RSL_ACT_OSMO_PDCH) {
+ if (!TLVP_PRESENT(&tp, RSL_IE_CHAN_MODE)) {
+ LOGP(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);
+ }
+
+ /* 9.3.7 Encryption Information */
+ if (TLVP_PRESENT(&tp, RSL_IE_ENCR_INFO)) {
+ uint8_t len = TLVP_LEN(&tp, RSL_IE_ENCR_INFO);
+ const uint8_t *val = TLVP_VAL(&tp, RSL_IE_ENCR_INFO);
+
+ if (encr_info2lchan(lchan, val, len) < 0) {
+ rsl_tx_error_report(msg->trx, RSL_ERR_IE_CONTENT, &dch->chan_nr, NULL, msg);
+ return rsl_tx_chan_act_acknack(lchan, RSL_ERR_ENCR_UNIMPL);
+ }
+ } else
+ memset(&lchan->encr, 0, sizeof(lchan->encr));
+
+ /* 9.3.9 Handover Reference */
+ if ((type == RSL_ACT_INTER_ASYNC ||
+ type == RSL_ACT_INTER_SYNC) &&
+ TLVP_PRES_LEN(&tp, RSL_IE_HANDO_REF, 1)) {
+ lchan->ho.active = HANDOVER_ENABLED;
+ lchan->ho.ref = *TLVP_VAL(&tp, RSL_IE_HANDO_REF);
+ }
+
+ /* 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);
+ /* 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;
+ }
+ /* 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);
+
+ /* 9.3.32 BS Power Parameters */
+ /* 9.3.31 MS Power Parameters */
+ /* 9.3.16 Physical Context */
+
+ /* 9.3.29 SACCH Information */
+ if (TLVP_PRESENT(&tp, RSL_IE_SACCH_INFO)) {
+ uint8_t tot_len = TLVP_LEN(&tp, RSL_IE_SACCH_INFO);
+ const uint8_t *val = TLVP_VAL(&tp, RSL_IE_SACCH_INFO);
+ const uint8_t *cur = val;
+ uint8_t num_msgs = *cur++;
+ unsigned int i;
+ for (i = 0; i < num_msgs; i++) {
+ uint8_t rsl_si = *cur++;
+ uint8_t si_len = *cur++;
+ uint8_t osmo_si;
+
+ if (!OSMO_IN_ARRAY(rsl_si, rsl_sacch_sitypes)) {
+ 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);
+ }
+
+ osmo_si = osmo_rsl2sitype(rsl_si);
+ if (osmo_si == SYSINFO_TYPE_NONE) {
+ LOGP(DRSL, LOGL_NOTICE, " Rx SACCH SI 0x%02x not supported.\n", rsl_si);
+ 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);
+ }
+
+ lapdm_ui_prefix_lchan(lchan, cur, osmo_si, si_len);
+
+ cur += si_len;
+ if (cur >= val + tot_len) {
+ LOGP(DRSL, LOGL_ERROR, "Error parsing SACCH INFO 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);
+ }
+ }
+ } else {
+ /* 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) {
+ LOGP(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;
+ }
+ /* 9.3.53 MultiRate Control */
+ /* 9.3.54 Supported Codec Types */
+
+ LOGP(DRSL, LOGL_INFO, "%s: chan_nr=%s type=0x%02x mode=%s\n",
+ gsm_lchan_name(lchan), rsl_chan_nr_str(dch->chan_nr), 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
+ && ts->dyn.pchan_want == GSM_PCHAN_PDCH) {
+ /*
+ * We ack the activation to the BSC right away, regardless of
+ * the PCU succeeding or not; if a dynamic timeslot fails to go
+ * to PDCH mode for any reason, the BSC should still be able to
+ * switch it back to TCH modes and should not put the time slot
+ * in an error state. So for operating dynamic TS, the BSC
+ * would not take any action if the PDCH mode failed, e.g.
+ * because the PCU is not yet running. Even if alerting the
+ * core network of broken GPRS service is desired, this only
+ * makes sense when the PCU has not shown up for some time.
+ * It's easiest to not forward activation delays to the BSC: if
+ * the BSC tells us to do PDCH, we do our best, and keep the
+ * details on the BTS and PCU level. This is kind of analogous
+ * to how plain PDCH TS operate. Directly call
+ * rsl_tx_chan_act_ack() instead of rsl_tx_chan_act_acknack()
+ * because we don't want/need to decide whether to drop due to
+ * lchan->rel_act_kind.
+ */
+ 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);
+
+ /*
+ * pcu_tx_info_ind() will pick up the ts->dyn.pchan_want. If
+ * the PCU is not connected yet, ignore for now; the PCU will
+ * catch up (and send the RSL ack) once it connects.
+ */
+ if (pcu_connected()) {
+ DEBUGP(DRSL, "%s Activate via PCU\n", gsm_ts_and_pchan_name(ts));
+ rc = pcu_tx_info_ind();
+ }
+ else {
+ DEBUGP(DRSL, "%s Activate via PCU when PCU connects\n",
+ gsm_ts_and_pchan_name(ts));
+ rc = 0;
+ }
+ if (rc) {
+ rsl_tx_error_report(msg->trx, RSL_ERR_NORMAL_UNSPEC, &dch->chan_nr, NULL, msg);
+ return rsl_tx_chan_act_acknack(lchan, RSL_ERR_NORMAL_UNSPEC);
+ }
+ return 0;
+ }
+
+ /* 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);
+ 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;
+
+ 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();
+}
+
+/* 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;"
+ " just ACKing the release\n",
+ gsm_ts_and_pchan_name(lchan->ts), lchan->nr,
+ gsm_lchans_name(lchan->state));
+ /* Just ack the release and ignore. Make sure to reflect the same chan_nr we received,
+ * 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);
+
+ return 0;
+}
+
+#ifdef FAKE_CIPH_MODE_COMPL
+/* ugly hack to send a fake CIPH MODE COMPLETE back to the BSC */
+#include <osmocom/gsm/protocol/gsm_04_08.h>
+#include <osmocom/gsm/gsm48.h>
+static int tx_ciph_mod_compl_hack(struct gsm_lchan *lchan, uint8_t link_id,
+ const char *imeisv)
+{
+ struct msgb *fake_msg;
+ struct gsm48_hdr *g48h;
+ uint8_t mid_buf[11];
+ int rc;
+
+ fake_msg = rsl_msgb_alloc(128);
+ if (!fake_msg)
+ return -ENOMEM;
+
+ /* generate 04.08 RR message */
+ g48h = (struct gsm48_hdr *) msgb_put(fake_msg, sizeof(*g48h));
+ g48h->proto_discr = GSM48_PDISC_RR;
+ g48h->msg_type = GSM48_MT_RR_CIPH_M_COMPL;
+
+ /* add IMEISV, if requested */
+ if (imeisv) {
+ rc = gsm48_generate_mid_from_imsi(mid_buf, imeisv);
+ if (rc > 0) {
+ mid_buf[2] = (mid_buf[2] & 0xf8) | GSM_MI_TYPE_IMEISV;
+ memcpy(msgb_put(fake_msg, rc), mid_buf, rc);
+ }
+ }
+
+ rsl_rll_push_l3(fake_msg, RSL_MT_DATA_IND, gsm_lchan2chan_nr(lchan),
+ link_id, 1);
+
+ fake_msg->lchan = lchan;
+ fake_msg->trx = lchan->ts->trx;
+
+ /* send it back to the BTS */
+ return abis_bts_rsl_sendmsg(fake_msg);
+}
+
+struct ciph_mod_compl {
+ struct osmo_timer_list timer;
+ struct gsm_lchan *lchan;
+ int send_imeisv;
+ uint8_t link_id;
+};
+
+static void cmc_timer_cb(void *data)
+{
+ struct ciph_mod_compl *cmc = data;
+ const char *imeisv = NULL;
+
+ LOGP(DRSL, LOGL_NOTICE,
+ "%s Sending FAKE CIPHERING MODE COMPLETE to BSC (Alg %u)\n",
+ gsm_lchan_name(cmc->lchan), cmc->lchan->encr.alg_id);
+
+ if (cmc->send_imeisv)
+ imeisv = "0123456789012345";
+
+ /* We have no clue whatsoever that this lchan still exists! */
+ tx_ciph_mod_compl_hack(cmc->lchan, cmc->link_id, imeisv);
+
+ talloc_free(cmc);
+}
+#endif
+
+
+/* 8.4.6 ENCRYPTION COMMAND */
+static int rsl_rx_encr_cmd(struct msgb *msg)
+{
+ struct gsm_lchan *lchan = msg->lchan;
+ struct abis_rsl_dchan_hdr *dch = msgb_l2(msg);
+ struct tlv_parsed tp;
+ 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);
+ }
+
+ if (!TLVP_PRESENT(&tp, RSL_IE_ENCR_INFO) ||
+ !TLVP_PRESENT(&tp, RSL_IE_L3_INFO) ||
+ !TLVP_PRESENT(&tp, RSL_IE_LINK_IDENT)) {
+ return rsl_tx_error_report(msg->trx, RSL_ERR_MAND_IE_ERROR, &dch->chan_nr, NULL, msg);
+ }
+
+ /* 9.3.7 Encryption Information */
+ if (TLVP_PRESENT(&tp, RSL_IE_ENCR_INFO)) {
+ uint8_t len = TLVP_LEN(&tp, RSL_IE_ENCR_INFO);
+ const uint8_t *val = TLVP_VAL(&tp, RSL_IE_ENCR_INFO);
+
+ if (encr_info2lchan(lchan, val, len) < 0) {
+ return rsl_tx_error_report(msg->trx, RSL_ERR_IE_CONTENT, &dch->chan_nr,
+ NULL, msg);
+ }
+ }
+
+ /* 9.3.2 Link Identifier */
+ link_id = *TLVP_VAL(&tp, RSL_IE_LINK_IDENT);
+
+ /* we have to set msg->l3h as rsl_rll_push_l3 will use it to
+ * determine the length field of the L3_INFO IE */
+ msg->l3h = (uint8_t *) TLVP_VAL(&tp, RSL_IE_L3_INFO);
+
+ /* pop the RSL dchan header, but keep L3 TLV */
+ msgb_pull(msg, msg->l3h - msg->data);
+
+ /* push a fake RLL DATA REQ header */
+ rsl_rll_push_l3(msg, RSL_MT_DATA_REQ, dch->chan_nr, link_id, 1);
+
+
+#ifdef FAKE_CIPH_MODE_COMPL
+ if (lchan->encr.alg_id != RSL_ENC_ALG_A5(0)) {
+ struct ciph_mod_compl *cmc;
+ struct gsm48_hdr *g48h = (struct gsm48_hdr *) msg->l3h;
+
+ cmc = talloc_zero(NULL, struct ciph_mod_compl);
+ if (g48h->data[0] & 0x10)
+ cmc->send_imeisv = 1;
+ cmc->lchan = lchan;
+ cmc->link_id = link_id;
+ cmc->timer.cb = cmc_timer_cb;
+ cmc->timer.data = cmc;
+ osmo_timer_schedule(&cmc->timer, 1, 0);
+
+ /* FIXME: send fake CM SERVICE ACCEPT to MS */
+
+ return 0;
+ } else
+#endif
+ {
+ LOGP(DRSL, LOGL_INFO, "%s Fwd RSL ENCR CMD (Alg %u) to LAPDm\n",
+ gsm_lchan_name(lchan), lchan->encr.alg_id);
+ /* hand it into RSLms for transmission of L3_INFO to the MS */
+ lapdm_rslms_recvmsg(msg, &lchan->lapdm_ch);
+ /* return 1 to make sure the msgb is not free'd */
+ return 1;
+ }
+}
+
+/* 8.4.11 MODE MODIFY NEGATIVE ACKNOWLEDGE */
+static int _rsl_tx_mode_modif_nack(struct gsm_bts_trx *trx, uint8_t chan_nr, uint8_t cause,
+ struct gsm_lchan *lchan)
+{
+ struct msgb *msg;
+
+ if (lchan)
+ LOGP(DRSL, LOGL_NOTICE, "%s: ", gsm_lchan_name(lchan));
+ else
+ LOGP(DRSL, LOGL_NOTICE, "0x%02x: ", chan_nr);
+ LOGPC(DRSL, LOGL_NOTICE, "Tx MODE MODIFY NACK (cause = 0x%02x)\n", cause);
+
+ msg = rsl_msgb_alloc(sizeof(struct abis_rsl_dchan_hdr));
+ if (!msg)
+ return -ENOMEM;
+
+ msg->len = 0;
+ msg->data = msg->tail = msg->l3h;
+
+ /* 9.3.26 Cause */
+ msgb_tlv_put(msg, RSL_IE_CAUSE, 1, &cause);
+ rsl_dch_push_hdr(msg, RSL_MT_MODE_MODIFY_NACK, chan_nr);
+ msg->trx = trx;
+
+ return abis_bts_rsl_sendmsg(msg);
+}
+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);
+}
+
+
+/* 8.4.10 MODE MODIFY ACK */
+static int rsl_tx_mode_modif_ack(struct gsm_lchan *lchan)
+{
+ struct msgb *msg;
+ uint8_t chan_nr = gsm_lchan2chan_nr(lchan);
+
+ LOGP(DRSL, LOGL_INFO, "%s Tx MODE MODIF ACK\n", gsm_lchan_name(lchan));
+
+ msg = rsl_msgb_alloc(sizeof(struct abis_rsl_dchan_hdr));
+ if (!msg)
+ return -ENOMEM;
+
+ rsl_dch_push_hdr(msg, RSL_MT_MODE_MODIFY_ACK, chan_nr);
+ msg->trx = lchan->ts->trx;
+
+ return abis_bts_rsl_sendmsg(msg);
+}
+
+/* 8.4.9 MODE MODIFY */
+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;
+
+ rsl_tlv_parse(&tp, msgb_l3(msg), msgb_l3len(msg));
+
+ /* 9.3.6 Channel Mode */
+ if (!TLVP_PRESENT(&tp, RSL_IE_CHAN_MODE)) {
+ LOGP(DRSL, LOGL_NOTICE, "missing Channel Mode\n");
+ return rsl_tx_mode_modif_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 (bts_supports_cm(lchan->ts->trx->bts, ts_pchan(lchan->ts), lchan->tch_mode) != 1) {
+ LOGP(DRSL, LOGL_ERROR,
+ "%s %s: invalid mode: %s (wrong BSC configuration?)\n",
+ gsm_ts_and_pchan_name(lchan->ts), gsm_lchan_name(lchan),
+ gsm48_chan_mode_name(lchan->tch_mode));
+ return rsl_tx_mode_modif_nack(lchan, RSL_ERR_SERV_OPT_UNAVAIL);
+ }
+
+ /* 9.3.7 Encryption Information */
+ if (TLVP_PRESENT(&tp, RSL_IE_ENCR_INFO)) {
+ uint8_t len = TLVP_LEN(&tp, RSL_IE_ENCR_INFO);
+ const uint8_t *val = TLVP_VAL(&tp, RSL_IE_ENCR_INFO);
+
+ if (encr_info2lchan(lchan, val, len) < 0) {
+ rsl_tx_error_report(msg->trx, RSL_ERR_IE_CONTENT, &dch->chan_nr, NULL, msg);
+ return rsl_tx_mode_modif_nack(lchan, RSL_ERR_ENCR_UNIMPL);
+ }
+ }
+
+ /* 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) {
+ LOGP(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;
+ }
+ /* 9.3.53 MultiRate Control */
+ /* 9.3.54 Supported Codec Types */
+
+ l1sap_chan_modify(lchan->ts->trx, dch->chan_nr);
+
+ /* FIXME: delay this until L1 says OK? */
+ rsl_tx_mode_modif_ack(lchan);
+
+ return 0;
+}
+
+/* 8.4.15 MS POWER CONTROL */
+static int rsl_rx_ms_pwr_ctrl(struct msgb *msg)
+{
+ struct gsm_lchan *lchan = msg->lchan;
+ struct tlv_parsed tp;
+
+ rsl_tlv_parse(&tp, msgb_l3(msg), msgb_l3len(msg));
+ if (TLVP_PRES_LEN(&tp, RSL_IE_MS_POWER, 1)) {
+ uint8_t pwr = *TLVP_VAL(&tp, RSL_IE_MS_POWER) & 0x1F;
+ lchan->ms_power_ctrl.fixed = 1;
+ lchan->ms_power_ctrl.current = pwr;
+
+ LOGP(DRSL, LOGL_NOTICE, "%s forcing power to %d\n",
+ gsm_lchan_name(lchan), lchan->ms_power_ctrl.current);
+ bts_model_adjst_ms_pwr(lchan);
+ }
+
+ return 0;
+}
+
+/* 8.4.20 SACCH INFO MODify */
+static int rsl_rx_sacch_inf_mod(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;
+ struct tlv_parsed tp;
+ uint8_t rsl_si, osmo_si;
+
+ rsl_tlv_parse(&tp, msgb_l3(msg), msgb_l3len(msg));
+
+ if (TLVP_PRESENT(&tp, RSL_IE_STARTNG_TIME)) {
+ LOGP(DRSL, LOGL_NOTICE, "Starting time not supported\n");
+ return rsl_tx_error_report(msg->trx, RSL_ERR_SERV_OPT_UNIMPL, &dch->chan_nr, NULL, msg);
+ }
+
+ /* 9.3.30 System Info Type */
+ if (!TLVP_PRESENT(&tp, RSL_IE_SYSINFO_TYPE))
+ return rsl_tx_error_report(msg->trx, RSL_ERR_MAND_IE_ERROR, &dch->chan_nr, NULL, msg);
+
+ rsl_si = *TLVP_VAL(&tp, RSL_IE_SYSINFO_TYPE);
+ if (!OSMO_IN_ARRAY(rsl_si, rsl_sacch_sitypes))
+ return rsl_tx_error_report(msg->trx, RSL_ERR_IE_CONTENT, &dch->chan_nr, NULL, msg);
+
+ osmo_si = osmo_rsl2sitype(rsl_si);
+ if (osmo_si == SYSINFO_TYPE_NONE) {
+ LOGP(DRSL, LOGL_NOTICE, "%s Rx SACCH SI 0x%02x not supported.\n",
+ gsm_lchan_name(lchan), rsl_si);
+ return rsl_tx_error_report(msg->trx, RSL_ERR_IE_CONTENT, &dch->chan_nr, NULL, msg);
+ }
+ if (TLVP_PRESENT(&tp, RSL_IE_L3_INFO)) {
+ uint16_t len = TLVP_LEN(&tp, RSL_IE_L3_INFO);
+
+ lapdm_ui_prefix_lchan(lchan, TLVP_VAL(&tp, RSL_IE_L3_INFO), osmo_si, len);
+ if (memcmp(GSM_BTS_SI(bts, osmo_si), TLVP_VAL(&tp, RSL_IE_L3_INFO), sizeof(sysinfo_buf_t) != 0))
+ lchan->si.overridden |= (1 << osmo_si);
+ else
+ lchan->si.overridden &= ~(1 << osmo_si);
+
+ LOGP(DRSL, LOGL_INFO, "%s Rx RSL SACCH FILLING (SI%s)\n",
+ gsm_lchan_name(lchan),
+ get_value_string(osmo_sitype_strs, osmo_si));
+ } else {
+ lchan->si.valid &= ~(1 << osmo_si);
+ LOGP(DRSL, LOGL_INFO, "%s Rx RSL Disabling SACCH FILLING (SI%s)\n",
+ gsm_lchan_name(lchan),
+ get_value_string(osmo_sitype_strs, osmo_si));
+ }
+
+ return 0;
+}
+
+/*
+ * ip.access related messages
+ */
+static void rsl_add_rtp_stats(struct gsm_lchan *lchan, struct msgb *msg)
+{
+ struct ipa_stats {
+ uint32_t packets_sent;
+ uint32_t octets_sent;
+ uint32_t packets_recv;
+ uint32_t octets_recv;
+ uint32_t packets_lost;
+ uint32_t arrival_jitter;
+ uint32_t avg_tx_delay;
+ } __attribute__((packed));
+
+ struct ipa_stats stats;
+
+ memset(&stats, 0, sizeof(stats));
+
+ if (lchan->abis_ip.rtp_socket)
+ osmo_rtp_socket_stats(lchan->abis_ip.rtp_socket,
+ &stats.packets_sent, &stats.octets_sent,
+ &stats.packets_recv, &stats.octets_recv,
+ &stats.packets_lost, &stats.arrival_jitter);
+ /* convert to network byte order */
+ stats.packets_sent = htonl(stats.packets_sent);
+ stats.octets_sent = htonl(stats.octets_sent);
+ stats.packets_recv = htonl(stats.packets_recv);
+ stats.octets_recv = htonl(stats.octets_recv);
+ stats.packets_lost = htonl(stats.packets_lost);
+
+ msgb_tlv_put(msg, RSL_IE_IPAC_CONN_STAT, sizeof(stats), (uint8_t *) &stats);
+}
+
+int rsl_tx_ipac_dlcx_ind(struct gsm_lchan *lchan, uint8_t cause)
+{
+ struct msgb *nmsg;
+
+ LOGP(DRSL, LOGL_NOTICE, "%s Sending RTP delete indication: cause = %s\n",
+ gsm_lchan_name(lchan), rsl_err_name(cause));
+
+ nmsg = rsl_msgb_alloc(sizeof(struct abis_rsl_dchan_hdr));
+ if (!nmsg)
+ return -ENOMEM;
+
+ 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));
+
+ nmsg->trx = lchan->ts->trx;
+
+ return abis_bts_rsl_sendmsg(nmsg);
+}
+
+/* transmit an CRCX ACK for the lchan */
+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);
+ const char *name;
+ struct in_addr ia;
+
+ if (orig_msgt == RSL_MT_IPAC_CRCX)
+ name = "CRCX";
+ else
+ name = "MDCX";
+
+ ia.s_addr = htonl(lchan->abis_ip.bound_ip);
+ LOGP(DRSL, LOGL_INFO, "%s RSL Tx IPAC_%s_ACK (local %s:%u, ",
+ gsm_lchan_name(lchan), name,
+ inet_ntoa(ia), lchan->abis_ip.bound_port);
+ ia.s_addr = htonl(lchan->abis_ip.connect_ip);
+ LOGPC(DRSL, LOGL_INFO, "remote %s:%u)\n",
+ inet_ntoa(ia), lchan->abis_ip.connect_port);
+
+ msg = rsl_msgb_alloc(sizeof(struct abis_rsl_dchan_hdr));
+ if (!msg)
+ return -ENOMEM;
+
+
+ /* Connection ID */
+ msgb_tv16_put(msg, RSL_IE_IPAC_CONN_ID, htons(lchan->abis_ip.conn_id));
+
+ /* locally bound IP */
+ msgb_v_put(msg, RSL_IE_IPAC_LOCAL_IP);
+ msgb_put_u32(msg, lchan->abis_ip.bound_ip);
+
+ /* locally bound port */
+ msgb_tv16_put(msg, RSL_IE_IPAC_LOCAL_PORT,
+ lchan->abis_ip.bound_port);
+
+ if (inc_pt2) {
+ /* RTP Payload Type 2 */
+ msgb_tv_put(msg, RSL_IE_IPAC_RTP_PAYLOAD2,
+ lchan->abis_ip.rtp_payload2);
+ }
+
+ /* push the header in front */
+ rsl_ipa_push_hdr(msg, orig_msgt + 1, chan_nr);
+ msg->trx = lchan->ts->trx;
+
+ return abis_bts_rsl_sendmsg(msg);
+}
+
+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);
+
+ LOGP(DRSL, LOGL_INFO, "%s RSL Tx IPAC_DLCX_ACK\n",
+ gsm_lchan_name(lchan));
+
+ msg = rsl_msgb_alloc(sizeof(struct abis_rsl_dchan_hdr));
+ if (!msg)
+ return -ENOMEM;
+
+ if (inc_conn_id) {
+ msgb_tv16_put(msg, RSL_IE_IPAC_CONN_ID, lchan->abis_ip.conn_id);
+ rsl_add_rtp_stats(lchan, msg);
+ }
+
+ rsl_ipa_push_hdr(msg, RSL_MT_IPAC_DLCX_ACK, chan_nr);
+ msg->trx = lchan->ts->trx;
+
+ return abis_bts_rsl_sendmsg(msg);
+}
+
+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);
+
+ LOGP(DRSL, LOGL_INFO, "%s RSL Tx IPAC_DLCX_NACK\n",
+ gsm_lchan_name(lchan));
+
+ msg = rsl_msgb_alloc(sizeof(struct abis_rsl_dchan_hdr));
+ if (!msg)
+ return -ENOMEM;
+
+ if (inc_conn_id)
+ msgb_tv_put(msg, RSL_IE_IPAC_CONN_ID, lchan->abis_ip.conn_id);
+
+ msgb_tlv_put(msg, RSL_IE_CAUSE, 1, &cause);
+
+ rsl_ipa_push_hdr(msg, RSL_MT_IPAC_DLCX_NACK, chan_nr);
+ msg->trx = lchan->ts->trx;
+
+ return abis_bts_rsl_sendmsg(msg);
+
+}
+
+
+/* transmit an CRCX NACK for the 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);
+
+ /* FIXME: allocate new msgb and copy old over */
+ LOGP(DRSL, LOGL_NOTICE, "%s RSL Tx IPAC_BIND_NACK\n",
+ gsm_lchan_name(lchan));
+
+ msg = rsl_msgb_alloc(sizeof(struct abis_rsl_dchan_hdr));
+ if (!msg)
+ return -ENOMEM;
+
+ if (inc_ipport) {
+ /* remote IP */
+ msgb_v_put(msg, RSL_IE_IPAC_REMOTE_IP);
+ msgb_put_u32(msg, lchan->abis_ip.connect_ip);
+
+ /* remote port */
+ msgb_tv16_put(msg, RSL_IE_IPAC_REMOTE_PORT,
+ htons(lchan->abis_ip.connect_port));
+ }
+
+ /* 9.3.26 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);
+ msg->trx = lchan->ts->trx;
+
+ return abis_bts_rsl_sendmsg(msg);
+}
+
+static char *get_rsl_local_ip(struct gsm_bts_trx *trx)
+{
+ struct e1inp_ts *ts = trx->rsl_link->ts;
+ struct sockaddr_storage ss;
+ socklen_t sa_len = sizeof(ss);
+ static char hostbuf[256];
+ int rc;
+
+ rc = getsockname(ts->driver.ipaccess.fd.fd, (struct sockaddr *) &ss,
+ &sa_len);
+ if (rc < 0)
+ return NULL;
+
+ rc = getnameinfo((struct sockaddr *)&ss, sa_len,
+ hostbuf, sizeof(hostbuf), NULL, 0,
+ NI_NUMERICHOST);
+ if (rc < 0)
+ return NULL;
+
+ 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;
+ uint32_t connect_ip = 0;
+ uint16_t connect_port = 0;
+ int rc, inc_ip_port = 0, port;
+ char *name;
+ struct in_addr ia;
+ struct in_addr addr;
+
+ if (dch->c.msg_type == RSL_MT_IPAC_CRCX)
+ name = "CRCX";
+ else
+ name = "MDCX";
+
+ /* check the kind of channel and reject */
+ if (lchan->type != GSM_LCHAN_TCH_F && lchan->type != GSM_LCHAN_TCH_H)
+ 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);
+
+ LOGP(DRSL, LOGL_DEBUG, "%s IPAC_%s: ", gsm_lchan_name(lchan), name);
+ if (TLVP_PRES_LEN(&tp, RSL_IE_IPAC_REMOTE_IP, 4)) {
+ 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));
+ }
+
+ speech_mode = TLVP_VAL(&tp, RSL_IE_IPAC_SPEECH_MODE);
+ if (speech_mode)
+ LOGPC(DRSL, LOGL_DEBUG, "speech_mode=%u ", *speech_mode);
+
+ payload_type = TLVP_VAL(&tp, RSL_IE_IPAC_RTP_PAYLOAD);
+ if (payload_type)
+ LOGPC(DRSL, LOGL_DEBUG, "payload_type=%u ", *payload_type);
+
+ LOGPC(DRSL, LOGL_DEBUG, "\n");
+
+ payload_type2 = TLVP_VAL(&tp, RSL_IE_IPAC_RTP_PAYLOAD2);
+
+ if (dch->c.msg_type == RSL_MT_IPAC_CRCX && connect_ip && connect_port)
+ inc_ip_port = 1;
+
+ if (payload_type && payload_type2) {
+ LOGP(DRSL, LOGL_ERROR, "%s Rx RSL IPAC %s, "
+ "RTP_PT and RTP_PT2 in same msg !?!\n",
+ gsm_lchan_name(lchan), name);
+ return tx_ipac_XXcx_nack(lchan, RSL_ERR_MAND_IE_ERROR,
+ 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) {
+ LOGP(DRSL, LOGL_ERROR, "%s Rx RSL IPAC CRCX, "
+ "but we already have socket!\n",
+ gsm_lchan_name(lchan));
+ return tx_ipac_XXcx_nack(lchan, RSL_ERR_RES_UNAVAIL,
+ 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) {
+ LOGP(DRTP, LOGL_ERROR,
+ "%s IPAC Failed to create RTP/RTCP sockets\n",
+ gsm_lchan_name(lchan));
+ oml_fail_rep(OSMO_EVT_CRIT_RTP_TOUT,
+ "%s IPAC Failed to create RTP/RTCP sockets",
+ gsm_lchan_name(lchan));
+ 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)
+ LOGP(DRTP, LOGL_ERROR,
+ "%s IPAC Failed to set RTP socket parameters: %s\n",
+ gsm_lchan_name(lchan), strerror(-rc));
+ else
+ LOGP(DRTP, LOGL_INFO,
+ "%s IPAC set RTP socket parameters: %d\n",
+ gsm_lchan_name(lchan), 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);
+ }
+ rc = bind_rtp(bts, lchan->abis_ip.rtp_socket, ipstr);
+ if (rc < 0) {
+ LOGP(DRTP, LOGL_ERROR,
+ "%s IPAC Failed to bind RTP/RTCP sockets\n",
+ gsm_lchan_name(lchan));
+ oml_fail_rep(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);
+ 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) {
+ LOGP(DRSL, LOGL_ERROR, "%s Rx RSL IPAC MDCX, "
+ "but we have no RTP socket!\n",
+ gsm_lchan_name(lchan));
+ 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) {
+ LOGP(DRTP, LOGL_ERROR,
+ "%s Failed to connect RTP/RTCP sockets\n",
+ 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);
+ 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)
+ LOGP(DRTP, LOGL_ERROR, "%s IPAC cannot obtain "
+ "locally bound IP/port: %d\n",
+ gsm_lchan_name(lchan), rc);
+ lchan->abis_ip.bound_port = port;
+
+ /* Everything has succeeded, we can store new values in lchan */
+ if (payload_type) {
+ lchan->abis_ip.rtp_payload = *payload_type;
+ if (lchan->abis_ip.rtp_socket)
+ osmo_rtp_socket_set_pt(lchan->abis_ip.rtp_socket,
+ *payload_type);
+ }
+ if (payload_type2) {
+ lchan->abis_ip.rtp_payload2 = *payload_type2;
+ if (lchan->abis_ip.rtp_socket)
+ osmo_rtp_socket_set_pt(lchan->abis_ip.rtp_socket,
+ *payload_type2);
+ }
+ if (speech_mode)
+ lchan->abis_ip.speech_mode = *speech_mode;
+
+ /* FIXME: CSD, jitterbuffer, compression */
+
+ return rsl_tx_ipac_XXcx_ack(lchan, payload_type2 ? 1 : 0,
+ dch->c.msg_type);
+}
+
+static int rsl_rx_ipac_dlcx(struct msgb *msg)
+{
+ struct tlv_parsed tp;
+ 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 (TLVP_PRESENT(&tp, RSL_IE_IPAC_CONN_ID))
+ inc_conn_id = 1;
+
+ rc = rsl_tx_ipac_dlcx_ack(lchan, inc_conn_id);
+ 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);
+ }
+ return rc;
+}
+
+/*
+ * dynamic TCH/F_PDCH related messages, originally ip.access specific but
+ * reused for other BTS models (sysmo-bts, ...)
+ */
+
+/* PDCH ACT/DEACT ACKNOWLEDGE */
+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);
+ struct msgb *msg;
+ uint8_t ie[2];
+
+ LOGP(DRSL, LOGL_NOTICE, "%s Tx PDCH %s ACK\n",
+ gsm_lchan_name(lchan), pdch_act? "ACT" : "DEACT");
+
+ msg = rsl_msgb_alloc(sizeof(struct abis_rsl_dchan_hdr));
+ if (!msg)
+ return -ENOMEM;
+
+ if (pdch_act) {
+ gsm48_gen_starting_time(ie, gtime);
+ msgb_tv_fixed_put(msg, RSL_IE_FRAME_NUMBER, 2, ie);
+ }
+ rsl_dch_push_hdr(msg,
+ pdch_act? RSL_MT_IPAC_PDCH_ACT_ACK
+ : RSL_MT_IPAC_PDCH_DEACT_ACK,
+ chan_nr);
+ msg->lchan = lchan;
+ msg->trx = lchan->ts->trx;
+
+ return abis_bts_rsl_sendmsg(msg);
+}
+
+/* PDCH ACT/DEACT NEGATIVE ACKNOWLEDGE */
+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);
+
+ LOGP(DRSL, LOGL_NOTICE, "%s Tx PDCH %s NACK (cause = 0x%02x)\n",
+ gsm_lchan_name(lchan), pdch_act? "ACT" : "DEACT", cause);
+
+ msg = rsl_msgb_alloc(sizeof(struct abis_rsl_dchan_hdr));
+ if (!msg)
+ return -ENOMEM;
+
+ msg->len = 0;
+ msg->data = msg->tail = msg->l3h;
+
+ /* 9.3.26 Cause */
+ msgb_tlv_put(msg, RSL_IE_CAUSE, 1, &cause);
+ rsl_dch_push_hdr(msg,
+ pdch_act? RSL_MT_IPAC_PDCH_ACT_NACK
+ : RSL_MT_IPAC_PDCH_DEACT_NACK,
+ chan_nr);
+ msg->lchan = lchan;
+ msg->trx = lchan->ts->trx;
+
+ return abis_bts_rsl_sendmsg(msg);
+}
+
+/*
+ * Starting point for dynamic PDCH switching. See osmo-gsm-manuals.git for a
+ * diagram of what will happen here. The implementation is as follows:
+ *
+ * PDCH ACT == TCH/F -> PDCH:
+ * 1. call bts_model_ts_disconnect() to disconnect TCH/F;
+ * 2. cb_ts_disconnected() is called when done;
+ * 3. call bts_model_ts_connect() to connect as PDTCH;
+ * 4. cb_ts_connected(rc) is called when done;
+ * 5. instruct the PCU to enable PDTCH;
+ * 6. the PCU will call back with an activation request;
+ * 7. l1sap_info_act_cnf() will call ipacc_dyn_pdch_complete() when SAPI
+ * activations are done;
+ * 8. send a PDCH ACT ACK.
+ *
+ * PDCH DEACT == PDCH -> TCH/F:
+ * 1. instruct the PCU to disable PDTCH;
+ * 2. the PCU will call back with a deactivation request;
+ * 3. l1sap_info_rel_cnf() will call bts_model_ts_disconnect() when SAPI
+ * deactivations are done;
+ * 4. cb_ts_disconnected() is called when done;
+ * 5. call bts_model_ts_connect() to connect as TCH/F;
+ * 6. cb_ts_connected(rc) is called when done;
+ * 7. directly call ipacc_dyn_pdch_complete(), since no further action required
+ * for TCH/F;
+ * 8. send a PDCH DEACT ACK.
+ *
+ * When an error happens along the way, a PDCH DE/ACT NACK is sent.
+ * TODO: may need to be made more waterproof in all stages, to send a NACK and
+ * clear the PDCH pending flags from ts->flags.
+ */
+static void rsl_rx_dyn_pdch(struct msgb *msg, bool pdch_act)
+{
+ int rc;
+ struct gsm_lchan *lchan = msg->lchan;
+ struct gsm_bts_trx_ts *ts = lchan->ts;
+ bool is_pdch_act = (ts->flags & TS_F_PDCH_ACTIVE);
+
+ if (ts->flags & TS_F_PDCH_PENDING_MASK) {
+ /* Only one of the pending flags should ever be set at the same
+ * time, but just log both in case both should be set. */
+ LOGP(DRSL, LOGL_ERROR,
+ "%s Request to PDCH %s, but PDCH%s%s is still pending\n",
+ gsm_lchan_name(lchan), pdch_act? "ACT" : "DEACT",
+ (ts->flags & TS_F_PDCH_ACT_PENDING)? " ACT" : "",
+ (ts->flags & TS_F_PDCH_DEACT_PENDING)? " DEACT" : "");
+ rsl_tx_dyn_pdch_nack(lchan, pdch_act, RSL_ERR_NORMAL_UNSPEC);
+ return;
+ }
+
+ if (lchan->state != LCHAN_S_NONE) {
+ LOGP(DRSL, LOGL_NOTICE,
+ "%s Request to PDCH %s, but lchan is still in state %s\n",
+ gsm_ts_and_pchan_name(ts), pdch_act? "ACT" : "DEACT",
+ gsm_lchans_name(lchan->state));
+ }
+
+ ts->flags |= pdch_act? TS_F_PDCH_ACT_PENDING
+ : TS_F_PDCH_DEACT_PENDING;
+
+ /* ensure that this is indeed a dynamic-PDCH channel */
+ if (ts->pchan != GSM_PCHAN_TCH_F_PDCH) {
+ LOGP(DRSL, LOGL_ERROR,
+ "%s Attempt to PDCH %s on TS that is not a TCH/F_PDCH (is %s)\n",
+ gsm_lchan_name(lchan), pdch_act? "ACT" : "DEACT",
+ gsm_pchan_name(ts->pchan));
+ ipacc_dyn_pdch_complete(ts, -EINVAL);
+ return;
+ }
+
+ if (is_pdch_act == pdch_act) {
+ LOGP(DL1C, LOGL_NOTICE,
+ "%s Request to PDCH %s, but is already so\n",
+ gsm_lchan_name(lchan), pdch_act? "ACT" : "DEACT");
+ ipacc_dyn_pdch_complete(ts, 0);
+ return;
+ }
+
+ 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 {
+ /* First, deactivate PDTCH through the PCU, to connect TCH
+ * later.
+ * pcu_tx_info_ind() will pick up TS_F_PDCH_DEACT_PENDING and
+ * trigger a deactivation.
+ * Except when the PCU is not connected yet, then trigger
+ * disconnect immediately from here. The PCU will catch up when
+ * it connects. */
+ /* TODO: timeout on channel connect / disconnect request from PCU? */
+ if (pcu_connected())
+ rc = pcu_tx_info_ind();
+ else
+ rc = bts_model_ts_disconnect(ts);
+ }
+
+ /* Error? then NACK right now. */
+ if (rc)
+ ipacc_dyn_pdch_complete(ts, rc);
+}
+
+static void ipacc_dyn_pdch_ts_disconnected(struct gsm_bts_trx_ts *ts)
+{
+ int rc;
+ 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));
+ 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));
+ as_pchan = GSM_PCHAN_PDCH;
+ } else
+ /* No reconnect pending. */
+ return;
+
+ rc = conf_lchans_as_pchan(ts, as_pchan);
+ if (rc)
+ goto error_nack;
+
+ bts_model_ts_connect(ts, as_pchan);
+ return;
+
+error_nack:
+ /* Error? then NACK right now. */
+ if (rc)
+ ipacc_dyn_pdch_complete(ts, rc);
+}
+
+static void osmo_dyn_ts_disconnected(struct gsm_bts_trx_ts *ts)
+{
+ DEBUGP(DRSL, "%s Disconnected\n", gsm_ts_and_pchan_name(ts));
+ ts->dyn.pchan_is = GSM_PCHAN_NONE;
+
+ switch (ts->dyn.pchan_want) {
+ case GSM_PCHAN_TCH_F:
+ case GSM_PCHAN_TCH_H:
+ case GSM_PCHAN_PDCH:
+ break;
+ default:
+ LOGP(DRSL, LOGL_ERROR,
+ "%s Dyn TS disconnected, but invalid desired pchan: %s\n",
+ gsm_ts_and_pchan_name(ts), gsm_pchan_name(ts->dyn.pchan_want));
+ ts->dyn.pchan_want = GSM_PCHAN_NONE;
+ /* TODO: how would this recover? */
+ return;
+ }
+
+ conf_lchans_as_pchan(ts, ts->dyn.pchan_want);
+ DEBUGP(DRSL, "%s Connect\n", gsm_ts_and_pchan_name(ts));
+ bts_model_ts_connect(ts, ts->dyn.pchan_want);
+}
+
+void cb_ts_disconnected(struct gsm_bts_trx_ts *ts)
+{
+ OSMO_ASSERT(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:
+ return osmo_dyn_ts_disconnected(ts);
+ default:
+ return;
+ }
+}
+
+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);
+ 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));
+
+ LOGP(DRSL, LOGL_DEBUG, "%s PDCH DEACT operation:"
+ " timeslot connected as TCH/F\n",
+ gsm_lchan_name(ts->lchan));
+
+ /* 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),
+ 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));
+
+ /* The PDTCH is connected, now tell the PCU about it. Except
+ * when the PCU is not connected (yet), then there's nothing
+ * left to do now. The PCU will catch up when it connects. */
+ if (!pcu_connected()) {
+ ipacc_dyn_pdch_complete(ts, 0);
+ return;
+ }
+
+ /* The PCU will request to activate the PDTCH SAPIs, which,
+ * when done, will call back to ipacc_dyn_pdch_complete(). */
+ /* TODO: timeout on channel connect / disconnect request from PCU? */
+ rc = pcu_tx_info_ind();
+
+ /* Error? then NACK right now. */
+ if (rc)
+ ipacc_dyn_pdch_complete(ts, 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;
+
+ if (rc) {
+ LOGP(DRSL, LOGL_NOTICE, "%s PDCH ACT OSMO operation failed (%d) in bts model\n",
+ gsm_lchan_name(ts->lchan), 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);
+}
+
+void cb_ts_connected(struct gsm_bts_trx_ts *ts, int rc)
+{
+ OSMO_ASSERT(ts);
+
+ 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:
+ return osmo_dyn_ts_connected(ts, rc);
+ default:
+ return;
+ }
+}
+
+void ipacc_dyn_pdch_complete(struct gsm_bts_trx_ts *ts, int rc)
+{
+ bool pdch_act;
+ OSMO_ASSERT(ts);
+
+ 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));
+
+ ts->flags &= ~TS_F_PDCH_PENDING_MASK;
+
+ if (rc != 0) {
+ LOGP(DRSL, LOGL_ERROR,
+ "PDCH %s on dynamic TCH/F_PDCH returned error %d\n",
+ pdch_act? "ACT" : "DEACT", rc);
+ rsl_tx_dyn_pdch_nack(ts->lchan, pdch_act, RSL_ERR_NORMAL_UNSPEC);
+ return;
+ }
+
+ if (pdch_act)
+ 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);
+
+ rc = rsl_tx_dyn_pdch_ack(ts->lchan, pdch_act);
+ if (rc)
+ LOGP(DRSL, LOGL_ERROR,
+ "Failed to transmit PDCH %s ACK, rc %d\n",
+ pdch_act? "ACT" : "DEACT", rc);
+}
+
+/* handle a message with an RSL CHAN_NR that is incompatible/unknown */
+static int rsl_reject_unknown_lchan(struct msgb *msg)
+{
+ struct abis_rsl_common_hdr *rh = msgb_l2(msg);
+ struct abis_rsl_dchan_hdr *dch;
+ int rc;
+
+ /* Handle GSM 08.58 7 Error Handling for the given input. This method will
+ * send either a CHANNEL ACTIVATION NACK, MODE MODIFY NACK or ERROR REPORT
+ * depending on the input of the method. */
+
+ /* TS 48.058 Section 7 explains how to do error handling */
+ switch (rh->msg_discr & 0xfe) {
+ case ABIS_RSL_MDISC_DED_CHAN:
+ dch = msgb_l2(msg);
+ switch (dch->c.msg_type) {
+ case RSL_MT_CHAN_ACTIV:
+ rc = _rsl_tx_chan_act_nack(msg->trx, dch->chan_nr,
+ RSL_ERR_MAND_IE_ERROR, NULL);
+ break;
+ case RSL_MT_MODE_MODIFY_REQ:
+ rc = _rsl_tx_mode_modif_nack(msg->trx, dch->chan_nr,
+ RSL_ERR_MAND_IE_ERROR, NULL);
+ break;
+ default:
+ rc = rsl_tx_error_report(msg->trx, RSL_ERR_MAND_IE_ERROR, NULL, NULL, msg);
+ break;
+ }
+ break;
+ case ABIS_RSL_MDISC_RLL:
+ /* fall-through */
+ case ABIS_RSL_MDISC_COM_CHAN:
+ /* fall-through */
+ case ABIS_RSL_MDISC_TRX:
+ /* fall-through */
+ case ABIS_RSL_MDISC_IPACCESS:
+ /* fall-through */
+ default:
+ /* ERROR REPORT */
+ rc = rsl_tx_error_report(msg->trx, RSL_ERR_MAND_IE_ERROR, NULL, NULL, msg);
+ }
+
+ msgb_free(msg);
+ return rc;
+}
+
+/*
+ * selecting message
+ */
+
+static int rsl_rx_rll(struct gsm_bts_trx *trx, struct msgb *msg)
+{
+ struct abis_rsl_rll_hdr *rh = msgb_l2(msg);
+ struct gsm_lchan *lchan;
+
+ if (msgb_l2len(msg) < sizeof(*rh)) {
+ LOGP(DRSL, LOGL_NOTICE, "RSL Radio Link Layer message too short\n");
+ rsl_tx_error_report(trx, RSL_ERR_PROTO, &rh->chan_nr, &rh->link_id, msg);
+ msgb_free(msg);
+ return -EIO;
+ }
+ msg->l3h = (unsigned char *)rh + sizeof(*rh);
+
+ if (!chan_nr_is_dchan(rh->chan_nr))
+ return rsl_reject_unknown_lchan(msg);
+
+ lchan = lchan_lookup(trx, rh->chan_nr, "RSL rx RLL: ");
+ if (!lchan) {
+ LOGP(DRLL, LOGL_NOTICE, "Rx RLL %s for unknown lchan\n",
+ rsl_msg_name(rh->c.msg_type));
+ return rsl_reject_unknown_lchan(msg);
+ }
+
+ DEBUGP(DRLL, "%s Rx RLL %s Abis -> LAPDm\n", gsm_lchan_name(lchan),
+ rsl_msg_name(rh->c.msg_type));
+
+ /* exception: RLL messages are _NOT_ freed as they are now
+ * owned by LAPDm which might have queued them */
+ return lapdm_rslms_recvmsg(msg, &lchan->lapdm_ch);
+}
+
+static inline int rsl_link_id_is_sacch(uint8_t link_id)
+{
+ if (link_id >> 6 == 1)
+ return 1;
+ else
+ return 0;
+}
+
+static int rslms_is_meas_rep(struct msgb *msg)
+{
+ struct abis_rsl_common_hdr *rh = msgb_l2(msg);
+ struct abis_rsl_rll_hdr *rllh;
+ struct gsm48_hdr *gh;
+
+ if ((rh->msg_discr & 0xfe) != ABIS_RSL_MDISC_RLL)
+ return 0;
+
+ if (rh->msg_type != RSL_MT_UNIT_DATA_IND)
+ return 0;
+
+ rllh = msgb_l2(msg);
+ if (rsl_link_id_is_sacch(rllh->link_id) == 0)
+ return 0;
+
+ gh = msgb_l3(msg);
+ if (gh->proto_discr != GSM48_PDISC_RR)
+ return 0;
+
+ switch (gh->msg_type) {
+ case GSM48_MT_RR_MEAS_REP:
+ case GSM48_MT_RR_EXT_MEAS_REP:
+ return 1;
+ default:
+ break;
+ }
+
+ /* FIXME: this does not cover the Bter frame format and the associated
+ * short RR protocol descriptor for ENHANCED MEASUREMENT REPORT */
+
+ return 0;
+}
+
+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;
+ int16_t toa256_max;
+ 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)
+{
+ struct msgb *msg;
+ uint8_t meas_res[16];
+ uint8_t chan_nr = gsm_lchan2chan_nr(lchan);
+ int res_valid = lchan->meas.flags & LC_UL_M_F_RES_VALID;
+ struct gsm_bts *bts = lchan->ts->trx->bts;
+
+ LOGP(DRSL, LOGL_DEBUG,
+ "%s chan_num:%u Tx MEAS RES valid(%d), flags(%02x)\n",
+ gsm_lchan_name(lchan), chan_nr, res_valid, lchan->meas.flags);
+
+ if (!res_valid)
+ return -EINPROGRESS;
+
+ msg = rsl_msgb_alloc(sizeof(struct abis_rsl_dchan_hdr));
+ if (!msg)
+ return -ENOMEM;
+
+ LOGP(DRSL, LOGL_DEBUG,
+ "%s 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",
+ gsm_lchan_name(lchan),
+ 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);
+
+ 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;
+ smi = (struct osmo_bts_supp_meas_info *) &meas_res[ie_len];
+ ie_len += sizeof(struct osmo_bts_supp_meas_info);
+ /* append signed 16bit value containing MS timing offset in 1/256th symbols
+ * in the vendor-specific "Supplementary Measurement Information" part of
+ * the uplink measurements IE. The lchan->meas.ext members are the current
+ * offset *relative* to the TA which the MS has already applied. As we want
+ * 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;
+ 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);
+ 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_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;
+ }
+
+ rsl_dch_push_hdr(msg, RSL_MT_MEAS_RES, chan_nr);
+ msg->trx = lchan->ts->trx;
+
+ return abis_bts_rsl_sendmsg(msg);
+}
+
+/* call-back for LAPDm code, called when it wants to send msgs UP */
+int lapdm_rll_tx_cb(struct msgb *msg, struct lapdm_entity *le, void *ctx)
+{
+ struct gsm_lchan *lchan = ctx;
+ struct abis_rsl_common_hdr *rh;
+
+ OSMO_ASSERT(msg);
+ rh = msgb_l2(msg);
+
+ if (lchan->state != LCHAN_S_ACTIVE) {
+ LOGP(DRSL, LOGL_ERROR, "%s(%s) is not active. Dropping message (len=%u): %s\n",
+ gsm_lchan_name(lchan), gsm_lchans_name(lchan->state),
+ msgb_l2len(msg), msgb_hexdump_l2(msg));
+ msgb_free(msg);
+ return 0;
+ }
+
+ 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;
+
+ LOGP(DRSL, LOGL_INFO, "%s Handing RLL msg %s from LAPDm to MEAS REP\n",
+ gsm_lchan_name(lchan), 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)) {
+ LOGP(DRSL, LOGL_INFO, "%s Scheduling %s to L3 in next associated TCH-RTS.ind\n",
+ gsm_lchan_name(lchan),
+ rsl_msg_name(rh->msg_type));
+
+ if(lchan->pending_rel_ind_msg) {
+ LOGP(DRSL, LOGL_INFO, "Dropping pending release indication message\n");
+ msgb_free(lchan->pending_rel_ind_msg);
+ }
+
+ lchan->pending_rel_ind_msg = msg;
+ return 0;
+ }
+
+ rc = rsl_tx_meas_res(lchan, msgb_l3(msg), msgb_l3len(msg), le);
+ msgb_free(msg);
+ return rc;
+ } else {
+ LOGP(DRSL, LOGL_INFO, "%s Fwd RLL msg %s from LAPDm to A-bis\n",
+ gsm_lchan_name(lchan), rsl_msg_name(rh->msg_type));
+
+ return abis_bts_rsl_sendmsg(msg);
+ }
+}
+
+static int rsl_rx_cchan(struct gsm_bts_trx *trx, struct msgb *msg)
+{
+ struct abis_rsl_cchan_hdr *cch = msgb_l2(msg);
+ int ret = 0;
+
+ if (msgb_l2len(msg) < sizeof(*cch)) {
+ LOGP(DRSL, LOGL_NOTICE, "RSL Common Channel Management message too short\n");
+ rsl_tx_error_report(trx, RSL_ERR_PROTO, NULL, NULL, msg);
+ msgb_free(msg);
+ return -EIO;
+ }
+ msg->l3h = (unsigned char *)cch + sizeof(*cch);
+
+ /* normally we don't permit dedicated channels here ... */
+ if (chan_nr_is_dchan(cch->chan_nr)) {
+ /* ... however, CBCH is on a SDCCH, so we must permit it */
+ if (cch->c.msg_type != RSL_MT_SMS_BC_CMD && cch->c.msg_type != RSL_MT_SMS_BC_REQ)
+ return rsl_reject_unknown_lchan(msg);
+ }
+
+ msg->lchan = lchan_lookup(trx, cch->chan_nr, "RSL rx CCHAN: ");
+ if (!msg->lchan) {
+ LOGP(DRSL, LOGL_ERROR, "Rx RSL %s for unknown lchan\n",
+ rsl_msg_name(cch->c.msg_type));
+ return rsl_reject_unknown_lchan(msg);
+ }
+
+ LOGP(DRSL, LOGL_INFO, "%s Rx RSL %s\n", gsm_lchan_name(msg->lchan),
+ rsl_msg_name(cch->c.msg_type));
+
+ switch (cch->c.msg_type) {
+ case RSL_MT_BCCH_INFO:
+ ret = rsl_rx_bcch_info(trx, msg);
+ break;
+ case RSL_MT_IMMEDIATE_ASSIGN_CMD:
+ ret = rsl_rx_imm_ass(trx, msg);
+ break;
+ case RSL_MT_PAGING_CMD:
+ ret = rsl_rx_paging_cmd(trx, msg);
+ break;
+ 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:
+ LOGP(DRSL, LOGL_NOTICE, "unimplemented RSL cchan msg_type %s\n",
+ rsl_msg_name(cch->c.msg_type));
+ break;
+ default:
+ LOGP(DRSL, LOGL_NOTICE, "undefined RSL cchan msg_type 0x%02x\n",
+ cch->c.msg_type);
+ ret = -EINVAL;
+ break;
+ }
+
+ if (ret != 1)
+ msgb_free(msg);
+
+ return ret;
+}
+
+static int rsl_rx_dchan(struct gsm_bts_trx *trx, struct msgb *msg)
+{
+ struct abis_rsl_dchan_hdr *dch = msgb_l2(msg);
+ int ret = 0;
+
+ if (msgb_l2len(msg) < sizeof(*dch)) {
+ LOGP(DRSL, LOGL_NOTICE, "RSL Dedicated Channel Management message too short\n");
+ msgb_free(msg);
+ return -EIO;
+ }
+ msg->l3h = (unsigned char *)dch + sizeof(*dch);
+
+ if (!chan_nr_is_dchan(dch->chan_nr))
+ return rsl_reject_unknown_lchan(msg);
+
+ msg->lchan = lchan_lookup(trx, dch->chan_nr, "RSL rx DCHAN: ");
+ if (!msg->lchan) {
+ LOGP(DRSL, LOGL_ERROR, "Rx RSL %s for unknown lchan\n",
+ rsl_or_ipac_msg_name(dch->c.msg_type));
+ return rsl_reject_unknown_lchan(msg);
+ }
+
+ LOGP(DRSL, LOGL_INFO, "%s ss=%d Rx RSL %s\n",
+ gsm_ts_and_pchan_name(msg->lchan->ts), msg->lchan->nr,
+ rsl_or_ipac_msg_name(dch->c.msg_type));
+
+ switch (dch->c.msg_type) {
+ case RSL_MT_CHAN_ACTIV:
+ ret = rsl_rx_chan_activ(msg);
+ break;
+ case RSL_MT_RF_CHAN_REL:
+ ret = rsl_rx_rf_chan_rel(msg->lchan, dch->chan_nr);
+ break;
+ case RSL_MT_SACCH_INFO_MODIFY:
+ ret = rsl_rx_sacch_inf_mod(msg);
+ break;
+ case RSL_MT_DEACTIVATE_SACCH:
+ ret = l1sap_chan_deact_sacch(trx, dch->chan_nr);
+ break;
+ case RSL_MT_ENCR_CMD:
+ ret = rsl_rx_encr_cmd(msg);
+ break;
+ case RSL_MT_MODE_MODIFY_REQ:
+ ret = rsl_rx_mode_modif(msg);
+ break;
+ case RSL_MT_MS_POWER_CONTROL:
+ ret = rsl_rx_ms_pwr_ctrl(msg);
+ break;
+ case RSL_MT_IPAC_PDCH_ACT:
+ case RSL_MT_IPAC_PDCH_DEACT:
+ rsl_rx_dyn_pdch(msg, dch->c.msg_type == RSL_MT_IPAC_PDCH_ACT);
+ ret = 0;
+ break;
+ case RSL_MT_PHY_CONTEXT_REQ:
+ case RSL_MT_PREPROC_CONFIG:
+ case RSL_MT_RTD_REP:
+ case RSL_MT_PRE_HANDO_NOTIF:
+ case RSL_MT_MR_CODEC_MOD_REQ:
+ case RSL_MT_TFO_MOD_REQ:
+ LOGP(DRSL, LOGL_NOTICE, "unimplemented RSL dchan msg_type %s\n",
+ rsl_msg_name(dch->c.msg_type));
+ break;
+ default:
+ LOGP(DRSL, LOGL_NOTICE, "undefined RSL dchan msg_type 0x%02x\n",
+ dch->c.msg_type);
+ ret = -EINVAL;
+ }
+
+ if (ret != 1)
+ msgb_free(msg);
+
+ return ret;
+}
+
+static int rsl_rx_trx(struct gsm_bts_trx *trx, struct msgb *msg)
+{
+ struct abis_rsl_common_hdr *th = msgb_l2(msg);
+ int ret = 0;
+
+ if (msgb_l2len(msg) < sizeof(*th)) {
+ LOGP(DRSL, LOGL_NOTICE, "RSL TRX message too short\n");
+ rsl_tx_error_report(trx, RSL_ERR_PROTO, NULL, NULL, msg);
+ msgb_free(msg);
+ return -EIO;
+ }
+ msg->l3h = (unsigned char *)th + sizeof(*th);
+
+ switch (th->msg_type) {
+ case RSL_MT_SACCH_FILL:
+ ret = rsl_rx_sacch_fill(trx, msg);
+ break;
+ default:
+ LOGP(DRSL, LOGL_NOTICE, "undefined RSL TRX msg_type 0x%02x\n",
+ th->msg_type);
+ ret = -EINVAL;
+ }
+
+ if (ret != 1)
+ msgb_free(msg);
+
+ return ret;
+}
+
+static int rsl_rx_ipaccess(struct gsm_bts_trx *trx, struct msgb *msg)
+{
+ struct abis_rsl_dchan_hdr *dch = msgb_l2(msg);
+ int ret = 0;
+
+ if (msgb_l2len(msg) < sizeof(*dch)) {
+ LOGP(DRSL, LOGL_NOTICE, "RSL ip.access message too short\n");
+ rsl_tx_error_report(trx, RSL_ERR_PROTO, NULL, NULL, msg);
+ msgb_free(msg);
+ return -EIO;
+ }
+ msg->l3h = (unsigned char *)dch + sizeof(*dch);
+
+ if (!chan_nr_is_dchan(dch->chan_nr))
+ return rsl_reject_unknown_lchan(msg);
+
+ msg->lchan = lchan_lookup(trx, dch->chan_nr, "RSL rx IPACC: ");
+ if (!msg->lchan) {
+ LOGP(DRSL, LOGL_ERROR, "Rx RSL %s for unknow lchan\n",
+ rsl_msg_name(dch->c.msg_type));
+ return rsl_reject_unknown_lchan(msg);
+ }
+
+ LOGP(DRSL, LOGL_INFO, "%s Rx RSL %s\n", gsm_lchan_name(msg->lchan),
+ rsl_ipac_msg_name(dch->c.msg_type));
+
+ switch (dch->c.msg_type) {
+ case RSL_MT_IPAC_CRCX:
+ case RSL_MT_IPAC_MDCX:
+ ret = rsl_rx_ipac_XXcx(msg);
+ break;
+ case RSL_MT_IPAC_DLCX:
+ ret = rsl_rx_ipac_dlcx(msg);
+ break;
+ default:
+ LOGP(DRSL, LOGL_NOTICE, "unsupported RSL ip.access msg_type 0x%02x\n",
+ dch->c.msg_type);
+ ret = -EINVAL;
+ }
+
+ if (ret != 1)
+ msgb_free(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;
+ int ret = 0;
+
+ OSMO_ASSERT(trx);
+ OSMO_ASSERT(msg);
+
+ rslh = msgb_l2(msg);
+
+ if (msgb_l2len(msg) < sizeof(*rslh)) {
+ LOGP(DRSL, LOGL_NOTICE, "RSL message too short\n");
+ rsl_tx_error_report(trx, RSL_ERR_PROTO, NULL, NULL, msg);
+ msgb_free(msg);
+ return -EIO;
+ }
+
+ switch (rslh->msg_discr & 0xfe) {
+ case ABIS_RSL_MDISC_RLL:
+ ret = rsl_rx_rll(trx, msg);
+ /* exception: RLL messages are _NOT_ freed as they are now
+ * owned by LAPDm which might have queued them */
+ break;
+ case ABIS_RSL_MDISC_COM_CHAN:
+ ret = rsl_rx_cchan(trx, msg);
+ break;
+ case ABIS_RSL_MDISC_DED_CHAN:
+ ret = rsl_rx_dchan(trx, msg);
+ break;
+ case ABIS_RSL_MDISC_TRX:
+ ret = rsl_rx_trx(trx, msg);
+ break;
+ case ABIS_RSL_MDISC_IPACCESS:
+ ret = rsl_rx_ipaccess(trx, msg);
+ break;
+ default:
+ LOGP(DRSL, LOGL_NOTICE, "unknown RSL msg_discr 0x%02x\n",
+ rslh->msg_discr);
+ rsl_tx_error_report(trx, RSL_ERR_MSG_DISCR, NULL, NULL, msg);
+ msgb_free(msg);
+ ret = -EINVAL;
+ }
+
+ /* we don't free here, as rsl_rx{cchan,dchan,trx,ipaccess,rll} are
+ * responsible for owning the msg */
+
+ return ret;
+}
diff --git a/src/common/scheduler.c b/src/common/scheduler.c
new file mode 100644
index 00000000..f705ddf7
--- /dev/null
+++ b/src/common/scheduler.c
@@ -0,0 +1,1025 @@
+/* Scheduler for OsmoBTS-TRX */
+
+/* (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>
+ *
+ * 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 <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stdint.h>
+#include <ctype.h>
+
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/bits.h>
+#include <osmocom/gsm/a5.h>
+
+#include <osmo-bts/gsm_data.h>
+#include <osmo-bts/logging.h>
+#include <osmo-bts/rsl.h>
+#include <osmo-bts/l1sap.h>
+#include <osmo-bts/scheduler.h>
+#include <osmo-bts/scheduler_backend.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);
+/*! \brief Dummy Burst (TS 05.02 Chapter 5.2.6) */
+static const ubit_t 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,
+ 0,1,0,1,1,1,0,0,0,1,0,1,1,1,0,0,0,1,0,1,0,1,1,1,0,1,0,0,1,0,1,0,
+ 0,0,1,1,0,0,1,1,0,0,1,1,1,0,0,1,1,1,1,0,1,0,0,1,1,1,1,1,0,0,0,1,
+ 0,0,1,0,1,1,1,1,1,0,1,0,1,0,
+ 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, },
+};
+
+const ubit_t _sched_egprs_tsc[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, },
+ { 1,1,1,1,1,1,0,0,1,1,1,1,0,0,1,0,0,1,1,1,1,0,0,1,0,0,
+ 1,0,0,1,1,1,1,0,0,1,0,0,1,0,0,1,0,0,1,1,1,1,1,1,1,1,
+ 1,1,0,0,1,1,1,1,0,0,1,0,0,1,1,1,1,0,0,1,0,0,1,0,0,1, },
+ { 1,1,1,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,1,0,0,1,0,0,
+ 1,1,1,1,0,0,1,0,0,1,0,0,1,1,1,1,0,0,1,1,1,1,1,1,1,0,
+ 0,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,1,0,0,1,0,0,1,1,1,1, },
+ { 1,1,1,0,0,1,1,1,1,1,1,1,1,1,1,0,0,1,0,0,1,0,0,1,0,0,
+ 1,1,1,1,0,0,1,0,0,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,1,1,1,0,0,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,0,0,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,0,0,1,1,1,1,1,1,1,1,1,1,1,
+ 1,1,1,1,1,0,0,1,0,0,1,1,1,1,0,0,1,1,1,1,0,0,1,0,0,1, },
+ { 1,1,1,0,0,1,1,1,1,1,1,1,0,0,1,0,0,1,0,0,1,1,1,1,0,0,
+ 1,1,1,1,0,0,1,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,
+ 0,1,1,1,1,1,1,1,0,0,1,0,0,1,0,0,1,1,1,1,0,0,1,1,1,1, },
+ { 0,0,1,1,1,1,0,0,1,1,1,1,1,1,1,0,0,1,0,0,1,0,0,1,0,0,
+ 1,0,0,1,1,1,1,0,0,1,0,0,1,1,1,1,1,1,1,1,1,1,0,0,1,1,
+ 1,1,0,0,1,1,1,1,1,1,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1, },
+ { 0,0,1,0,0,1,0,0,1,1,1,1,0,0,1,0,0,1,0,0,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,1,1,1,0,0,1,0,0,1,0,0,1,0,0,1,1,1,1,1,1,1, },
+};
+
+/*! \brief SCH training sequence (TS 05.02 Chapter 5.2.5) */
+const ubit_t _sched_sch_train[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,
+};
+
+/*
+ * subchannel description structure
+ */
+
+const struct trx_chan_desc trx_chan_desc[_TRX_CHAN_MAX] = {
+ /* is_pdch chan_type chan_nr link_id name rts_fn dl_fn ul_fn auto_active */
+ { 0, TRXC_IDLE, 0, LID_DEDIC, "IDLE", NULL, tx_idle_fn, NULL, 1 },
+ { 0, TRXC_FCCH, 0, LID_DEDIC, "FCCH", NULL, tx_fcch_fn, NULL, 1 },
+ { 0, TRXC_SCH, 0, LID_DEDIC, "SCH", NULL, tx_sch_fn, NULL, 1 },
+ { 0, TRXC_BCCH, 0x80, LID_DEDIC, "BCCH", rts_data_fn, tx_data_fn, NULL, 1 },
+ { 0, TRXC_RACH, 0x88, LID_DEDIC, "RACH", NULL, NULL, rx_rach_fn, 1 },
+ { 0, TRXC_CCCH, 0x90, LID_DEDIC, "CCCH", rts_data_fn, tx_data_fn, NULL, 1 },
+ { 0, TRXC_TCHF, 0x08, LID_DEDIC, "TCH/F", rts_tchf_fn, tx_tchf_fn, rx_tchf_fn, 0 },
+ { 0, TRXC_TCHH_0, 0x10, LID_DEDIC, "TCH/H(0)", rts_tchh_fn, tx_tchh_fn, rx_tchh_fn, 0 },
+ { 0, TRXC_TCHH_1, 0x18, LID_DEDIC, "TCH/H(1)", rts_tchh_fn, tx_tchh_fn, rx_tchh_fn, 0 },
+ { 0, TRXC_SDCCH4_0, 0x20, LID_DEDIC, "SDCCH/4(0)", rts_data_fn, tx_data_fn, rx_data_fn, 0 },
+ { 0, TRXC_SDCCH4_1, 0x28, LID_DEDIC, "SDCCH/4(1)", rts_data_fn, tx_data_fn, rx_data_fn, 0 },
+ { 0, TRXC_SDCCH4_2, 0x30, LID_DEDIC, "SDCCH/4(2)", rts_data_fn, tx_data_fn, rx_data_fn, 0 },
+ { 0, TRXC_SDCCH4_3, 0x38, LID_DEDIC, "SDCCH/4(3)", rts_data_fn, tx_data_fn, rx_data_fn, 0 },
+ { 0, TRXC_SDCCH8_0, 0x40, LID_DEDIC, "SDCCH/8(0)", rts_data_fn, tx_data_fn, rx_data_fn, 0 },
+ { 0, TRXC_SDCCH8_1, 0x48, LID_DEDIC, "SDCCH/8(1)", rts_data_fn, tx_data_fn, rx_data_fn, 0 },
+ { 0, TRXC_SDCCH8_2, 0x50, LID_DEDIC, "SDCCH/8(2)", rts_data_fn, tx_data_fn, rx_data_fn, 0 },
+ { 0, TRXC_SDCCH8_3, 0x58, LID_DEDIC, "SDCCH/8(3)", rts_data_fn, tx_data_fn, rx_data_fn, 0 },
+ { 0, TRXC_SDCCH8_4, 0x60, LID_DEDIC, "SDCCH/8(4)", rts_data_fn, tx_data_fn, rx_data_fn, 0 },
+ { 0, TRXC_SDCCH8_5, 0x68, LID_DEDIC, "SDCCH/8(5)", rts_data_fn, tx_data_fn, rx_data_fn, 0 },
+ { 0, TRXC_SDCCH8_6, 0x70, LID_DEDIC, "SDCCH/8(6)", rts_data_fn, tx_data_fn, rx_data_fn, 0 },
+ { 0, TRXC_SDCCH8_7, 0x78, LID_DEDIC, "SDCCH/8(7)", rts_data_fn, tx_data_fn, rx_data_fn, 0 },
+ { 0, TRXC_SACCHTF, 0x08, LID_SACCH, "SACCH/TF", rts_data_fn, tx_data_fn, rx_data_fn, 0 },
+ { 0, TRXC_SACCHTH_0, 0x10, LID_SACCH, "SACCH/TH(0)", rts_data_fn, tx_data_fn, rx_data_fn, 0 },
+ { 0, TRXC_SACCHTH_1, 0x18, LID_SACCH, "SACCH/TH(1)", rts_data_fn, tx_data_fn, rx_data_fn, 0 },
+ { 0, TRXC_SACCH4_0, 0x20, LID_SACCH, "SACCH/4(0)", rts_data_fn, tx_data_fn, rx_data_fn, 0 },
+ { 0, TRXC_SACCH4_1, 0x28, LID_SACCH, "SACCH/4(1)", rts_data_fn, tx_data_fn, rx_data_fn, 0 },
+ { 0, TRXC_SACCH4_2, 0x30, LID_SACCH, "SACCH/4(2)", rts_data_fn, tx_data_fn, rx_data_fn, 0 },
+ { 0, TRXC_SACCH4_3, 0x38, LID_SACCH, "SACCH/4(3)", rts_data_fn, tx_data_fn, rx_data_fn, 0 },
+ { 0, TRXC_SACCH8_0, 0x40, LID_SACCH, "SACCH/8(0)", rts_data_fn, tx_data_fn, rx_data_fn, 0 },
+ { 0, TRXC_SACCH8_1, 0x48, LID_SACCH, "SACCH/8(1)", rts_data_fn, tx_data_fn, rx_data_fn, 0 },
+ { 0, TRXC_SACCH8_2, 0x50, LID_SACCH, "SACCH/8(2)", rts_data_fn, tx_data_fn, rx_data_fn, 0 },
+ { 0, TRXC_SACCH8_3, 0x58, LID_SACCH, "SACCH/8(3)", rts_data_fn, tx_data_fn, rx_data_fn, 0 },
+ { 0, TRXC_SACCH8_4, 0x60, LID_SACCH, "SACCH/8(4)", rts_data_fn, tx_data_fn, rx_data_fn, 0 },
+ { 0, TRXC_SACCH8_5, 0x68, LID_SACCH, "SACCH/8(5)", rts_data_fn, tx_data_fn, rx_data_fn, 0 },
+ { 0, TRXC_SACCH8_6, 0x70, LID_SACCH, "SACCH/8(6)", rts_data_fn, tx_data_fn, rx_data_fn, 0 },
+ { 0, TRXC_SACCH8_7, 0x78, LID_SACCH, "SACCH/8(7)", rts_data_fn, tx_data_fn, rx_data_fn, 0 },
+ { 1, TRXC_PDTCH, 0xc0, LID_DEDIC, "PDTCH", rts_data_fn, tx_pdtch_fn, rx_pdtch_fn, 0 },
+ { 1, TRXC_PTCCH, 0xc0, LID_DEDIC, "PTCCH", rts_data_fn, tx_data_fn, rx_data_fn, 0 },
+ { 0, TRXC_CBCH, 0xc8, LID_DEDIC, "CBCH", rts_data_fn, tx_data_fn, NULL, 1 },
+};
+
+const struct value_string trx_chan_type_names[] = {
+ OSMO_VALUE_STRING(TRXC_IDLE),
+ OSMO_VALUE_STRING(TRXC_FCCH),
+ OSMO_VALUE_STRING(TRXC_SCH),
+ OSMO_VALUE_STRING(TRXC_BCCH),
+ OSMO_VALUE_STRING(TRXC_RACH),
+ OSMO_VALUE_STRING(TRXC_CCCH),
+ OSMO_VALUE_STRING(TRXC_TCHF),
+ OSMO_VALUE_STRING(TRXC_TCHH_0),
+ OSMO_VALUE_STRING(TRXC_TCHH_1),
+ OSMO_VALUE_STRING(TRXC_SDCCH4_0),
+ OSMO_VALUE_STRING(TRXC_SDCCH4_1),
+ OSMO_VALUE_STRING(TRXC_SDCCH4_2),
+ OSMO_VALUE_STRING(TRXC_SDCCH4_3),
+ OSMO_VALUE_STRING(TRXC_SDCCH8_0),
+ OSMO_VALUE_STRING(TRXC_SDCCH8_1),
+ OSMO_VALUE_STRING(TRXC_SDCCH8_2),
+ OSMO_VALUE_STRING(TRXC_SDCCH8_3),
+ OSMO_VALUE_STRING(TRXC_SDCCH8_4),
+ OSMO_VALUE_STRING(TRXC_SDCCH8_5),
+ OSMO_VALUE_STRING(TRXC_SDCCH8_6),
+ OSMO_VALUE_STRING(TRXC_SDCCH8_7),
+ OSMO_VALUE_STRING(TRXC_SACCHTF),
+ OSMO_VALUE_STRING(TRXC_SACCHTH_0),
+ OSMO_VALUE_STRING(TRXC_SACCHTH_1),
+ OSMO_VALUE_STRING(TRXC_SACCH4_0),
+ OSMO_VALUE_STRING(TRXC_SACCH4_1),
+ OSMO_VALUE_STRING(TRXC_SACCH4_2),
+ OSMO_VALUE_STRING(TRXC_SACCH4_3),
+ OSMO_VALUE_STRING(TRXC_SACCH8_0),
+ OSMO_VALUE_STRING(TRXC_SACCH8_1),
+ OSMO_VALUE_STRING(TRXC_SACCH8_2),
+ OSMO_VALUE_STRING(TRXC_SACCH8_3),
+ OSMO_VALUE_STRING(TRXC_SACCH8_4),
+ OSMO_VALUE_STRING(TRXC_SACCH8_5),
+ OSMO_VALUE_STRING(TRXC_SACCH8_6),
+ OSMO_VALUE_STRING(TRXC_SACCH8_7),
+ OSMO_VALUE_STRING(TRXC_PDTCH),
+ OSMO_VALUE_STRING(TRXC_PTCCH),
+ OSMO_VALUE_STRING(TRXC_CBCH),
+ OSMO_VALUE_STRING(_TRX_CHAN_MAX),
+ { 0, NULL }
+};
+
+/*
+ * init / exit
+ */
+
+int trx_sched_init(struct l1sched_trx *l1t, struct gsm_bts_trx *trx)
+{
+ uint8_t tn;
+ unsigned int i;
+
+ if (!trx)
+ return -EINVAL;
+
+ l1t->trx = trx;
+
+ LOGP(DL1C, LOGL_NOTICE, "Init 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);
+
+ 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;
+ }
+ }
+
+ return 0;
+}
+
+void trx_sched_exit(struct l1sched_trx *l1t)
+{
+ struct gsm_bts_trx_ts *ts;
+ uint8_t tn;
+ int i;
+
+ 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);
+ }
+}
+
+/* close all logical channels and reset timeslots */
+void trx_sched_reset(struct l1sched_trx *l1t)
+{
+ trx_sched_exit(l1t);
+ trx_sched_init(l1t, l1t->trx);
+}
+
+struct msgb *_sched_dequeue_prim(struct l1sched_trx *l1t, int8_t tn, uint32_t fn,
+ enum trx_chan_type chan)
+{
+ struct msgb *msg, *msg2;
+ struct osmo_phsap_prim *l1sap;
+ uint32_t prim_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;
+ }
+ 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);
+ 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);
+ break;
+ default:
+ goto wrong_type;
+ }
+ if (prim_fn > 100) {
+ LOGL1S(DL1P, LOGL_NOTICE, l1t, tn, chan, fn,
+ "Prim %u is out of range (100), 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,
+ get_value_string(trx_chan_type_names, chan));
+ /* unlink and free message */
+ llist_del(&msg->list);
+ msgb_free(msg);
+ continue;
+ }
+ if (prim_fn > 0)
+ continue;
+
+ goto found_msg;
+ }
+
+ 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 */
+ llist_del(&msg->list);
+ return msg;
+}
+
+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,
+ 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);
+
+ /* compose primitive */
+ msg = l1sap_msgb_alloc(l2_len);
+ l1sap = msgb_l1sap_prim(msg);
+ osmo_prim_init(&l1sap->oph, SAP_GSM_PH, PRIM_PH_DATA,
+ PRIM_OP_INDICATION, msg);
+ l1sap->u.data.chan_nr = chan_nr;
+ l1sap->u.data.link_id = trx_chan_desc[chan].link_id;
+ l1sap->u.data.fn = fn;
+ l1sap->u.data.rssi = (int8_t) (rssi);
+ l1sap->u.data.ber10k = ber10k;
+ 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;
+
+ /* forward primitive */
+ l1sap_up(l1t->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)
+{
+ 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)];
+
+ /* compose primitive */
+ msg = l1sap_msgb_alloc(tch_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)));
+ /* forward primitive */
+ l1sap_up(l1t->trx, l1sap);
+
+ return 0;
+}
+
+
+
+/*
+ * data request (from upper layer)
+ */
+
+int trx_sched_ph_data_req(struct l1sched_trx *l1t, struct osmo_phsap_prim *l1sap)
+{
+ uint8_t tn = l1sap->u.data.chan_nr & 7;
+ struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn);
+
+ LOGL1S(DL1P, LOGL_INFO, l1t, tn, -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)) {
+ msgb_free(l1sap->oph.msg);
+ return 0;
+ }
+
+ msgb_enqueue(&l1ts->dl_prims, l1sap->oph.msg);
+
+ return 0;
+}
+
+int trx_sched_tch_req(struct l1sched_trx *l1t, struct osmo_phsap_prim *l1sap)
+{
+ uint8_t tn = l1sap->u.tch.chan_nr & 7;
+ struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn);
+
+ 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();
+
+ /* ignore empty frame */
+ if (!msgb_l2len(l1sap->oph.msg)) {
+ msgb_free(l1sap->oph.msg);
+ return 0;
+ }
+
+ 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)
+{
+ 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;
+
+ 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);
+
+ /* generate prim */
+ msg = l1sap_msgb_alloc(200);
+ if (!msg)
+ return -ENOMEM;
+ l1sap = msgb_l1sap_prim(msg);
+ osmo_prim_init(&l1sap->oph, SAP_GSM_PH, PRIM_PH_RTS,
+ PRIM_OP_INDICATION, msg);
+ l1sap->u.data.chan_nr = chan_nr;
+ l1sap->u.data.link_id = link_id;
+ l1sap->u.data.fn = fn;
+
+ return l1sap_up(l1t->trx, l1sap);
+}
+
+static int rts_tch_common(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn,
+ enum trx_chan_type chan, int 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;
+
+ 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);
+
+ /* only send, if FACCH is selected */
+ if (facch) {
+ /* generate prim */
+ msg = l1sap_msgb_alloc(200);
+ if (!msg)
+ return -ENOMEM;
+ l1sap = msgb_l1sap_prim(msg);
+ osmo_prim_init(&l1sap->oph, SAP_GSM_PH, PRIM_PH_RTS,
+ PRIM_OP_INDICATION, msg);
+ l1sap->u.data.chan_nr = chan_nr;
+ l1sap->u.data.link_id = link_id;
+ l1sap->u.data.fn = fn;
+
+ rc = l1sap_up(l1t->trx, l1sap);
+ }
+
+ /* dont send, if TCH is in signalling only mode */
+ if (l1ts->chan_state[chan].rsl_cmode != RSL_CMOD_SPD_SIGN) {
+ /* generate prim */
+ msg = l1sap_msgb_alloc(200);
+ if (!msg)
+ return -ENOMEM;
+ l1sap = msgb_l1sap_prim(msg);
+ 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;
+
+ return l1sap_up(l1t->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)
+{
+ /* TCH/F may include FACCH on every 4th burst */
+ return rts_tch_common(l1t, tn, fn, chan, 1);
+}
+
+
+/* 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)
+{
+ /* 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);
+}
+
+/* set multiframe scheduler to given pchan */
+int trx_sched_set_pchan(struct l1sched_trx *l1t, uint8_t tn,
+ 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);
+ if (i < 0) {
+ LOGP(DL1C, LOGL_NOTICE, "Failed to configure multiframe "
+ "trx=%d ts=%d\n", l1t->trx->nr, tn);
+ 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);
+ return 0;
+}
+
+/* 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)
+{
+ 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;
+
+ /* 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 */
+ if ((trx_sched_multiframes[l1ts->mf_index].pchan
+ == GSM_PCHAN_PDCH)
+ != trx_chan_desc[i].pdch)
+ 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;
+ }
+ }
+
+ /* disable handover detection (on deactivation) */
+ if (!active)
+ _sched_act_rach_det(l1t, tn, ss, 0);
+
+ return rc;
+}
+
+/* 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,
+ 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)
+{
+ 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)
+ return 0;
+
+ /* 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);
+ chan_state->rsl_cmode = rsl_cmode;
+ chan_state->tch_mode = tch_mode;
+ chan_state->ho_rach_detect = handover;
+ if (rsl_cmode == RSL_CMOD_SPD_SPEECH
+ && tch_mode == GSM48_CMODE_SPEECH_AMR) {
+ chan_state->codecs = codecs;
+ chan_state->codec[0] = codec0;
+ chan_state->codec[1] = codec1;
+ chan_state->codec[2] = codec2;
+ chan_state->codec[3] = codec3;
+ chan_state->ul_ft = initial_id;
+ 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;
+ }
+ rc = 0;
+ }
+ }
+
+ /* command rach detection
+ * always enable handover, even if state is still set (due to loss
+ * 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);
+
+ 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)
+{
+ 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;
+
+ /* no cipher for PDCH */
+ if (trx_sched_multiframes[l1ts->mf_index].pchan == GSM_PCHAN_PDCH)
+ return 0;
+
+ /* 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);
+ 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].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 (downlink) {
+ chan_state->dl_encr_algo = algo;
+ memcpy(chan_state->dl_encr_key, key, key_len);
+ chan_state->dl_encr_key_len = 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;
+ }
+ rc = 0;
+ }
+ }
+
+ return rc;
+}
+
+/* process ready-to-send */
+int _sched_rts(struct l1sched_trx *l1t, uint8_t tn, 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;
+ enum trx_chan_type chan;
+
+ /* no multiframe set */
+ if (!l1ts->mf_index)
+ return 0;
+
+ /* get frame from multiframe */
+ period = l1ts->mf_period;
+ offset = fn % period;
+ frame = l1ts->mf_frames + offset;
+
+ chan = frame->dl_chan;
+ bid = frame->dl_bid;
+ func = trx_chan_desc[frame->dl_chan].rts_fn;
+
+ /* only on bid == 0 */
+ if (bid != 0)
+ return 0;
+
+ /* no RTS function */
+ if (!func)
+ return 0;
+
+ /* check if channel is active */
+ if (!trx_chan_desc[chan].auto_active
+ && !l1ts->chan_state[chan].active)
+ return -EINVAL;
+
+ return func(l1t, tn, fn, frame->dl_chan);
+}
+
+/* process downlink burst */
+const ubit_t *_sched_dl_burst(struct l1sched_trx *l1t, uint8_t tn,
+ uint32_t fn, uint16_t *nbits)
+{
+ struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn);
+ struct l1sched_chan_state *l1cs;
+ const struct trx_sched_frame *frame;
+ uint8_t offset, period, bid;
+ trx_sched_dl_func *func;
+ enum trx_chan_type chan;
+ ubit_t *bits = NULL;
+
+ if (!l1ts->mf_index)
+ goto no_data;
+
+ /* get frame from multiframe */
+ period = l1ts->mf_period;
+ offset = fn % period;
+ frame = l1ts->mf_frames + offset;
+
+ chan = frame->dl_chan;
+ bid = frame->dl_bid;
+ func = trx_chan_desc[chan].dl_fn;
+
+ l1cs = &l1ts->chan_state[chan];
+
+ /* check if channel is active */
+ if (!trx_chan_desc[chan].auto_active && !l1cs->active) {
+ if (nbits)
+ *nbits = GSM_BURST_LEN;
+ goto no_data;
+ }
+
+ /* get burst from function */
+ bits = func(l1t, tn, fn, chan, bid, nbits);
+
+ /* encrypt */
+ if (bits && l1cs->dl_encr_algo) {
+ ubit_t ks[114];
+ int i;
+
+ osmo_a5(l1cs->dl_encr_algo, l1cs->dl_encr_key, fn, ks, NULL);
+ for (i = 0; i < 57; i++) {
+ bits[i + 3] ^= ks[i];
+ bits[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)
+{
+ 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;
+
+ /**
+ * When a channel is just activated, the MS needs some time
+ * to synchronize and start burst transmission,
+ * so let's wait until the first UL burst...
+ */
+ 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) {
+ case TRXC_IDLE:
+ case TRXC_RACH:
+ case TRXC_PDTCH:
+ case TRXC_PTCCH:
+ return 0;
+ default:
+ /* No applicable if we are waiting for handover RACH */
+ if (l1cs->ho_rach_detect)
+ return 0;
+ }
+
+ /* How many frames elapsed since the last one? */
+ elapsed_fs = TDMA_FN_SUB(fn, l1cs->last_tdma_fn);
+ if (elapsed_fs > l1ts->mf_period) { /* Too many! */
+ LOGL1S(DL1P, LOGL_ERROR, l1t, tn, frame_head->ul_chan, fn,
+ "Too many (>%u) contiguous TDMA frames=%u elapsed "
+ "since the last processed fn=%u\n", l1ts->mf_period,
+ elapsed_fs, l1cs->last_tdma_fn);
+ /* FIXME: how should this affect the measurements? */
+ return -EINVAL;
+ }
+
+ /**
+ * 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.
+ *
+ * Start counting from the last_fn + 1.
+ */
+ for (i = 1; i < elapsed_fs; i++) {
+ fn_i = 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)
+ l1cs->lost_tdma_fs++;
+ }
+
+ if (l1cs->lost_tdma_fs > 0) {
+ LOGL1S(DL1P, LOGL_NOTICE, l1t, tn, frame_head->ul_chan, fn,
+ "At least %u TDMA frames were lost since the last "
+ "processed fn=%u\n", l1cs->lost_tdma_fs, l1cs->last_tdma_fn);
+
+ /**
+ * HACK: substitute lost bursts by zero-filled ones
+ *
+ * Instead of doing this, it makes sense to use the
+ * amount of lost frames in measurement calculations.
+ */
+ static sbit_t zero_burst[GSM_BURST_LEN] = { 0 };
+ trx_sched_ul_func *func;
+
+ for (i = 1; i < elapsed_fs; i++) {
+ fn_i = 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)
+ continue;
+
+ LOGL1S(DL1P, LOGL_NOTICE, l1t, tn, frame->ul_chan, fn,
+ "Substituting lost TDMA frame=%u by all-zero "
+ "dummy burst\n", fn_i);
+
+ func(l1t, tn, fn_i, frame->ul_chan, frame->ul_bid,
+ zero_burst, GSM_BURST_LEN, -128, 0);
+
+ l1cs->lost_tdma_fs--;
+ }
+ }
+
+ return 0;
+}
+
+/* process uplink burst */
+int trx_sched_ul_burst(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn,
+ sbit_t *bits, uint16_t nbits, int8_t rssi, int16_t toa256)
+{
+ struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn);
+ struct l1sched_chan_state *l1cs;
+ const struct trx_sched_frame *frame;
+ uint8_t offset, period, bid;
+ trx_sched_ul_func *func;
+ enum trx_chan_type chan;
+
+ if (!l1ts->mf_index)
+ return -EINVAL;
+
+ /* get frame from multiframe */
+ period = l1ts->mf_period;
+ offset = 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;
+
+ /* check if channel is active */
+ if (!trx_chan_desc[chan].auto_active && !l1cs->active)
+ return -EINVAL;
+
+ /* 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, tn, fn);
+
+ /* update TDMA frame counters */
+ l1cs->last_tdma_fn = fn;
+ l1cs->proc_tdma_fs++;
+
+ /* decrypt */
+ if (bits && l1cs->ul_encr_algo) {
+ ubit_t ks[114];
+ int i;
+
+ osmo_a5(l1cs->ul_encr_algo, l1cs->ul_encr_key, fn, NULL, ks);
+ for (i = 0; i < 57; i++) {
+ if (ks[i])
+ bits[i + 3] = - bits[i + 3];
+ if (ks[i + 57])
+ bits[i + 88] = - bits[i + 88];
+ }
+ }
+
+ /* put burst to function */
+ func(l1t, tn, fn, chan, bid, bits, nbits, rssi, toa256);
+
+ 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
new file mode 100644
index 00000000..b969407c
--- /dev/null
+++ b/src/common/scheduler_mframe.c
@@ -0,0 +1,1040 @@
+/* Scheduler for OsmoBTS-TRX */
+
+/* (C) 2013 by Andreas Eversberg <jolly@eversberg.eu>
+ * (C) 2015 by Alexander Chemeris <Alexander.Chemeris@fairwaves.co>
+ * (C) 2015-2018 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stdint.h>
+#include <ctype.h>
+
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/talloc.h>
+
+#include <osmo-bts/gsm_data.h>
+#include <osmo-bts/logging.h>
+#include <osmo-bts/scheduler.h>
+
+
+/*
+ * multiframe structure
+ */
+
+static const struct trx_sched_frame frame_bcch[51] = {
+/* dl_chan dl_bid ul_chan ul_bid */
+ { TRXC_FCCH, 0, TRXC_RACH, 0 },
+ { TRXC_SCH, 0, TRXC_RACH, 0 },
+ { TRXC_BCCH, 0, TRXC_RACH, 0 }, { TRXC_BCCH, 1, TRXC_RACH, 0 }, { TRXC_BCCH, 2, TRXC_RACH, 0 }, { TRXC_BCCH, 3, TRXC_RACH, 0 },
+ { TRXC_CCCH, 0, TRXC_RACH, 0 }, { TRXC_CCCH, 1, TRXC_RACH, 0 }, { TRXC_CCCH, 2, TRXC_RACH, 0 }, { TRXC_CCCH, 3, TRXC_RACH, 0 },
+ { TRXC_FCCH, 0, TRXC_RACH, 0 },
+ { TRXC_SCH, 0, TRXC_RACH, 0 },
+ { TRXC_CCCH, 0, TRXC_RACH, 0 }, { TRXC_CCCH, 1, TRXC_RACH, 0 }, { TRXC_CCCH, 2, TRXC_RACH, 0 }, { TRXC_CCCH, 3, TRXC_RACH, 0 },
+ { TRXC_CCCH, 0, TRXC_RACH, 0 }, { TRXC_CCCH, 1, TRXC_RACH, 0 }, { TRXC_CCCH, 2, TRXC_RACH, 0 }, { TRXC_CCCH, 3, TRXC_RACH, 0 },
+ { TRXC_FCCH, 0, TRXC_RACH, 0 },
+ { TRXC_SCH, 0, TRXC_RACH, 0 },
+ { TRXC_CCCH, 0, TRXC_RACH, 0 }, { TRXC_CCCH, 1, TRXC_RACH, 0 }, { TRXC_CCCH, 2, TRXC_RACH, 0 }, { TRXC_CCCH, 3, TRXC_RACH, 0 },
+ { TRXC_CCCH, 0, TRXC_RACH, 0 }, { TRXC_CCCH, 1, TRXC_RACH, 0 }, { TRXC_CCCH, 2, TRXC_RACH, 0 }, { TRXC_CCCH, 3, TRXC_RACH, 0 },
+ { TRXC_FCCH, 0, TRXC_RACH, 0 },
+ { TRXC_SCH, 0, TRXC_RACH, 0 },
+ { TRXC_CCCH, 0, TRXC_RACH, 0 }, { TRXC_CCCH, 1, TRXC_RACH, 0 }, { TRXC_CCCH, 2, TRXC_RACH, 0 }, { TRXC_CCCH, 3, TRXC_RACH, 0 },
+ { TRXC_CCCH, 0, TRXC_RACH, 0 }, { TRXC_CCCH, 1, TRXC_RACH, 0 }, { TRXC_CCCH, 2, TRXC_RACH, 0 }, { TRXC_CCCH, 3, TRXC_RACH, 0 },
+ { TRXC_FCCH, 0, TRXC_RACH, 0 },
+ { TRXC_SCH, 0, TRXC_RACH, 0 },
+ { TRXC_CCCH, 0, TRXC_RACH, 0 }, { TRXC_CCCH, 1, TRXC_RACH, 0 }, { TRXC_CCCH, 2, TRXC_RACH, 0 }, { TRXC_CCCH, 3, TRXC_RACH, 0 },
+ { TRXC_CCCH, 0, TRXC_RACH, 0 }, { TRXC_CCCH, 1, TRXC_RACH, 0 }, { TRXC_CCCH, 2, TRXC_RACH, 0 }, { TRXC_CCCH, 3, TRXC_RACH, 0 },
+ { TRXC_IDLE, 0, TRXC_RACH, 0 },
+};
+
+static const struct trx_sched_frame frame_bcch_sdcch4[102] = {
+/* dl_chan dl_bid ul_chan ul_bid */
+ { TRXC_FCCH, 0, TRXC_SDCCH4_3, 0 },
+ { TRXC_SCH, 0, TRXC_SDCCH4_3, 1 },
+ { TRXC_BCCH, 0, TRXC_SDCCH4_3, 2 },
+ { TRXC_BCCH, 1, TRXC_SDCCH4_3, 3 },
+ { TRXC_BCCH, 2, TRXC_RACH, 0 },
+ { TRXC_BCCH, 3, TRXC_RACH, 0 },
+ { TRXC_CCCH, 0, TRXC_SACCH4_2, 0 },
+ { TRXC_CCCH, 1, TRXC_SACCH4_2, 1 },
+ { TRXC_CCCH, 2, TRXC_SACCH4_2, 2 },
+ { TRXC_CCCH, 3, TRXC_SACCH4_2, 3 },
+ { TRXC_FCCH, 0, TRXC_SACCH4_3, 0 },
+ { TRXC_SCH, 0, TRXC_SACCH4_3, 1 },
+ { TRXC_CCCH, 0, TRXC_SACCH4_3, 2 },
+ { TRXC_CCCH, 1, TRXC_SACCH4_3, 3 },
+ { TRXC_CCCH, 2, TRXC_RACH, 0 },
+ { TRXC_CCCH, 3, TRXC_RACH, 0 },
+ { TRXC_CCCH, 0, TRXC_RACH, 0 },
+ { TRXC_CCCH, 1, TRXC_RACH, 0 },
+ { TRXC_CCCH, 2, TRXC_RACH, 0 },
+ { TRXC_CCCH, 3, TRXC_RACH, 0 },
+ { TRXC_FCCH, 0, TRXC_RACH, 0 },
+ { TRXC_SCH, 0, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_0, 0, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_0, 1, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_0, 2, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_0, 3, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_1, 0, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_1, 1, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_1, 2, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_1, 3, TRXC_RACH, 0 },
+ { TRXC_FCCH, 0, TRXC_RACH, 0 },
+ { TRXC_SCH, 0, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_2, 0, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_2, 1, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_2, 2, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_2, 3, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_3, 0, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_3, 1, TRXC_SDCCH4_0, 0 },
+ { TRXC_SDCCH4_3, 2, TRXC_SDCCH4_0, 1 },
+ { TRXC_SDCCH4_3, 3, TRXC_SDCCH4_0, 2 },
+ { TRXC_FCCH, 0, TRXC_SDCCH4_0, 3 },
+ { TRXC_SCH, 0, TRXC_SDCCH4_1, 0 },
+ { TRXC_SACCH4_0, 0, TRXC_SDCCH4_1, 1 },
+ { TRXC_SACCH4_0, 1, TRXC_SDCCH4_1, 2 },
+ { TRXC_SACCH4_0, 2, TRXC_SDCCH4_1, 3 },
+ { TRXC_SACCH4_0, 3, TRXC_RACH, 0 },
+ { TRXC_SACCH4_1, 0, TRXC_RACH, 0 },
+ { TRXC_SACCH4_1, 1, TRXC_SDCCH4_2, 0 },
+ { TRXC_SACCH4_1, 2, TRXC_SDCCH4_2, 1 },
+ { TRXC_SACCH4_1, 3, TRXC_SDCCH4_2, 2 },
+ { TRXC_IDLE, 0, TRXC_SDCCH4_2, 3 },
+
+ { TRXC_FCCH, 0, TRXC_SDCCH4_3, 0 },
+ { TRXC_SCH, 0, TRXC_SDCCH4_3, 1 },
+ { TRXC_BCCH, 0, TRXC_SDCCH4_3, 2 },
+ { TRXC_BCCH, 1, TRXC_SDCCH4_3, 3 },
+ { TRXC_BCCH, 2, TRXC_RACH, 0 },
+ { TRXC_BCCH, 3, TRXC_RACH, 0 },
+ { TRXC_CCCH, 0, TRXC_SACCH4_0, 0 },
+ { TRXC_CCCH, 1, TRXC_SACCH4_0, 1 },
+ { TRXC_CCCH, 2, TRXC_SACCH4_0, 2 },
+ { TRXC_CCCH, 3, TRXC_SACCH4_0, 3 },
+ { TRXC_FCCH, 0, TRXC_SACCH4_1, 0 },
+ { TRXC_SCH, 0, TRXC_SACCH4_1, 1 },
+ { TRXC_CCCH, 0, TRXC_SACCH4_1, 2 },
+ { TRXC_CCCH, 1, TRXC_SACCH4_1, 3 },
+ { TRXC_CCCH, 2, TRXC_RACH, 0 },
+ { TRXC_CCCH, 3, TRXC_RACH, 0 },
+ { TRXC_CCCH, 0, TRXC_RACH, 0 },
+ { TRXC_CCCH, 1, TRXC_RACH, 0 },
+ { TRXC_CCCH, 2, TRXC_RACH, 0 },
+ { TRXC_CCCH, 3, TRXC_RACH, 0 },
+ { TRXC_FCCH, 0, TRXC_RACH, 0 },
+ { TRXC_SCH, 0, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_0, 0, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_0, 1, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_0, 2, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_0, 3, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_1, 0, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_1, 1, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_1, 2, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_1, 3, TRXC_RACH, 0 },
+ { TRXC_FCCH, 0, TRXC_RACH, 0 },
+ { TRXC_SCH, 0, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_2, 0, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_2, 1, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_2, 2, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_2, 3, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_3, 0, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_3, 1, TRXC_SDCCH4_0, 0 },
+ { TRXC_SDCCH4_3, 2, TRXC_SDCCH4_0, 1 },
+ { TRXC_SDCCH4_3, 3, TRXC_SDCCH4_0, 2 },
+ { TRXC_FCCH, 0, TRXC_SDCCH4_0, 3 },
+ { TRXC_SCH, 0, TRXC_SDCCH4_1, 0 },
+ { TRXC_SACCH4_2, 0, TRXC_SDCCH4_1, 1 },
+ { TRXC_SACCH4_2, 1, TRXC_SDCCH4_1, 2 },
+ { TRXC_SACCH4_2, 2, TRXC_SDCCH4_1, 3 },
+ { TRXC_SACCH4_2, 3, TRXC_RACH, 0 },
+ { TRXC_SACCH4_3, 0, TRXC_RACH, 0 },
+ { TRXC_SACCH4_3, 1, TRXC_SDCCH4_2, 0 },
+ { TRXC_SACCH4_3, 2, TRXC_SDCCH4_2, 1 },
+ { TRXC_SACCH4_3, 3, TRXC_SDCCH4_2, 2 },
+ { TRXC_IDLE, 0, TRXC_SDCCH4_2, 3 },
+};
+
+static const struct trx_sched_frame frame_bcch_sdcch4_cbch[102] = {
+/* dl_chan dl_bid ul_chan ul_bid */
+ { TRXC_FCCH, 0, TRXC_SDCCH4_3, 0 },
+ { TRXC_SCH, 0, TRXC_SDCCH4_3, 1 },
+ { TRXC_BCCH, 0, TRXC_SDCCH4_3, 2 },
+ { TRXC_BCCH, 1, TRXC_SDCCH4_3, 3 },
+ { TRXC_BCCH, 2, TRXC_RACH, 0 },
+ { TRXC_BCCH, 3, TRXC_RACH, 0 },
+ { TRXC_CCCH, 0, TRXC_IDLE, 0 },
+ { TRXC_CCCH, 1, TRXC_IDLE, 0 },
+ { TRXC_CCCH, 2, TRXC_IDLE, 0 },
+ { TRXC_CCCH, 3, TRXC_IDLE, 0 },
+ { TRXC_FCCH, 0, TRXC_SACCH4_3, 0 },
+ { TRXC_SCH, 0, TRXC_SACCH4_3, 1 },
+ { TRXC_CCCH, 0, TRXC_SACCH4_3, 2 },
+ { TRXC_CCCH, 1, TRXC_SACCH4_3, 3 },
+ { TRXC_CCCH, 2, TRXC_RACH, 0 },
+ { TRXC_CCCH, 3, TRXC_RACH, 0 },
+ { TRXC_CCCH, 0, TRXC_RACH, 0 },
+ { TRXC_CCCH, 1, TRXC_RACH, 0 },
+ { TRXC_CCCH, 2, TRXC_RACH, 0 },
+ { TRXC_CCCH, 3, TRXC_RACH, 0 },
+ { TRXC_FCCH, 0, TRXC_RACH, 0 },
+ { TRXC_SCH, 0, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_0, 0, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_0, 1, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_0, 2, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_0, 3, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_1, 0, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_1, 1, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_1, 2, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_1, 3, TRXC_RACH, 0 },
+ { TRXC_FCCH, 0, TRXC_RACH, 0 },
+ { TRXC_SCH, 0, TRXC_RACH, 0 },
+ { TRXC_CBCH, 0, TRXC_RACH, 0 },
+ { TRXC_CBCH, 1, TRXC_RACH, 0 },
+ { TRXC_CBCH, 2, TRXC_RACH, 0 },
+ { TRXC_CBCH, 3, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_3, 0, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_3, 1, TRXC_SDCCH4_0, 0 },
+ { TRXC_SDCCH4_3, 2, TRXC_SDCCH4_0, 1 },
+ { TRXC_SDCCH4_3, 3, TRXC_SDCCH4_0, 2 },
+ { TRXC_FCCH, 0, TRXC_SDCCH4_0, 3 },
+ { TRXC_SCH, 0, TRXC_SDCCH4_1, 0 },
+ { TRXC_SACCH4_0, 0, TRXC_SDCCH4_1, 1 },
+ { TRXC_SACCH4_0, 1, TRXC_SDCCH4_1, 2 },
+ { TRXC_SACCH4_0, 2, TRXC_SDCCH4_1, 3 },
+ { TRXC_SACCH4_0, 3, TRXC_RACH, 0 },
+ { TRXC_SACCH4_1, 0, TRXC_RACH, 0 },
+ { TRXC_SACCH4_1, 1, TRXC_IDLE, 0 },
+ { TRXC_SACCH4_1, 2, TRXC_IDLE, 0 },
+ { TRXC_SACCH4_1, 3, TRXC_IDLE, 0 },
+ { TRXC_IDLE, 0, TRXC_IDLE, 0 },
+
+ { TRXC_FCCH, 0, TRXC_SDCCH4_3, 0 },
+ { TRXC_SCH, 0, TRXC_SDCCH4_3, 1 },
+ { TRXC_BCCH, 0, TRXC_SDCCH4_3, 2 },
+ { TRXC_BCCH, 1, TRXC_SDCCH4_3, 3 },
+ { TRXC_BCCH, 2, TRXC_RACH, 0 },
+ { TRXC_BCCH, 3, TRXC_RACH, 0 },
+ { TRXC_CCCH, 0, TRXC_SACCH4_0, 0 },
+ { TRXC_CCCH, 1, TRXC_SACCH4_0, 1 },
+ { TRXC_CCCH, 2, TRXC_SACCH4_0, 2 },
+ { TRXC_CCCH, 3, TRXC_SACCH4_0, 3 },
+ { TRXC_FCCH, 0, TRXC_SACCH4_1, 0 },
+ { TRXC_SCH, 0, TRXC_SACCH4_1, 1 },
+ { TRXC_CCCH, 0, TRXC_SACCH4_1, 2 },
+ { TRXC_CCCH, 1, TRXC_SACCH4_1, 3 },
+ { TRXC_CCCH, 2, TRXC_RACH, 0 },
+ { TRXC_CCCH, 3, TRXC_RACH, 0 },
+ { TRXC_CCCH, 0, TRXC_RACH, 0 },
+ { TRXC_CCCH, 1, TRXC_RACH, 0 },
+ { TRXC_CCCH, 2, TRXC_RACH, 0 },
+ { TRXC_CCCH, 3, TRXC_RACH, 0 },
+ { TRXC_FCCH, 0, TRXC_RACH, 0 },
+ { TRXC_SCH, 0, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_0, 0, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_0, 1, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_0, 2, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_0, 3, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_1, 0, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_1, 1, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_1, 2, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_1, 3, TRXC_RACH, 0 },
+ { TRXC_FCCH, 0, TRXC_RACH, 0 },
+ { TRXC_SCH, 0, TRXC_RACH, 0 },
+ { TRXC_CBCH, 0, TRXC_RACH, 0 },
+ { TRXC_CBCH, 1, TRXC_RACH, 0 },
+ { TRXC_CBCH, 2, TRXC_RACH, 0 },
+ { TRXC_CBCH, 3, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_3, 0, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_3, 1, TRXC_SDCCH4_0, 0 },
+ { TRXC_SDCCH4_3, 2, TRXC_SDCCH4_0, 1 },
+ { TRXC_SDCCH4_3, 3, TRXC_SDCCH4_0, 2 },
+ { TRXC_FCCH, 0, TRXC_SDCCH4_0, 3 },
+ { TRXC_SCH, 0, TRXC_SDCCH4_1, 0 },
+ { TRXC_IDLE, 0, TRXC_SDCCH4_1, 1 },
+ { TRXC_IDLE, 0, TRXC_SDCCH4_1, 2 },
+ { TRXC_IDLE, 0, TRXC_SDCCH4_1, 3 },
+ { TRXC_IDLE, 0, TRXC_RACH, 0 },
+ { TRXC_SACCH4_3, 0, TRXC_RACH, 0 },
+ { TRXC_SACCH4_3, 1, TRXC_IDLE, 0 },
+ { TRXC_SACCH4_3, 2, TRXC_IDLE, 0 },
+ { TRXC_SACCH4_3, 3, TRXC_IDLE, 0 },
+ { TRXC_IDLE, 0, TRXC_IDLE, 0 },
+};
+
+static const struct trx_sched_frame frame_sdcch8[102] = {
+/* dl_chan dl_bid ul_chan ul_bid */
+ { TRXC_SDCCH8_0, 0, TRXC_SACCH8_5, 0 },
+ { TRXC_SDCCH8_0, 1, TRXC_SACCH8_5, 1 },
+ { TRXC_SDCCH8_0, 2, TRXC_SACCH8_5, 2 },
+ { TRXC_SDCCH8_0, 3, TRXC_SACCH8_5, 3 },
+ { TRXC_SDCCH8_1, 0, TRXC_SACCH8_6, 0 },
+ { TRXC_SDCCH8_1, 1, TRXC_SACCH8_6, 1 },
+ { TRXC_SDCCH8_1, 2, TRXC_SACCH8_6, 2 },
+ { TRXC_SDCCH8_1, 3, TRXC_SACCH8_6, 3 },
+ { TRXC_SDCCH8_2, 0, TRXC_SACCH8_7, 0 },
+ { TRXC_SDCCH8_2, 1, TRXC_SACCH8_7, 1 },
+ { TRXC_SDCCH8_2, 2, TRXC_SACCH8_7, 2 },
+ { TRXC_SDCCH8_2, 3, TRXC_SACCH8_7, 3 },
+ { TRXC_SDCCH8_3, 0, TRXC_IDLE, 0 },
+ { TRXC_SDCCH8_3, 1, TRXC_IDLE, 0 },
+ { TRXC_SDCCH8_3, 2, TRXC_IDLE, 0 },
+ { TRXC_SDCCH8_3, 3, TRXC_SDCCH8_0, 0 },
+ { TRXC_SDCCH8_4, 0, TRXC_SDCCH8_0, 1 },
+ { TRXC_SDCCH8_4, 1, TRXC_SDCCH8_0, 2 },
+ { TRXC_SDCCH8_4, 2, TRXC_SDCCH8_0, 3 },
+ { TRXC_SDCCH8_4, 3, TRXC_SDCCH8_1, 0 },
+ { TRXC_SDCCH8_5, 0, TRXC_SDCCH8_1, 1 },
+ { TRXC_SDCCH8_5, 1, TRXC_SDCCH8_1, 2 },
+ { TRXC_SDCCH8_5, 2, TRXC_SDCCH8_1, 3 },
+ { TRXC_SDCCH8_5, 3, TRXC_SDCCH8_2, 0 },
+ { TRXC_SDCCH8_6, 0, TRXC_SDCCH8_2, 1 },
+ { TRXC_SDCCH8_6, 1, TRXC_SDCCH8_2, 2 },
+ { TRXC_SDCCH8_6, 2, TRXC_SDCCH8_2, 3 },
+ { TRXC_SDCCH8_6, 3, TRXC_SDCCH8_3, 0 },
+ { TRXC_SDCCH8_7, 0, TRXC_SDCCH8_3, 1 },
+ { TRXC_SDCCH8_7, 1, TRXC_SDCCH8_3, 2 },
+ { TRXC_SDCCH8_7, 2, TRXC_SDCCH8_3, 3 },
+ { TRXC_SDCCH8_7, 3, TRXC_SDCCH8_4, 0 },
+ { TRXC_SACCH8_0, 0, TRXC_SDCCH8_4, 1 },
+ { TRXC_SACCH8_0, 1, TRXC_SDCCH8_4, 2 },
+ { TRXC_SACCH8_0, 2, TRXC_SDCCH8_4, 3 },
+ { TRXC_SACCH8_0, 3, TRXC_SDCCH8_5, 0 },
+ { TRXC_SACCH8_1, 0, TRXC_SDCCH8_5, 1 },
+ { TRXC_SACCH8_1, 1, TRXC_SDCCH8_5, 2 },
+ { TRXC_SACCH8_1, 2, TRXC_SDCCH8_5, 3 },
+ { TRXC_SACCH8_1, 3, TRXC_SDCCH8_6, 0 },
+ { TRXC_SACCH8_2, 0, TRXC_SDCCH8_6, 1 },
+ { TRXC_SACCH8_2, 1, TRXC_SDCCH8_6, 2 },
+ { TRXC_SACCH8_2, 2, TRXC_SDCCH8_6, 3 },
+ { TRXC_SACCH8_2, 3, TRXC_SDCCH8_7, 0 },
+ { TRXC_SACCH8_3, 0, TRXC_SDCCH8_7, 1 },
+ { TRXC_SACCH8_3, 1, TRXC_SDCCH8_7, 2 },
+ { TRXC_SACCH8_3, 2, TRXC_SDCCH8_7, 3 },
+ { TRXC_SACCH8_3, 3, TRXC_SACCH8_0, 0 },
+ { TRXC_IDLE, 0, TRXC_SACCH8_0, 1 },
+ { TRXC_IDLE, 0, TRXC_SACCH8_0, 2 },
+ { TRXC_IDLE, 0, TRXC_SACCH8_0, 3 },
+
+ { TRXC_SDCCH8_0, 0, TRXC_SACCH8_1, 0 },
+ { TRXC_SDCCH8_0, 1, TRXC_SACCH8_1, 1 },
+ { TRXC_SDCCH8_0, 2, TRXC_SACCH8_1, 2 },
+ { TRXC_SDCCH8_0, 3, TRXC_SACCH8_1, 3 },
+ { TRXC_SDCCH8_1, 0, TRXC_SACCH8_2, 0 },
+ { TRXC_SDCCH8_1, 1, TRXC_SACCH8_2, 1 },
+ { TRXC_SDCCH8_1, 2, TRXC_SACCH8_2, 2 },
+ { TRXC_SDCCH8_1, 3, TRXC_SACCH8_2, 3 },
+ { TRXC_SDCCH8_2, 0, TRXC_SACCH8_3, 0 },
+ { TRXC_SDCCH8_2, 1, TRXC_SACCH8_3, 1 },
+ { TRXC_SDCCH8_2, 2, TRXC_SACCH8_3, 2 },
+ { TRXC_SDCCH8_2, 3, TRXC_SACCH8_3, 3 },
+ { TRXC_SDCCH8_3, 0, TRXC_IDLE, 0 },
+ { TRXC_SDCCH8_3, 1, TRXC_IDLE, 0 },
+ { TRXC_SDCCH8_3, 2, TRXC_IDLE, 0 },
+ { TRXC_SDCCH8_3, 3, TRXC_SDCCH8_0, 0 },
+ { TRXC_SDCCH8_4, 0, TRXC_SDCCH8_0, 1 },
+ { TRXC_SDCCH8_4, 1, TRXC_SDCCH8_0, 2 },
+ { TRXC_SDCCH8_4, 2, TRXC_SDCCH8_0, 3 },
+ { TRXC_SDCCH8_4, 3, TRXC_SDCCH8_1, 0 },
+ { TRXC_SDCCH8_5, 0, TRXC_SDCCH8_1, 1 },
+ { TRXC_SDCCH8_5, 1, TRXC_SDCCH8_1, 2 },
+ { TRXC_SDCCH8_5, 2, TRXC_SDCCH8_1, 3 },
+ { TRXC_SDCCH8_5, 3, TRXC_SDCCH8_2, 0 },
+ { TRXC_SDCCH8_6, 0, TRXC_SDCCH8_2, 1 },
+ { TRXC_SDCCH8_6, 1, TRXC_SDCCH8_2, 2 },
+ { TRXC_SDCCH8_6, 2, TRXC_SDCCH8_2, 3 },
+ { TRXC_SDCCH8_6, 3, TRXC_SDCCH8_3, 0 },
+ { TRXC_SDCCH8_7, 0, TRXC_SDCCH8_3, 1 },
+ { TRXC_SDCCH8_7, 1, TRXC_SDCCH8_3, 2 },
+ { TRXC_SDCCH8_7, 2, TRXC_SDCCH8_3, 3 },
+ { TRXC_SDCCH8_7, 3, TRXC_SDCCH8_4, 0 },
+ { TRXC_SACCH8_4, 0, TRXC_SDCCH8_4, 1 },
+ { TRXC_SACCH8_4, 1, TRXC_SDCCH8_4, 2 },
+ { TRXC_SACCH8_4, 2, TRXC_SDCCH8_4, 3 },
+ { TRXC_SACCH8_4, 3, TRXC_SDCCH8_5, 0 },
+ { TRXC_SACCH8_5, 0, TRXC_SDCCH8_5, 1 },
+ { TRXC_SACCH8_5, 1, TRXC_SDCCH8_5, 2 },
+ { TRXC_SACCH8_5, 2, TRXC_SDCCH8_5, 3 },
+ { TRXC_SACCH8_5, 3, TRXC_SDCCH8_6, 0 },
+ { TRXC_SACCH8_6, 0, TRXC_SDCCH8_6, 1 },
+ { TRXC_SACCH8_6, 1, TRXC_SDCCH8_6, 2 },
+ { TRXC_SACCH8_6, 2, TRXC_SDCCH8_6, 3 },
+ { TRXC_SACCH8_6, 3, TRXC_SDCCH8_7, 0 },
+ { TRXC_SACCH8_7, 0, TRXC_SDCCH8_7, 1 },
+ { TRXC_SACCH8_7, 1, TRXC_SDCCH8_7, 2 },
+ { TRXC_SACCH8_7, 2, TRXC_SDCCH8_7, 3 },
+ { TRXC_SACCH8_7, 3, TRXC_SACCH8_4, 0 },
+ { TRXC_IDLE, 0, TRXC_SACCH8_4, 1 },
+ { TRXC_IDLE, 0, TRXC_SACCH8_4, 2 },
+ { TRXC_IDLE, 0, TRXC_SACCH8_4, 3 },
+};
+
+static const struct trx_sched_frame frame_sdcch8_cbch[102] = {
+/* dl_chan dl_bid ul_chan ul_bid */
+ { TRXC_SDCCH8_0, 0, TRXC_SACCH8_5, 0 },
+ { TRXC_SDCCH8_0, 1, TRXC_SACCH8_5, 1 },
+ { TRXC_SDCCH8_0, 2, TRXC_SACCH8_5, 2 },
+ { TRXC_SDCCH8_0, 3, TRXC_SACCH8_5, 3 },
+ { TRXC_SDCCH8_1, 0, TRXC_SACCH8_6, 0 },
+ { TRXC_SDCCH8_1, 1, TRXC_SACCH8_6, 1 },
+ { TRXC_SDCCH8_1, 2, TRXC_SACCH8_6, 2 },
+ { TRXC_SDCCH8_1, 3, TRXC_SACCH8_6, 3 },
+ { TRXC_CBCH, 0, TRXC_SACCH8_7, 0 },
+ { TRXC_CBCH, 1, TRXC_SACCH8_7, 1 },
+ { TRXC_CBCH, 2, TRXC_SACCH8_7, 2 },
+ { TRXC_CBCH, 3, TRXC_SACCH8_7, 3 },
+ { TRXC_SDCCH8_3, 0, TRXC_IDLE, 0 },
+ { TRXC_SDCCH8_3, 1, TRXC_IDLE, 0 },
+ { TRXC_SDCCH8_3, 2, TRXC_IDLE, 0 },
+ { TRXC_SDCCH8_3, 3, TRXC_SDCCH8_0, 0 },
+ { TRXC_SDCCH8_4, 0, TRXC_SDCCH8_0, 1 },
+ { TRXC_SDCCH8_4, 1, TRXC_SDCCH8_0, 2 },
+ { TRXC_SDCCH8_4, 2, TRXC_SDCCH8_0, 3 },
+ { TRXC_SDCCH8_4, 3, TRXC_SDCCH8_1, 0 },
+ { TRXC_SDCCH8_5, 0, TRXC_SDCCH8_1, 1 },
+ { TRXC_SDCCH8_5, 1, TRXC_SDCCH8_1, 2 },
+ { TRXC_SDCCH8_5, 2, TRXC_SDCCH8_1, 3 },
+ { TRXC_SDCCH8_5, 3, TRXC_IDLE, 0 },
+ { TRXC_SDCCH8_6, 0, TRXC_IDLE, 1 },
+ { TRXC_SDCCH8_6, 1, TRXC_IDLE, 2 },
+ { TRXC_SDCCH8_6, 2, TRXC_IDLE, 3 },
+ { TRXC_SDCCH8_6, 3, TRXC_SDCCH8_3, 0 },
+ { TRXC_SDCCH8_7, 0, TRXC_SDCCH8_3, 1 },
+ { TRXC_SDCCH8_7, 1, TRXC_SDCCH8_3, 2 },
+ { TRXC_SDCCH8_7, 2, TRXC_SDCCH8_3, 3 },
+ { TRXC_SDCCH8_7, 3, TRXC_SDCCH8_4, 0 },
+ { TRXC_SACCH8_0, 0, TRXC_SDCCH8_4, 1 },
+ { TRXC_SACCH8_0, 1, TRXC_SDCCH8_4, 2 },
+ { TRXC_SACCH8_0, 2, TRXC_SDCCH8_4, 3 },
+ { TRXC_SACCH8_0, 3, TRXC_SDCCH8_5, 0 },
+ { TRXC_SACCH8_1, 0, TRXC_SDCCH8_5, 1 },
+ { TRXC_SACCH8_1, 1, TRXC_SDCCH8_5, 2 },
+ { TRXC_SACCH8_1, 2, TRXC_SDCCH8_5, 3 },
+ { TRXC_SACCH8_1, 3, TRXC_SDCCH8_6, 0 },
+ { TRXC_IDLE, 0, TRXC_SDCCH8_6, 1 },
+ { TRXC_IDLE, 1, TRXC_SDCCH8_6, 2 },
+ { TRXC_IDLE, 2, TRXC_SDCCH8_6, 3 },
+ { TRXC_IDLE, 3, TRXC_SDCCH8_7, 0 },
+ { TRXC_SACCH8_3, 0, TRXC_SDCCH8_7, 1 },
+ { TRXC_SACCH8_3, 1, TRXC_SDCCH8_7, 2 },
+ { TRXC_SACCH8_3, 2, TRXC_SDCCH8_7, 3 },
+ { TRXC_SACCH8_3, 3, TRXC_SACCH8_0, 0 },
+ { TRXC_IDLE, 0, TRXC_SACCH8_0, 1 },
+ { TRXC_IDLE, 0, TRXC_SACCH8_0, 2 },
+ { TRXC_IDLE, 0, TRXC_SACCH8_0, 3 },
+
+ { TRXC_SDCCH8_0, 0, TRXC_SACCH8_1, 0 },
+ { TRXC_SDCCH8_0, 1, TRXC_SACCH8_1, 1 },
+ { TRXC_SDCCH8_0, 2, TRXC_SACCH8_1, 2 },
+ { TRXC_SDCCH8_0, 3, TRXC_SACCH8_1, 3 },
+ { TRXC_SDCCH8_1, 0, TRXC_IDLE, 0 },
+ { TRXC_SDCCH8_1, 1, TRXC_IDLE, 1 },
+ { TRXC_SDCCH8_1, 2, TRXC_IDLE, 2 },
+ { TRXC_SDCCH8_1, 3, TRXC_IDLE, 3 },
+ { TRXC_CBCH, 0, TRXC_SACCH8_3, 0 },
+ { TRXC_CBCH, 1, TRXC_SACCH8_3, 1 },
+ { TRXC_CBCH, 2, TRXC_SACCH8_3, 2 },
+ { TRXC_CBCH, 3, TRXC_SACCH8_3, 3 },
+ { TRXC_SDCCH8_3, 0, TRXC_IDLE, 0 },
+ { TRXC_SDCCH8_3, 1, TRXC_IDLE, 0 },
+ { TRXC_SDCCH8_3, 2, TRXC_IDLE, 0 },
+ { TRXC_SDCCH8_3, 3, TRXC_SDCCH8_0, 0 },
+ { TRXC_SDCCH8_4, 0, TRXC_SDCCH8_0, 1 },
+ { TRXC_SDCCH8_4, 1, TRXC_SDCCH8_0, 2 },
+ { TRXC_SDCCH8_4, 2, TRXC_SDCCH8_0, 3 },
+ { TRXC_SDCCH8_4, 3, TRXC_SDCCH8_1, 0 },
+ { TRXC_SDCCH8_5, 0, TRXC_SDCCH8_1, 1 },
+ { TRXC_SDCCH8_5, 1, TRXC_SDCCH8_1, 2 },
+ { TRXC_SDCCH8_5, 2, TRXC_SDCCH8_1, 3 },
+ { TRXC_SDCCH8_5, 3, TRXC_IDLE, 0 },
+ { TRXC_SDCCH8_6, 0, TRXC_IDLE, 1 },
+ { TRXC_SDCCH8_6, 1, TRXC_IDLE, 2 },
+ { TRXC_SDCCH8_6, 2, TRXC_IDLE, 3 },
+ { TRXC_SDCCH8_6, 3, TRXC_SDCCH8_3, 0 },
+ { TRXC_SDCCH8_7, 0, TRXC_SDCCH8_3, 1 },
+ { TRXC_SDCCH8_7, 1, TRXC_SDCCH8_3, 2 },
+ { TRXC_SDCCH8_7, 2, TRXC_SDCCH8_3, 3 },
+ { TRXC_SDCCH8_7, 3, TRXC_SDCCH8_4, 0 },
+ { TRXC_SACCH8_4, 0, TRXC_SDCCH8_4, 1 },
+ { TRXC_SACCH8_4, 1, TRXC_SDCCH8_4, 2 },
+ { TRXC_SACCH8_4, 2, TRXC_SDCCH8_4, 3 },
+ { TRXC_SACCH8_4, 3, TRXC_SDCCH8_5, 0 },
+ { TRXC_SACCH8_5, 0, TRXC_SDCCH8_5, 1 },
+ { TRXC_SACCH8_5, 1, TRXC_SDCCH8_5, 2 },
+ { TRXC_SACCH8_5, 2, TRXC_SDCCH8_5, 3 },
+ { TRXC_SACCH8_5, 3, TRXC_SDCCH8_6, 0 },
+ { TRXC_SACCH8_6, 0, TRXC_SDCCH8_6, 1 },
+ { TRXC_SACCH8_6, 1, TRXC_SDCCH8_6, 2 },
+ { TRXC_SACCH8_6, 2, TRXC_SDCCH8_6, 3 },
+ { TRXC_SACCH8_6, 3, TRXC_SDCCH8_7, 0 },
+ { TRXC_SACCH8_7, 0, TRXC_SDCCH8_7, 1 },
+ { TRXC_SACCH8_7, 1, TRXC_SDCCH8_7, 2 },
+ { TRXC_SACCH8_7, 2, TRXC_SDCCH8_7, 3 },
+ { TRXC_SACCH8_7, 3, TRXC_SACCH8_4, 0 },
+ { TRXC_IDLE, 0, TRXC_SACCH8_4, 1 },
+ { TRXC_IDLE, 0, TRXC_SACCH8_4, 2 },
+ { TRXC_IDLE, 0, TRXC_SACCH8_4, 3 },
+};
+
+static const struct trx_sched_frame frame_tchf_ts0[104] = {
+/* dl_chan dl_bid ul_chan ul_bid */
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_SACCHTF, 0, TRXC_SACCHTF, 0 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_IDLE, 0, TRXC_IDLE, 0 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_SACCHTF, 1, TRXC_SACCHTF, 1 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_IDLE, 0, TRXC_IDLE, 0 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_SACCHTF, 2, TRXC_SACCHTF, 2 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_IDLE, 0, TRXC_IDLE, 0 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_SACCHTF, 3, TRXC_SACCHTF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_IDLE, 0, TRXC_IDLE, 0 },
+};
+
+static const struct trx_sched_frame frame_tchf_ts1[104] = {
+/* dl_chan dl_bid ul_chan ul_bid */
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_IDLE, 0, TRXC_IDLE, 0 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_SACCHTF, 0, TRXC_SACCHTF, 0 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_IDLE, 0, TRXC_IDLE, 0 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_SACCHTF, 1, TRXC_SACCHTF, 1 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_IDLE, 0, TRXC_IDLE, 0 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_SACCHTF, 2, TRXC_SACCHTF, 2 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_IDLE, 0, TRXC_IDLE, 0 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_SACCHTF, 3, TRXC_SACCHTF, 3 },
+};
+
+static const struct trx_sched_frame frame_tchf_ts2[104] = {
+/* dl_chan dl_bid ul_chan ul_bid */
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_SACCHTF, 3, TRXC_SACCHTF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_IDLE, 0, TRXC_IDLE, 0 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_SACCHTF, 0, TRXC_SACCHTF, 0 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_IDLE, 0, TRXC_IDLE, 0 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_SACCHTF, 1, TRXC_SACCHTF, 1 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_IDLE, 0, TRXC_IDLE, 0 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_SACCHTF, 2, TRXC_SACCHTF, 2 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_IDLE, 0, TRXC_IDLE, 0 },
+};
+
+static const struct trx_sched_frame frame_tchf_ts3[104] = {
+/* dl_chan dl_bid ul_chan ul_bid */
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_IDLE, 0, TRXC_IDLE, 0 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_SACCHTF, 3, TRXC_SACCHTF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_IDLE, 0, TRXC_IDLE, 0 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_SACCHTF, 0, TRXC_SACCHTF, 0 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_IDLE, 0, TRXC_IDLE, 0 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_SACCHTF, 1, TRXC_SACCHTF, 1 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_IDLE, 0, TRXC_IDLE, 0 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_SACCHTF, 2, TRXC_SACCHTF, 2 },
+};
+
+static const struct trx_sched_frame frame_tchf_ts4[104] = {
+/* dl_chan dl_bid ul_chan ul_bid */
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_SACCHTF, 2, TRXC_SACCHTF, 2 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_IDLE, 0, TRXC_IDLE, 0 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_SACCHTF, 3, TRXC_SACCHTF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_IDLE, 0, TRXC_IDLE, 0 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_SACCHTF, 0, TRXC_SACCHTF, 0 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_IDLE, 0, TRXC_IDLE, 0 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_SACCHTF, 1, TRXC_SACCHTF, 1 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_IDLE, 0, TRXC_IDLE, 0 },
+};
+
+static const struct trx_sched_frame frame_tchf_ts5[104] = {
+/* dl_chan dl_bid ul_chan ul_bid */
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_IDLE, 0, TRXC_IDLE, 0 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_SACCHTF, 2, TRXC_SACCHTF, 2 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_IDLE, 0, TRXC_IDLE, 0 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_SACCHTF, 3, TRXC_SACCHTF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_IDLE, 0, TRXC_IDLE, 0 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_SACCHTF, 0, TRXC_SACCHTF, 0 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_IDLE, 0, TRXC_IDLE, 0 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_SACCHTF, 1, TRXC_SACCHTF, 1 },
+};
+
+static const struct trx_sched_frame frame_tchf_ts6[104] = {
+/* dl_chan dl_bid ul_chan ul_bid */
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_SACCHTF, 1, TRXC_SACCHTF, 1 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_IDLE, 0, TRXC_IDLE, 0 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_SACCHTF, 2, TRXC_SACCHTF, 2 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_IDLE, 0, TRXC_IDLE, 0 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_SACCHTF, 3, TRXC_SACCHTF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_IDLE, 0, TRXC_IDLE, 0 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_SACCHTF, 0, TRXC_SACCHTF, 0 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_IDLE, 0, TRXC_IDLE, 0 },
+};
+
+static const struct trx_sched_frame frame_tchf_ts7[104] = {
+/* dl_chan dl_bid ul_chan ul_bid */
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_IDLE, 0, TRXC_IDLE, 0 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_SACCHTF, 1, TRXC_SACCHTF, 1 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_IDLE, 0, TRXC_IDLE, 0 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_SACCHTF, 2, TRXC_SACCHTF, 2 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_IDLE, 0, TRXC_IDLE, 0 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_SACCHTF, 3, TRXC_SACCHTF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_IDLE, 0, TRXC_IDLE, 0 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_SACCHTF, 0, TRXC_SACCHTF, 0 },
+};
+
+static const struct trx_sched_frame frame_tchh_ts01[104] = {
+/* dl_chan dl_bid ul_chan ul_bid */
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_SACCHTH_0, 0, TRXC_SACCHTH_0, 0 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_SACCHTH_1, 0, TRXC_SACCHTH_1, 0 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_SACCHTH_0, 1, TRXC_SACCHTH_0, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_SACCHTH_1, 1, TRXC_SACCHTH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_SACCHTH_0, 2, TRXC_SACCHTH_0, 2 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_SACCHTH_1, 2, TRXC_SACCHTH_1, 2 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_SACCHTH_0, 3, TRXC_SACCHTH_0, 3 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_SACCHTH_1, 3, TRXC_SACCHTH_1, 3 },
+};
+
+static const struct trx_sched_frame frame_tchh_ts23[104] = {
+/* dl_chan dl_bid ul_chan ul_bid */
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_SACCHTH_0, 3, TRXC_SACCHTH_0, 3 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_SACCHTH_1, 3, TRXC_SACCHTH_1, 3 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_SACCHTH_0, 0, TRXC_SACCHTH_0, 0 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_SACCHTH_1, 0, TRXC_SACCHTH_1, 0 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_SACCHTH_0, 1, TRXC_SACCHTH_0, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_SACCHTH_1, 1, TRXC_SACCHTH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_SACCHTH_0, 2, TRXC_SACCHTH_0, 2 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_SACCHTH_1, 2, TRXC_SACCHTH_1, 2 },
+};
+
+static const struct trx_sched_frame frame_tchh_ts45[104] = {
+/* dl_chan dl_bid ul_chan ul_bid */
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_SACCHTH_0, 2, TRXC_SACCHTH_0, 2 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_SACCHTH_1, 2, TRXC_SACCHTH_1, 2 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_SACCHTH_0, 3, TRXC_SACCHTH_0, 3 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_SACCHTH_1, 3, TRXC_SACCHTH_1, 3 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_SACCHTH_0, 0, TRXC_SACCHTH_0, 0 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_SACCHTH_1, 0, TRXC_SACCHTH_1, 0 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_SACCHTH_0, 1, TRXC_SACCHTH_0, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_SACCHTH_1, 1, TRXC_SACCHTH_1, 1 },
+};
+
+static const struct trx_sched_frame frame_tchh_ts67[104] = {
+/* dl_chan dl_bid ul_chan ul_bid */
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_SACCHTH_0, 1, TRXC_SACCHTH_0, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_SACCHTH_1, 1, TRXC_SACCHTH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_SACCHTH_0, 2, TRXC_SACCHTH_0, 2 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_SACCHTH_1, 2, TRXC_SACCHTH_1, 2 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_SACCHTH_0, 3, TRXC_SACCHTH_0, 3 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_SACCHTH_1, 3, TRXC_SACCHTH_1, 3 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_SACCHTH_0, 0, TRXC_SACCHTH_0, 0 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_SACCHTH_1, 0, TRXC_SACCHTH_1, 0 },
+};
+
+static const struct trx_sched_frame frame_pdch[104] = {
+/* dl_chan dl_bid ul_chan ul_bid */
+ { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, { TRXC_PDTCH, 3, TRXC_PDTCH, 3 },
+ { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, { TRXC_PDTCH, 3, TRXC_PDTCH, 3 },
+ { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, { TRXC_PDTCH, 3, TRXC_PDTCH, 3 },
+ { TRXC_PTCCH, 0, TRXC_PTCCH, 0 },
+ { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, { TRXC_PDTCH, 3, TRXC_PDTCH, 3 },
+ { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, { TRXC_PDTCH, 3, TRXC_PDTCH, 3 },
+ { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, { TRXC_PDTCH, 3, TRXC_PDTCH, 3 },
+ { TRXC_IDLE, 0, TRXC_IDLE, 0 },
+ { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, { TRXC_PDTCH, 3, TRXC_PDTCH, 3 },
+ { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, { TRXC_PDTCH, 3, TRXC_PDTCH, 3 },
+ { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, { TRXC_PDTCH, 3, TRXC_PDTCH, 3 },
+ { TRXC_PTCCH, 1, TRXC_PTCCH, 1 },
+ { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, { TRXC_PDTCH, 3, TRXC_PDTCH, 3 },
+ { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, { TRXC_PDTCH, 3, TRXC_PDTCH, 3 },
+ { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, { TRXC_PDTCH, 3, TRXC_PDTCH, 3 },
+ { TRXC_IDLE, 0, TRXC_IDLE, 0 },
+ { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, { TRXC_PDTCH, 3, TRXC_PDTCH, 3 },
+ { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, { TRXC_PDTCH, 3, TRXC_PDTCH, 3 },
+ { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, { TRXC_PDTCH, 3, TRXC_PDTCH, 3 },
+ { TRXC_PTCCH, 2, TRXC_PTCCH, 2 },
+ { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, { TRXC_PDTCH, 3, TRXC_PDTCH, 3 },
+ { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, { TRXC_PDTCH, 3, TRXC_PDTCH, 3 },
+ { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, { TRXC_PDTCH, 3, TRXC_PDTCH, 3 },
+ { TRXC_IDLE, 0, TRXC_IDLE, 0 },
+ { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, { TRXC_PDTCH, 3, TRXC_PDTCH, 3 },
+ { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, { TRXC_PDTCH, 3, TRXC_PDTCH, 3 },
+ { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, { TRXC_PDTCH, 3, TRXC_PDTCH, 3 },
+ { TRXC_PTCCH, 3, TRXC_PTCCH, 3 },
+ { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, { TRXC_PDTCH, 3, TRXC_PDTCH, 3 },
+ { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, { TRXC_PDTCH, 3, TRXC_PDTCH, 3 },
+ { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, { TRXC_PDTCH, 3, TRXC_PDTCH, 3 },
+ { TRXC_IDLE, 0, TRXC_IDLE, 0 },
+};
+
+const struct trx_sched_multiframe trx_sched_multiframes[] = {
+ { GSM_PCHAN_NONE, 0xff, 0, NULL, "NONE"},
+ { GSM_PCHAN_CCCH, 0xff, 51, frame_bcch, "BCCH+CCCH" },
+ { GSM_PCHAN_CCCH_SDCCH4, 0xff, 102, frame_bcch_sdcch4, "BCCH+CCCH+SDCCH/4+SACCH/4" },
+ { GSM_PCHAN_CCCH_SDCCH4_CBCH, 0xff, 102, frame_bcch_sdcch4_cbch, "BCCH+CCCH+SDCCH/4+SACCH/4+CBCH" },
+ { GSM_PCHAN_SDCCH8_SACCH8C, 0xff, 102, frame_sdcch8, "SDCCH/8+SACCH/8" },
+ { GSM_PCHAN_SDCCH8_SACCH8C_CBCH,0xff, 102, frame_sdcch8_cbch, "SDCCH/8+SACCH/8+CBCH" },
+ { GSM_PCHAN_TCH_F, 0x01, 104, frame_tchf_ts0, "TCH/F+SACCH" },
+ { GSM_PCHAN_TCH_F, 0x02, 104, frame_tchf_ts1, "TCH/F+SACCH" },
+ { GSM_PCHAN_TCH_F, 0x04, 104, frame_tchf_ts2, "TCH/F+SACCH" },
+ { GSM_PCHAN_TCH_F, 0x08, 104, frame_tchf_ts3, "TCH/F+SACCH" },
+ { GSM_PCHAN_TCH_F, 0x10, 104, frame_tchf_ts4, "TCH/F+SACCH" },
+ { GSM_PCHAN_TCH_F, 0x20, 104, frame_tchf_ts5, "TCH/F+SACCH" },
+ { GSM_PCHAN_TCH_F, 0x40, 104, frame_tchf_ts6, "TCH/F+SACCH" },
+ { GSM_PCHAN_TCH_F, 0x80, 104, frame_tchf_ts7, "TCH/F+SACCH" },
+ { GSM_PCHAN_TCH_H, 0x03, 104, frame_tchh_ts01, "TCH/H+SACCH" },
+ { GSM_PCHAN_TCH_H, 0x0c, 104, frame_tchh_ts23, "TCH/H+SACCH" },
+ { GSM_PCHAN_TCH_H, 0x30, 104, frame_tchh_ts45, "TCH/H+SACCH" },
+ { GSM_PCHAN_TCH_H, 0xc0, 104, frame_tchh_ts67, "TCH/H+SACCH" },
+ { GSM_PCHAN_PDCH, 0xff, 104, frame_pdch, "PDCH" },
+};
+
+
+/*
+ * scheduler functions
+ */
+
+int find_sched_mframe_idx(enum gsm_phys_chan_config pchan, uint8_t tn)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(trx_sched_multiframes); i++) {
+ if (trx_sched_multiframes[i].pchan == pchan
+ && (trx_sched_multiframes[i].slotmask & (1 << tn))) {
+ return i;
+ }
+ }
+ return -1;
+}
+
+/* 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)
+{
+ 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);
+ if (i < 0)
+ return -EINVAL;
+ sched = &trx_sched_multiframes[i];
+ frame = &sched->frames[fn % sched->period];
+ if (uplink)
+ ch_type = frame->ul_chan;
+ else
+ ch_type = frame->dl_chan;
+
+ switch (ch_type) {
+ case TRXC_SACCH4_0:
+ case TRXC_SACCH4_1:
+ case TRXC_SACCH4_2:
+ case TRXC_SACCH4_3:
+ case TRXC_SACCH8_0:
+ case TRXC_SACCH8_1:
+ case TRXC_SACCH8_2:
+ case TRXC_SACCH8_3:
+ case TRXC_SACCH8_4:
+ case TRXC_SACCH8_5:
+ case TRXC_SACCH8_6:
+ case TRXC_SACCH8_7:
+ case TRXC_SACCHTF:
+ case TRXC_SACCHTH_0:
+ case TRXC_SACCHTH_1:
+ return true;
+ default:
+ return false;
+ }
+}
diff --git a/src/common/sysinfo.c b/src/common/sysinfo.c
new file mode 100644
index 00000000..5c66e086
--- /dev/null
+++ b/src/common/sysinfo.c
@@ -0,0 +1,177 @@
+/* (C) 2011 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU 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 <osmocom/gsm/gsm_utils.h>
+#include <osmocom/gsm/sysinfo.h>
+
+#include <osmo-bts/logging.h>
+#include <osmo-bts/gsm_data.h>
+
+/* properly increment SI2q index and return SI2q data for scheduling */
+static inline uint8_t *get_si2q_inc_index(struct gsm_bts *bts)
+{
+ uint8_t i = bts->si2q_index;
+ /* si2q_count is the max si2q_index value, not the number of messages */
+ bts->si2q_index = (bts->si2q_index + 1) % (bts->si2q_count + 1);
+
+ return (uint8_t *)GSM_BTS_SI2Q(bts, i);
+}
+
+/* Apply the rules from 05.02 6.3.1.3 Mapping of BCCH Data */
+uint8_t *bts_sysinfo_get(struct gsm_bts *bts, const struct gsm_time *g_time)
+{
+ unsigned int tc4_cnt = 0;
+ unsigned int tc4_sub[4];
+
+ /* System information type 2 bis or 2 ter messages are sent if
+ * needed, as determined by the system operator. If only one of
+ * them is needed, it is sent when TC = 5. If both are needed,
+ * 2bis is sent when TC = 5 and 2ter is sent at least once
+ * within any of 4 consecutive occurrences of TC = 4. */
+ /* System information type 2 quater is sent if needed, as
+ * determined by the system operator. If sent on BCCH Norm, it
+ * shall be sent when TC = 5 if neither of 2bis and 2ter are
+ * used, otherwise it shall be sent at least once within any of
+ * 4 consecutive occurrences of TC = 4. If sent on BCCH Ext, it
+ * is sent at least once within any of 4 consecutive occurrences
+ * of TC = 5. */
+ /* System Information type 9 is sent in those blocks with
+ * TC = 4 which are specified in system information type 3 as
+ * defined in 3GPP TS 04.08. */
+ /* System Information Type 13 need only be sent if GPRS support
+ * is indicated in one or more of System Information Type 3 or 4
+ * or 7 or 8 messages. These messages also indicate if the
+ * message is sent on the BCCH Norm or if the message is
+ * transmitted on the BCCH Ext. In the case that the message is
+ * sent on the BCCH Norm, it is sent at least once within any of
+ * 4 consecutive occurrences of TC = 4. */
+
+ /* We only implement BCCH Norm at this time */
+ switch (g_time->tc) {
+ case 0:
+ /* System Information Type 1 need only be sent if
+ * frequency hopping is in use or when the NCH is
+ * present in a cell. If the MS finds another message
+ * when TC = 0, it can assume that System Information
+ * Type 1 is not in use. */
+ if (GSM_BTS_HAS_SI(bts, SYSINFO_TYPE_1))
+ return GSM_BTS_SI(bts, SYSINFO_TYPE_1);
+ return GSM_BTS_SI(bts, SYSINFO_TYPE_2);
+ case 1:
+ /* A SI 2 message will be sent at least every time TC = 1. */
+ return GSM_BTS_SI(bts, SYSINFO_TYPE_2);
+ case 2:
+ return GSM_BTS_SI(bts, SYSINFO_TYPE_3);
+ case 3:
+ return GSM_BTS_SI(bts, SYSINFO_TYPE_4);
+ case 4:
+ /* iterate over 2ter, 2quater, 9, 13 */
+ /* determine how many SI we need to send on TC=4,
+ * and which of them we send when */
+ if (GSM_BTS_HAS_SI(bts, SYSINFO_TYPE_2ter) && GSM_BTS_HAS_SI(bts, SYSINFO_TYPE_2bis)) {
+ tc4_sub[tc4_cnt] = SYSINFO_TYPE_2ter;
+ tc4_cnt += 1;
+ }
+ if (GSM_BTS_HAS_SI(bts, SYSINFO_TYPE_2quater) &&
+ (GSM_BTS_HAS_SI(bts, SYSINFO_TYPE_2bis) || GSM_BTS_HAS_SI(bts, SYSINFO_TYPE_2ter))) {
+ tc4_sub[tc4_cnt] = SYSINFO_TYPE_2quater;
+ tc4_cnt += 1;
+ }
+ if (GSM_BTS_HAS_SI(bts, SYSINFO_TYPE_13)) {
+ tc4_sub[tc4_cnt] = SYSINFO_TYPE_13;
+ tc4_cnt += 1;
+ }
+ if (GSM_BTS_HAS_SI(bts, SYSINFO_TYPE_9)) {
+ /* FIXME: check SI3 scheduling info! */
+ tc4_sub[tc4_cnt] = SYSINFO_TYPE_9;
+ tc4_cnt += 1;
+ }
+ /* simply send SI2 if we have nothing else to send */
+ if (tc4_cnt == 0)
+ return GSM_BTS_SI(bts, SYSINFO_TYPE_2);
+ else {
+ /* increment static counter by one, modulo count */
+ bts->si.tc4_ctr = (bts->si.tc4_ctr + 1) % tc4_cnt;
+
+ if (tc4_sub[bts->si.tc4_ctr] == SYSINFO_TYPE_2quater)
+ return get_si2q_inc_index(bts);
+
+ return GSM_BTS_SI(bts, tc4_sub[bts->si.tc4_ctr]);
+ }
+ case 5:
+ /* 2bis, 2ter, 2quater */
+ if (GSM_BTS_HAS_SI(bts, SYSINFO_TYPE_2bis) && !GSM_BTS_HAS_SI(bts, SYSINFO_TYPE_2ter))
+ return GSM_BTS_SI(bts, SYSINFO_TYPE_2bis);
+
+ else if (GSM_BTS_HAS_SI(bts, SYSINFO_TYPE_2ter) && !GSM_BTS_HAS_SI(bts, SYSINFO_TYPE_2bis))
+ return GSM_BTS_SI(bts, SYSINFO_TYPE_2ter);
+
+ else if (GSM_BTS_HAS_SI(bts, SYSINFO_TYPE_2bis) && GSM_BTS_HAS_SI(bts, SYSINFO_TYPE_2ter))
+ return GSM_BTS_SI(bts, SYSINFO_TYPE_2bis);
+
+ else if (GSM_BTS_HAS_SI(bts, SYSINFO_TYPE_2quater) &&
+ !GSM_BTS_HAS_SI(bts, SYSINFO_TYPE_2bis) && !GSM_BTS_HAS_SI(bts, SYSINFO_TYPE_2ter))
+ return get_si2q_inc_index(bts);
+
+ /* simply send SI2 if we have nothing else to send */
+ else
+ return GSM_BTS_SI(bts, SYSINFO_TYPE_2);
+ break;
+ case 6:
+ return GSM_BTS_SI(bts, SYSINFO_TYPE_3);
+ case 7:
+ return GSM_BTS_SI(bts, SYSINFO_TYPE_4);
+ }
+
+ /* this should never bve reached. We must transmit a BCCH
+ * message on the normal BCCH in all cases. */
+ OSMO_ASSERT(0);
+ return 0;
+}
+
+uint8_t num_agch(struct gsm_bts_trx *trx, const char * arg)
+{
+ struct gsm_bts *b = trx->bts;
+ 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 "
+ "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)
+{
+ 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);
+ }
+ LOGP(DL1P, LOGL_NOTICE, "%s SACCH no SI available\n", gsm_lchan_name(lchan));
+ return NULL;
+}
diff --git a/src/common/tx_power.c b/src/common/tx_power.c
new file mode 100644
index 00000000..e418cec5
--- /dev/null
+++ b/src/common/tx_power.c
@@ -0,0 +1,306 @@
+/* Transmit Power computation */
+
+/* (C) 2014 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 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/utils.h>
+
+#include <osmo-bts/logging.h>
+#include <osmo-bts/gsm_data.h>
+#include <osmo-bts/bts_model.h>
+#include <osmo-bts/tx_power.h>
+
+static int get_pa_drive_level_mdBm(const struct power_amp *pa,
+ int desired_p_out_mdBm, unsigned int arfcn)
+{
+ if (arfcn >= ARRAY_SIZE(pa->calib.delta_mdB))
+ return INT_MIN;
+
+ /* FIXME: temperature compensation */
+
+ return desired_p_out_mdBm - pa->nominal_gain_mdB - pa->calib.delta_mdB[arfcn];
+}
+
+/* maximum output power of the system */
+int get_p_max_out_mdBm(struct gsm_bts_trx *trx)
+{
+ 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)
+{
+ /* P_max_out subtracted by OML maximum power reduction IE */
+ return get_p_max_out_mdBm(trx) - to_mdB(trx->max_power_red);
+}
+
+/* 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)
+{
+ /* Pn subtracted by RSL BS Power IE (in 2 dB steps) */
+ return get_p_nominal_mdBm(trx) - to_mdB(bs_power_ie * 2);
+}
+int get_p_target_mdBm_lchan(struct gsm_lchan *lchan)
+{
+ return get_p_target_mdBm(lchan->ts->trx, lchan->bs_power);
+}
+
+/* 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)
+{
+ struct trx_power_params *tpp = &trx->power_params;
+
+ /* P_target subtracted by ramp attenuation */
+ return p_target_mdBm - tpp->ramp.attenuation_mdB;
+}
+
+/* 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)
+{
+ 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;
+}
+
+/* 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)
+{
+ struct trx_power_params *tpp = &trx->power_params;
+ int p_actual_mdBm, user_pa_drvlvl_mdBm, pa_drvlvl_mdBm;
+ unsigned int arfcn = trx->arfcn;
+
+ /* P_actual subtracted by any bulk gain added by the user */
+ p_actual_mdBm = get_p_eff_mdBm(trx, p_target_mdBm) - 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_actual_mdBm, arfcn);
+
+ /* determine input drive level required at input to internal PA */
+ pa_drvlvl_mdBm = get_pa_drive_level_mdBm(&tpp->pa, user_pa_drvlvl_mdBm, arfcn);
+
+ /* internal PA input drive level is TRX output power */
+ return pa_drvlvl_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)
+{
+ 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;
+
+ /* 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);
+
+ /* determine input drive level required at input to internal PA */
+ pa_drvlvl_mdBm = get_pa_drive_level_mdBm(&tpp->pa, user_pa_drvlvl_mdBm, arfcn);
+
+ /* internal PA input drive level is TRX output power */
+ return pa_drvlvl_mdBm;
+}
+int get_p_trxout_target_mdBm_lchan(struct gsm_lchan *lchan)
+{
+ return get_p_trxout_target_mdBm(lchan->ts->trx, lchan->bs_power);
+}
+
+
+/* output power ramping code */
+
+/* The idea here is to avoid a hard switch from 0 to 100, but to actually
+ * slowly and gradually ramp up or down the power. This is needed on the
+ * one hand side to avoid very fast dynamic load changes towards the PA power
+ * supply, but is also needed in order to avoid a DoS by too many subscriber
+ * 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)
+{
+ struct trx_power_params *tpp = &trx->power_params;
+
+ if (tpp->p_total_tgt_mdBm > tpp->p_total_cur_mdBm)
+ return 1;
+ else
+ return 0;
+}
+
+static void power_ramp_do_step(struct gsm_bts_trx *trx, int first);
+
+/* timer call-back for the ramp timer */
+static void power_ramp_timer_cb(void *_trx)
+{
+ struct gsm_bts_trx *trx = _trx;
+ struct trx_power_params *tpp = &trx->power_params;
+ int p_trxout_eff_mdBm;
+
+ /* compute new actual total output power (= minus ramp attenuation) */
+ tpp->p_total_cur_mdBm = get_p_actual_mdBm(trx, tpp->p_total_tgt_mdBm);
+
+ /* 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, "
+ "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,
+ "ramping TRX board output power to %d mdBm.\n", p_trxout_eff_mdBm);
+
+ /* Instruct L1 to apply new effective TRX output power required */
+ bts_model_change_power(trx, p_trxout_eff_mdBm);
+}
+
+/* BTS model call-back once one a call to bts_model_change_power()
+ * completes, indicating actual L1 transmit power */
+void power_trx_change_compl(struct gsm_bts_trx *trx, int p_trxout_cur_mdBm)
+{
+ struct trx_power_params *tpp = &trx->power_params;
+ int p_trxout_should_mdBm;
+
+ p_trxout_should_mdBm = get_p_trxout_eff_mdBm(trx, tpp->p_total_tgt_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);
+ }
+
+ /* and do another step... */
+ power_ramp_do_step(trx, 0);
+}
+
+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)
+ return;
+
+ if (we_are_ramping_up(trx)) {
+ /* ramp up power -> ramp down attenuation */
+ tpp->ramp.attenuation_mdB -= tpp->ramp.step_size_mdB;
+ if (tpp->ramp.attenuation_mdB <= 0) {
+ /* we are done */
+ tpp->ramp.attenuation_mdB = 0;
+ }
+ } else {
+ /* ramp down power -> ramp up attenuation */
+ tpp->ramp.attenuation_mdB += tpp->ramp.step_size_mdB;
+ if (tpp->ramp.attenuation_mdB >= 0) {
+ /* we are done */
+ tpp->ramp.attenuation_mdB = 0;
+ }
+ }
+
+ /* schedule timer for the next step */
+ tpp->ramp.step_timer.data = trx;
+ tpp->ramp.step_timer.cb = power_ramp_timer_cb;
+ 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)
+{
+ struct trx_power_params *tpp = &trx->power_params;
+
+ /* The input to this function is the actual desired output power, i.e.
+ * 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);
+
+ 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));
+ return -ERANGE;
+ }
+
+ /* Cancel any pending request */
+ osmo_timer_del(&tpp->ramp.step_timer);
+
+ /* set the new target */
+ tpp->p_total_tgt_mdBm = p_total_tgt_mdBm;
+
+ 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);
+ /* 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 */
+ /* Set attenuation to cause no power change right now */
+ tpp->ramp.attenuation_mdB = tpp->p_total_tgt_mdBm - tpp->p_total_cur_mdBm;
+
+ /* start with the first step */
+ power_ramp_do_step(trx, 1);
+ }
+ } else {
+ /* Set ramp attenuation to negative value, and increase that by
+ * steps until it reaches 0 */
+ tpp->ramp.attenuation_mdB = tpp->p_total_tgt_mdBm - tpp->p_total_cur_mdBm;
+
+ /* start with the first step */
+ power_ramp_do_step(trx, 1);
+ }
+
+ return 0;
+}
+
+/* determine the initial transceiver output power at start-up time */
+int power_ramp_initial_power_mdBm(struct gsm_bts_trx *trx)
+{
+ struct trx_power_params *tpp = &trx->power_params;
+ int pout_mdBm;
+
+ /* this is the maximum initial output on the antenna connector
+ * towards the antenna */
+ pout_mdBm = tpp->ramp.max_initial_pout_mdBm;
+
+ /* use this as input to compute transceiver board power
+ * (reflecting gains in internal/external amplifiers */
+ return get_p_trxout_eff_mdBm(trx, pout_mdBm);
+}
diff --git a/src/common/vty.c b/src/common/vty.c
new file mode 100644
index 00000000..bb88c578
--- /dev/null
+++ b/src/common/vty.c
@@ -0,0 +1,1651 @@
+/* OsmoBTS VTY interface */
+
+/* (C) 2011-2014 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 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"
+
+#include <inttypes.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <ctype.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/gsm/abis_nm.h>
+#include <osmocom/gsm/protocol/gsm_04_08.h>
+#include <osmocom/vty/vty.h>
+#include <osmocom/vty/command.h>
+#include <osmocom/vty/logging.h>
+#include <osmocom/vty/misc.h>
+#include <osmocom/vty/ports.h>
+#include <osmocom/core/gsmtap.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/trau/osmo_ortp.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/rsl.h>
+#include <osmo-bts/oml.h>
+#include <osmo-bts/signal.h>
+#include <osmo-bts/bts_model.h>
+#include <osmo-bts/pcuif_proto.h>
+#include <osmo-bts/measurement.h>
+#include <osmo-bts/vty.h>
+#include <osmo-bts/l1sap.h>
+
+#define VTY_STR "Configure the VTY\n"
+
+#define BTS_NR_STR "BTS Number\n"
+#define TRX_NR_STR "TRX Number\n"
+#define TS_NR_STR "Timeslot Number\n"
+#define LCHAN_NR_STR "Logical Channel Number\n"
+#define BTS_TRX_STR BTS_NR_STR TRX_NR_STR
+#define BTS_TRX_TS_STR BTS_TRX_STR TS_NR_STR
+#define BTS_TRX_TS_LCHAN_STR BTS_TRX_TS_STR LCHAN_NR_STR
+
+int g_vty_port_num = OSMO_VTY_PORT_BTS;
+
+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",
+ 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",
+ inst_nr, VTY_NEWLINE);
+ return NULL;
+ }
+ return pinst;
+}
+
+int bts_vty_go_parent(struct vty *vty)
+{
+ switch (vty->node) {
+ case PHY_INST_NODE:
+ vty->node = PHY_NODE;
+ {
+ struct phy_instance *pinst = vty->index;
+ vty->index = pinst->phy_link;
+ }
+ break;
+ case TRX_NODE:
+ vty->node = BTS_NODE;
+ {
+ struct gsm_bts_trx *trx = vty->index;
+ vty->index = trx->bts;
+ }
+ break;
+ case PHY_NODE:
+ default:
+ vty->node = CONFIG_NODE;
+ }
+ 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"
+ "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";
+
+struct vty_app_info bts_vty_info = {
+ .name = "OsmoBTS",
+ .version = PACKAGE_VERSION,
+ .copyright = osmobts_copyright,
+ .go_parent_cb = bts_vty_go_parent,
+ .is_config_node = bts_vty_is_config_node,
+};
+
+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)# ",
+ 1,
+};
+
+static struct cmd_node trx_node = {
+ TRX_NODE,
+ "%s(trx)# ",
+ 1,
+};
+
+gDEFUN(cfg_bts_auto_band, cfg_bts_auto_band_cmd,
+ "auto-band",
+ "Automatically select band for ARFCN based on configured band\n")
+{
+ struct gsm_bts *bts = vty->index;
+
+ bts->auto_band = 1;
+ return CMD_SUCCESS;
+}
+
+gDEFUN(cfg_bts_no_auto_band, cfg_bts_no_auto_band_cmd,
+ "no auto-band",
+ NO_STR "Automatically select band for ARFCN based on configured band\n")
+{
+ struct gsm_bts *bts = vty->index;
+
+ bts->auto_band = 0;
+ return CMD_SUCCESS;
+}
+
+
+DEFUN(cfg_bts_trx, cfg_bts_trx_cmd,
+ "trx <0-254>",
+ "Select a TRX to configure\n" "TRX number\n")
+{
+ int trx_nr = atoi(argv[0]);
+ struct gsm_bts *bts = vty->index;
+ struct gsm_bts_trx *trx;
+
+
+ if (trx_nr > bts->num_trx) {
+ vty_out(vty, "%% The next unused TRX number is %u%s",
+ bts->num_trx, VTY_NEWLINE);
+ return CMD_WARNING;
+ } else if (trx_nr == bts->num_trx) {
+ /* Allocate a new TRX
+ * Remark: TRX0 was already created during gsm_bts_alloc() and
+ * initialized in bts_init(), not here.
+ */
+ trx = gsm_bts_trx_alloc(bts);
+ if (trx)
+ bts_trx_init(trx);
+ } else
+ trx = gsm_bts_trx_num(bts, trx_nr);
+
+ if (!trx) {
+ vty_out(vty, "%% Unable to allocate TRX %u%s",
+ trx_nr, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ vty->index = trx;
+ vty->index_sub = &trx->description;
+ vty->node = TRX_NODE;
+
+ return CMD_SUCCESS;
+}
+
+static void config_write_bts_single(struct vty *vty, struct gsm_bts *bts)
+{
+ struct gsm_bts_trx *trx;
+ const char *sapi_buf;
+ int i;
+
+ vty_out(vty, "bts %u%s", bts->nr, VTY_NEWLINE);
+ if (bts->description)
+ vty_out(vty, " description %s%s", bts->description, VTY_NEWLINE);
+ vty_out(vty, " band %s%s", gsm_band_name(bts->band), VTY_NEWLINE);
+ if (bts->auto_band)
+ 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);
+ 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);
+ 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);
+ 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)
+ vty_out(vty, " agch-queue-mgmt threshold %d low %d high %d%s",
+ 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));
+ vty_out(vty, " gsmtap-sapi %s%s", sapi_buf, VTY_NEWLINE);
+ }
+ }
+ if (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);
+ }
+ vty_out(vty, " min-qual-rach %.0f%s", bts->min_qual_rach * 10.0f,
+ VTY_NEWLINE);
+ vty_out(vty, " min-qual-norm %.0f%s", bts->min_qual_norm * 10.0f,
+ VTY_NEWLINE);
+ vty_out(vty, " max-ber10k-rach %u%s", bts->max_ber10k_rach,
+ 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->supp_meas_toa256)
+ vty_out(vty, " supp-meas-info toa256%s", VTY_NEWLINE);
+
+ 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);
+ vty_out(vty, " trx %u%s", trx->nr, VTY_NEWLINE);
+
+ if (trx->power_params.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",
+ tpp->ramp.max_initial_pout_mdBm, VTY_NEWLINE);
+ vty_out(vty, " power-ramp step-size %d mdB%s",
+ tpp->ramp.step_size_mdB, VTY_NEWLINE);
+ 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",
+ VTY_NEWLINE);
+ vty_out(vty, " phy %u instance %u%s", pinst->phy_link->num,
+ pinst->num, VTY_NEWLINE);
+
+ bts_model_config_write_trx(vty, trx);
+ }
+}
+
+static int config_write_bts(struct vty *vty)
+{
+ struct gsm_network *net = gsmnet_from_vty(vty);
+ struct gsm_bts *bts;
+
+ llist_for_each_entry(bts, &net->bts_list, list)
+ config_write_bts_single(vty, bts);
+
+ return CMD_SUCCESS;
+}
+
+static void config_write_phy_single(struct vty *vty, struct phy_link *plink)
+{
+ int i;
+
+ vty_out(vty, "phy %u%s", plink->num, VTY_NEWLINE);
+ bts_model_config_write_phy(vty, plink);
+
+ for (i = 0; i < 255; i++) {
+ struct phy_instance *pinst = phy_instance_by_num(plink, i);
+ if (!pinst)
+ break;
+ vty_out(vty, " instance %u%s", pinst->num, VTY_NEWLINE);
+ bts_model_config_write_phy_inst(vty, pinst);
+ }
+}
+
+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);
+ if (!plink)
+ break;
+ config_write_phy_single(vty, plink);
+ }
+
+ return CMD_SUCCESS;
+}
+
+static int config_write_dummy(struct vty *vty)
+{
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_vty_telnet_port, cfg_vty_telnet_port_cmd,
+ "vty telnet-port <0-65535>",
+ VTY_STR "Set the VTY telnet port\n"
+ "TCP Port number\n")
+{
+ g_vty_port_num = atoi(argv[0]);
+ return CMD_SUCCESS;
+}
+
+/* per-BTS configuration */
+DEFUN(cfg_bts,
+ cfg_bts_cmd,
+ "bts BTS_NR",
+ "Select a BTS to configure\n"
+ "BTS Number\n")
+{
+ struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+ int bts_nr = atoi(argv[0]);
+ struct gsm_bts *bts;
+
+ if (bts_nr >= gsmnet->num_bts) {
+ vty_out(vty, "%% Unknown BTS number %u (num %u)%s",
+ bts_nr, gsmnet->num_bts, VTY_NEWLINE);
+ return CMD_WARNING;
+ } else
+ bts = gsm_bts_num(gsmnet, bts_nr);
+
+ vty->index = bts;
+ vty->index_sub = &bts->description;
+ vty->node = BTS_NODE;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_unit_id,
+ cfg_bts_unit_id_cmd,
+ "ipa unit-id <0-65534> <0-255>",
+ "ip.access RSL commands\n"
+ "Set the Unit ID of this BTS\n"
+ "Site ID\n" "Unit ID\n")
+{
+ struct gsm_bts *bts = vty->index;
+ int site_id = atoi(argv[0]);
+ int bts_id = atoi(argv[1]);
+
+ bts->ip_access.site_id = site_id;
+ bts->ip_access.bts_id = bts_id;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_band,
+ cfg_bts_band_cmd,
+ "band (450|GSM450|480|GSM480|750|GSM750|810|GSM810|850|GSM850|900|GSM900|1800|DCS1800|1900|PCS1900)",
+ "Set the frequency band of this BTS\n"
+ "Alias for GSM450\n450Mhz\n"
+ "Alias for GSM480\n480Mhz\n"
+ "Alias for GSM750\n750Mhz\n"
+ "Alias for GSM810\n810Mhz\n"
+ "Alias for GSM850\n850Mhz\n"
+ "Alias for GSM900\n900Mhz\n"
+ "Alias for DCS1800\n1800Mhz\n"
+ "Alias for PCS1900\n1900Mhz\n")
+{
+ struct gsm_bts *bts = vty->index;
+ int band = gsm_band_parse(argv[0]);
+
+ if (band < 0) {
+ vty_out(vty, "%% BAND %d is not a valid GSM band%s",
+ band, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ bts->band = band;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_oml_ip,
+ cfg_bts_oml_ip_cmd,
+ "oml remote-ip A.B.C.D",
+ "OML Parameters\n" "OML IP Address\n" "OML IP Address\n")
+{
+ struct gsm_bts *bts = vty->index;
+
+ if (bts->bsc_oml_host)
+ talloc_free(bts->bsc_oml_host);
+
+ bts->bsc_oml_host = talloc_strdup(bts, argv[0]);
+
+ return CMD_SUCCESS;
+}
+
+#define RTP_STR "RTP parameters\n"
+
+DEFUN_DEPRECATED(cfg_bts_rtp_bind_ip,
+ cfg_bts_rtp_bind_ip_cmd,
+ "rtp bind-ip A.B.C.D",
+ RTP_STR "RTP local bind IP Address\n" "RTP local bind IP Address\n")
+{
+ vty_out(vty, "%% rtp bind-ip is now deprecated%s", VTY_NEWLINE);
+
+ 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")
+{
+ struct gsm_bts *bts = vty->index;
+
+ bts->rtp_jitter_buf_ms = atoi(argv[0]);
+ if (argc > 1)
+ bts->rtp_jitter_adaptive = true;
+
+ return CMD_SUCCESS;
+}
+
+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")
+{
+ struct gsm_bts *bts = vty->index;
+ unsigned int start;
+ unsigned int end;
+
+ start = atoi(argv[0]);
+ end = atoi(argv[1]);
+
+ if (end < start) {
+ 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",
+ 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",
+ end, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ bts->rtp_port_range_start = start;
+ bts->rtp_port_range_end = end;
+ bts->rtp_port_range_next = bts->rtp_port_range_start;
+
+ 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")
+{
+ struct gsm_bts *bts = vty->index;
+
+ paging_set_queue_max(bts->paging_state, atoi(argv[0]));
+
+ 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")
+{
+ struct gsm_bts *bts = vty->index;
+
+ paging_set_lifetime(bts->paging_state, atoi(argv[0]));
+
+ return CMD_SUCCESS;
+}
+
+#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")
+{
+ struct gsm_bts *bts = vty->index;
+
+ bts->agch_queue.thresh_level = atoi(argv[0]);
+ bts->agch_queue.low_level = atoi(argv[1]);
+ bts->agch_queue.high_level = atoi(argv[2]);
+
+ 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")
+{
+ struct gsm_bts *bts = vty->index;
+
+ 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;
+
+ 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")
+{
+ struct gsm_bts *bts = vty->index;
+
+ bts->ul_power_target = atoi(argv[0]);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_min_qual_rach, cfg_bts_min_qual_rach_cmd,
+ "min-qual-rach <-100-100>",
+ "Set the minimum quality level of RACH burst to be accpeted\n"
+ "C/I level in tenth of dB\n")
+{
+ struct gsm_bts *bts = vty->index;
+
+ bts->min_qual_rach = strtof(argv[0], NULL) / 10.0f;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_min_qual_norm, cfg_bts_min_qual_norm_cmd,
+ "min-qual-norm <-100-100>",
+ "Set the minimum quality level of normal burst to be accpeted\n"
+ "C/I level in tenth of dB\n")
+{
+ struct gsm_bts *bts = vty->index;
+
+ bts->min_qual_norm = strtof(argv[0], NULL) / 10.0f;
+
+ 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")
+{
+ struct gsm_bts *bts = vty->index;
+
+ bts->max_ber10k_rach = strtoul(argv[0], NULL, 10);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_pcu_sock, cfg_bts_pcu_sock_cmd,
+ "pcu-socket PATH",
+ "Configure the PCU socket file/path name\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]);
+ /* FIXME: re-open the interface? */
+
+ 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")
+{
+ struct gsm_bts *bts = vty->index;
+
+ bts->supp_meas_toa256 = true;
+ 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")
+{
+ struct gsm_bts *bts = vty->index;
+
+ bts->supp_meas_toa256 = false;
+ return CMD_SUCCESS;
+}
+
+
+#define DB_DBM_STR \
+ "Unit is dB (decibels)\n" \
+ "Unit is mdB (milli-decibels, or rather 1/10000 bel)\n"
+
+static int parse_mdbm(const char *valstr, const char *unit)
+{
+ int val = atoi(valstr);
+
+ if (!strcmp(unit, "dB") || !strcmp(unit, "dBm"))
+ return val * 1000;
+ else
+ 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)
+{
+ struct gsm_bts_trx *trx = vty->index;
+
+ trx->power_params.user_gain_mdB = parse_mdbm(argv[0], argv[1]);
+
+ return CMD_SUCCESS;
+}
+
+#define PR_STR "Power-Ramp settings"
+DEFUN(cfg_trx_pr_max_initial, cfg_trx_pr_max_initial_cmd,
+ "power-ramp max-initial <0-100000> (dBm|mdBm)",
+ PR_STR "Maximum initial power\n"
+ "Value\n" DB_DBM_STR)
+{
+ struct gsm_bts_trx *trx = vty->index;
+
+ trx->power_params.ramp.max_initial_pout_mdBm =
+ parse_mdbm(argv[0], argv[1]);
+
+ return CMD_SUCCESS;
+}
+
+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)
+{
+ struct gsm_bts_trx *trx = vty->index;
+
+ trx->power_params.ramp.step_size_mdB =
+ parse_mdbm(argv[0], argv[1]);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_trx_pr_step_interval, cfg_trx_pr_step_interval_cmd,
+ "power-ramp step-interval <1-100>",
+ PR_STR "Power increase by step\n"
+ "Step time in seconds\n")
+{
+ struct gsm_bts_trx *trx = vty->index;
+
+ trx->power_params.ramp.step_interval_sec = atoi(argv[0]);
+ return CMD_SUCCESS;
+}
+
+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"
+ "Handled by DSP\n" "Handled by OsmoBTS\n")
+{
+ struct gsm_bts_trx *trx = vty->index;
+
+ trx->ms_power_control = argv[0][0] == 'd' ? 0 : 1;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_trx_phy, cfg_trx_phy_cmd,
+ "phy <0-255> instance <0-255>",
+ "Configure PHY Link+Instance for this TRX\n"
+ "PHY Link number\n" "PHY instance\n" "PHY Instance number")
+{
+ struct gsm_bts_trx *trx = vty->index;
+ struct phy_link *plink = phy_link_by_num(atoi(argv[0]));
+ struct phy_instance *pinst;
+
+ if (!plink) {
+ 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",
+ argv[0], argv[1], VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ trx->role_bts.l1h = pinst;
+ pinst->trx = trx;
+
+ return CMD_SUCCESS;
+}
+
+/* ======================================================================
+ * SHOW
+ * ======================================================================*/
+
+static void net_dump_nmstate(struct vty *vty, struct gsm_nm_state *nms)
+{
+ vty_out(vty,"Oper '%s', Admin '%s', Avail '%s'%s",
+ abis_nm_opstate_name(nms->operational),
+ get_value_string(abis_nm_adm_state_names, nms->administrative),
+ abis_nm_avail_name(nms->availability), VTY_NEWLINE);
+}
+
+static unsigned int llist_length(struct llist_head *list)
+{
+ unsigned int len = 0;
+ struct llist_head *pos;
+
+ llist_for_each(pos, list)
+ len++;
+
+ return len;
+}
+
+static void bts_dump_vty_features(struct vty *vty, struct gsm_bts *bts)
+{
+ unsigned int i;
+ bool no_features = true;
+ vty_out(vty, " Features:%s", VTY_NEWLINE);
+
+ for (i = 0; i < _NUM_BTS_FEAT; i++) {
+ if (gsm_bts_has_feature(bts, i)) {
+ vty_out(vty, " %03u ", i);
+ vty_out(vty, "%-40s%s", get_value_string(gsm_bts_features_descs, i), VTY_NEWLINE);
+ no_features = false;
+ }
+ }
+
+ if (no_features)
+ vty_out(vty, " (not available)%s", VTY_NEWLINE);
+}
+
+static void bts_dump_vty(struct vty *vty, struct gsm_bts *bts)
+{
+ struct gsm_bts_trx *trx;
+
+ vty_out(vty, "BTS %u is of %s type in band %s, has CI %u LAC %u, "
+ "BSIC %u and %u TRX%s",
+ bts->nr, "FIXME", gsm_band_name(bts->band),
+ bts->cell_identity,
+ bts->location_area_code, bts->bsic,
+ bts->num_trx, VTY_NEWLINE);
+ vty_out(vty, " Description: %s%s",
+ 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);
+ 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);
+ if (strnlen(bts->pcu_version, MAX_VERSION_LENGTH))
+ vty_out(vty, " PCU version %s connected%s",
+ bts->pcu_version, VTY_NEWLINE);
+ vty_out(vty, " Paging: Queue size %u, occupied %u, lifetime %us%s",
+ paging_get_queue_max(bts->paging_state), paging_queue_length(bts->paging_state),
+ paging_get_lifetime(bts->paging_state), VTY_NEWLINE);
+ vty_out(vty, " AGCH: Queue limit %u, occupied %d, "
+ "dropped %"PRIu64", merged %"PRIu64", rejected %"PRIu64", "
+ "ag-res %"PRIu64", non-res %"PRIu64"%s",
+ bts->agch_queue.max_length, bts->agch_queue.length,
+ bts->agch_queue.dropped_msgs, bts->agch_queue.merged_msgs,
+ bts->agch_queue.rejected_msgs, bts->agch_queue.agch_msgs,
+ bts->agch_queue.pch_msgs,
+ VTY_NEWLINE);
+ vty_out(vty, " CBCH backlog queue length: %u%s",
+ llist_length(&bts->smscb_state.queue), VTY_NEWLINE);
+ vty_out(vty, " Paging: queue length %d, buffer space %d%s",
+ paging_queue_length(bts->paging_state), paging_buffer_space(bts->paging_state),
+ VTY_NEWLINE);
+ vty_out(vty, " OML Link state: %s.%s",
+ bts->oml_link ? "connected" : "disconnected", VTY_NEWLINE);
+
+ llist_for_each_entry(trx, &bts->trx_list, list) {
+ 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);
+ if (pinst->description)
+ vty_out(vty, " (%s)", pinst->description);
+ vty_out(vty, "%s", VTY_NEWLINE);
+ }
+ }
+
+ bts_dump_vty_features(vty, bts);
+ vty_out_rate_ctr_group(vty, " ", bts->ctrs);
+}
+
+
+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) {
+ vty_out(vty, "%% can't find BTS '%s'%s", argv[0],
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ bts_dump_vty(vty, gsm_bts_num(net, bts_nr));
+ return CMD_SUCCESS;
+ }
+ /* print all BTS's */
+ for (bts_nr = 0; bts_nr < net->num_bts; bts_nr++)
+ bts_dump_vty(vty, gsm_bts_num(net, bts_nr));
+
+ return CMD_SUCCESS;
+}
+
+static void trx_dump_vty(struct vty *vty, struct gsm_bts_trx *trx)
+{
+ vty_out(vty, "TRX %u of BTS %u is on ARFCN %u%s",
+ trx->nr, trx->bts->nr, trx->arfcn, VTY_NEWLINE);
+ vty_out(vty, "Description: %s%s",
+ trx->description ? trx->description : "(null)", VTY_NEWLINE);
+ vty_out(vty, " RF Nominal Power: %d dBm, reduced by %u dB, "
+ "resulting BS power: %d dBm%s",
+ trx->nominal_power, trx->max_power_red,
+ trx->nominal_power - trx->max_power_red, VTY_NEWLINE);
+ vty_out(vty, " NM State: ");
+ net_dump_nmstate(vty, &trx->mo.nm_state);
+ vty_out(vty, " RSL State: %s%s", trx->rsl_link? "connected" : "disconnected", VTY_NEWLINE);
+ vty_out(vty, " Baseband Transceiver NM State: ");
+ net_dump_nmstate(vty, &trx->bb_transc.mo.nm_state);
+ vty_out(vty, " IPA stream ID: 0x%02x%s", trx->rsl_tei, VTY_NEWLINE);
+}
+
+static inline void print_all_trx(struct vty *vty, const struct gsm_bts *bts)
+{
+ uint8_t trx_nr;
+ for (trx_nr = 0; trx_nr < bts->num_trx; trx_nr++)
+ trx_dump_vty(vty, gsm_bts_trx_num(bts, trx_nr));
+}
+
+DEFUN(show_trx,
+ show_trx_cmd,
+ "show trx [<0-255>] [<0-255>]",
+ SHOW_STR "Display information about a TRX\n"
+ BTS_TRX_STR)
+{
+ struct gsm_network *net = gsmnet_from_vty(vty);
+ struct gsm_bts *bts = NULL;
+ int bts_nr, trx_nr;
+
+ if (argc >= 1) {
+ /* use the BTS number that the user has specified */
+ bts_nr = atoi(argv[0]);
+ if (bts_nr >= net->num_bts) {
+ vty_out(vty, "%% can't find BTS '%s'%s", argv[0],
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ bts = gsm_bts_num(net, bts_nr);
+ }
+ if (argc >= 2) {
+ trx_nr = atoi(argv[1]);
+ if (trx_nr >= bts->num_trx) {
+ vty_out(vty, "%% can't find TRX '%s'%s", argv[1],
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ trx_dump_vty(vty, gsm_bts_trx_num(bts, trx_nr));
+ return CMD_SUCCESS;
+ }
+ if (bts) {
+ /* print all TRX in this BTS */
+ print_all_trx(vty, bts);
+ return CMD_SUCCESS;
+ }
+
+ for (bts_nr = 0; bts_nr < net->num_bts; bts_nr++)
+ print_all_trx(vty, gsm_bts_num(net, bts_nr));
+
+ return CMD_SUCCESS;
+}
+
+
+static void ts_dump_vty(struct vty *vty, struct gsm_bts_trx_ts *ts)
+{
+ vty_out(vty, "BTS %u, TRX %u, Timeslot %u, phys cfg %s, TSC %u",
+ ts->trx->bts->nr, ts->trx->nr, ts->nr,
+ gsm_pchan_name(ts->pchan), gsm_ts_tsc(ts));
+ if (ts->pchan == GSM_PCHAN_TCH_F_PDCH)
+ vty_out(vty, " (%s mode)",
+ ts->flags & TS_F_PDCH_ACTIVE ? "PDCH" : "TCH/F");
+ vty_out(vty, "%s", VTY_NEWLINE);
+ vty_out(vty, " NM State: ");
+ net_dump_nmstate(vty, &ts->mo.nm_state);
+}
+
+DEFUN(show_ts,
+ show_ts_cmd,
+ "show timeslot [<0-255>] [<0-255>] [<0-7>]",
+ SHOW_STR "Display information about a TS\n"
+ BTS_TRX_TS_STR)
+{
+ struct gsm_network *net = gsmnet_from_vty(vty);
+ struct gsm_bts *bts = NULL;
+ struct gsm_bts_trx *trx = NULL;
+ struct gsm_bts_trx_ts *ts = NULL;
+ int bts_nr, trx_nr, ts_nr;
+
+ if (argc >= 1) {
+ /* use the BTS number that the user has specified */
+ bts_nr = atoi(argv[0]);
+ if (bts_nr >= net->num_bts) {
+ vty_out(vty, "%% can't find BTS '%s'%s", argv[0],
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ bts = gsm_bts_num(net, bts_nr);
+ }
+ if (argc >= 2) {
+ trx_nr = atoi(argv[1]);
+ if (trx_nr >= bts->num_trx) {
+ vty_out(vty, "%% can't find TRX '%s'%s", argv[1],
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ trx = gsm_bts_trx_num(bts, trx_nr);
+ }
+ if (argc >= 3) {
+ ts_nr = atoi(argv[2]);
+ if (ts_nr >= TRX_NR_TS) {
+ vty_out(vty, "%% can't find TS '%s'%s", argv[2],
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ /* Fully Specified: print and exit */
+ ts = &trx->ts[ts_nr];
+ ts_dump_vty(vty, ts);
+ return CMD_SUCCESS;
+ }
+
+ if (bts && trx) {
+ /* Iterate over all TS in this TRX */
+ for (ts_nr = 0; ts_nr < TRX_NR_TS; ts_nr++) {
+ ts = &trx->ts[ts_nr];
+ ts_dump_vty(vty, ts);
+ }
+ } else if (bts) {
+ /* Iterate over all TRX in this BTS, TS in each TRX */
+ for (trx_nr = 0; trx_nr < bts->num_trx; trx_nr++) {
+ trx = gsm_bts_trx_num(bts, trx_nr);
+ for (ts_nr = 0; ts_nr < TRX_NR_TS; ts_nr++) {
+ ts = &trx->ts[ts_nr];
+ ts_dump_vty(vty, ts);
+ }
+ }
+ } else {
+ /* Iterate over all BTS, TRX in each BTS, TS in each TRX */
+ for (bts_nr = 0; bts_nr < net->num_bts; bts_nr++) {
+ bts = gsm_bts_num(net, bts_nr);
+ for (trx_nr = 0; trx_nr < bts->num_trx; trx_nr++) {
+ trx = gsm_bts_trx_num(bts, trx_nr);
+ for (ts_nr = 0; ts_nr < TRX_NR_TS; ts_nr++) {
+ ts = &trx->ts[ts_nr];
+ ts_dump_vty(vty, ts);
+ }
+ }
+ }
+ }
+
+ return CMD_SUCCESS;
+}
+
+/* FIXME: move this to libosmogsm */
+static const struct value_string gsm48_cmode_names[] = {
+ { GSM48_CMODE_SIGN, "signalling" },
+ { GSM48_CMODE_SPEECH_V1, "FR or HR" },
+ { GSM48_CMODE_SPEECH_EFR, "EFR" },
+ { GSM48_CMODE_SPEECH_AMR, "AMR" },
+ { GSM48_CMODE_DATA_14k5, "CSD(14k5)" },
+ { GSM48_CMODE_DATA_12k0, "CSD(12k0)" },
+ { GSM48_CMODE_DATA_6k0, "CSD(6k0)" },
+ { GSM48_CMODE_DATA_3k6, "CSD(3k6)" },
+ { 0, NULL }
+};
+
+/* call vty_out() to print a string like " as TCH/H" for dynamic timeslots.
+ * Don't do anything if the ts is not dynamic. */
+static void vty_out_dyn_ts_status(struct vty *vty, struct gsm_bts_trx_ts *ts)
+{
+ switch (ts->pchan) {
+ case GSM_PCHAN_TCH_F_TCH_H_PDCH:
+ if (ts->dyn.pchan_is == ts->dyn.pchan_want)
+ vty_out(vty, " as %s",
+ gsm_pchan_name(ts->dyn.pchan_is));
+ else
+ vty_out(vty, " switching %s -> %s",
+ gsm_pchan_name(ts->dyn.pchan_is),
+ gsm_pchan_name(ts->dyn.pchan_want));
+ break;
+ case GSM_PCHAN_TCH_F_PDCH:
+ if ((ts->flags & TS_F_PDCH_PENDING_MASK) == 0)
+ vty_out(vty, " as %s",
+ (ts->flags & TS_F_PDCH_ACTIVE)? "PDCH"
+ : "TCH/F");
+ else
+ vty_out(vty, " switching %s -> %s",
+ (ts->flags & TS_F_PDCH_ACTIVE)? "PDCH"
+ : "TCH/F",
+ (ts->flags & TS_F_PDCH_ACT_PENDING)? "PDCH"
+ : "TCH/F");
+ break;
+ default:
+ /* no dyn ts */
+ break;
+ }
+}
+
+static void lchan_dump_full_vty(struct vty *vty, struct gsm_lchan *lchan)
+{
+ struct in_addr ia;
+
+ vty_out(vty, "BTS %u, TRX %u, Timeslot %u, Lchan %u: Type %s%s",
+ lchan->ts->trx->bts->nr, lchan->ts->trx->nr, lchan->ts->nr,
+ lchan->nr, gsm_lchant_name(lchan->type), VTY_NEWLINE);
+ /* show dyn TS details, if applicable */
+ switch (lchan->ts->pchan) {
+ case GSM_PCHAN_TCH_F_TCH_H_PDCH:
+ vty_out(vty, " Osmocom Dyn TS:");
+ vty_out_dyn_ts_status(vty, lchan->ts);
+ vty_out(vty, VTY_NEWLINE);
+ break;
+ case GSM_PCHAN_TCH_F_PDCH:
+ vty_out(vty, " IPACC Dyn PDCH TS:");
+ vty_out_dyn_ts_status(vty, lchan->ts);
+ vty_out(vty, VTY_NEWLINE);
+ break;
+ default:
+ /* no dyn ts */
+ break;
+ }
+ vty_out(vty, " State: %s%s%s%s",
+ gsm_lchans_name(lchan->state),
+ lchan->state == LCHAN_S_BROKEN ? " Error reason: " : "",
+ lchan->state == LCHAN_S_BROKEN ? lchan->broken_reason : "",
+ VTY_NEWLINE);
+ vty_out(vty, " BS Power: %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),
+ 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",
+ inet_ntoa(ia), lchan->abis_ip.bound_port,
+ lchan->abis_ip.rtp_payload2, lchan->abis_ip.conn_id,
+ VTY_NEWLINE);
+ }
+ if (lchan->abis_ip.connect_ip) {
+ ia.s_addr = htonl(lchan->abis_ip.connect_ip);
+ vty_out(vty, " Conn. IP: %s Port %u RTP_TYPE=%u SPEECH_MODE=0x%02u%s",
+ inet_ntoa(ia), lchan->abis_ip.connect_port,
+ lchan->abis_ip.rtp_payload, lchan->abis_ip.speech_mode,
+ VTY_NEWLINE);
+ }
+#define LAPDM_ESTABLISHED(link, sapi_idx) \
+ (link).datalink[sapi_idx].dl.state == LAPD_STATE_MF_EST
+ vty_out(vty, " LAPDm SAPIs: DCCH %c%c, SACCH %c%c%s",
+ LAPDM_ESTABLISHED(lchan->lapdm_ch.lapdm_dcch, DL_SAPI0) ? '0' : '-',
+ LAPDM_ESTABLISHED(lchan->lapdm_ch.lapdm_dcch, DL_SAPI3) ? '3' : '-',
+ LAPDM_ESTABLISHED(lchan->lapdm_ch.lapdm_acch, DL_SAPI0) ? '0' : '-',
+ LAPDM_ESTABLISHED(lchan->lapdm_ch.lapdm_acch, DL_SAPI3) ? '3' : '-',
+ VTY_NEWLINE);
+#undef LAPDM_ESTABLISHED
+ vty_out(vty, " Valid System Information: 0x%08x%s",
+ lchan->si.valid, VTY_NEWLINE);
+ /* TODO: L1 SAPI (but those are BTS speific :( */
+ /* TODO: AMR bits */
+ vty_out(vty, " MS Timing Offset: %d, propagation delay: %d symbols %s",
+ lchan->ms_t_offs, lchan->p_offs, VTY_NEWLINE);
+ if (lchan->encr.alg_id) {
+ vty_out(vty, " Ciphering A5/%u State: %s, N(S)=%u%s",
+ lchan->encr.alg_id-1, lchan_ciph_state_name(lchan->ciph_state),
+ lchan->ciph_ns, VTY_NEWLINE);
+ }
+ 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 */
+}
+
+static void lchan_dump_short_vty(struct vty *vty, 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",
+ lchan->ts->trx->bts->nr, lchan->ts->trx->nr, lchan->ts->nr,
+ gsm_pchan_name(lchan->ts->pchan));
+ vty_out_dyn_ts_status(vty, lchan->ts);
+ vty_out(vty, ", Lchan %u, Type %s, State %s - "
+ "RXL-FULL-ul: %4d dBm%s",
+ lchan->nr,
+ gsm_lchant_name(lchan->type), gsm_lchans_name(lchan->state),
+ rxlev2dbm(mru->full.rx_lev),
+ VTY_NEWLINE);
+}
+
+static int dump_lchan_trx_ts(struct gsm_bts_trx_ts *ts, struct vty *vty,
+ void (*dump_cb)(struct vty *, struct gsm_lchan *))
+{
+ int lchan_nr;
+ for (lchan_nr = 0; lchan_nr < TS_MAX_LCHAN; lchan_nr++) {
+ struct gsm_lchan *lchan = &ts->lchan[lchan_nr];
+ if (lchan->state == LCHAN_S_NONE)
+ continue;
+ dump_cb(vty, lchan);
+ }
+
+ return CMD_SUCCESS;
+}
+
+static int dump_lchan_trx(struct gsm_bts_trx *trx, struct vty *vty,
+ void (*dump_cb)(struct vty *, struct gsm_lchan *))
+{
+ int ts_nr;
+
+ for (ts_nr = 0; ts_nr < TRX_NR_TS; ts_nr++) {
+ struct gsm_bts_trx_ts *ts = &trx->ts[ts_nr];
+ dump_lchan_trx_ts(ts, vty, dump_cb);
+ }
+
+ return CMD_SUCCESS;
+}
+
+static int dump_lchan_bts(struct gsm_bts *bts, struct vty *vty,
+ void (*dump_cb)(struct vty *, struct gsm_lchan *))
+{
+ int trx_nr;
+
+ for (trx_nr = 0; trx_nr < bts->num_trx; trx_nr++) {
+ struct gsm_bts_trx *trx = gsm_bts_trx_num(bts, trx_nr);
+ dump_lchan_trx(trx, vty, dump_cb);
+ }
+
+ return CMD_SUCCESS;
+}
+
+static int lchan_summary(struct vty *vty, int argc, const char **argv,
+ void (*dump_cb)(struct vty *, struct gsm_lchan *))
+{
+ struct gsm_network *net = gsmnet_from_vty(vty);
+ struct gsm_bts *bts;
+ struct gsm_bts_trx *trx;
+ struct gsm_bts_trx_ts *ts;
+ struct gsm_lchan *lchan;
+ int bts_nr, trx_nr, ts_nr, lchan_nr;
+
+ if (argc >= 1) {
+ /* use the BTS number that the user has specified */
+ bts_nr = atoi(argv[0]);
+ if (bts_nr >= net->num_bts) {
+ vty_out(vty, "%% can't find BTS %s%s", argv[0],
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ bts = gsm_bts_num(net, bts_nr);
+
+ if (argc == 1)
+ return dump_lchan_bts(bts, vty, dump_cb);
+ }
+ if (argc >= 2) {
+ trx_nr = atoi(argv[1]);
+ if (trx_nr >= bts->num_trx) {
+ vty_out(vty, "%% can't find TRX %s%s", argv[1],
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ trx = gsm_bts_trx_num(bts, trx_nr);
+
+ if (argc == 2)
+ return dump_lchan_trx(trx, vty, dump_cb);
+ }
+ if (argc >= 3) {
+ ts_nr = atoi(argv[2]);
+ if (ts_nr >= TRX_NR_TS) {
+ vty_out(vty, "%% can't find TS %s%s", argv[2],
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ ts = &trx->ts[ts_nr];
+
+ if (argc == 3)
+ return dump_lchan_trx_ts(ts, vty, dump_cb);
+ }
+ if (argc >= 4) {
+ lchan_nr = atoi(argv[3]);
+ if (lchan_nr >= TS_MAX_LCHAN) {
+ vty_out(vty, "%% can't find LCHAN %s%s", argv[3],
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ lchan = &ts->lchan[lchan_nr];
+ dump_cb(vty, lchan);
+ return CMD_SUCCESS;
+ }
+
+ for (bts_nr = 0; bts_nr < net->num_bts; bts_nr++) {
+ bts = gsm_bts_num(net, bts_nr);
+ dump_lchan_bts(bts, vty, dump_cb);
+ }
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(show_lchan,
+ show_lchan_cmd,
+ "show lchan [<0-255>] [<0-255>] [<0-7>] [<0-7>]",
+ SHOW_STR "Display information about a logical channel\n"
+ BTS_TRX_TS_LCHAN_STR)
+{
+ return lchan_summary(vty, argc, argv, lchan_dump_full_vty);
+}
+
+DEFUN(show_lchan_summary,
+ show_lchan_summary_cmd,
+ "show lchan summary [<0-255>] [<0-255>] [<0-7>] [<0-7>]",
+ SHOW_STR "Display information about a logical channel\n"
+ "Short summary\n"
+ BTS_TRX_TS_LCHAN_STR)
+{
+ return lchan_summary(vty, argc, argv, lchan_dump_short_vty);
+}
+
+static struct gsm_lchan *resolve_lchan(struct gsm_network *net,
+ 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]);
+ struct gsm_bts *bts;
+ struct gsm_bts_trx *trx;
+ struct gsm_bts_trx_ts *ts;
+
+ bts = gsm_bts_num(net, bts_nr);
+ if (!bts)
+ return NULL;
+
+ trx = gsm_bts_trx_num(bts, trx_nr);
+ if (!trx)
+ return NULL;
+
+ if (ts_nr >= ARRAY_SIZE(trx->ts))
+ return NULL;
+ ts = &trx->ts[ts_nr];
+
+ if (lchan_nr >= ARRAY_SIZE(ts->lchan))
+ return NULL;
+
+ return &ts->lchan[lchan_nr];
+}
+
+#define BTS_T_T_L_STR \
+ "BTS related commands\n" \
+ "BTS number\n" \
+ "TRX related commands\n" \
+ "TRX number\n" \
+ "timeslot related commands\n" \
+ "timeslot number\n" \
+ "logical channel commands\n" \
+ "logical channel number\n"
+
+DEFUN(cfg_trx_gsmtap_sapi, cfg_trx_gsmtap_sapi_cmd,
+ "HIDDEN", "HIDDEN")
+{
+ int sapi;
+
+ sapi = get_string_value(gsmtap_sapi_names, argv[0]);
+ OSMO_ASSERT(sapi >= 0);
+
+ if (sapi == GSMTAP_CHANNEL_ACCH)
+ gsmtap_sapi_acch = 1;
+ else
+ gsmtap_sapi_mask |= (1 << sapi);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_trx_no_gsmtap_sapi, cfg_trx_no_gsmtap_sapi_cmd,
+ "HIDDEN", "HIDDEN")
+{
+ int sapi;
+
+ sapi = get_string_value(gsmtap_sapi_names, argv[0]);
+ OSMO_ASSERT(sapi >= 0);
+
+ if (sapi == GSMTAP_CHANNEL_ACCH)
+ gsmtap_sapi_acch = 0;
+ else
+ gsmtap_sapi_mask &= ~(1 << sapi);
+
+ return CMD_SUCCESS;
+}
+
+static struct cmd_node phy_node = {
+ PHY_NODE,
+ "%s(phy)# ",
+ 1,
+};
+
+static struct cmd_node phy_inst_node = {
+ PHY_INST_NODE,
+ "%s(phy-inst)# ",
+ 1,
+};
+
+DEFUN(cfg_phy, cfg_phy_cmd,
+ "phy <0-255>",
+ "Select a PHY to configure\n" "PHY number\n")
+{
+ int phy_nr = atoi(argv[0]);
+ struct phy_link *plink;
+
+ plink = phy_link_by_num(phy_nr);
+ if (!plink)
+ plink = phy_link_create(tall_bts_ctx, phy_nr);
+ if (!plink)
+ return CMD_WARNING;
+
+ vty->index = plink;
+ vty->index_sub = &plink->description;
+ vty->node = PHY_NODE;
+
+ 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")
+{
+ int inst_nr = atoi(argv[0]);
+ struct phy_link *plink = vty->index;
+ struct phy_instance *pinst;
+
+ pinst = phy_instance_by_num(plink, inst_nr);
+ if (!pinst) {
+ pinst = phy_instance_create(plink, inst_nr);
+ if (!pinst) {
+ vty_out(vty, "Unable to create phy%u instance %u%s",
+ plink->num, inst_nr, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ }
+
+ vty->index = pinst;
+ vty->index_sub = &pinst->description;
+ vty->node = PHY_INST_NODE;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_phy_no_inst, cfg_phy_no_inst_cmd,
+ "no instance <0-255>"
+ NO_STR "Select a PHY instance to remove\n", "PHY Instance number\n")
+{
+ int inst_nr = atoi(argv[0]);
+ struct phy_link *plink = vty->index;
+ struct phy_instance *pinst;
+
+ pinst = phy_instance_by_num(plink, inst_nr);
+ if (!pinst) {
+ vty_out(vty, "No such instance %u%s", inst_nr, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ phy_instance_destroy(pinst);
+
+ return CMD_SUCCESS;
+}
+
+#if 0
+DEFUN(cfg_phy_type, cfg_phy_type_cmd,
+ "type (sysmobts|osmo-trx|virtual)",
+ "configure the type of the PHY\n"
+ "sysmocom sysmoBTS PHY\n"
+ "OsmoTRX based PHY\n"
+ "Virtual PHY (GSMTAP based)\n")
+{
+ struct phy_link *plink = vty->index;
+
+ if (plink->state != PHY_LINK_SHUTDOWN) {
+ vty_out(vty, "Cannot change type of active PHY%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (!strcmp(argv[0], "sysmobts"))
+ plink->type = PHY_LINK_T_SYSMOBTS;
+ else if (!strcmp(argv[0], "osmo-trx"))
+ plink->type = PHY_LINK_T_OSMOTRX;
+ else if (!strcmp(argv[0], "virtual"))
+ plink->type = PHY_LINK_T_VIRTUAL;
+}
+#endif
+
+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_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);
+ if (!lchan) {
+ vty_out(vty, "%% can't find BTS%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ if (!lchan->abis_ip.rtp_socket) {
+ vty_out(vty, "%% this channel has no active RTP stream%s",
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ rc = osmo_rtp_socket_set_param(lchan->abis_ip.rtp_socket,
+ lchan->ts->trx->bts->rtp_jitter_adaptive ?
+ OSMO_RTP_P_JIT_ADAP : OSMO_RTP_P_JITBUF,
+ jitbuf_ms);
+ if (rc < 0)
+ vty_out(vty, "%% error setting jitter parameters: %s%s",
+ strerror(-rc), VTY_NEWLINE);
+ else
+ vty_out(vty, "%% jitter parameters set: %d%s", rc, VTY_NEWLINE);
+
+ 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")
+{
+ struct gsm_network *net = gsmnet_from_vty(vty);
+ struct gsm_lchan *lchan;
+
+ lchan = resolve_lchan(net, argv, 0);
+ if (!lchan) {
+ vty_out(vty, "%% can't find BTS%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ lchan->loopback = 1;
+
+ 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")
+{
+ struct gsm_network *net = gsmnet_from_vty(vty);
+ struct gsm_lchan *lchan;
+
+ lchan = resolve_lchan(net, argv, 0);
+ if (!lchan) {
+ vty_out(vty, "%% can't find BTS%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ lchan->loopback = 0;
+
+ return CMD_SUCCESS;
+}
+
+int bts_vty_init(struct gsm_bts *bts, const struct log_info *cat)
+{
+ cfg_trx_gsmtap_sapi_cmd.string = vty_cmd_string_from_valstr(bts, 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",
+ "\n", "", 0);
+
+ cfg_trx_no_gsmtap_sapi_cmd.string = vty_cmd_string_from_valstr(bts, 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",
+ "\n", "", 0);
+
+ install_element_ve(&show_bts_cmd);
+ install_element_ve(&show_trx_cmd);
+ install_element_ve(&show_ts_cmd);
+ install_element_ve(&show_lchan_cmd);
+ install_element_ve(&show_lchan_summary_cmd);
+
+ logging_vty_add_cmds(cat);
+ osmo_talloc_vty_add_cmds();
+
+ install_node(&bts_node, config_write_bts);
+ install_element(CONFIG_NODE, &cfg_bts_cmd);
+ install_element(CONFIG_NODE, &cfg_vty_telnet_port_cmd);
+ install_element(BTS_NODE, &cfg_bts_unit_id_cmd);
+ install_element(BTS_NODE, &cfg_bts_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_band_cmd);
+ install_element(BTS_NODE, &cfg_description_cmd);
+ install_element(BTS_NODE, &cfg_no_description_cmd);
+ install_element(BTS_NODE, &cfg_bts_paging_queue_size_cmd);
+ install_element(BTS_NODE, &cfg_bts_paging_lifetime_cmd);
+ 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_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_supp_meas_toa256_cmd);
+ install_element(BTS_NODE, &cfg_bts_no_supp_meas_toa256_cmd);
+
+ install_element(BTS_NODE, &cfg_trx_gsmtap_sapi_cmd);
+ install_element(BTS_NODE, &cfg_trx_no_gsmtap_sapi_cmd);
+
+ /* add and link to TRX config node */
+ install_element(BTS_NODE, &cfg_bts_trx_cmd);
+ install_node(&trx_node, config_write_dummy);
+
+ install_element(TRX_NODE, &cfg_trx_user_gain_cmd);
+ install_element(TRX_NODE, &cfg_trx_pr_max_initial_cmd);
+ 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_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(CONFIG_NODE, &cfg_phy_cmd);
+ install_node(&phy_node, config_write_phy);
+ install_element(PHY_NODE, &cfg_phy_inst_cmd);
+ install_element(PHY_NODE, &cfg_phy_no_inst_cmd);
+
+ install_node(&phy_inst_node, config_write_dummy);
+
+ return 0;
+}