diff options
Diffstat (limited to 'src/common')
-rw-r--r-- | src/common/Makefile.am | 17 | ||||
-rw-r--r-- | src/common/abis.c | 295 | ||||
-rw-r--r-- | src/common/amr.c | 170 | ||||
-rw-r--r-- | src/common/bts.c | 759 | ||||
-rw-r--r-- | src/common/bts_ctrl_commands.c | 93 | ||||
-rw-r--r-- | src/common/bts_ctrl_lookup.c | 115 | ||||
-rw-r--r-- | src/common/cbch.c | 198 | ||||
-rw-r--r-- | src/common/dtx_dl_amr_fsm.c | 477 | ||||
-rw-r--r-- | src/common/gsm_data_shared.c | 807 | ||||
-rw-r--r-- | src/common/handover.c | 164 | ||||
-rw-r--r-- | src/common/l1sap.c | 1549 | ||||
-rw-r--r-- | src/common/lchan.c | 49 | ||||
-rw-r--r-- | src/common/load_indication.c | 94 | ||||
-rw-r--r-- | src/common/logging.c | 150 | ||||
-rw-r--r-- | src/common/main.c | 358 | ||||
-rw-r--r-- | src/common/measurement.c | 723 | ||||
-rw-r--r-- | src/common/msg_utils.c | 603 | ||||
-rw-r--r-- | src/common/oml.c | 1495 | ||||
-rw-r--r-- | src/common/paging.c | 630 | ||||
-rw-r--r-- | src/common/pcu_sock.c | 982 | ||||
-rw-r--r-- | src/common/phy_link.c | 163 | ||||
-rw-r--r-- | src/common/power_control.c | 89 | ||||
-rw-r--r-- | src/common/rsl.c | 2996 | ||||
-rw-r--r-- | src/common/scheduler.c | 1025 | ||||
-rw-r--r-- | src/common/scheduler_mframe.c | 1040 | ||||
-rw-r--r-- | src/common/sysinfo.c | 177 | ||||
-rw-r--r-- | src/common/tx_power.c | 306 | ||||
-rw-r--r-- | src/common/vty.c | 1651 |
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(¶m, 0, sizeof(param)); + param.sched_priority = rt_prio; + rc = sched_setscheduler(getpid(), SCHED_RR, ¶m); + 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; +} |