From d41b7c7f830e90c7c4ce1d8ed97f13cfd3ed8cad Mon Sep 17 00:00:00 2001 From: Harald Welte Date: Thu, 13 Jun 2019 09:41:58 +0200 Subject: Cell Broadcast: CBSP and CBCH scheduling support This adds code to handle CBSP (Cell Broadcast Service Protocol) from the CBC (Cell Broadcast Centre), as well as BSC-internal data structures for scheduling the various SMSCB on the CBCH of each BTS. There are currently one known shortcoming in the code: We don't yet verify if keepalives are received within repetition period. Change-Id: Ia0a0de862a104d0f447a5d6e56c7c83981b825c7 --- doc/manuals/chapters/smscb.adoc | 82 ++++ doc/manuals/osmobsc-usermanual.adoc | 4 +- doc/manuals/vty/bsc_vty_reference.xml | 79 +++- include/osmocom/bsc/Makefile.am | 1 + include/osmocom/bsc/bsc_msc_data.h | 3 + include/osmocom/bsc/debug.h | 1 + include/osmocom/bsc/gsm_data.h | 53 +++ include/osmocom/bsc/smscb.h | 59 +++ include/osmocom/bsc/vty.h | 1 + src/osmo-bsc/Makefile.am | 4 + src/osmo-bsc/abis_rsl.c | 35 +- src/osmo-bsc/bsc_init.c | 15 + src/osmo-bsc/bsc_vty.c | 14 + src/osmo-bsc/cbch_scheduler.c | 287 ++++++++++++ src/osmo-bsc/cbsp_link.c | 417 +++++++++++++++++ src/osmo-bsc/gsm_data.c | 9 + src/osmo-bsc/osmo_bsc_main.c | 13 + src/osmo-bsc/smscb.c | 823 ++++++++++++++++++++++++++++++++++ tests/handover/Makefile.am | 5 + 19 files changed, 1898 insertions(+), 7 deletions(-) create mode 100644 doc/manuals/chapters/smscb.adoc create mode 100644 include/osmocom/bsc/smscb.h create mode 100644 src/osmo-bsc/cbch_scheduler.c create mode 100644 src/osmo-bsc/cbsp_link.c create mode 100644 src/osmo-bsc/smscb.c diff --git a/doc/manuals/chapters/smscb.adoc b/doc/manuals/chapters/smscb.adoc new file mode 100644 index 000000000..f7469a375 --- /dev/null +++ b/doc/manuals/chapters/smscb.adoc @@ -0,0 +1,82 @@ +[[smscb]] +== SMSCB (Cell Broadcast) + +OsmoBSC supports SMS Cell Broadcast (SMSCB) services (CBS). This +includes the CBSP protocol to interact with a CBC (Cell Broadcast +Centre) such as OsmoCBC, as well as the scheduling of SMSCB messages on +both the BASIC and EXTENDED CBCH and transmission of related RSL +messages to the attached BTS. + +More high-level information can be found at +https://en.wikipedia.org/wiki/Cell_Broadcast and the related +specification is <<3gpp-ts-23-041>>. + +In order to use SMSCB with OsmoBSC, you will need to + +* Configure the CBSP server and/or client +* Use a channel combination including a CBCH on the BTSs + +=== Enabling a CBCH channel combination + +On the Um interface, SMSCB are transmitted via the CBCH (Cell Broadcast +Channel). The CBCH is a separate downlink-only logical channel which +must be activated on any of the BTSs requiring CBSP support. + +The channel combination is configured in the `timeslot` node of each TRX. + +The two `phys_chan_config` supporting CBCH are `CCCH+SDCCH4+CBCH` and +`SDCCH/8+CBCH`. Please note that the CBCH steals one of the SDCCH, so +a SDCCH/4 will only have three remaining SDCCH, and a SDCCH/8 will +have only seven remaining SDCCH. + +=== Configuring the CBSP connection + +CBSP is the protocol between BSC and CBC. It operates over TCP. + +According to 3GPP TS 48.049, a BSC typically operates as a TCP server, +and the CBC connects as TCP client. This would require the CBC to have +out-of-band knowledge of all the BSCs in the network (and their IP +addresses). + +In order to comply with the specifications, OsmoBSC supports this mode +of operation as CBSP TCP server. However, to make network operation and +configuration more simple, it also can operate in TCP client mode, +connecting to the CBC. This way the all the BSCs need to know is the CBC IP +address, but not vice-versa. + +The BSC can operate both CBSP TCP server and CBSP TCP client mode in +parallel. + +The CBC related configuration of OsmoBSC can be found in the `cbc` configuration +node of the VTY interface. + +.Example: Configure CBSP TCP client to connect to CBC at 1.2.3.4:48049 +---- +OsmoBSC> enable +OsmoBSC# configure terminal +OsmoBSC(config)# cbc +OsmoBSC(config-cbc)# remote-ip 1.2.3.4 +OsmoBSC(config-cbc)# remote-port 48049 +OsmoBSC(config-cbc)# end +---- + +.Example: Disable CBSP TCP client +---- +OsmoBSC> enable +OsmoBSC# configure terminal +OsmoBSC(config)# cbc +OsmoBSC(config-cbc)# no remote-ip +OsmoBSC(config-cbc)# end +---- + +.Example: Configure CBSP TCP server to listen for CBC at 127.0.0.2:9999 +---- +OsmoBSC> enable +OsmoBSC# configure terminal +OsmoBSC(config)# cbc +OsmoBSC(config-cbc)# listen-ip 127.0.0.2 +OsmoBSC(config-cbc)# listen-port 9999 +OsmoBSC(config-cbc)# end +---- + +For more details on the available configuration commands, please check the OsmoBSC VTY Reference. diff --git a/doc/manuals/osmobsc-usermanual.adoc b/doc/manuals/osmobsc-usermanual.adoc index 766a11f40..26f49da03 100644 --- a/doc/manuals/osmobsc-usermanual.adoc +++ b/doc/manuals/osmobsc-usermanual.adoc @@ -24,6 +24,8 @@ include::./common/chapters/bsc.adoc[] include::{srcdir}/chapters/handover.adoc[] +include::{srcdir}/chapters/smscb.adoc[] + include::./common/chapters/counters-overview.adoc[] include::{srcdir}/chapters/counters.adoc[] @@ -34,8 +36,6 @@ include::./common/chapters/control_if.adoc[] include::{srcdir}/chapters/control.adoc[] -include::./common/chapters/cell-broadcast.adoc[] - include::{srcdir}/chapters/osmux_bsc.adoc[] include::./common/chapters/port_numbers.adoc[] diff --git a/doc/manuals/vty/bsc_vty_reference.xml b/doc/manuals/vty/bsc_vty_reference.xml index 85c0cb7e7..6a3e4fb57 100644 --- a/doc/manuals/vty/bsc_vty_reference.xml +++ b/doc/manuals/vty/bsc_vty_reference.xml @@ -320,7 +320,7 @@ - + @@ -343,6 +343,7 @@ + @@ -514,6 +515,22 @@ + + + + + + + + + + + + + + + + @@ -971,7 +988,7 @@ - + @@ -994,6 +1011,7 @@ + @@ -1356,6 +1374,16 @@ + + + + + + + + + + @@ -1735,6 +1763,11 @@ + + + + + @@ -1833,7 +1866,7 @@ - + @@ -1856,6 +1889,7 @@ + @@ -4930,4 +4964,43 @@ + + config-cbc + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/include/osmocom/bsc/Makefile.am b/include/osmocom/bsc/Makefile.am index f44e7fc86..396604eb1 100644 --- a/include/osmocom/bsc/Makefile.am +++ b/include/osmocom/bsc/Makefile.am @@ -55,4 +55,5 @@ noinst_HEADERS = \ gsm_08_08.h \ penalty_timers.h \ osmo_bsc_lcls.h \ + smscb.h \ $(NULL) diff --git a/include/osmocom/bsc/bsc_msc_data.h b/include/osmocom/bsc/bsc_msc_data.h index 56124830c..b9df4ba8f 100644 --- a/include/osmocom/bsc/bsc_msc_data.h +++ b/include/osmocom/bsc/bsc_msc_data.h @@ -150,6 +150,7 @@ struct bsc_msc_data { /* * Per BSC data. */ +struct bsc_cbc_link; struct osmo_bsc_data { struct gsm_network *network; @@ -167,6 +168,8 @@ struct osmo_bsc_data { char *ussd_no_msc_txt; char *acc_lst_name; + + struct bsc_cbc_link *cbc; }; diff --git a/include/osmocom/bsc/debug.h b/include/osmocom/bsc/debug.h index 326012185..adc6abbe1 100644 --- a/include/osmocom/bsc/debug.h +++ b/include/osmocom/bsc/debug.h @@ -27,6 +27,7 @@ enum { DCHAN, DTS, DAS, + DCBS, Debug_LastEntry, }; diff --git a/include/osmocom/bsc/gsm_data.h b/include/osmocom/bsc/gsm_data.h index d82d1bac8..8dfbc6425 100644 --- a/include/osmocom/bsc/gsm_data.h +++ b/include/osmocom/bsc/gsm_data.h @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -957,6 +958,53 @@ struct gsm_bts_ref { struct gsm_bts *bts; }; +/* A single Page of a SMSCB message */ +struct bts_smscb_page { + /* SMSCB message we're part of */ + struct bts_smscb_message *msg; + /* Page Number within message (1 to 15) */ + uint8_t nr; + /* number of valid blocks in data (up to 4) */ + uint8_t num_blocks; + /* up to four blocks of 22 bytes each */ + uint8_t data[88]; +}; + +/* A SMSCB message (received from CBSP) */ +struct bts_smscb_message { + /* entry in bts_smscb_chan_state.messages */ + struct llist_head list; + struct { + /* input data from CBSP (CBC) side */ + uint16_t msg_id; + uint16_t serial_nr; + enum cbsp_category category; + uint16_t rep_period; + uint16_t num_bcast_req; + uint8_t dcs; + } input; + /* how often have all pages of this message been broadcast? */ + uint32_t bcast_count; + /* actual page data of this message */ + uint8_t num_pages; /* up to 15 */ + struct bts_smscb_page page[15]; +}; + +/* per-channel (basic/extended) CBCH state for a single BTS */ +struct bts_smscb_chan_state { + /* back-pointer to BTS */ + struct gsm_bts *bts; + /* list of bts_smscb_message */ + struct llist_head messages; + /* scheduling array; pointer of SMSCB pages */ + struct bts_smscb_page **sched_arr; + size_t sched_arr_size; + /* index of the next to be transmitted page into the scheduler array */ + size_t next_idx; + /* number of messages we have to pause due to overflow */ + uint8_t overflow; +}; + /* One BTS */ struct gsm_bts { /* list header in net->bts_list */ @@ -1213,6 +1261,11 @@ struct gsm_bts { struct load_counter chan_load_samples[7]; int chan_load_samples_idx; uint8_t chan_load_avg; /* current channel load average in percent (0 - 100). */ + + /* cell broadcast system */ + struct osmo_timer_list cbch_timer; + struct bts_smscb_chan_state cbch_basic; + struct bts_smscb_chan_state cbch_extended; }; /* One rejected BTS */ diff --git a/include/osmocom/bsc/smscb.h b/include/osmocom/bsc/smscb.h new file mode 100644 index 000000000..22a258da9 --- /dev/null +++ b/include/osmocom/bsc/smscb.h @@ -0,0 +1,59 @@ +#pragma once +#include + +#include +#include +#include + +struct bsc_cbc_link; + +/* smscb.c */ +void bts_smscb_del(struct bts_smscb_message *smscb, struct bts_smscb_chan_state *cstate, + const char *reason); +const char *bts_smscb_msg2str(const struct bts_smscb_message *smscb); +struct bts_smscb_chan_state *bts_get_smscb_chan(struct gsm_bts *bts, bool extended); +int cbsp_rx_decoded(struct bsc_cbc_link *cbc, const struct osmo_cbsp_decoded *dec); +int cbsp_tx_restart(struct bsc_cbc_link *cbc, bool is_emerg); +const char *bts_smscb_chan_state_name(const struct bts_smscb_chan_state *cstate); +unsigned int bts_smscb_chan_load_percent(const struct bts_smscb_chan_state *cstate); +unsigned int bts_smscb_chan_page_count(const struct bts_smscb_chan_state *cstate); +void smscb_vty_init(void); + +/* cbch_scheduler.c */ +int bts_smscb_gen_sched_arr(struct bts_smscb_chan_state *cstate, struct bts_smscb_page ***arr_out); +struct bts_smscb_page *bts_smscb_pull_page(struct bts_smscb_chan_state *cstate); +void bts_smscb_page_done(struct bts_smscb_chan_state *cstate, struct bts_smscb_page *page); +int bts_smscb_rx_cbch_load_ind(struct gsm_bts *bts, bool cbch_extended, bool is_overflow, + uint8_t slot_count); +void bts_cbch_timer_schedule(struct gsm_bts *bts); + +/* cbsp_link.c */ +struct bsc_cbc_link { + struct gsm_network *net; + struct { + /* hostname/IP of CBC */ + char *cbc_hostname; + /* TCP port (Default: 48049) of CBC */ + int cbc_port; + /* local listening port (0 for disabling local server) */ + int listen_port; + /* local listening hostname/IP */ + char *listen_hostname; + } config; + /* for handling inbound TCP connections */ + struct { + struct osmo_stream_srv *srv; + struct osmo_stream_srv_link *link; + char *sock_name; + struct msgb *msg; + } server; + /* for handling outbound TCP connections */ + struct { + struct osmo_stream_cli *cli; + char *sock_name; + struct msgb *msg; + } client; +}; +void cbc_vty_init(void); +int bsc_cbc_link_restart(void); +int cbsp_tx_decoded(struct bsc_cbc_link *cbc, struct osmo_cbsp_decoded *decoded); diff --git a/include/osmocom/bsc/vty.h b/include/osmocom/bsc/vty.h index 7e3c5053c..10ce16b2d 100644 --- a/include/osmocom/bsc/vty.h +++ b/include/osmocom/bsc/vty.h @@ -24,6 +24,7 @@ enum bsc_vty_node { OM2K_NODE, OM2K_CON_GROUP_NODE, BSC_NODE, + CBC_NODE, }; struct log_info; diff --git a/src/osmo-bsc/Makefile.am b/src/osmo-bsc/Makefile.am index d50515b0f..51d887579 100644 --- a/src/osmo-bsc/Makefile.am +++ b/src/osmo-bsc/Makefile.am @@ -88,6 +88,9 @@ osmo_bsc_SOURCES = \ rest_octets.c \ system_information.c \ timeslot_fsm.c \ + smscb.c \ + cbch_scheduler.c \ + cbsp_link.c \ $(NULL) osmo_bsc_LDADD = \ @@ -96,6 +99,7 @@ osmo_bsc_LDADD = \ $(LIBOSMOGSM_LIBS) \ $(LIBOSMOVTY_LIBS) \ $(LIBOSMOCTRL_LIBS) \ + $(LIBOSMONETIF_LIBS) \ $(COVERAGE_LDFLAGS) \ $(LIBOSMOABIS_LIBS) \ $(LIBOSMOSIGTRAN_LIBS) \ diff --git a/src/osmo-bsc/abis_rsl.c b/src/osmo-bsc/abis_rsl.c index 0b68d7c53..06d19a5d5 100644 --- a/src/osmo-bsc/abis_rsl.c +++ b/src/osmo-bsc/abis_rsl.c @@ -1,7 +1,7 @@ /* GSM Radio Signalling Link messages on the A-bis interface * 3GPP TS 08.58 version 8.6.0 Release 1999 / ETSI TS 100 596 V8.6.0 */ -/* (C) 2008-2010 by Harald Welte +/* (C) 2008-2019 by Harald Welte * (C) 2012 by Holger Hans Peter Freyther * * All Rights Reserved @@ -52,6 +52,7 @@ #include #include #include +#include #define RSL_ALLOC_SIZE 1024 #define RSL_ALLOC_HEADROOM 128 @@ -1489,6 +1490,36 @@ static int rsl_rx_ccch_load(struct msgb *msg) return 0; } +/* 8.5.9 current load on the CBCH (Cell Broadcast) */ +static int rsl_rx_cbch_load(struct msgb *msg) +{ + struct e1inp_sign_link *sign_link = msg->dst; + struct abis_rsl_dchan_hdr *rslh = msgb_l2(msg); + struct gsm_bts *bts = sign_link->trx->bts; + bool cbch_extended = false; + bool is_overflow = false; + int8_t load_info; + struct tlv_parsed tp; + uint8_t slot_count; + + rsl_tlv_parse(&tp, rslh->data, msgb_l2len(msg) - sizeof(*rslh)); + if (!TLVP_PRESENT(&tp, RSL_IE_CBCH_LOAD_INFO)) { + LOG_BTS(bts, DRSL, LOGL_ERROR, "CBCH LOAD IND without mandatory CBCH Load Info IE\n"); + return -1; + } + /* 9.4.43 */ + load_info = *TLVP_VAL(&tp, RSL_IE_CBCH_LOAD_INFO); + if (load_info & 0x80) + is_overflow = true; + slot_count = load_info & 0x0F; + + if (TLVP_PRES_LEN(&tp, RSL_IE_SMSCB_CHAN_INDICATOR, 1) && + (*TLVP_VAL(&tp, RSL_IE_SMSCB_CHAN_INDICATOR) & 0x0F) == 0x01) + cbch_extended = true; + + return bts_smscb_rx_cbch_load_ind(bts, cbch_extended, is_overflow, slot_count); +} + /* Ericsson specific: Immediate Assign Sent */ static int rsl_rx_ericsson_imm_assign_sent(struct msgb *msg) { @@ -1537,7 +1568,7 @@ static int abis_rsl_rx_cchan(struct msgb *msg) break; case RSL_MT_CBCH_LOAD_IND: /* current load on the CBCH */ - /* FIXME: handle this. Ignore for now */ + rc = rsl_rx_cbch_load(msg); break; case RSL_MT_ERICSSON_IMM_ASS_SENT: rc = rsl_rx_ericsson_imm_assign_sent(msg); diff --git a/src/osmo-bsc/bsc_init.c b/src/osmo-bsc/bsc_init.c index 7d29d4fbe..18776f33d 100644 --- a/src/osmo-bsc/bsc_init.c +++ b/src/osmo-bsc/bsc_init.c @@ -37,6 +37,9 @@ #include #include +#include +#include + #include #include #include @@ -244,6 +247,11 @@ static struct gsm_network *bsc_network_init(void *ctx) talloc_free(net); return NULL; } + net->bsc_data->cbc = talloc_zero(net->bsc_data, struct bsc_cbc_link); + if (!net->bsc_data->cbc) { + talloc_free(net); + return NULL; + } /* Init back pointer */ net->bsc_data->auto_off_timeout = -1; @@ -272,6 +280,13 @@ static struct gsm_network *bsc_network_init(void *ctx) osmo_timer_setup(&net->t3122_chan_load_timer, update_t3122_chan_load_timer, net); osmo_timer_schedule(&net->t3122_chan_load_timer, T3122_CHAN_LOAD_SAMPLE_INTERVAL, 0); + net->bsc_data->cbc->net = net; + /* no cbc_hostname: client not started by default */ + net->bsc_data->cbc->config.cbc_port = CBSP_TCP_PORT; + /* listen_port == -1: server not started by default */ + net->bsc_data->cbc->config.listen_port = -1; + net->bsc_data->cbc->config.listen_hostname = talloc_strdup(net->bsc_data->cbc, "127.0.0.1"); + return net; } diff --git a/src/osmo-bsc/bsc_vty.c b/src/osmo-bsc/bsc_vty.c index 6de2d4b4d..06c06de7c 100644 --- a/src/osmo-bsc/bsc_vty.c +++ b/src/osmo-bsc/bsc_vty.c @@ -70,6 +70,7 @@ #include #include #include +#include #include #include @@ -318,6 +319,14 @@ static void vty_out_neigh_list(struct vty *vty, struct bitvec *bv) vty_out(vty, " (%d)", count); } +static void bts_dump_vty_cbch(struct vty *vty, const struct bts_smscb_chan_state *cstate) +{ + vty_out(vty, " CBCH %s: %u messages, %u pages, %lu-entry sched_arr, %u%% load%s", + bts_smscb_chan_state_name(cstate), llist_count(&cstate->messages), + bts_smscb_chan_page_count(cstate), cstate->sched_arr_size, + bts_smscb_chan_load_percent(cstate), VTY_NEWLINE); +} + static void bts_dump_vty_features(struct vty *vty, struct gsm_bts *bts) { unsigned int i; @@ -504,6 +513,9 @@ static void bts_dump_vty(struct vty *vty, struct gsm_bts *bts) vty_out(vty, " Current Channel Load:%s", VTY_NEWLINE); dump_pchan_load_vty(vty, " ", &pl); + bts_dump_vty_cbch(vty, &bts->cbch_basic); + bts_dump_vty_cbch(vty, &bts->cbch_extended); + vty_out(vty, " Channel Requests : %"PRIu64" total, %"PRIu64" no channel%s", bts->bts_ctrs->ctr[BTS_CTR_CHREQ_TOTAL].current, bts->bts_ctrs->ctr[BTS_CTR_CHREQ_NO_CHANNEL].current, @@ -5427,6 +5439,8 @@ int bsc_vty_init(struct gsm_network *network) osmo_fsm_vty_add_cmds(); ho_vty_init(); + cbc_vty_init(); + smscb_vty_init(); bsc_vty_init_extra(); diff --git a/src/osmo-bsc/cbch_scheduler.c b/src/osmo-bsc/cbch_scheduler.c new file mode 100644 index 000000000..ef93b7a4a --- /dev/null +++ b/src/osmo-bsc/cbch_scheduler.c @@ -0,0 +1,287 @@ +/* CBCH (Cell Broadcast Channel) Scheduler for OsmoBSC */ +/* + * (C) 2019 by Harald Welte + * + * 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 . + * + */ + +#include +#include +#include +#include + +#include +#include +#include +#include + +/* add all pages of given SMSCB so they appear as soon as possible *after* (included) base_idx. */ +static int bts_smscb_sched_add_after(struct bts_smscb_page **sched_arr, int sched_arr_size, + int base_idx, struct bts_smscb_message *smscb) +{ + int arr_idx = base_idx; + int i; + + OSMO_ASSERT(smscb->num_pages <= ARRAY_SIZE(smscb->page)); + for (i = 0; i < smscb->num_pages; i++) { + while (sched_arr[arr_idx]) { + arr_idx++; + if (arr_idx >= sched_arr_size) + return -ENOSPC; + } + sched_arr[arr_idx] = &smscb->page[i]; + } + return arr_idx; +} + +/* add all pages of given smscb so they appear *before* (included) last_idx. */ +static int bts_smscb_sched_add_before(struct bts_smscb_page **sched_arr, int sched_arr_size, + int last_idx, struct bts_smscb_message *smscb) +{ + int arr_idx = last_idx; + int last_used_idx = 0; + int i; + + OSMO_ASSERT(smscb->num_pages <= ARRAY_SIZE(smscb->page)); + OSMO_ASSERT(smscb->num_pages >= 1); + + for (i = smscb->num_pages - 1; i >= 0; i--) { + while (sched_arr[arr_idx]) { + arr_idx--; + if (arr_idx < 0) + return -ENOSPC; + } + sched_arr[arr_idx] = &smscb->page[i]; + if (i == smscb->num_pages) + last_used_idx = i; + } + return last_used_idx; +} + +/* obtain the least frequently scheduled SMSCB for given SMSCB channel */ +static struct bts_smscb_message * +bts_smscb_chan_get_least_frequent_smscb(struct bts_smscb_chan_state *cstate) +{ + if (llist_empty(&cstate->messages)) + return NULL; + /* messages are expected to be ordered with increasing period, so we're + * able to return the last message in the list */ + return llist_entry(cstate->messages.prev, struct bts_smscb_message, list); +} + +/*! Generate per-BTS SMSCB scheduling array + * \param[in] cstate BTS CBCH channel state + * \param[out] arr_out return argument for allocated + generated scheduling array + * \return size of returned scheduling array arr_out in number of entries; negative on error */ +int bts_smscb_gen_sched_arr(struct bts_smscb_chan_state *cstate, struct bts_smscb_page ***arr_out) +{ + struct bts_smscb_message *smscb, *least_freq; + struct bts_smscb_page **arr; + int arr_size; + int rc; + + /* start with one instance of the least frequent message at position 0, as we + * need to transmit it exactly once during the duration of the scheduling array */ + least_freq = bts_smscb_chan_get_least_frequent_smscb(cstate); + if (!least_freq) { + LOG_BTS(cstate->bts, DCBS, LOGL_DEBUG, "No SMSCB; cannot create schedule array\n"); + *arr_out = NULL; + return 0; + } + arr_size = least_freq->input.rep_period; + arr = talloc_zero_array(cstate->bts, struct bts_smscb_page *, arr_size); + OSMO_ASSERT(arr); + rc = bts_smscb_sched_add_after(arr, arr_size, 0, least_freq); + if (rc < 0) { + LOG_BTS(cstate->bts, DCBS, LOGL_ERROR, "Unable to schedule first instance of " + "very first SMSCB %s ?!?\n", bts_smscb_msg2str(least_freq)); + talloc_free(arr); + return rc; + } + + /* continue filling with repetitions of the more frequent messages, starting from + * the most frequent message to the least frequent one, repeating them as needed + * throughout the duration of the array */ + llist_for_each_entry(smscb, &cstate->messages, list) { + int last_page; + if (smscb == least_freq) + continue; + /* messages are expected to be ordered with increasing period, so we're + * starting with the most frequent / shortest period first */ + rc = bts_smscb_sched_add_after(arr, arr_size, 0, smscb); + if (rc < 0) { + LOG_BTS(cstate->bts, DCBS, LOGL_ERROR, "Unable to schedule first instance of " + "SMSCB %s\n", bts_smscb_msg2str(smscb)); + talloc_free(arr); + return rc; + } + last_page = rc; + + while (last_page < cstate->sched_arr_size) { + /* store further instances in a way that the last block of the N+1th instance + * happens no later than "interval" after the last block of the Nth instance */ + rc = bts_smscb_sched_add_before(arr, arr_size, + last_page + smscb->input.rep_period, smscb); + if (rc < 0) { + LOG_BTS(cstate->bts, DCBS, LOGL_ERROR, "Unable to schedule further " + "SMSCB %s\n", bts_smscb_msg2str(smscb)); + talloc_free(arr); + return rc; + } + last_page = rc; + } + } + *arr_out = arr; + return arr_size; +} + +/*! Pull the next to-be-transmitted SMSCB page out of the scheduler for the given channel */ +struct bts_smscb_page *bts_smscb_pull_page(struct bts_smscb_chan_state *cstate) +{ + struct bts_smscb_page *page; + + /* if there are no messages to schedule, there is no array */ + if (!cstate->sched_arr) + return NULL; + + /* obtain the page from the scheduler array */ + page = cstate->sched_arr[cstate->next_idx]; + + /* increment the index for the next call to this function */ + cstate->next_idx = (cstate->next_idx + 1) % cstate->sched_arr_size; + + /* the array can have gaps in between where there is nothing scheduled */ + if (!page) + return NULL; + + return page; +} + +/*! To be called after bts_smscb_pull_page() in order to update transmission count and + * check if SMSCB is complete. + * \param[in] cstate BTS CBC channel state + * \param[in] page SMSCB Page which had been returned by bts_smscb_pull_page() and which + * is no longer needed now */ +void bts_smscb_page_done(struct bts_smscb_chan_state *cstate, struct bts_smscb_page *page) +{ + struct bts_smscb_message *smscb = page->msg; + + /* If this is the last page of a SMSCB, increment the SMSCB number-of-xmit counter */ + if (page->nr == smscb->num_pages) { + smscb->bcast_count++; + /* Check if the SMSCB transmission duration is now over */ + if (smscb->bcast_count >= smscb->input.num_bcast_req) + bts_smscb_del(smscb, cstate, "COMPLETE"); + } +} + + +/*********************************************************************** + * BTS / RSL side + ***********************************************************************/ + +static void bts_cbch_send_one(struct bts_smscb_chan_state *cstate) +{ + struct bts_smscb_page *page; + struct gsm_bts *bts = cstate->bts; + struct rsl_ie_cb_cmd_type cb_cmd; + bool is_extended = false; + + if (cstate == &bts->cbch_extended) + is_extended = true; + + if (cstate->overflow) { + LOG_BTS(bts, DCBS, LOGL_DEBUG, "Skipping SMSCB due to overflow (%u)\n", + cstate->overflow); + cstate->overflow--; + return; + } + + page = bts_smscb_pull_page(cstate); + if (!page) { + LOG_BTS(bts, DCBS, LOGL_DEBUG, "Skipping SMSCB: No page available\n"); + return; + } + + cb_cmd.spare = 0; + cb_cmd.def_bcast = 0; + cb_cmd.command = RSL_CB_CMD_TYPE_NORMAL; + switch (page->num_blocks) { + case 1: + cb_cmd.last_block = RSL_CB_CMD_LASTBLOCK_1; + break; + case 2: + cb_cmd.last_block = RSL_CB_CMD_LASTBLOCK_2; + break; + case 3: + cb_cmd.last_block = RSL_CB_CMD_LASTBLOCK_3; + break; + case 4: + cb_cmd.last_block = RSL_CB_CMD_LASTBLOCK_4; + break; + default: + osmo_panic("SMSCB Page must have 1..4 blocks, not %d\n", page->num_blocks); + } + rsl_sms_cb_command(bts, RSL_CHAN_SDCCH4_ACCH, cb_cmd, is_extended, + page->data, sizeof(page->data)); + + bts_smscb_page_done(cstate, page); +} + +static void bts_cbch_timer(void *data) +{ + struct gsm_bts *bts = (struct gsm_bts *)data; + + bts_cbch_send_one(&bts->cbch_basic); + bts_cbch_send_one(&bts->cbch_extended); + + bts_cbch_timer_schedule(bts); +} + +/* There is one SMSCB message (page) per eight 51-multiframes, i.e. 1.882 seconds */ +void bts_cbch_timer_schedule(struct gsm_bts *bts) +{ + osmo_timer_setup(&bts->cbch_timer, &bts_cbch_timer, bts); + osmo_timer_schedule(&bts->cbch_timer, 1, 882920); +} + +/*! Receive a (decoded) incoming CBCH LOAD IND from given bts. See TS 48.058 8.5.9 + * \param[in] bts The BTS for which the load indication was received + * \param[in] cbch_extended Is this report for extended (true) or basic CBCH + * \param[in] is_overflow Is this report and overflow (true) or underflow report + * \param[in] slot_count amount of SMSCB messages needed / delay needed */ +int bts_smscb_rx_cbch_load_ind(struct gsm_bts *bts, bool cbch_extended, bool is_overflow, + uint8_t slot_count) +{ + struct bts_smscb_chan_state *cstate = bts_get_smscb_chan(bts, cbch_extended); + int i; + + if (!gsm_bts_get_cbch(bts)) + return -ENODEV; + + if (is_overflow) { + /* halt/delay transmission of further CBCH messages */ + cstate->overflow = slot_count; + } else { + for (i = 0; i < slot_count; i++) + bts_cbch_send_one(cstate); + /* re-schedule the timer to count from now on */ + bts_cbch_timer_schedule(bts); + } + + return 0; +} diff --git a/src/osmo-bsc/cbsp_link.c b/src/osmo-bsc/cbsp_link.c new file mode 100644 index 000000000..8840afa52 --- /dev/null +++ b/src/osmo-bsc/cbsp_link.c @@ -0,0 +1,417 @@ +/* CBSP (Cell Broadcast Service Protocol) Handling for OsmoBSC */ +/* + * (C) 2019 by Harald Welte + * + * 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 . + * + */ + + +#include +#include +#include +#include +#include + +#include +#include +#include + +/* if a CBC IP/port has been configured, we continuously try to re-establish the TCP + * connection (as a client) to the CBC. If none has been configured, and we have a listen + * TCP port, we expect the CBC to connect to us. If neither of the two is configured, + * CBSP is effectively disabled */ + +/********************************************************************************* + * CBSP Server (inbound TCP connection from CBC) + *********************************************************************************/ + +static int cbsp_srv_closed_cb(struct osmo_stream_srv *conn) +{ + struct bsc_cbc_link *cbc = osmo_stream_srv_get_data(conn); + //struct osmo_fd *ofd = osmo_stream_srv_get_ofd(conn); + + LOGP(DCBS, LOGL_NOTICE, "CBSP Server lost connection from %s\n", cbc->server.sock_name); + talloc_free(cbc->server.sock_name); + cbc->server.sock_name = NULL; + cbc->server.srv = NULL; + return 0; +} + +static int cbsp_srv_cb(struct osmo_stream_srv *conn) +{ + struct bsc_cbc_link *cbc = osmo_stream_srv_get_data(conn); + struct osmo_fd *ofd = osmo_stream_srv_get_ofd(conn); + struct osmo_cbsp_decoded *decoded; + struct msgb *msg; + int rc; + + /* READ */ + rc = osmo_cbsp_recv_buffered(cbc, ofd->fd, &msg, &cbc->server.msg); + if (rc <= 0) { + if (rc == -EAGAIN || rc == -EINTR) { + /* more data needs to be read */ + return 0; + } else if (rc == -EPIPE || rc == -ECONNRESET) { + /* lost connection */ + } else if (rc == 0) { + /* connection closed */ + } + osmo_stream_srv_destroy(conn); + cbc->server.srv = NULL; + return -EBADF; + } + OSMO_ASSERT(msg); + decoded = osmo_cbsp_decode(conn, msg); + if (decoded) { + LOGP(DCBS, LOGL_DEBUG, "Received CBSP %s\n", + get_value_string(cbsp_msg_type_names, decoded->msg_type)); + cbsp_rx_decoded(cbc, decoded); + talloc_free(decoded); + } else { + LOGP(DCBS, LOGL_ERROR, "Unable to decode CBSP %s: '%s'\n", + msgb_hexdump(msg), osmo_cbsp_errstr); + } + msgb_free(msg); + return 0; + +} + +static int cbsp_srv_link_accept_cb(struct osmo_stream_srv_link *link, int fd) +{ + struct bsc_cbc_link *cbc = osmo_stream_srv_link_get_data(link); + struct osmo_stream_srv *srv; + + LOGP(DCBS, LOGL_INFO, "CBSP Server received inbound connection from CBC: %s\n", + osmo_sock_get_name2(fd)); + + if (cbc->server.srv) { + LOGP(DCBS, LOGL_NOTICE, "CBSP Server refusing further connection (%s) " + "while we already have another connection (%s)\n", + osmo_sock_get_name2(fd), cbc->server.sock_name); + return -1; + } + + srv = osmo_stream_srv_create(cbc, link, fd, cbsp_srv_cb, cbsp_srv_closed_cb, cbc); + if (!srv) { + LOGP(DCBS, LOGL_ERROR, "Unable to create stream server for %s\n", + osmo_sock_get_name2(fd)); + return -1; + } + + cbc->server.srv = srv; + if (cbc->server.sock_name) + talloc_free(cbc->server.sock_name); + cbc->server.sock_name = osmo_sock_get_name(cbc, fd); + LOGP(DCBS, LOGL_NOTICE, "CBSP Server link established from CBC %s\n", cbc->server.sock_name); + /* TODO: introduce ourselves to the peer using some osmcoom extensions */ + cbsp_tx_restart(cbc, false); + return 0; +} + +/********************************************************************************* + * CBSP Client (outbound TCP connection to CBC) + *********************************************************************************/ + +static int cbsp_client_connect_cb(struct osmo_stream_cli *cli) +{ + struct bsc_cbc_link *cbc = osmo_stream_cli_get_data(cli); + struct osmo_fd *ofd = osmo_stream_cli_get_ofd(cli); + + if (cbc->client.sock_name) + talloc_free(cbc->client.sock_name); + cbc->client.sock_name = osmo_sock_get_name(cbc, ofd->fd); + + LOGP(DCBS, LOGL_NOTICE, "CBSP Client connected to CBC: %s\n", cbc->client.sock_name); + + /* TODO: introduce ourselves to the peer using some osmcoom extensions */ + cbsp_tx_restart(cbc, false); + + return 0; +} + +static int cbsp_client_disconnect_cb(struct osmo_stream_cli *cli) +{ + struct bsc_cbc_link *cbc = osmo_stream_cli_get_data(cli); + + LOGP(DCBS, LOGL_NOTICE, "CBSP Client lost connection to %s\n", cbc->client.sock_name); + talloc_free(cbc->client.sock_name); + cbc->client.sock_name = NULL; + return 0; +} + +static int cbsp_client_read_cb(struct osmo_stream_cli *cli) +{ + struct bsc_cbc_link *cbc = osmo_stream_cli_get_data(cli); + struct osmo_fd *ofd = osmo_stream_cli_get_ofd(cli); + struct osmo_cbsp_decoded *decoded; + struct msgb *msg = NULL; + int rc; + + /* READ */ + rc = osmo_cbsp_recv_buffered(cbc, ofd->fd, &msg, &cbc->client.msg); + if (rc <= 0) { + if (rc == -EAGAIN || rc == -EINTR) { + /* more data needs to be read */ + return 0; + } else if (rc == -EPIPE || rc == -ECONNRESET) { + /* lost connection */ + } else if (rc == 0) { + /* connection closed */ + } + osmo_stream_cli_reconnect(cli); + return -EBADF; + } + OSMO_ASSERT(msg); + decoded = osmo_cbsp_decode(cli, msg); + if (decoded) { + LOGP(DCBS, LOGL_DEBUG, "Received CBSP %s\n", + get_value_string(cbsp_msg_type_names, decoded->msg_type)); + cbsp_rx_decoded(cbc, decoded); + talloc_free(decoded); + } else { + LOGP(DCBS, LOGL_ERROR, "Unable to decode CBSP %s: '%s'\n", + msgb_hexdump(msg), osmo_cbsp_errstr); + } + msgb_free(msg); + return 0; +} + +int bsc_cbc_link_restart(void) +{ + struct bsc_cbc_link *cbc = bsc_gsmnet->bsc_data->cbc; + + /* shut down client, if no longer configured */ + if (cbc->client.cli && !cbc->config.cbc_hostname) { + LOGP(DCBS, LOGL_NOTICE, "Stopping CBSP client\n"); + osmo_stream_cli_close(cbc->client.cli); + osmo_stream_cli_destroy(cbc->client.cli); + cbc->client.cli = NULL; + } + + /* shut down server, if no longer configured */ + if (cbc->config.listen_port == -1) { + if (cbc->server.srv || cbc->server.link) + LOGP(DCBS, LOGL_NOTICE, "Stopping CBSP server\n"); + if (cbc->server.srv) { + osmo_stream_srv_destroy(cbc->server.srv); + cbc->server.srv = NULL; + } + if (cbc->server.link) { + osmo_stream_srv_link_close(cbc->server.link); + osmo_stream_srv_link_destroy(cbc->server.link); + cbc->server.link = NULL; + } + } + + /* start client, if configured */ + if (cbc->config.cbc_hostname) { + LOGP(DCBS, LOGL_NOTICE, "Starting CBSP Client (to CBC at %s:%u)\n", + cbc->config.cbc_hostname, cbc->config.cbc_port); + if (!cbc->client.cli) { + cbc->client.cli = osmo_stream_cli_create(cbc); + osmo_stream_cli_set_data(cbc->client.cli, cbc); + osmo_stream_cli_set_connect_cb(cbc->client.cli, cbsp_client_connect_cb); + osmo_stream_cli_set_disconnect_cb(cbc->client.cli, cbsp_client_disconnect_cb); + osmo_stream_cli_set_read_cb(cbc->client.cli, cbsp_client_read_cb); + } + /* CBC side */ + osmo_stream_cli_set_addr(cbc->client.cli, cbc->config.cbc_hostname); + osmo_stream_cli_set_port(cbc->client.cli, cbc->config.cbc_port); + /* Close/Reconnect? */ + osmo_stream_cli_open(cbc->client.cli); + } + + /* start server, if configured */ + if (cbc->config.listen_port != -1) { + LOGP(DCBS, LOGL_NOTICE, "Starting CBSP Server (bound to %s:%u)\n", + cbc->config.listen_hostname, cbc->config.listen_port); + if (!cbc->server.srv) { + cbc->server.link = osmo_stream_srv_link_create(cbc); + osmo_stream_srv_link_set_data(cbc->server.link, cbc); + osmo_stream_srv_link_set_accept_cb(cbc->server.link, cbsp_srv_link_accept_cb); + } + osmo_stream_srv_link_set_addr(cbc->server.link, cbc->config.listen_hostname); + osmo_stream_srv_link_set_port(cbc->server.link, cbc->config.listen_port); + } + return 0; +} + +/*! Encode + Transmit a 'decoded' CBSP message over given CBC link + * \param[in] cbc Data structure representing the BSCs link to the CBC + * \param[in] cbsp Decoded CBSP message to be transmitted. Ownership is transferred. + * \return 0 on success, negative otherwise */ +int cbsp_tx_decoded(struct bsc_cbc_link *cbc, struct osmo_cbsp_decoded *cbsp) +{ + struct msgb *msg; + + msg = osmo_cbsp_encode(cbc, cbsp); + if (!msg) { + LOGP(DCBS, LOGL_ERROR, "Unable to encode CBSP Message Type %s: %s\n", + get_value_string(cbsp_msg_type_names, cbsp->msg_type), osmo_cbsp_errstr); + talloc_free(cbsp); + return -1; + } + if (cbc->client.cli) + osmo_stream_cli_send(cbc->client.cli, msg); + else if (cbc->server.srv) + osmo_stream_srv_send(cbc->server.srv, msg); + else { + LOGP(DCBS, LOGL_ERROR, "Discarding CBSP Message, link is down: %s\n", msgb_hexdump(msg)); + msgb_free(msg); + } + + talloc_free(cbsp); + return 0; +} + +static struct bsc_cbc_link *vty_cbc_data(struct vty *vty) +{ + return bsc_gsmnet->bsc_data->cbc; +} + +/********************************************************************************* + * VTY Interface (Configuration + Introspection) + *********************************************************************************/ + +DEFUN(cfg_cbc, cfg_cbc_cmd, + "cbc", "Configure CBSP Link to Cell Broadcast Centre\n") +{ + vty->node = CBC_NODE; + return CMD_SUCCESS; +} + +DEFUN(cfg_cbc_remote_ip, cfg_cbc_remote_ip_cmd, + "remote-ip A.B.C.D", + "IP Address of the Cell Broadcast Centre\n" + "IP Address of the Cell Broadcast Centre\n") +{ + struct bsc_cbc_link *cbc = vty_cbc_data(vty); + osmo_talloc_replace_string(cbc, &cbc->config.cbc_hostname, argv[0]); + return CMD_SUCCESS; +} +DEFUN(cfg_cbc_no_remote_ip, cfg_cbc_no_remote_ip_cmd, + "no remote-ip", + NO_STR "Remove IP address of CBC; disables outbound CBSP connections\n") +{ + struct bsc_cbc_link *cbc = vty_cbc_data(vty); + talloc_free(cbc->config.cbc_hostname); + cbc->config.cbc_hostname = NULL; + return CMD_SUCCESS; +} + +DEFUN(cfg_cbc_remote_port, cfg_cbc_remote_port_cmd, + "remote-port <1-65535>", + "TCP Port number of the Cell Broadcast Centre (Default: 48049)\n" + "TCP Port number of the Cell Broadcast Centre (Default: 48049)\n") +{ + struct bsc_cbc_link *cbc = vty_cbc_data(vty); + cbc->config.cbc_port = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_cbc_listen_port, cfg_cbc_listen_port_cmd, + "listen-port <1-65535>", + "Local TCP port at which BSC listens for incoming CBSP connections from CBC\n" + "Local TCP port at which BSC listens for incoming CBSP connections from CBC\n") +{ + struct bsc_cbc_link *cbc = vty_cbc_data(vty); + cbc->config.listen_port = atoi(argv[0]); + return CMD_SUCCESS; +} +DEFUN(cfg_cbc_no_listen_port, cfg_cbc_no_listen_port_cmd, + "no listen-port", + NO_STR "Remove CBSP Listen Port; disables inbound CBSP connections\n") +{ + struct bsc_cbc_link *cbc = vty_cbc_data(vty); + cbc->config.listen_port = -1; + return CMD_SUCCESS; +} + +DEFUN(cfg_cbc_listen_ip, cfg_cbc_listen_ip_cmd, + "listen-ip A.B.C.D", + "Local IP Address where BSC listens for incoming CBC connections (Default: 0.0.0.0)\n" + "Local IP Address where BSC listens for incoming CBC connections\n") +{ + struct bsc_cbc_link *cbc = vty_cbc_data(vty); + osmo_talloc_replace_string(cbc, &cbc->config.listen_hostname, argv[0]); + return CMD_SUCCESS; +} + +static struct cmd_node cbc_node = { + CBC_NODE, + "%s(config-cbc)# ", + 1, +}; + +static int config_write_cbc(struct vty *vty) +{ + struct bsc_cbc_link *cbc = vty_cbc_data(vty); + + vty_out(vty, "cbc%s", VTY_NEWLINE); + + if (cbc->config.cbc_hostname) { + vty_out(vty, " remote-ip %s%s", cbc->config.cbc_hostname, VTY_NEWLINE); + vty_out(vty, " remote-port %u%s", cbc->config.cbc_port, VTY_NEWLINE); + } else + vty_out(vty, " no remote-ip%s", VTY_NEWLINE); + + if (cbc->config.listen_port >= 0) { + vty_out(vty, " listen-port %u%s", cbc->config.listen_port, VTY_NEWLINE); + vty_out(vty, " listen-ip %s%s", cbc->config.listen_hostname, VTY_NEWLINE); + } else + vty_out(vty, " no listen-port%s", VTY_NEWLINE); + + return 0; +} + +DEFUN(show_cbc, show_cbc_cmd, + "show cbc", + SHOW_STR "Display state of CBC / CBSP\n") +{ + struct bsc_cbc_link *cbc = vty_cbc_data(vty); + + if (!cbc->config.cbc_hostname) + vty_out(vty, "CBSP Client Config: Disabled%s", VTY_NEWLINE); + else { + vty_out(vty, "CBSP Client Config: CBC IP=%s, CBC Port=%u%s", + cbc->config.cbc_hostname, cbc->config.cbc_port, VTY_NEWLINE); + vty_out(vty, "CBSP Client Connection: %s%s", + cbc->client.sock_name ? cbc->client.sock_name : "Disconnected", VTY_NEWLINE); + } + if (cbc->config.listen_port < 0) + vty_out(vty, "CBSP Server Config: Disabled%s\n", VTY_NEWLINE); + else { + vty_out(vty, "CBSP Server Config: Listen IP=%s, Port=%u%s\n", + cbc->config.listen_hostname, cbc->config.listen_port, VTY_NEWLINE); + vty_out(vty, "CBSP Server Connection: %s%s", + cbc->server.sock_name ? cbc->server.sock_name : "Disconnected", VTY_NEWLINE); + } + return CMD_SUCCESS; +} + +void cbc_vty_init(void) +{ + install_element(VIEW_NODE, &show_cbc_cmd); + install_element(CONFIG_NODE, &cfg_cbc_cmd); + install_node(&cbc_node, config_write_cbc); + install_element(CBC_NODE, &cfg_cbc_remote_ip_cmd); + install_element(CBC_NODE, &cfg_cbc_no_remote_ip_cmd); + install_element(CBC_NODE, &cfg_cbc_remote_port_cmd); + install_element(CBC_NODE, &cfg_cbc_listen_port_cmd); + install_element(CBC_NODE, &cfg_cbc_no_listen_port_cmd); + install_element(CBC_NODE, &cfg_cbc_listen_ip_cmd); +} diff --git a/src/osmo-bsc/gsm_data.c b/src/osmo-bsc/gsm_data.c index ea338173e..c2cfacf01 100644 --- a/src/osmo-bsc/gsm_data.c +++ b/src/osmo-bsc/gsm_data.c @@ -767,6 +767,12 @@ static const struct gprs_rlc_cfg rlc_cfg_default = { .initial_mcs = 6, }; +static void bts_init_cbch_state(struct bts_smscb_chan_state *cstate, struct gsm_bts *bts) +{ + cstate->bts = bts; + INIT_LLIST_HEAD(&cstate->messages); +} + /* Initialize those parts that don't require osmo-bsc specific dependencies. * This part is shared among the thin programs in osmo-bsc/src/utils/. * osmo-bsc requires further initialization that pulls in more dependencies (see @@ -945,6 +951,9 @@ struct gsm_bts *gsm_bts_alloc(struct gsm_network *net, uint8_t bts_num) } bts->mr_half.num_modes = 3; + bts_init_cbch_state(&bts->cbch_basic, bts); + bts_init_cbch_state(&bts->cbch_extended, bts); + return bts; } diff --git a/src/osmo-bsc/osmo_bsc_main.c b/src/osmo-bsc/osmo_bsc_main.c index aba8c40c7..dacd61a08 100644 --- a/src/osmo-bsc/osmo_bsc_main.c +++ b/src/osmo-bsc/osmo_bsc_main.c @@ -37,6 +37,7 @@ #include #include #include +#include #include #include @@ -310,6 +311,10 @@ static void bootstrap_rsl(struct gsm_bts_trx *trx) OSMO_ASSERT(ts->fi); osmo_fsm_inst_dispatch(ts->fi, TS_EV_RSL_READY, NULL); } + + /* Start CBCH transmit timer if CBCH is present */ + if (trx->nr == 0 && gsm_bts_get_cbch(trx->bts)) + bts_cbch_timer_schedule(trx->bts); } static void all_ts_dispatch_event(struct gsm_bts_trx *trx, uint32_t event) @@ -379,6 +384,8 @@ static int inp_sig_cb(unsigned int subsys, unsigned int signal, rate_ctr_inc(&trx->bts->bts_ctrs->ctr[BTS_CTR_BTS_RSL_FAIL]); acc_ramp_abort(&trx->bts->acc_ramp); all_ts_dispatch_event(trx, TS_EV_RSL_DOWN); + if (trx->nr == 0) + osmo_timer_del(&trx->bts->cbch_timer); } gsm_bts_mo_reset(trx->bts); @@ -764,6 +771,11 @@ static const struct log_info_cat osmo_bsc_categories[] = { .description = "Local Call, Local Switch", .enabled = 1, .loglevel = LOGL_NOTICE, }, + [DCBS] = { + .name = "DCBS", + .description = "Cell Broadcast System", + .enabled = 1, .loglevel = LOGL_NOTICE, + } }; @@ -912,6 +924,7 @@ int main(int argc, char **argv) handover_decision_1_init(); hodec2_init(bsc_gsmnet); + bsc_cbc_link_restart(); signal(SIGINT, &signal_handler); signal(SIGTERM, &signal_handler); diff --git a/src/osmo-bsc/smscb.c b/src/osmo-bsc/smscb.c new file mode 100644 index 000000000..6b9608652 --- /dev/null +++ b/src/osmo-bsc/smscb.c @@ -0,0 +1,823 @@ +/* SMSCB (SMS Cell Broadcast) Handling for OsmoBSC */ +/* + * (C) 2019 by Harald Welte + * + * 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 . + * + */ + +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include +#include +#include +#include + +/********************************************************************************* + * Helper Functions + *********************************************************************************/ + +/* replace the old head of an entire list with a new head; effectively moves the entire + * list from old to new head */ +static void llist_replace_head(struct llist_head *new, struct llist_head *old) +{ + if (llist_empty(old)) + INIT_LLIST_HEAD(new); + else + __llist_add(new, old->prev, old->next); + INIT_LLIST_HEAD(old); +} + +/*! Obtain SMSCB Channel State for given BTS (basic or extended CBCH) */ +struct bts_smscb_chan_state *bts_get_smscb_chan(struct gsm_bts *bts, bool extended) +{ + struct bts_smscb_chan_state *chan_state; + + if (extended) + chan_state = &bts->cbch_extended; + else + chan_state = &bts->cbch_basic; + + return chan_state; +} + +/* do an ordered list insertion. we keep the list with increasing period, i.e. the most + * frequent message first */ +static void __bts_smscb_add(struct bts_smscb_chan_state *cstate, struct bts_smscb_message *new) +{ + struct bts_smscb_message *tmp, *tmp2; + + if (llist_empty(&cstate->messages)) { + llist_add(&new->list, &cstate->messages); + return; + } + + llist_for_each_entry_safe(tmp, tmp2, &cstate->messages, list) { + if (tmp->input.rep_period > new->input.rep_period) { + /* we found the first message with longer period than the new message, + * we must insert ourselves before that one */ + __llist_add(&new->list, tmp->list.prev, &tmp->list); + return; + } + } +} + +/* stringify a SMSCB for logging */ +const char *bts_smscb_msg2str(const struct bts_smscb_message *smscb) +{ + static char buf[128]; + snprintf(buf, sizeof(buf), "MsgId=0x%04x/SerialNr=0x%04x/Pages=%u/Period=%u/NumBcastReq=%u", + smscb->input.msg_id, smscb->input.serial_nr, smscb->num_pages, + smscb->input.rep_period, smscb->input.num_bcast_req); + return buf; +} + +const char *bts_smscb_chan_state_name(const struct bts_smscb_chan_state *cstate) +{ + if (cstate == &cstate->bts->cbch_basic) + return "BASIC"; + else if (cstate == &cstate->bts->cbch_extended) + return "EXTENDED"; + else + return "UNKNOWN"; +} + +unsigned int bts_smscb_chan_load_percent(const struct bts_smscb_chan_state *cstate) +{ + unsigned int sched_arr_used = 0; + unsigned int i; + + if (cstate->sched_arr_size == 0) + return 0; + + /* count the number of used slots */ + for (i = 0; i < cstate->sched_arr_size; i++) { + if (cstate->sched_arr[i]) + sched_arr_used++; + } + + OSMO_ASSERT(sched_arr_used <= UINT_MAX/100); + return (sched_arr_used * 100) / cstate->sched_arr_size; +} + +unsigned int bts_smscb_chan_page_count(const struct bts_smscb_chan_state *cstate) +{ + struct bts_smscb_message *smscb; + unsigned int page_count = 0; + + llist_for_each_entry(smscb, &cstate->messages, list) + page_count += smscb->num_pages; + + return page_count; +} + + +/*! Obtain the Cell Global Identifier (CGI) of given BTS; returned in static buffer. */ +static struct osmo_cell_global_id *bts_get_cgi(struct gsm_bts *bts) +{ + static struct osmo_cell_global_id cgi; + cgi.lai.plmn = bts->network->plmn; + cgi.lai.lac = bts->location_area_code; + cgi.cell_identity = bts->cell_identity; + return &cgi; +} + +/* represents the various lists that the BSC can create as part of a response */ +struct response_state { + struct osmo_cbsp_cell_list success; /* osmo_cbsp_cell_ent */ + struct llist_head fail; /* osmo_cbsp_fail_ent */ + struct osmo_cbsp_num_compl_list num_completed; /* osmo_cbsp_num_compl_ent */ + struct osmo_cbsp_loading_list loading; /* osmo_cbsp_loading_ent */ +}; + +/*! per-BTS callback function used by cbsp_per_bts(). + * \param[in] bts BTS currently being processed + * \param[in] dec decoded CBSP message currently being processed + * \param r_state response state accumulating cell lists (success/failure/...) + * \param priv opaque private data provided by caller of cbsp_per_bts() + * \returns 0 on success; negative TS 48.049 cause value on error */ +typedef int bts_cb_fn(struct gsm_bts *bts, const struct osmo_cbsp_decoded *dec, + struct response_state *r_state, void *priv); + +/* append a success for given cell to response state */ +static void append_success(struct response_state *r_state, struct gsm_bts *bts) +{ + struct osmo_cbsp_cell_ent *cent = talloc_zero(r_state, struct osmo_cbsp_cell_ent); + struct osmo_cell_global_id *cgi = bts_get_cgi(bts); + + LOG_BTS(bts, DCBS, LOGL_INFO, "Success\n"); + + OSMO_ASSERT(cent); + + cent->cell_id.global = *cgi; + llist_add_tail(¢->list, &r_state->success.list); +} + +/* append a failure for given cell to response state */ +static void append_fail(struct response_state *r_state, struct gsm_bts *bts, uint8_t cause) +{ + struct osmo_cbsp_fail_ent *fent = talloc_zero(r_state, struct osmo_cbsp_fail_ent); + struct osmo_cell_global_id *cgi = bts_get_cgi(bts); + + LOG_BTS(bts, DCBS, LOGL_NOTICE, "Failure Cause 0x%02x\n", cause); + + OSMO_ASSERT(fent); + + fent->id_discr = CELL_IDENT_WHOLE_GLOBAL; + fent->cell_id.global = *cgi; + fent->cause = cause; + llist_add_tail(&fent->list, &r_state->fail); +} + +/* append a 'number of broadcasts completed' for given cell to response state */ +static void append_bcast_compl(struct response_state *r_state, struct gsm_bts *bts, + struct bts_smscb_message *smscb) +{ + struct osmo_cbsp_num_compl_ent *cent = talloc_zero(r_state, struct osmo_cbsp_num_compl_ent); + struct osmo_cell_global_id *cgi = bts_get_cgi(bts); + + LOG_BTS(bts, DCBS, LOGL_DEBUG, "Number of Broadcasts Completed: %u\n", smscb->bcast_count); + + OSMO_ASSERT(cent); + + r_state->num_completed.id_discr = CELL_IDENT_WHOLE_GLOBAL; + cent->cell_id.global = *cgi; + if (smscb->bcast_count > INT16_MAX) { + cent->num_compl = INT16_MAX; + cent->num_bcast_info = 0x01; /* Overflow */ + } else { + cent->num_compl = smscb->bcast_count; + cent->num_bcast_info = 0x00; + } + llist_add_tail(¢->list, &r_state->num_completed.list); +} + +/*! Iterate over all BTSs, find matching ones, execute command on BTS, add result + * to succeeded/failed lists. + * \param[in] net GSM network in which we operate + * \param[in] caller-allocated Response state structure collecting results + * \param[in] cell_list Decoded CBSP cell list describing BTSs to operate on + * \param[in] cb_fn Call-back function to call for each matching BTS + * \param[in] priv Opqaue private data; passed to cb_fn + * */ +static int cbsp_per_bts(struct gsm_network *net, struct response_state *r_state, + const struct osmo_cbsp_cell_list *cell_list, + bts_cb_fn *cb_fn, const struct osmo_cbsp_decoded *dec, void *priv) +{ + struct osmo_cbsp_cell_ent *ent; + struct gsm_bts *bts; + uint8_t bts_status[net->num_bts]; + int rc, ret = 0; + + memset(bts_status, 0, sizeof(bts_status)); + INIT_LLIST_HEAD(&r_state->success.list); + INIT_LLIST_HEAD(&r_state->fail); + INIT_LLIST_HEAD(&r_state->num_completed.list); + INIT_LLIST_HEAD(&r_state->loading.list); + + /* special case as cell_list->list is empty in this case */ + if (cell_list->id_discr == CELL_IDENT_BSS) { + llist_for_each_entry(bts, &net->bts_list, list) { + bts_status[bts->nr] = 1; + /* call function on this BTS */ + rc = cb_fn(bts, dec, r_state, priv); + if (rc < 0) { + append_fail(r_state, bts, -rc); + ret = -1; + } else + append_success(r_state, bts); + } + } else { + /* normal case: iterate over cell list */ + llist_for_each_entry(ent, &cell_list->list, list) { + bool found_at_least_one = false; + /* find all matching BTSs for this entry */ + llist_for_each_entry(bts, &net->bts_list, list) { + struct gsm0808_cell_id cell_id = { + .id_discr = cell_list->id_discr, + .id = ent->cell_id + }; + if (!gsm_bts_matches_cell_id(bts, &cell_id)) + continue; + found_at_least_one = true; + /* skip any BTSs which we've already processed */ + if (bts_status[bts->nr]) + continue; + bts_status[bts->nr] = 1; + /* call function on this BTS */ + rc = cb_fn(bts, dec, r_state, priv); + if (rc < 0) { + append_fail(r_state, bts, -rc); + ret = -1; + } else + append_success(r_state, bts); + } + if (!found_at_least_one) { + struct osmo_cbsp_fail_ent *fent; + LOGP(DCBS, LOGL_NOTICE, "CBSP: Couldn't find a single matching BTS\n"); + fent = talloc_zero(r_state, struct osmo_cbsp_fail_ent); + OSMO_ASSERT(fent); + fent->id_discr = cell_list->id_discr; + fent->cell_id = ent->cell_id; + llist_add_tail(&fent->list, &r_state->fail); + ret = -1; + } + } + } + return ret; +} + +/*! Find an existing SMSCB message within given BTS. + * \param[in] chan_state BTS CBCH channel state + * \param[in] msg_id Message Id of to-be-found message + * \param[in] serial_nr Serial Number of to-be-found message + * \returns SMSCB message if found; NULL otherwise */ +struct bts_smscb_message *bts_find_smscb(struct bts_smscb_chan_state *chan_state, + uint16_t msg_id, uint16_t serial_nr) +{ + struct bts_smscb_message *smscb; + + llist_for_each_entry(smscb, &chan_state->messages, list) { + if (smscb->input.msg_id == msg_id && smscb->input.serial_nr == serial_nr) + return smscb; + } + return NULL; +} + +/*! create a new SMSCB message for specified BTS; don't link it yet. + * \param[in] bts BTS for which the SMSCB is to be allocated + * \param[in] wrepl CBSP write-replace message + * \returns callee-allocated SMSCB message filled with data from wrepl */ +static struct bts_smscb_message *bts_smscb_msg_from_wrepl(struct gsm_bts *bts, + const struct osmo_cbsp_write_replace *wrepl) +{ + struct bts_smscb_message *smscb = talloc_zero(bts, struct bts_smscb_message); + struct osmo_cbsp_content *cont; + int i; + + if (!smscb) + return NULL; + + OSMO_ASSERT(wrepl->is_cbs); + + /* initialize all pages inside the message */ + for (i = 0; i < ARRAY_SIZE(smscb->page); i++) { + struct bts_smscb_page *page = &smscb->page[i]; + page->nr = i+1; /* page numbers are 1-based */ + page->msg = smscb; + } + + /* initialize "header" part */ + smscb->input.msg_id = wrepl->msg_id; + smscb->input.serial_nr = wrepl->new_serial_nr; + smscb->input.category = wrepl->u.cbs.category; + smscb->input.rep_period = wrepl->u.cbs.rep_period; + smscb->input.num_bcast_req = wrepl->u.cbs.num_bcast_req; + smscb->input.dcs = wrepl->u.cbs.dcs; + smscb->num_pages = llist_count(&wrepl->u.cbs.msg_content); + if (smscb->num_pages > ARRAY_SIZE(smscb->page)) { + LOG_BTS(bts, DCBS, LOGL_ERROR, "SMSCB with too many pages (%u > %lu)\n", + smscb->num_pages, ARRAY_SIZE(smscb->page)); + talloc_free(smscb); + return NULL; + } + + i = 0; + llist_for_each_entry(cont, &wrepl->u.cbs.msg_content, list) { + struct gsm23041_msg_param_gsm *msg_param; + struct bts_smscb_page *page; + size_t bytes_used; + + /* we have just ensured a few lines above that this cannot overflow */ + page = &smscb->page[i++]; + msg_param = (struct gsm23041_msg_param_gsm *) &page->data[0]; + + /* build 6 byte header according to TS 23.041 9.4.1.2 */ + osmo_store16be(wrepl->new_serial_nr, &msg_param->serial_nr); + osmo_store16be(wrepl->msg_id, &msg_param->message_id); + msg_param->dcs = wrepl->u.cbs.dcs; + msg_param->page_param.num_pages = smscb->num_pages; + msg_param->page_param.page_nr = page->nr; + + OSMO_ASSERT(cont->user_len <= ARRAY_SIZE(cont->data)); + OSMO_ASSERT(cont->user_len <= ARRAY_SIZE(page->data) - sizeof(*msg_param)); + memcpy(&msg_param->content, cont->data, cont->user_len); + bytes_used = sizeof(*msg_param) + cont->user_len; + /* compute number of valid blocks in page */ + page->num_blocks = bytes_used / 22; + if (bytes_used % 22) + page->num_blocks += 1; + } + + return smscb; +} + +/*! remove a SMSCB message */ +void bts_smscb_del(struct bts_smscb_message *smscb, struct bts_smscb_chan_state *cstate, + const char *reason) +{ + struct bts_smscb_page **arr; + int rc; + + LOG_BTS(cstate->bts, DCBS, LOGL_INFO, "%s Deleting %s (Reason: %s)\n", + bts_smscb_chan_state_name(cstate), bts_smscb_msg2str(smscb), reason); + llist_del(&smscb->list); + + /* we must recompute the scheduler array here, as the old one will have pointers + * to the pages of the just-to-be-deleted message */ + rc = bts_smscb_gen_sched_arr(cstate, &arr); + if (rc < 0) { + LOG_BTS(cstate->bts, DCBS, LOGL_ERROR, "Cannot generate new CBCH scheduler array after " + "removing message %s. WTF?\n", bts_smscb_msg2str(smscb)); + /* we cannot free the message now, to ensure the page pointers in the old + * array are still valid. let's re-add it to keep things sane */ + __bts_smscb_add(cstate, smscb); + } else { + /* success */ + talloc_free(smscb); + + /* replace array with new one */ + talloc_free(cstate->sched_arr); + cstate->sched_arr = arr; + cstate->sched_arr_size = rc; + cstate->next_idx = 0; + } +} + + +/********************************************************************************* + * Transmit of CBSP to CBC + *********************************************************************************/ + +/* transmit a CBSP RESTART message stating all message data was lost for entire BSS */ +int cbsp_tx_restart(struct bsc_cbc_link *cbc, bool is_emerg) +{ + struct osmo_cbsp_decoded *cbsp = osmo_cbsp_decoded_alloc(cbc, CBSP_MSGT_RESTART); + + if (is_emerg) + cbsp->u.restart.bcast_msg_type = 0x01; + cbsp->u.restart.recovery_ind = 0x01; /* message data lost */ + cbsp->u.restart.cell_list.id_discr = CELL_IDENT_BSS; + + return cbsp_tx_decoded(cbc, cbsp); +} + +/* transmit a CBSP KEEPALIVE COMPLETE to the CBC */ +static int tx_cbsp_keepalive_compl(struct bsc_cbc_link *cbc) +{ + struct osmo_cbsp_decoded *cbsp = osmo_cbsp_decoded_alloc(cbc, CBSP_MSGT_KEEP_ALIVE_COMPL); + return cbsp_tx_decoded(cbc, cbsp); +} + +/********************************************************************************* + * Per-BTS Processing of CBSP from CBC, called via cbsp_per_bts() + *********************************************************************************/ + +/*! Try to execute a write-replace operation; roll-back if it fails. + * \param[in] chan_state BTS CBCH channel state + * \param[in] extended_cbch Basic (false) or Extended (true) CBCH + * \param[in] new_msg New SMSCB message which should be added + * \param[in] exclude_msg Existing SMSCB message that shall be replaced (if possible). Can be NULL + * \return 0 on success; negative on error */ +static int bts_try_write_replace(struct bts_smscb_chan_state *chan_state, + struct bts_smscb_message *new_msg, + struct bts_smscb_message *exclude_msg, + struct response_state *r_state) +{ + struct bts_smscb_page **arr; + int rc; + + if (exclude_msg) { + /* temporarily remove from list of SMSCB */ + llist_del(&exclude_msg->list); + } + /* temporarily add new_msg to list of SMSCB */ + __bts_smscb_add(chan_state, new_msg); + + /* attempt to create scheduling array */ + rc = bts_smscb_gen_sched_arr(chan_state, &arr); + if (rc < 0) { + /* it didn't work out; we couldn't schedule it */ + /* remove the new message again */ + llist_del(&new_msg->list); + /* up to the caller to free() it */ + if (exclude_msg) { + /* re-add the temporarily removed message */ + __bts_smscb_add(chan_state, new_msg); + } + return -1; + } + + /* success! */ + if (exclude_msg) { + LOG_BTS(chan_state->bts, DCBS, LOGL_INFO, "%s Replaced MsgId=0x%04x/Serial=0x%04x, " + "pages(%u -> %u), period(%u -> %u), num_bcast(%u -> %u)\n", + bts_smscb_chan_state_name(chan_state), + new_msg->input.msg_id, new_msg->input.serial_nr, + exclude_msg->num_pages, new_msg->num_pages, + exclude_msg->input.rep_period, new_msg->input.rep_period, + exclude_msg->input.num_bcast_req, new_msg->input.num_bcast_req); + append_bcast_compl(r_state, chan_state->bts, exclude_msg); + talloc_free(exclude_msg); + } else + LOG_BTS(chan_state->bts, DCBS, LOGL_INFO, "%s Added %s\n", + bts_smscb_chan_state_name(chan_state), bts_smscb_msg2str(new_msg)); + + /* replace array with new one */ + talloc_free(chan_state->sched_arr); + chan_state->sched_arr = arr; + chan_state->sched_arr_size = rc; + chan_state->next_idx = 0; + return 0; +} + + +static int bts_rx_write_replace(struct gsm_bts *bts, const struct osmo_cbsp_decoded *dec, + struct response_state *r_state, void *priv) +{ + const struct osmo_cbsp_write_replace *wrepl = &dec->u.write_replace; + bool extended_cbch = wrepl->u.cbs.channel_ind; + struct bts_smscb_chan_state *chan_state = bts_get_smscb_chan(bts, extended_cbch); + struct bts_smscb_message *smscb; + int rc; + + if (!wrepl->is_cbs) { + LOG_BTS(bts, DCBS, LOGL_ERROR, "(Primary) Emergency Message not supported\n"); + return -CBSP_CAUSE_CB_NOT_SUPPORTED; + } + + /* check if cell has a CBCH at all */ + if (!gsm_bts_get_cbch(bts)) + return -CBSP_CAUSE_CB_NOT_SUPPORTED; + + /* check for duplicate */ + if (bts_find_smscb(chan_state, wrepl->msg_id, wrepl->new_serial_nr)) + return -CBSP_CAUSE_MSG_REF_ALREADY_USED; + + if (!wrepl->old_serial_nr) { /* new message */ + /* create new message */ + smscb = bts_smscb_msg_from_wrepl(bts, wrepl); + if (!smscb) + return -CBSP_CAUSE_BSC_MEMORY_EXCEEDED; + /* check if scheduling permits this additional message */ + rc = bts_try_write_replace(chan_state, smscb, NULL, r_state); + if (rc < 0) { + talloc_free(smscb); + return -CBSP_CAUSE_BSC_CAPACITY_EXCEEDED; + } + } else { /* modify / replace existing message */ + struct bts_smscb_message *smscb_old; + /* find existing message */ + smscb_old = bts_find_smscb(chan_state, wrepl->msg_id, *wrepl->old_serial_nr); + if (!smscb_old) + return -CBSP_CAUSE_MSG_REF_NOT_IDENTIFIED; + /* create new message */ + smscb = bts_smscb_msg_from_wrepl(bts, wrepl); + if (!smscb) + return -CBSP_CAUSE_BSC_MEMORY_EXCEEDED; + /* check if scheduling permits this modified message */ + rc = bts_try_write_replace(chan_state, smscb, smscb_old, r_state); + if (rc < 0) { + talloc_free(smscb); + return -CBSP_CAUSE_BSC_CAPACITY_EXCEEDED; + } + } + return 0; +} + +static int bts_rx_kill(struct gsm_bts *bts, const struct osmo_cbsp_decoded *dec, + struct response_state *r_state, void *priv) +{ + const struct osmo_cbsp_kill *kill = &dec->u.kill; + struct bts_smscb_chan_state *chan_state; + struct bts_smscb_message *smscb; + bool extended = false; + + if (kill->channel_ind && *kill->channel_ind == 0x01) + extended = true; + chan_state = bts_get_smscb_chan(bts, extended); + + /* Find message by msg_id + old_serial_nr */ + smscb = bts_find_smscb(chan_state, kill->msg_id, kill->old_serial_nr); + if (!smscb) + return -CBSP_CAUSE_MSG_REF_NOT_IDENTIFIED; + + /* Remove it */ + bts_smscb_del(smscb, chan_state, "KILL"); + return 0; +} + +static int bts_rx_reset(struct gsm_bts *bts, const struct osmo_cbsp_decoded *dec, + struct response_state *r_state, void *priv) +{ + struct bts_smscb_chan_state *chan_state; + struct bts_smscb_message *smscb, *smscb2; + + /* remove all SMSCB from CBCH BASIC this BTS */ + chan_state = bts_get_smscb_chan(bts, false); + llist_for_each_entry_safe(smscb, smscb2, &chan_state->messages, list) + bts_smscb_del(smscb, chan_state, "RESET"); + + /* remove all SMSCB from CBCH EXTENDED this BTS */ + chan_state = bts_get_smscb_chan(bts, true); + llist_for_each_entry_safe(smscb, smscb2, &chan_state->messages, list) + bts_smscb_del(smscb, chan_state, "RESET"); + + return 0; +} + +/********************************************************************************* + * Receive of CBSP from CBC + *********************************************************************************/ + +static int cbsp_rx_write_replace(struct bsc_cbc_link *cbc, const struct osmo_cbsp_decoded *dec) +{ + const struct osmo_cbsp_write_replace *wrepl = &dec->u.write_replace; + struct gsm_network *net = cbc->net; + struct response_state *r_state = talloc_zero(cbc, struct response_state); + struct osmo_cbsp_decoded *resp; + enum cbsp_channel_ind channel_ind; + int rc; + + LOGP(DCBS, LOGL_INFO, "CBSP Rx WRITE_REPLACE (%s)\n", wrepl->is_cbs ? "CBS" : "EMERGENCY"); + + rc = cbsp_per_bts(net, r_state, &dec->u.write_replace.cell_list, + bts_rx_write_replace, dec, NULL); + /* generate response */ + if (rc < 0) { + resp = osmo_cbsp_decoded_alloc(cbc, CBSP_MSGT_WRITE_REPLACE_FAIL); + struct osmo_cbsp_write_replace_failure *fail = &resp->u.write_replace_fail; + fail->msg_id = wrepl->msg_id; + fail->new_serial_nr = wrepl->new_serial_nr; + fail->old_serial_nr = wrepl->old_serial_nr; + llist_replace_head(&fail->fail_list, &r_state->fail); + fail->cell_list.id_discr = r_state->success.id_discr; + llist_replace_head(&fail->cell_list.list, &r_state->success.list); + if (wrepl->is_cbs) { + channel_ind = wrepl->u.cbs.channel_ind; + fail->channel_ind = &channel_ind; + } + if (wrepl->old_serial_nr) { + fail->num_compl_list.id_discr = r_state->num_completed.id_discr; + llist_replace_head(&fail->num_compl_list.list, &r_state->num_completed.list); + } + } else { + resp = osmo_cbsp_decoded_alloc(cbc, CBSP_MSGT_WRITE_REPLACE_COMPL); + struct osmo_cbsp_write_replace_complete *compl = &resp->u.write_replace_compl; + compl->msg_id = wrepl->msg_id; + compl->new_serial_nr = wrepl->new_serial_nr; + compl->old_serial_nr = wrepl->old_serial_nr; + compl->cell_list.id_discr = r_state->success.id_discr; + llist_replace_head(&compl->cell_list.list, &r_state->success.list); + if (wrepl->is_cbs) { + channel_ind = wrepl->u.cbs.channel_ind; + compl->channel_ind = &channel_ind; + } + if (wrepl->old_serial_nr) { + compl->num_compl_list.id_discr = r_state->num_completed.id_discr; + llist_replace_head(&compl->num_compl_list.list, &r_state->num_completed.list); + } + } + + cbsp_tx_decoded(cbc, resp); + talloc_free(r_state); + return rc; +} + +static int cbsp_rx_keep_alive(struct bsc_cbc_link *cbc, const struct osmo_cbsp_decoded *dec) +{ + LOGP(DCBS, LOGL_DEBUG, "CBSP Rx KEEP_ALIVE\n"); + + /* FIXME: repetition period */ + return tx_cbsp_keepalive_compl(cbc); +} + +static int cbsp_rx_kill(struct bsc_cbc_link *cbc, const struct osmo_cbsp_decoded *dec) +{ + const struct osmo_cbsp_kill *kill = &dec->u.kill; + struct gsm_network *net = cbc->net; + struct response_state *r_state = talloc_zero(cbc, struct response_state); + struct osmo_cbsp_decoded *resp; + int rc; + + LOGP(DCBS, LOGL_DEBUG, "CBSP Rx KILL\n"); + + rc = cbsp_per_bts(net, r_state, &dec->u.kill.cell_list, bts_rx_kill, dec, NULL); + if (rc < 0) { + resp = osmo_cbsp_decoded_alloc(cbc, CBSP_MSGT_KILL_FAIL); + struct osmo_cbsp_kill_failure *fail = &resp->u.kill_fail; + fail->msg_id = kill->msg_id; + fail->old_serial_nr = kill->old_serial_nr; + fail->channel_ind = kill->channel_ind; + llist_replace_head(&fail->fail_list, &r_state->fail); + + fail->cell_list.id_discr = r_state->success.id_discr; + llist_replace_head(&fail->cell_list.list, &r_state->success.list); + + fail->num_compl_list.id_discr = r_state->num_completed.id_discr; + llist_replace_head(&fail->num_compl_list.list, &r_state->num_completed.list); + } else { + resp = osmo_cbsp_decoded_alloc(cbc, CBSP_MSGT_KILL_COMPL); + struct osmo_cbsp_kill_complete *compl = &resp->u.kill_compl; + compl->msg_id = kill->msg_id; + compl->old_serial_nr = kill->old_serial_nr; + compl->channel_ind = kill->channel_ind; + + compl->cell_list.id_discr = r_state->success.id_discr; + llist_replace_head(&compl->cell_list.list, &r_state->success.list); + + compl->num_compl_list.id_discr = r_state->num_completed.id_discr; + llist_replace_head(&compl->num_compl_list.list, &r_state->num_completed.list); + } + + cbsp_tx_decoded(cbc, resp); + talloc_free(r_state); + return rc; +} + +static int cbsp_rx_reset(struct bsc_cbc_link *cbc, const struct osmo_cbsp_decoded *dec) +{ + struct gsm_network *net = cbc->net; + struct response_state *r_state = talloc_zero(cbc, struct response_state); + struct osmo_cbsp_decoded *resp; + int rc; + + LOGP(DCBS, LOGL_DEBUG, "CBSP Rx RESET\n"); + + rc = cbsp_per_bts(net, r_state, &dec->u.reset.cell_list, bts_rx_reset, dec, NULL); + if (rc < 0) { + resp = osmo_cbsp_decoded_alloc(cbc, CBSP_MSGT_RESET_FAIL); + struct osmo_cbsp_reset_failure *fail = &resp->u.reset_fail; + llist_replace_head(&fail->fail_list, &r_state->fail); + + fail->cell_list.id_discr = r_state->success.id_discr; + llist_replace_head(&fail->cell_list.list, &r_state->success.list); + } else { + resp = osmo_cbsp_decoded_alloc(cbc, CBSP_MSGT_RESET_COMPL); + struct osmo_cbsp_reset_complete *compl = &resp->u.reset_compl; + if (dec->u.reset.cell_list.id_discr == CELL_IDENT_BSS) { + /* replace the list of individual cell identities with CELL_IDENT_BSS */ + compl->cell_list.id_discr = CELL_IDENT_BSS; + /* no need to free success_list entries, hierarchical talloc works */ + } else { + compl->cell_list.id_discr = r_state->success.id_discr; + llist_replace_head(&compl->cell_list.list, &r_state->success.list); + } + } + cbsp_tx_decoded(cbc, resp); + talloc_free(r_state); + return rc; +} + + +/*! process an incoming, already decoded CBSP message from the CBC. + * \param[in] cbc link to the CBC + * \param[in] dec decoded CBSP message structure. Ownership not transferred. + * \returns 0 on success; negative on error. */ +int cbsp_rx_decoded(struct bsc_cbc_link *cbc, const struct osmo_cbsp_decoded *dec) +{ + int rc = -1; + + switch (dec->msg_type) { + case CBSP_MSGT_WRITE_REPLACE: /* create or modify message */ + rc = cbsp_rx_write_replace(cbc, dec); + break; + case CBSP_MSGT_KEEP_ALIVE: /* solicit an acknowledgement */ + rc = cbsp_rx_keep_alive(cbc, dec); + break; + case CBSP_MSGT_KILL: /* remove message */ + rc = cbsp_rx_kill(cbc, dec); + break; + case CBSP_MSGT_RESET: /* stop broadcasting of all messages */ + rc = cbsp_rx_reset(cbc, dec); + break; + case CBSP_MSGT_LOAD_QUERY: + case CBSP_MSGT_MSG_STATUS_QUERY: + case CBSP_MSGT_SET_DRX: + LOGP(DCBS, LOGL_ERROR, "Received Unimplemented CBSP Message Type %s", + get_value_string(cbsp_msg_type_names, dec->msg_type)); + /* we should implement those eventually */ + break; + default: + LOGP(DCBS, LOGL_ERROR, "Received Unknown/Unexpected CBSP Message Type %s", + get_value_string(cbsp_msg_type_names, dec->msg_type)); + break; + } + return rc; +} + +/********************************************************************************* + * VTY Interface (Introspection) + *********************************************************************************/ + +static void vty_dump_smscb_chan_state(struct vty *vty, const struct bts_smscb_chan_state *cs) +{ + const struct bts_smscb_message *sm; + + vty_out(vty, "%s CBCH:%s", cs == &cs->bts->cbch_basic ? "BASIC" : "EXTENDED", VTY_NEWLINE); + + vty_out(vty, " MsgId | SerNo | Pg | Category | Perd | #Tx | #Req | DCS%s", VTY_NEWLINE); + vty_out(vty, "-------|-------|----|---------------|------|------|------|----%s", VTY_NEWLINE); + llist_for_each_entry(sm, &cs->messages, list) { + vty_out(vty, " %04x | %04x | %2u | %13s | %4u | %4u | %4u | %02x%s", + sm->input.msg_id, sm->input.serial_nr, sm->num_pages, + get_value_string(cbsp_category_names, sm->input.category), + sm->input.rep_period, sm->bcast_count, sm->input.num_bcast_req, + sm->input.dcs, VTY_NEWLINE); + } + vty_out(vty, "%s", VTY_NEWLINE); +} + +DEFUN(bts_show_cbs, bts_show_cbs_cmd, + "show bts <0-255> smscb [(basic|extended)]", + SHOW_STR "Display information about a BTS\n" "BTS number\n" + "SMS Cell Broadcast State\n" + "Show only information related to CBCH BASIC\n" + "Show only information related to CBCH EXTENDED\n") +{ + struct gsm_network *net = gsmnet_from_vty(vty); + int bts_nr = atoi(argv[0]); + struct gsm_bts *bts; + + 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 || !strcmp(argv[1], "basic")) + vty_dump_smscb_chan_state(vty, &bts->cbch_basic); + if (argc < 2 || !strcmp(argv[1], "extended")) + vty_dump_smscb_chan_state(vty, &bts->cbch_extended); + + return CMD_SUCCESS; +} + +void smscb_vty_init(void) +{ + install_element_ve(&bts_show_cbs_cmd); +} diff --git a/tests/handover/Makefile.am b/tests/handover/Makefile.am index 84c341e25..8bd001259 100644 --- a/tests/handover/Makefile.am +++ b/tests/handover/Makefile.am @@ -11,6 +11,7 @@ AM_CFLAGS = \ $(LIBOSMOCTRL_CFLAGS) \ $(LIBOSMOVTY_CFLAGS) \ $(LIBOSMOABIS_CFLAGS) \ + $(LIBOSMONETIF_CFLAGS) \ $(LIBOSMOSIGTRAN_CFLAGS) \ $(LIBOSMOMGCPCLIENT_CFLAGS) \ $(NULL) @@ -91,11 +92,15 @@ handover_test_LDADD = \ $(top_builddir)/src/osmo-bsc/rest_octets.o \ $(top_builddir)/src/osmo-bsc/system_information.o \ $(top_builddir)/src/osmo-bsc/timeslot_fsm.o \ + $(top_builddir)/src/osmo-bsc/smscb.o \ + $(top_builddir)/src/osmo-bsc/cbch_scheduler.o \ + $(top_builddir)/src/osmo-bsc/cbsp_link.o \ $(LIBOSMOCORE_LIBS) \ $(LIBOSMOGSM_LIBS) \ $(LIBOSMOCTRL_LIBS) \ $(LIBOSMOVTY_LIBS) \ $(LIBOSMOABIS_LIBS) \ + $(LIBOSMONETIF_LIBS) \ $(LIBOSMOSIGTRAN_LIBS) \ $(LIBOSMOMGCPCLIENT_LIBS) \ $(NULL) -- cgit v1.2.3